From 0b88ad298b639105ca98ed44aa426bc3d922c686 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sun, 21 Jan 2024 23:42:48 -0600 Subject: [PATCH] Completely rip out the old msgpack stuff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Did this break anything? ¯\_(ツ)_/¯ --- Cargo.lock | 30 - Cargo.toml | 2 - src/api.rs | 2030 +++++++++++++++++++++++++++++++++++++---- src/api/handlers.rs | 824 ----------------- src/api/msg.rs | 335 ------- src/api/protocol.rs | 1887 -------------------------------------- src/backend/udev.rs | 36 +- src/config.rs | 84 +- src/input.rs | 138 +-- src/input/libinput.rs | 104 +-- src/main.rs | 2 +- src/state.rs | 33 +- 12 files changed, 1911 insertions(+), 3594 deletions(-) delete mode 100644 src/api/handlers.rs delete mode 100644 src/api/msg.rs delete mode 100644 src/api/protocol.rs diff --git a/Cargo.lock b/Cargo.lock index 8109427..cde96be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 6d17cde..bba02d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/api.rs b/src/api.rs index adb5d96..ba6d2fe 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,227 +1,1887 @@ -// SPDX-License-Identifier: GPL-3.0-or-later +use std::{ffi::OsString, num::NonZeroU32, pin::Pin, process::Stdio}; -pub mod handlers; -pub mod msg; -pub mod protocol; +use pinnacle_api_defs::pinnacle::{ + input::v0alpha1::{ + set_libinput_setting_request::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap}, + set_mousebind_request::MouseEdge, + SetKeybindRequest, SetKeybindResponse, SetLibinputSettingRequest, SetMousebindRequest, + SetMousebindResponse, SetRepeatRateRequest, SetXkbConfigRequest, + }, + output::v0alpha1::{ConnectForAllRequest, ConnectForAllResponse, SetLocationRequest}, + process::v0alpha1::{SetEnvRequest, SpawnRequest, SpawnResponse}, + tag::v0alpha1::{ + AddRequest, AddResponse, RemoveRequest, SetActiveRequest, SetLayoutRequest, SwitchToRequest, + }, + v0alpha1::{Geometry, QuitRequest}, + window::v0alpha1::{ + AddWindowRuleRequest, CloseRequest, FullscreenOrMaximized, MoveGrabRequest, + MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest, SetFullscreenRequest, + SetGeometryRequest, SetMaximizedRequest, SetTagRequest, WindowRule, WindowRuleCondition, + }, +}; +use smithay::{ + desktop::space::SpaceElement, + input::keyboard::XkbConfig, + reexports::{calloop, input as libinput, wayland_protocols::xdg::shell::server}, + utils::{Point, Rectangle, SERIAL_COUNTER}, + wayland::{compositor, shell::xdg::XdgToplevelSurfaceData}, +}; +use sysinfo::ProcessRefreshKind; +use tokio::io::AsyncBufReadExt; +use tokio_stream::Stream; +use tonic::{Request, Response, Status}; -use std::{ - io::{self, Read, Write}, - os::unix::net::{UnixListener, UnixStream}, - path::Path, - sync::{Arc, Mutex}, +use crate::{ + config::ConnectorSavedState, + focus::FocusTarget, + input::ModifierMask, + output::OutputName, + state::{State, WithState}, + tag::{Tag, TagId}, + window::{window_state::WindowId, WindowElement}, }; -use self::msg::{Msg, OutgoingMsg}; -use anyhow::Context; -use calloop::RegistrationToken; -use smithay::reexports::calloop::{ - self, channel::Sender, generic::Generic, EventSource, Interest, Mode, PostAction, -}; +type ResponseStream = Pin> + Send>>; +pub type StateFnSender = calloop::channel::Sender>; -pub const SOCKET_NAME: &str = "pinnacle_socket"; +pub struct PinnacleService { + pub sender: StateFnSender, +} -/// Handle a config process. -/// -/// `stream` is the incoming stream where messages will be received, -/// and `sender` sends decoded messages to the main state's handler. -fn handle_client( - mut stream: UnixStream, - sender: Sender, -) -> Result<(), Box> { - loop { - let mut len_marker_bytes = [0u8; 4]; - if let Err(err) = stream.read_exact(&mut len_marker_bytes) { - if err.kind() == io::ErrorKind::UnexpectedEof { - tracing::warn!("stream closed: {}", err); - stream.shutdown(std::net::Shutdown::Both)?; - break Ok(()); - } - }; +#[tonic::async_trait] +impl pinnacle_api_defs::pinnacle::v0alpha1::pinnacle_service_server::PinnacleService + for PinnacleService +{ + async fn quit(&self, _request: Request) -> Result, Status> { + tracing::trace!("PinnacleService.quit"); + let f = Box::new(|state: &mut State| { + state.shutdown(); + }); + // Expect is ok here, if it panics then the state was dropped beforehand + self.sender.send(f).expect("failed to send f"); - let len_marker = u32::from_ne_bytes(len_marker_bytes); - let mut msg_bytes = vec![0u8; len_marker as usize]; - - if let Err(err) = stream.read_exact(msg_bytes.as_mut_slice()) { - if err.kind() == io::ErrorKind::UnexpectedEof { - tracing::warn!("stream closed: {}", err); - stream.shutdown(std::net::Shutdown::Both)?; - break Ok(()); - } - }; - let msg: Msg = rmp_serde::from_slice(msg_bytes.as_slice())?; // TODO: handle error - - sender.send(msg)?; + Ok(Response::new(())) } } -/// A socket source for an event loop that will listen for config processes. -pub struct PinnacleSocketSource { - /// The socket listener - socket: Generic, - /// The sender that will send messages from clients to the main event loop. - sender: Sender, +pub struct InputService { + pub sender: StateFnSender, } -impl PinnacleSocketSource { - /// Create a loop source that listens for connections to the provided `socket_dir`. - /// This will also set PINNACLE_SOCKET for use in API implementations. - pub fn new( - sender: Sender, - socket_dir: &Path, - multiple_instances: bool, - ) -> anyhow::Result { - tracing::debug!("Creating socket source for dir {socket_dir:?}"); +#[tonic::async_trait] +impl pinnacle_api_defs::pinnacle::input::v0alpha1::input_service_server::InputService + for InputService +{ + type SetKeybindStream = ResponseStream; + type SetMousebindStream = ResponseStream; - // Test if you are running multiple instances of Pinnacle - // let multiple_instances = system.processes_by_exact_name("pinnacle").count() > 1; + async fn set_keybind( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); - // If you are, append a suffix to the socket name - let socket_name = if multiple_instances { - let mut suffix: u8 = 1; - while let Ok(true) = socket_dir - .join(format!("{SOCKET_NAME}_{suffix}")) - .try_exists() - { - suffix += 1; + tracing::debug!(request = ?request); + + // TODO: impl From<&[Modifier]> for ModifierMask + let modifiers = request + .modifiers() + .fold(ModifierMask::empty(), |acc, modifier| match modifier { + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Unspecified => acc, + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Shift => { + acc | ModifierMask::SHIFT + } + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Ctrl => { + acc | ModifierMask::CTRL + } + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Alt => { + acc | ModifierMask::ALT + } + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Super => { + acc | ModifierMask::SUPER + } + }); + let key = request + .key + .ok_or_else(|| Status::invalid_argument("no key specified"))?; + + use pinnacle_api_defs::pinnacle::input::v0alpha1::set_keybind_request::Key; + let keysym = match key { + Key::RawCode(num) => { + tracing::info!("set keybind: {:?}, raw {}", modifiers, num); + xkbcommon::xkb::Keysym::new(num) } - format!("{SOCKET_NAME}_{suffix}") - } else { - SOCKET_NAME.to_string() - }; - - let socket_path = socket_dir.join(socket_name); - - // If there are multiple instances, don't touch other sockets - if multiple_instances { - if let Ok(exists) = socket_path.try_exists() { - if exists { - std::fs::remove_file(&socket_path) - .context(format!("Failed to remove old socket at {socket_path:?}",))?; + Key::XkbName(s) => { + if s.chars().count() == 1 { + let Some(ch) = s.chars().next() else { unreachable!() }; + let keysym = xkbcommon::xkb::Keysym::from_char(ch); + tracing::info!("set keybind: {:?}, {:?}", modifiers, keysym); + keysym + } else { + let keysym = + xkbcommon::xkb::keysym_from_name(&s, xkbcommon::xkb::KEYSYM_NO_FLAGS); + tracing::info!("set keybind: {:?}, {:?}", modifiers, keysym); + keysym } } - } else { - // If there aren't, remove them all - for file in std::fs::read_dir(socket_dir)? - .filter_map(|entry| entry.ok()) - .filter(|entry| entry.file_name().to_string_lossy().starts_with(SOCKET_NAME)) - { - tracing::debug!("Removing socket at {:?}", file.path()); - std::fs::remove_file(file.path()) - .context(format!("Failed to remove old socket at {:?}", file.path()))?; + }; + + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + + self.sender + .send(Box::new(move |state| { + state + .input_state + .keybinds + .insert((modifiers, keysym), sender); + })) + .map_err(|_| Status::internal("internal state was not running"))?; + + let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); + + Ok(Response::new(Box::pin(receiver_stream))) + } + + async fn set_mousebind( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + tracing::debug!(request = ?request); + + let modifiers = request + .modifiers() + .fold(ModifierMask::empty(), |acc, modifier| match modifier { + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Unspecified => acc, + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Shift => { + acc | ModifierMask::SHIFT + } + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Ctrl => { + acc | ModifierMask::CTRL + } + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Alt => { + acc | ModifierMask::ALT + } + pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Super => { + acc | ModifierMask::SUPER + } + }); + let button = request + .button + .ok_or_else(|| Status::invalid_argument("no key specified"))?; + + let edge = request.edge(); + + if let MouseEdge::Unspecified = edge { + return Err(Status::invalid_argument("press or release not specified")); + } + + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + + self.sender + .send(Box::new(move |state| { + state + .input_state + .mousebinds + .insert((modifiers, button, edge), sender); + })) + .map_err(|_| Status::internal("internal state was not running"))?; + + let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); + + Ok(Response::new(Box::pin(receiver_stream))) + } + + async fn set_xkb_config( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let f = Box::new(move |state: &mut State| { + let new_config = XkbConfig { + rules: request.rules(), + variant: request.variant(), + model: request.model(), + layout: request.layout(), + options: request.options.clone(), + }; + if let Some(kb) = state.seat.get_keyboard() { + if let Err(err) = kb.set_xkb_config(state, new_config) { + tracing::error!("Failed to set xkbconfig: {err}"); + } } - } + }); - let listener = UnixListener::bind(&socket_path) - .with_context(|| format!("Failed to bind to socket at {socket_path:?}"))?; - tracing::info!("Bound to socket at {socket_path:?}"); + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; - listener - .set_nonblocking(true) - .context("Failed to set socket to nonblocking")?; + Ok(Response::new(())) + } - let socket = Generic::new(listener, Interest::READ, Mode::Level); + async fn set_repeat_rate( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); - std::env::set_var("PINNACLE_SOCKET", socket_path); + let rate = request + .rate + .ok_or_else(|| Status::invalid_argument("no rate specified"))?; + let delay = request + .delay + .ok_or_else(|| Status::invalid_argument("no rate specified"))?; - Ok(Self { socket, sender }) + let f = Box::new(move |state: &mut State| { + if let Some(kb) = state.seat.get_keyboard() { + kb.change_repeat_info(rate, delay); + } + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn set_libinput_setting( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let setting = request + .setting + .ok_or_else(|| Status::invalid_argument("no setting specified"))?; + + let discriminant = std::mem::discriminant(&setting); + + use pinnacle_api_defs::pinnacle::input::v0alpha1::set_libinput_setting_request::Setting; + let apply_setting: Box = match setting { + Setting::AccelProfile(profile) => { + let profile = AccelProfile::try_from(profile).unwrap_or(AccelProfile::Unspecified); + + match profile { + AccelProfile::Unspecified => { + return Err(Status::invalid_argument("unspecified accel profile")); + } + AccelProfile::Flat => Box::new(|device| { + let _ = device.config_accel_set_profile(libinput::AccelProfile::Flat); + }), + AccelProfile::Adaptive => Box::new(|device| { + let _ = device.config_accel_set_profile(libinput::AccelProfile::Adaptive); + }), + } + } + Setting::AccelSpeed(speed) => Box::new(move |device| { + let _ = device.config_accel_set_speed(speed); + }), + Setting::CalibrationMatrix(matrix) => { + let matrix = <[f32; 6]>::try_from(matrix.matrix).map_err(|vec| { + Status::invalid_argument(format!( + "matrix requires exactly 6 floats but {} were specified", + vec.len() + )) + })?; + + Box::new(move |device| { + let _ = device.config_calibration_set_matrix(matrix); + }) + } + Setting::ClickMethod(method) => { + let method = ClickMethod::try_from(method).unwrap_or(ClickMethod::Unspecified); + + match method { + ClickMethod::Unspecified => { + return Err(Status::invalid_argument("unspecified click method")) + } + ClickMethod::ButtonAreas => Box::new(|device| { + let _ = device.config_click_set_method(libinput::ClickMethod::ButtonAreas); + }), + ClickMethod::ClickFinger => Box::new(|device| { + let _ = device.config_click_set_method(libinput::ClickMethod::Clickfinger); + }), + } + } + Setting::DisableWhileTyping(disable) => Box::new(move |device| { + let _ = device.config_dwt_set_enabled(disable); + }), + Setting::LeftHanded(enable) => Box::new(move |device| { + let _ = device.config_left_handed_set(enable); + }), + Setting::MiddleEmulation(enable) => Box::new(move |device| { + let _ = device.config_middle_emulation_set_enabled(enable); + }), + Setting::RotationAngle(angle) => Box::new(move |device| { + let _ = device.config_rotation_set_angle(angle % 360); + }), + Setting::ScrollButton(button) => Box::new(move |device| { + let _ = device.config_scroll_set_button(button); + }), + Setting::ScrollButtonLock(enable) => Box::new(move |device| { + let _ = device.config_scroll_set_button_lock(match enable { + true => libinput::ScrollButtonLockState::Enabled, + false => libinput::ScrollButtonLockState::Disabled, + }); + }), + Setting::ScrollMethod(method) => { + let method = ScrollMethod::try_from(method).unwrap_or(ScrollMethod::Unspecified); + + match method { + ScrollMethod::Unspecified => { + return Err(Status::invalid_argument("unspecified scroll method")); + } + ScrollMethod::NoScroll => Box::new(|device| { + let _ = device.config_scroll_set_method(libinput::ScrollMethod::NoScroll); + }), + ScrollMethod::TwoFinger => Box::new(|device| { + let _ = device.config_scroll_set_method(libinput::ScrollMethod::TwoFinger); + }), + ScrollMethod::Edge => Box::new(|device| { + let _ = device.config_scroll_set_method(libinput::ScrollMethod::Edge); + }), + ScrollMethod::OnButtonDown => Box::new(|device| { + let _ = + device.config_scroll_set_method(libinput::ScrollMethod::OnButtonDown); + }), + } + } + Setting::NaturalScroll(enable) => Box::new(move |device| { + let _ = device.config_scroll_set_natural_scroll_enabled(enable); + }), + Setting::TapButtonMap(map) => { + let map = TapButtonMap::try_from(map).unwrap_or(TapButtonMap::Unspecified); + + match map { + TapButtonMap::Unspecified => { + return Err(Status::invalid_argument("unspecified tap button map")); + } + TapButtonMap::LeftRightMiddle => Box::new(|device| { + let _ = device + .config_tap_set_button_map(libinput::TapButtonMap::LeftRightMiddle); + }), + TapButtonMap::LeftMiddleRight => Box::new(|device| { + let _ = device + .config_tap_set_button_map(libinput::TapButtonMap::LeftMiddleRight); + }), + } + } + Setting::TapDrag(enable) => Box::new(move |device| { + let _ = device.config_tap_set_drag_enabled(enable); + }), + Setting::TapDragLock(enable) => Box::new(move |device| { + let _ = device.config_tap_set_drag_lock_enabled(enable); + }), + Setting::Tap(enable) => Box::new(move |device| { + let _ = device.config_tap_set_enabled(enable); + }), + }; + + let f = Box::new(move |state: &mut State| { + for device in state.input_state.libinput_devices.iter_mut() { + apply_setting(device); + } + + state + .input_state + .libinput_settings + .insert(discriminant, apply_setting); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) } } -/// Send a message to a client. -pub fn send_to_client( - stream: &mut UnixStream, - msg: &OutgoingMsg, -) -> Result<(), rmp_serde::encode::Error> { - tracing::trace!("Sending {msg:?}"); - - let msg = rmp_serde::to_vec_named(msg)?; - let msg_len = msg.len() as u32; - let bytes = msg_len.to_ne_bytes(); - - if let Err(err) = stream.write_all(&bytes) { - if err.kind() == io::ErrorKind::BrokenPipe { - // TODO: notify user that config daemon is ded - return Ok(()); // TODO: - } - } - - if let Err(err) = stream.write_all(msg.as_slice()) { - if err.kind() == io::ErrorKind::BrokenPipe { - // TODO: something - return Ok(()); // TODO: - } - } - - Ok(()) +pub struct ProcessService { + pub sender: StateFnSender, } -impl EventSource for PinnacleSocketSource { - type Event = UnixStream; +#[tonic::async_trait] +impl pinnacle_api_defs::pinnacle::process::v0alpha1::process_service_server::ProcessService + for ProcessService +{ + type SpawnStream = ResponseStream; - type Metadata = (); + async fn spawn( + &self, + request: Request, + ) -> Result, Status> { + tracing::debug!("ProcessService.spawn"); + let request = request.into_inner(); - type Ret = (); + let once = request.once(); + let has_callback = request.has_callback(); + let mut command = request.args.into_iter(); + let arg0 = command + .next() + .ok_or_else(|| Status::invalid_argument("no args specified"))?; - type Error = io::Error; + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); - fn process_events( - &mut self, - readiness: calloop::Readiness, - token: calloop::Token, - mut callback: F, - ) -> Result - where - F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, - { - self.socket - .process_events(readiness, token, |_readiness, listener| { - while let Ok((stream, _sock_addr)) = listener.accept() { - let sender = self.sender.clone(); - let callback_stream = stream.try_clone()?; + let f = Box::new(move |state: &mut State| { + if once { + state + .system_processes + .refresh_processes_specifics(ProcessRefreshKind::new()); - callback(callback_stream, &mut ()); + let compositor_pid = std::process::id(); + let already_running = + state + .system_processes + .processes_by_exact_name(&arg0) + .any(|proc| { + proc.parent() + .is_some_and(|parent_pid| parent_pid.as_u32() == compositor_pid) + }); - // Handle the client in another thread as to not block the main one. - // - // No idea if this is even needed or if it's premature optimization. - std::thread::spawn(move || { - if let Err(err) = handle_client(stream, sender) { - tracing::error!("handle_client errored: {err}"); + if already_running { + return; + } + } + + let Ok(mut child) = tokio::process::Command::new(OsString::from(arg0.clone())) + .envs( + [("WAYLAND_DISPLAY", state.socket_name.clone())] + .into_iter() + .chain(state.xdisplay.map(|xdisp| ("DISPLAY", format!(":{xdisp}")))), + ) + .stdin(match has_callback { + true => Stdio::piped(), + false => Stdio::null(), + }) + .stdout(match has_callback { + true => Stdio::piped(), + false => Stdio::null(), + }) + .stderr(match has_callback { + true => Stdio::piped(), + false => Stdio::null(), + }) + .args(command) + .spawn() + else { + tracing::warn!("Tried to run {arg0}, but it doesn't exist",); + return; + }; + + if !has_callback { + return; + } + + let stdout = child.stdout.take(); + let stderr = child.stderr.take(); + + if let Some(stdout) = stdout { + let sender = sender.clone(); + + let mut reader = tokio::io::BufReader::new(stdout).lines(); + + tokio::spawn(async move { + while let Ok(Some(line)) = reader.next_line().await { + let response: Result<_, Status> = Ok(SpawnResponse { + stdout: Some(line), + ..Default::default() + }); + + // TODO: handle error + match sender.send(response) { + Ok(_) => (), + Err(err) => { + tracing::error!(err = ?err); + break; + } + } + } + }); + } + + if let Some(stderr) = stderr { + let sender = sender.clone(); + + let mut reader = tokio::io::BufReader::new(stderr).lines(); + + tokio::spawn(async move { + while let Ok(Some(line)) = reader.next_line().await { + let response: Result<_, Status> = Ok(SpawnResponse { + stderr: Some(line), + ..Default::default() + }); + + // TODO: handle error + match sender.send(response) { + Ok(_) => (), + Err(err) => { + tracing::error!(err = ?err); + break; + } + } + } + }); + } + + tokio::spawn(async move { + match child.wait().await { + Ok(exit_status) => { + let response = Ok(SpawnResponse { + exit_code: exit_status.code(), + exit_message: Some(exit_status.to_string()), + ..Default::default() + }); + // TODO: handle error + let _ = sender.send(response); + } + Err(err) => tracing::warn!("child wait() err: {err}"), + } + }); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); + + Ok(Response::new(Box::pin(receiver_stream))) + } + + async fn set_env(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let key = request + .key + .ok_or_else(|| Status::invalid_argument("no key specified"))?; + let value = request + .value + .ok_or_else(|| Status::invalid_argument("no value specified"))?; + + if key.is_empty() { + return Err(Status::invalid_argument("key was empty")); + } + + if key.contains(['\0', '=']) { + return Err(Status::invalid_argument("key contained NUL or =")); + } + + if value.contains('\0') { + return Err(Status::invalid_argument("value contained NUL")); + } + + std::env::set_var(key, value); + + Ok(Response::new(())) + } +} + +pub struct TagService { + pub sender: StateFnSender, +} + +#[tonic::async_trait] +impl pinnacle_api_defs::pinnacle::tag::v0alpha1::tag_service_server::TagService for TagService { + async fn set_active(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let tag_id = TagId::Some( + request + .tag_id + .ok_or_else(|| Status::invalid_argument("no tag specified"))?, + ); + + let set_or_toggle = match request.set_or_toggle { + Some( + pinnacle_api_defs::pinnacle::tag::v0alpha1::set_active_request::SetOrToggle::Set( + set, + ), + ) => Some(set), + Some( + pinnacle_api_defs::pinnacle::tag::v0alpha1::set_active_request::SetOrToggle::Toggle( + _, + ), + ) => None, + None => return Err(Status::invalid_argument("unspecified set or toggle")), + }; + + let f = Box::new(move |state: &mut State| { + let Some(tag) = tag_id.tag(state) else { + return; + }; + match set_or_toggle { + Some(set) => tag.set_active(set), + None => tag.set_active(!tag.active()), + } + + let Some(output) = tag.output(state) else { + return; + }; + + state.update_windows(&output); + state.update_focus(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn switch_to(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let tag_id = TagId::Some( + request + .tag_id + .ok_or_else(|| Status::invalid_argument("no tag specified"))?, + ); + + let f = Box::new(move |state: &mut State| { + let Some(tag) = tag_id.tag(state) else { return }; + let Some(output) = tag.output(state) else { return }; + + output.with_state(|state| { + for op_tag in state.tags.iter_mut() { + op_tag.set_active(false); + } + tag.set_active(true); + }); + + state.update_windows(&output); + state.update_focus(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn add(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let output_name = OutputName( + request + .output_name + .ok_or_else(|| Status::invalid_argument("no output specified"))?, + ); + + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::(); + + let f = Box::new(move |state: &mut State| { + let new_tags = request + .tag_names + .into_iter() + .map(Tag::new) + .collect::>(); + + let tag_ids = new_tags + .iter() + .map(|tag| tag.id()) + .map(|id| match id { + TagId::None => unreachable!(), + TagId::Some(id) => id, + }) + .collect::>(); + + let _ = sender.send(AddResponse { tag_ids }); + + if let Some(saved_state) = state.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 { + state.config.connector_saved_states.insert( + output_name.clone(), + crate::config::ConnectorSavedState { + tags: new_tags.clone(), + ..Default::default() + }, + ); + } + + let Some(output) = state + .space + .outputs() + .find(|output| output.name() == output_name.0) + else { + return; + }; + + output.with_state(|state| { + state.tags.extend(new_tags.clone()); + tracing::debug!("tags added, are now {:?}", state.tags); + }); + + for tag in new_tags { + for window in state.windows.iter() { + window.with_state(|state| { + for win_tag in state.tags.iter_mut() { + if win_tag.id() == tag.id() { + *win_tag = tag.clone(); + } } }); } + } + }); - Ok(PostAction::Continue) + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let response = receiver + .recv() + .await + .ok_or_else(|| Status::internal("internal state was not running"))?; + + Ok(Response::new(response)) + } + + // TODO: test + async fn remove(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let tag_ids = request.tag_ids.into_iter().map(TagId::Some); + + let f = Box::new(move |state: &mut State| { + let tags_to_remove = tag_ids.flat_map(|id| id.tag(state)).collect::>(); + + for output in state.space.outputs().cloned().collect::>() { + // TODO: seriously, convert state.tags into a hashset + output.with_state(|state| { + for tag_to_remove in tags_to_remove.iter() { + state.tags.retain(|tag| tag != tag_to_remove); + } + }); + + state.update_windows(&output); + state.schedule_render(&output); + } + + for conn_saved_state in state.config.connector_saved_states.values_mut() { + for tag_to_remove in tags_to_remove.iter() { + conn_saved_state.tags.retain(|tag| tag != tag_to_remove); + } + } + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn set_layout(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let tag_id = TagId::Some( + request + .tag_id + .ok_or_else(|| Status::invalid_argument("no tag specified"))?, + ); + + use pinnacle_api_defs::pinnacle::tag::v0alpha1::set_layout_request::Layout; + + // TODO: from impl + let layout = match request.layout() { + Layout::Unspecified => return Err(Status::invalid_argument("unspecified layout")), + Layout::MasterStack => crate::layout::Layout::MasterStack, + Layout::Dwindle => crate::layout::Layout::Dwindle, + Layout::Spiral => crate::layout::Layout::Spiral, + Layout::CornerTopLeft => crate::layout::Layout::CornerTopLeft, + Layout::CornerTopRight => crate::layout::Layout::CornerTopRight, + Layout::CornerBottomLeft => crate::layout::Layout::CornerBottomLeft, + Layout::CornerBottomRight => crate::layout::Layout::CornerBottomRight, + }; + + let f = Box::new(move |state: &mut State| { + let Some(tag) = tag_id.tag(state) else { return }; + + tag.set_layout(layout); + + let Some(output) = tag.output(state) else { return }; + + state.update_windows(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn get( + &self, + _request: Request, + ) -> Result, Status> { + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< + pinnacle_api_defs::pinnacle::tag::v0alpha1::GetResponse, + >(); + + let f = Box::new(move |state: &mut State| { + let tag_ids = state + .space + .outputs() + .flat_map(|op| op.with_state(|state| state.tags.clone())) + .map(|tag| tag.id()) + .map(|id| match id { + TagId::None => unreachable!(), + TagId::Some(id) => id, + }) + .collect::>(); + + let _ = + sender.send(pinnacle_api_defs::pinnacle::tag::v0alpha1::GetResponse { tag_ids }); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let response = receiver + .recv() + .await + .ok_or_else(|| Status::internal("internal state was not running"))?; + + Ok(Response::new(response)) + } + + async fn get_properties( + &self, + request: Request, + ) -> Result, Status> + { + let request = request.into_inner(); + + let tag_id = TagId::Some( + request + .tag_id + .ok_or_else(|| Status::invalid_argument("no tag specified"))?, + ); + + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< + pinnacle_api_defs::pinnacle::tag::v0alpha1::GetPropertiesResponse, + >(); + + let f = Box::new(move |state: &mut State| { + let tag = tag_id.tag(state); + + let output_name = tag + .as_ref() + .and_then(|tag| tag.output(state)) + .map(|output| output.name()); + let active = tag.as_ref().map(|tag| tag.active()); + let name = tag.as_ref().map(|tag| tag.name()); + + let _ = sender.send( + pinnacle_api_defs::pinnacle::tag::v0alpha1::GetPropertiesResponse { + active, + name, + output_name, + }, + ); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let response = receiver + .recv() + .await + .ok_or_else(|| Status::internal("internal state was not running"))?; + + Ok(Response::new(response)) + } +} + +pub struct OutputService { + pub sender: StateFnSender, +} + +#[tonic::async_trait] +impl pinnacle_api_defs::pinnacle::output::v0alpha1::output_service_server::OutputService + for OutputService +{ + type ConnectForAllStream = ResponseStream; + + async fn set_location( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let output_name = OutputName( + request + .output_name + .ok_or_else(|| Status::invalid_argument("no output specified"))?, + ); + + let x = request.x; + let y = request.y; + + let f = Box::new(move |state: &mut State| { + if let Some(saved_state) = state.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 { + state.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(state) 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)); + state.space.map_output(&output, loc); + tracing::debug!("Mapping output {} to {loc:?}", output.name()); + state.update_windows(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + // TODO: remove this and integrate it into a signal/event system + async fn connect_for_all( + &self, + _request: Request, + ) -> Result, Status> { + tracing::trace!("OutputService.connect_for_all"); + let (sender, receiver) = + tokio::sync::mpsc::unbounded_channel::>(); + + let f = Box::new(move |state: &mut State| { + // for output in state.space.outputs() { + // let _ = sender.send(Ok(ConnectForAllResponse { + // output_name: Some(output.name()), + // })); + // tracing::debug!(name = output.name(), "sent connect_for_all"); + // } + + state.config.output_callback_senders.push(sender); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); + + Ok(Response::new(Box::pin(receiver_stream))) + } + + async fn get( + &self, + _request: Request, + ) -> Result, Status> { + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< + pinnacle_api_defs::pinnacle::output::v0alpha1::GetResponse, + >(); + + let f = Box::new(move |state: &mut State| { + let output_names = state + .space + .outputs() + .map(|output| output.name()) + .collect::>(); + + let _ = sender + .send(pinnacle_api_defs::pinnacle::output::v0alpha1::GetResponse { output_names }); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let response = receiver + .recv() + .await + .ok_or_else(|| Status::internal("internal state was not running"))?; + + Ok(Response::new(response)) + } + + async fn get_properties( + &self, + request: Request, + ) -> Result< + Response, + Status, + > { + let request = request.into_inner(); + + let output_name = OutputName( + request + .output_name + .ok_or_else(|| Status::invalid_argument("no output specified"))?, + ); + + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< + pinnacle_api_defs::pinnacle::output::v0alpha1::GetPropertiesResponse, + >(); + + let f = Box::new(move |state: &mut State| { + let output = output_name.output(state); + + let pixel_width = output + .as_ref() + .and_then(|output| output.current_mode().map(|mode| mode.size.w as u32)); + + let pixel_height = output + .as_ref() + .and_then(|output| output.current_mode().map(|mode| mode.size.h as u32)); + + let refresh_rate = output + .as_ref() + .and_then(|output| output.current_mode().map(|mode| mode.refresh as u32)); + + let model = output + .as_ref() + .map(|output| output.physical_properties().model); + + let physical_width = output + .as_ref() + .map(|output| output.physical_properties().size.w as u32); + + let physical_height = output + .as_ref() + .map(|output| output.physical_properties().size.h as u32); + + let make = output + .as_ref() + .map(|output| output.physical_properties().make); + + let x = output.as_ref().map(|output| output.current_location().x); + + let y = output.as_ref().map(|output| output.current_location().y); + + let focused = state + .focus_state + .focused_output + .as_ref() + .and_then(|foc_op| output.as_ref().map(|op| op == foc_op)); + + let tag_ids = output + .as_ref() + .map(|output| { + output.with_state(|state| { + state + .tags + .iter() + .map(|tag| match tag.id() { + TagId::None => unreachable!(), + TagId::Some(id) => id, + }) + .collect::>() + }) + }) + .unwrap_or_default(); + + let _ = sender.send( + pinnacle_api_defs::pinnacle::output::v0alpha1::GetPropertiesResponse { + make, + model, + x, + y, + pixel_width, + pixel_height, + refresh_rate, + physical_width, + physical_height, + focused, + tag_ids, + }, + ); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let response = receiver + .recv() + .await + .ok_or_else(|| Status::internal("internal state was not running"))?; + + Ok(Response::new(response)) + } +} + +pub struct WindowService { + pub sender: StateFnSender, +} + +#[tonic::async_trait] +impl pinnacle_api_defs::pinnacle::window::v0alpha1::window_service_server::WindowService + for WindowService +{ + async fn close(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let f = Box::new(move |state: &mut State| { + let Some(window) = window_id.window(state) else { return }; + + match window { + WindowElement::Wayland(window) => window.toplevel().send_close(), + WindowElement::X11(surface) => surface.close().expect("failed to close x11 win"), + WindowElement::X11OverrideRedirect(_) => { + tracing::warn!("tried to close override redirect window"); + } + _ => unreachable!(), + } + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn set_geometry( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let geometry = request.geometry.unwrap_or_default(); + let x = geometry.x; + let y = geometry.y; + let width = geometry.width; + let height = geometry.height; + + let f = Box::new(move |state: &mut State| { + let Some(window) = window_id.window(state) else { return }; + + // TODO: with no x or y, defaults unmapped windows to 0, 0 + let mut window_loc = state + .space + .element_location(&window) + .unwrap_or((x.unwrap_or_default(), y.unwrap_or_default()).into()); + window_loc.x = x.unwrap_or(window_loc.x); + window_loc.y = y.unwrap_or(window_loc.y); + + let mut window_size = window.geometry().size; + window_size.w = width.unwrap_or(window_size.w); + window_size.h = height.unwrap_or(window_size.h); + + let rect = Rectangle::from_loc_and_size(window_loc, window_size); + // window.change_geometry(rect); + window.with_state(|state| { + use crate::window::window_state::FloatingOrTiled; + state.floating_or_tiled = match state.floating_or_tiled { + FloatingOrTiled::Floating(_) => FloatingOrTiled::Floating(rect), + FloatingOrTiled::Tiled(_) => FloatingOrTiled::Tiled(Some(rect)), + } + }); + + for output in state.space.outputs_for_element(&window) { + state.update_windows(&output); + state.schedule_render(&output); + } + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn set_fullscreen( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let set_or_toggle = match request.set_or_toggle { + Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_fullscreen_request::SetOrToggle::Set(set)) => { + Some(set) + } + Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_fullscreen_request::SetOrToggle::Toggle(_)) => { + None + } + None => return Err(Status::invalid_argument("unspecified set or toggle")), + }; + + let f = Box::new(move |state: &mut State| { + let Some(window) = window_id.window(state) else { + return; + }; + match set_or_toggle { + Some(set) => { + let is_fullscreen = + window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()); + if set != is_fullscreen { + window.toggle_fullscreen(); + } + } + None => window.toggle_fullscreen(), + } + + let Some(output) = window.output(state) else { + return; + }; + + state.update_windows(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn set_maximized( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let set_or_toggle = match request.set_or_toggle { + Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_maximized_request::SetOrToggle::Set(set)) => { + Some(set) + } + Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_maximized_request::SetOrToggle::Toggle(_)) => None, + None => return Err(Status::invalid_argument("unspecified set or toggle")), + }; + + let f = Box::new(move |state: &mut State| { + let Some(window) = window_id.window(state) else { + return; + }; + match set_or_toggle { + Some(set) => { + let is_maximized = + window.with_state(|state| state.fullscreen_or_maximized.is_maximized()); + if set != is_maximized { + window.toggle_maximized(); + } + } + None => window.toggle_maximized(), + } + + let Some(output) = window.output(state) else { + return; + }; + + state.update_windows(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn set_floating( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let set_or_toggle = match request.set_or_toggle { + Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_floating_request::SetOrToggle::Set(set)) => { + Some(set) + } + Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_floating_request::SetOrToggle::Toggle(_)) => None, + None => return Err(Status::invalid_argument("unspecified set or toggle")), + }; + + let f = Box::new(move |state: &mut State| { + let Some(window) = window_id.window(state) else { + return; + }; + match set_or_toggle { + Some(set) => { + let is_floating = + window.with_state(|state| state.floating_or_tiled.is_floating()); + if set != is_floating { + window.toggle_floating(); + } + } + None => window.toggle_floating(), + } + + let Some(output) = window.output(state) else { + return; + }; + + state.update_windows(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn move_to_tag( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let tag_id = TagId::Some( + request + .tag_id + .ok_or_else(|| Status::invalid_argument("no tag specified"))?, + ); + + let f = Box::new(move |state: &mut State| { + let Some(window) = window_id.window(state) else { return }; + let Some(tag) = tag_id.tag(state) else { return }; + window.with_state(|state| { + state.tags = vec![tag.clone()]; + }); + let Some(output) = tag.output(state) else { return }; + state.update_windows(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn set_tag(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let tag_id = TagId::Some( + request + .tag_id + .ok_or_else(|| Status::invalid_argument("no tag specified"))?, + ); + + let set_or_toggle = match request.set_or_toggle { + Some( + pinnacle_api_defs::pinnacle::window::v0alpha1::set_tag_request::SetOrToggle::Set( + set, + ), + ) => Some(set), + Some( + pinnacle_api_defs::pinnacle::window::v0alpha1::set_tag_request::SetOrToggle::Toggle( + _, + ), + ) => None, + None => return Err(Status::invalid_argument("unspecified set or toggle")), + }; + + let f = Box::new(move |state: &mut State| { + let Some(window) = window_id.window(state) else { return }; + let Some(tag) = tag_id.tag(state) else { return }; + + // TODO: turn state.tags into a hashset + match set_or_toggle { + Some(set) => { + if set { + window.with_state(|state| { + state.tags.retain(|tg| tg != &tag); + state.tags.push(tag.clone()); + }) + } else { + window.with_state(|state| { + state.tags.retain(|tg| tg != &tag); + }) + } + } + None => window.with_state(|state| { + if !state.tags.contains(&tag) { + state.tags.push(tag.clone()); + } else { + state.tags.retain(|tg| tg != &tag); + } + }), + } + + let Some(output) = tag.output(state) else { return }; + state.update_windows(&output); + state.schedule_render(&output); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn move_grab(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let button = request + .button + .ok_or_else(|| Status::invalid_argument("no button specified"))?; + + let f = Box::new(move |state: &mut State| { + let Some((FocusTarget::Window(window), _)) = + state.focus_target_under(state.pointer_location) + else { + return; + }; + let Some(wl_surf) = window.wl_surface() else { return }; + let seat = state.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( + state, + &wl_surf, + &seat, + SERIAL_COUNTER.next_serial(), + button, + ); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn resize_grab( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let button = request + .button + .ok_or_else(|| Status::invalid_argument("no button specified"))?; + + let f = Box::new(move |state: &mut State| { + let pointer_loc = state.pointer_location; + let Some((FocusTarget::Window(window), window_loc)) = + state.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) => + { + server::xdg_toplevel::ResizeEdge::TopLeft + } + Point { x, y, .. } + if (half_width..=full_width).contains(&x) + && (window_y..=half_height).contains(&y) => + { + server::xdg_toplevel::ResizeEdge::TopRight + } + Point { x, y, .. } + if (window_x..=half_width).contains(&x) + && (half_height..=full_height).contains(&y) => + { + server::xdg_toplevel::ResizeEdge::BottomLeft + } + Point { x, y, .. } + if (half_width..=full_width).contains(&x) + && (half_height..=full_height).contains(&y) => + { + server::xdg_toplevel::ResizeEdge::BottomRight + } + _ => server::xdg_toplevel::ResizeEdge::None, + }; + + crate::grab::resize_grab::resize_request_server( + state, + &wl_surf, + &state.seat.clone(), + SERIAL_COUNTER.next_serial(), + edges.into(), + button, + ); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } + + async fn get( + &self, + _request: Request, + ) -> Result, Status> { + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< + pinnacle_api_defs::pinnacle::window::v0alpha1::GetResponse, + >(); + + let f = Box::new(move |state: &mut State| { + let window_ids = state + .windows + .iter() + .map(|win| { + win.with_state(|state| match state.id { + WindowId::None => unreachable!(), + WindowId::Some(id) => id, + }) + }) + .collect::>(); + + let _ = sender + .send(pinnacle_api_defs::pinnacle::window::v0alpha1::GetResponse { window_ids }); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let response = receiver + .recv() + .await + .ok_or_else(|| Status::internal("internal state was not running"))?; + + Ok(Response::new(response)) + } + + async fn get_properties( + &self, + request: Request, + ) -> Result< + Response, + Status, + > { + let request = request.into_inner(); + + let window_id = WindowId::Some( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< + pinnacle_api_defs::pinnacle::window::v0alpha1::GetPropertiesResponse, + >(); + + let f = Box::new(move |state: &mut State| { + let window = window_id.window(state); + + let width = window.as_ref().map(|win| win.geometry().size.w); + + let height = window.as_ref().map(|win| win.geometry().size.h); + + let x = window + .as_ref() + .and_then(|win| state.space.element_location(win)) + .map(|loc| loc.x); + + let y = window + .as_ref() + .and_then(|win| state.space.element_location(win)) + .map(|loc| loc.y); + + let geometry = if width.is_none() && height.is_none() && x.is_none() && y.is_none() { + None + } else { + Some(Geometry { + x, + y, + width, + height, + }) + }; + + 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::() + .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(state)?; + state.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)) + .map(|fs_or_max| match fs_or_max { + // TODO: from impl + crate::window::window_state::FullscreenOrMaximized::Neither => { + FullscreenOrMaximized::Neither + } + crate::window::window_state::FullscreenOrMaximized::Fullscreen => { + FullscreenOrMaximized::Fullscreen + } + crate::window::window_state::FullscreenOrMaximized::Maximized => { + FullscreenOrMaximized::Maximized + } + } as i32); + + let tag_ids = window + .as_ref() + .map(|win| { + win.with_state(|state| { + state + .tags + .iter() + .map(|tag| match tag.id() { + TagId::Some(id) => id, + TagId::None => unreachable!(), + }) + .collect::>() + }) + }) + .unwrap_or_default(); + + let _ = sender.send( + pinnacle_api_defs::pinnacle::window::v0alpha1::GetPropertiesResponse { + geometry, + class, + title, + focused, + floating, + fullscreen_or_maximized, + tag_ids, + }, + ); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + let response = receiver + .recv() + .await + .ok_or_else(|| Status::internal("internal state was not running"))?; + + Ok(Response::new(response)) + } + + async fn add_window_rule( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let cond = request + .cond + .ok_or_else(|| Status::invalid_argument("no condition specified"))? + .into(); + + let rule = request + .rule + .ok_or_else(|| Status::invalid_argument("no rule specified"))? + .into(); + + let f = Box::new(move |state: &mut State| { + state.config.window_rules.push((cond, rule)); + }); + + self.sender + .send(f) + .map_err(|_| Status::internal("internal state was not running"))?; + + Ok(Response::new(())) + } +} + +impl From for crate::window::rules::WindowRuleCondition { + fn from(cond: WindowRuleCondition) -> Self { + let cond_any = match cond.any.is_empty() { + true => None, + false => Some( + cond.any + .into_iter() + .map(crate::window::rules::WindowRuleCondition::from) + .collect::>(), + ), + }; + + let cond_all = match cond.all.is_empty() { + true => None, + false => Some( + cond.all + .into_iter() + .map(crate::window::rules::WindowRuleCondition::from) + .collect::>(), + ), + }; + + let class = match cond.classes.is_empty() { + true => None, + false => Some(cond.classes), + }; + + let title = match cond.titles.is_empty() { + true => None, + false => Some(cond.titles), + }; + + let tag = match cond.tags.is_empty() { + true => None, + false => Some(cond.tags.into_iter().map(TagId::Some).collect::>()), + }; + + crate::window::rules::WindowRuleCondition { + cond_any, + cond_all, + class, + title, + tag, + } + } +} + +impl From for crate::window::rules::WindowRule { + fn from(rule: WindowRule) -> Self { + let fullscreen_or_maximized = match rule.fullscreen_or_maximized() { + FullscreenOrMaximized::Unspecified => None, + FullscreenOrMaximized::Neither => { + Some(crate::window::window_state::FullscreenOrMaximized::Neither) + } + FullscreenOrMaximized::Fullscreen => { + Some(crate::window::window_state::FullscreenOrMaximized::Fullscreen) + } + FullscreenOrMaximized::Maximized => { + Some(crate::window::window_state::FullscreenOrMaximized::Maximized) + } + }; + let output = rule.output.map(OutputName); + let tags = match rule.tags.is_empty() { + true => None, + false => Some(rule.tags.into_iter().map(TagId::Some).collect::>()), + }; + let floating_or_tiled = rule.floating.map(|floating| match floating { + true => crate::window::rules::FloatingOrTiled::Floating, + false => crate::window::rules::FloatingOrTiled::Tiled, + }); + let size = rule.width.and_then(|w| { + rule.height.and_then(|h| { + Some(( + NonZeroU32::try_from(w as u32).ok()?, + NonZeroU32::try_from(h as u32).ok()?, + )) }) - } + }); + let location = rule.x.and_then(|x| rule.y.map(|y| (x, y))); - fn register( - &mut self, - poll: &mut calloop::Poll, - token_factory: &mut calloop::TokenFactory, - ) -> calloop::Result<()> { - self.socket.register(poll, token_factory) - } - - fn reregister( - &mut self, - poll: &mut calloop::Poll, - token_factory: &mut calloop::TokenFactory, - ) -> calloop::Result<()> { - self.socket.reregister(poll, token_factory) - } - - fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { - self.socket.unregister(poll) + crate::window::rules::WindowRule { + output, + tags, + floating_or_tiled, + fullscreen_or_maximized, + size, + location, + } } } - -pub struct ApiState { - // TODO: this may not need to be in an arc mutex because of the move to async - /// The stream API messages are being sent through. - pub stream: Option>>, - /// A token used to remove the socket source from the event loop on config restart. - pub socket_token: Option, - /// The sending channel used to send API messages received from the socket source to a handler. - pub tx_channel: Sender, -} diff --git a/src/api/handlers.rs b/src/api/handlers.rs deleted file mode 100644 index 0373f82..0000000 --- a/src/api/handlers.rs +++ /dev/null @@ -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::>(); - 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::>(); - - 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::>(); - - 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::() - .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::>(); - - 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::>() - }) - }); - - 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::>(); - - 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, callback_id: Option) { - 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); - } - } -} diff --git a/src/api/msg.rs b/src/api/msg.rs deleted file mode 100644 index 588177b..0000000 --- a/src/api/msg.rs +++ /dev/null @@ -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, - callback_id: CallbackId, - }, - SetMousebind { - modifiers: Vec, - button: u32, - edge: MouseEdge, - callback_id: CallbackId, - }, - - // Window management - CloseWindow { - window_id: WindowId, - }, - SetWindowSize { - window_id: WindowId, - #[serde(default)] - width: Option, - #[serde(default)] - height: Option, - }, - 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, - }, - RemoveTags { - /// The name of the output you want these tags removed from. - tag_ids: Vec, - }, - SetLayout { - tag_id: TagId, - layout: Layout, - }, - - // Output management - ConnectForAllOutputs { - callback_id: CallbackId, - }, - SetOutputLocation { - output_name: OutputName, - #[serde(default)] - x: Option, - #[serde(default)] - y: Option, - }, - - // Process management - /// Spawn a program with an optional callback. - Spawn { - command: Vec, - #[serde(default)] - callback_id: Option, - }, - SpawnOnce { - command: Vec, - #[serde(default)] - callback_id: Option, - }, - SetEnv { - key: String, - value: String, - }, - - // Pinnacle management - /// Quit the compositor. - Quit, - - // Input management - SetXkbConfig { - #[serde(default)] - rules: Option, - #[serde(default)] - variant: Option, - #[serde(default)] - layout: Option, - #[serde(default)] - model: Option, - #[serde(default)] - options: Option, - }, - - 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> for ModifierMask { - fn from(value: Vec) -> 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 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 { - let mut res = Vec::::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, - }, - 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, - #[serde(default)] - stderr: Option, - #[serde(default)] - exit_code: Option, - #[serde(default)] - exit_msg: Option, - }, - ConnectForAllOutputs { - output_name: String, - }, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub enum RequestResponse { - Window { - window_id: Option, - }, - Windows { - window_ids: Vec, - }, - WindowProps { - size: Option<(i32, i32)>, - loc: Option<(i32, i32)>, - class: Option, - title: Option, - focused: Option, - floating: Option, - fullscreen_or_maximized: Option, - }, - Output { - output_name: Option, - }, - Outputs { - output_names: Vec, - }, - OutputProps { - /// The make of the output. - make: Option, - /// The model of the output. - model: Option, - /// 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, - /// The size of the output, in millimeters. - physical_size: Option<(i32, i32)>, - /// Whether the output is focused or not. - focused: Option, - tag_ids: Option>, - }, - Tags { - tag_ids: Vec, - }, - TagProps { - active: Option, - name: Option, - output_name: Option, - }, -} diff --git a/src/api/protocol.rs b/src/api/protocol.rs deleted file mode 100644 index 23b0fee..0000000 --- a/src/api/protocol.rs +++ /dev/null @@ -1,1887 +0,0 @@ -use std::{ffi::OsString, num::NonZeroU32, pin::Pin, process::Stdio}; - -use pinnacle_api_defs::pinnacle::{ - input::v0alpha1::{ - set_libinput_setting_request::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap}, - set_mousebind_request::MouseEdge, - SetKeybindRequest, SetKeybindResponse, SetLibinputSettingRequest, SetMousebindRequest, - SetMousebindResponse, SetRepeatRateRequest, SetXkbConfigRequest, - }, - output::v0alpha1::{ConnectForAllRequest, ConnectForAllResponse, SetLocationRequest}, - process::v0alpha1::{SetEnvRequest, SpawnRequest, SpawnResponse}, - tag::v0alpha1::{ - AddRequest, AddResponse, RemoveRequest, SetActiveRequest, SetLayoutRequest, SwitchToRequest, - }, - v0alpha1::{Geometry, QuitRequest}, - window::v0alpha1::{ - AddWindowRuleRequest, CloseRequest, FullscreenOrMaximized, MoveGrabRequest, - MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest, SetFullscreenRequest, - SetGeometryRequest, SetMaximizedRequest, SetTagRequest, WindowRule, WindowRuleCondition, - }, -}; -use smithay::{ - desktop::space::SpaceElement, - input::keyboard::XkbConfig, - reexports::{calloop, input as libinput, wayland_protocols::xdg::shell::server}, - utils::{Point, Rectangle, SERIAL_COUNTER}, - wayland::{compositor, shell::xdg::XdgToplevelSurfaceData}, -}; -use sysinfo::ProcessRefreshKind; -use tokio::io::AsyncBufReadExt; -use tokio_stream::Stream; -use tonic::{Request, Response, Status}; - -use crate::{ - config::ConnectorSavedState, - focus::FocusTarget, - input::ModifierMask, - output::OutputName, - state::{State, WithState}, - tag::{Tag, TagId}, - window::{window_state::WindowId, WindowElement}, -}; - -type ResponseStream = Pin> + Send>>; -pub type StateFnSender = calloop::channel::Sender>; - -pub struct PinnacleService { - pub sender: StateFnSender, -} - -#[tonic::async_trait] -impl pinnacle_api_defs::pinnacle::v0alpha1::pinnacle_service_server::PinnacleService - for PinnacleService -{ - async fn quit(&self, _request: Request) -> Result, Status> { - tracing::trace!("PinnacleService.quit"); - let f = Box::new(|state: &mut State| { - state.shutdown(); - }); - // Expect is ok here, if it panics then the state was dropped beforehand - self.sender.send(f).expect("failed to send f"); - - Ok(Response::new(())) - } -} - -pub struct InputService { - pub sender: StateFnSender, -} - -#[tonic::async_trait] -impl pinnacle_api_defs::pinnacle::input::v0alpha1::input_service_server::InputService - for InputService -{ - type SetKeybindStream = ResponseStream; - type SetMousebindStream = ResponseStream; - - async fn set_keybind( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - tracing::debug!(request = ?request); - - // TODO: impl From<&[Modifier]> for ModifierMask - let modifiers = request - .modifiers() - .fold(ModifierMask::empty(), |acc, modifier| match modifier { - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Unspecified => acc, - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Shift => { - acc | ModifierMask::SHIFT - } - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Ctrl => { - acc | ModifierMask::CTRL - } - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Alt => { - acc | ModifierMask::ALT - } - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Super => { - acc | ModifierMask::SUPER - } - }); - let key = request - .key - .ok_or_else(|| Status::invalid_argument("no key specified"))?; - - use pinnacle_api_defs::pinnacle::input::v0alpha1::set_keybind_request::Key; - let keysym = match key { - Key::RawCode(num) => { - tracing::info!("set keybind: {:?}, raw {}", modifiers, num); - xkbcommon::xkb::Keysym::new(num) - } - Key::XkbName(s) => { - if s.chars().count() == 1 { - let Some(ch) = s.chars().next() else { unreachable!() }; - let keysym = xkbcommon::xkb::Keysym::from_char(ch); - tracing::info!("set keybind: {:?}, {:?}", modifiers, keysym); - keysym - } else { - let keysym = - xkbcommon::xkb::keysym_from_name(&s, xkbcommon::xkb::KEYSYM_NO_FLAGS); - tracing::info!("set keybind: {:?}, {:?}", modifiers, keysym); - keysym - } - } - }; - - let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); - - self.sender - .send(Box::new(move |state| { - state - .input_state - .grpc_keybinds - .insert((modifiers, keysym), sender); - })) - .map_err(|_| Status::internal("internal state was not running"))?; - - let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); - - Ok(Response::new(Box::pin(receiver_stream))) - } - - async fn set_mousebind( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - tracing::debug!(request = ?request); - - let modifiers = request - .modifiers() - .fold(ModifierMask::empty(), |acc, modifier| match modifier { - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Unspecified => acc, - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Shift => { - acc | ModifierMask::SHIFT - } - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Ctrl => { - acc | ModifierMask::CTRL - } - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Alt => { - acc | ModifierMask::ALT - } - pinnacle_api_defs::pinnacle::input::v0alpha1::Modifier::Super => { - acc | ModifierMask::SUPER - } - }); - let button = request - .button - .ok_or_else(|| Status::invalid_argument("no key specified"))?; - - let edge = request.edge(); - - if let MouseEdge::Unspecified = edge { - return Err(Status::invalid_argument("press or release not specified")); - } - - let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); - - self.sender - .send(Box::new(move |state| { - state - .input_state - .grpc_mousebinds - .insert((modifiers, button, edge), sender); - })) - .map_err(|_| Status::internal("internal state was not running"))?; - - let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); - - Ok(Response::new(Box::pin(receiver_stream))) - } - - async fn set_xkb_config( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let f = Box::new(move |state: &mut State| { - let new_config = XkbConfig { - rules: request.rules(), - variant: request.variant(), - model: request.model(), - layout: request.layout(), - options: request.options.clone(), - }; - if let Some(kb) = state.seat.get_keyboard() { - if let Err(err) = kb.set_xkb_config(state, new_config) { - tracing::error!("Failed to set xkbconfig: {err}"); - } - } - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_repeat_rate( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let rate = request - .rate - .ok_or_else(|| Status::invalid_argument("no rate specified"))?; - let delay = request - .delay - .ok_or_else(|| Status::invalid_argument("no rate specified"))?; - - let f = Box::new(move |state: &mut State| { - if let Some(kb) = state.seat.get_keyboard() { - kb.change_repeat_info(rate, delay); - } - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_libinput_setting( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let setting = request - .setting - .ok_or_else(|| Status::invalid_argument("no setting specified"))?; - - let discriminant = std::mem::discriminant(&setting); - - use pinnacle_api_defs::pinnacle::input::v0alpha1::set_libinput_setting_request::Setting; - let apply_setting: Box = match setting { - Setting::AccelProfile(profile) => { - let profile = AccelProfile::try_from(profile).unwrap_or(AccelProfile::Unspecified); - - match profile { - AccelProfile::Unspecified => { - return Err(Status::invalid_argument("unspecified accel profile")); - } - AccelProfile::Flat => Box::new(|device| { - let _ = device.config_accel_set_profile(libinput::AccelProfile::Flat); - }), - AccelProfile::Adaptive => Box::new(|device| { - let _ = device.config_accel_set_profile(libinput::AccelProfile::Adaptive); - }), - } - } - Setting::AccelSpeed(speed) => Box::new(move |device| { - let _ = device.config_accel_set_speed(speed); - }), - Setting::CalibrationMatrix(matrix) => { - let matrix = <[f32; 6]>::try_from(matrix.matrix).map_err(|vec| { - Status::invalid_argument(format!( - "matrix requires exactly 6 floats but {} were specified", - vec.len() - )) - })?; - - Box::new(move |device| { - let _ = device.config_calibration_set_matrix(matrix); - }) - } - Setting::ClickMethod(method) => { - let method = ClickMethod::try_from(method).unwrap_or(ClickMethod::Unspecified); - - match method { - ClickMethod::Unspecified => { - return Err(Status::invalid_argument("unspecified click method")) - } - ClickMethod::ButtonAreas => Box::new(|device| { - let _ = device.config_click_set_method(libinput::ClickMethod::ButtonAreas); - }), - ClickMethod::ClickFinger => Box::new(|device| { - let _ = device.config_click_set_method(libinput::ClickMethod::Clickfinger); - }), - } - } - Setting::DisableWhileTyping(disable) => Box::new(move |device| { - let _ = device.config_dwt_set_enabled(disable); - }), - Setting::LeftHanded(enable) => Box::new(move |device| { - let _ = device.config_left_handed_set(enable); - }), - Setting::MiddleEmulation(enable) => Box::new(move |device| { - let _ = device.config_middle_emulation_set_enabled(enable); - }), - Setting::RotationAngle(angle) => Box::new(move |device| { - let _ = device.config_rotation_set_angle(angle % 360); - }), - Setting::ScrollButton(button) => Box::new(move |device| { - let _ = device.config_scroll_set_button(button); - }), - Setting::ScrollButtonLock(enable) => Box::new(move |device| { - let _ = device.config_scroll_set_button_lock(match enable { - true => libinput::ScrollButtonLockState::Enabled, - false => libinput::ScrollButtonLockState::Disabled, - }); - }), - Setting::ScrollMethod(method) => { - let method = ScrollMethod::try_from(method).unwrap_or(ScrollMethod::Unspecified); - - match method { - ScrollMethod::Unspecified => { - return Err(Status::invalid_argument("unspecified scroll method")); - } - ScrollMethod::NoScroll => Box::new(|device| { - let _ = device.config_scroll_set_method(libinput::ScrollMethod::NoScroll); - }), - ScrollMethod::TwoFinger => Box::new(|device| { - let _ = device.config_scroll_set_method(libinput::ScrollMethod::TwoFinger); - }), - ScrollMethod::Edge => Box::new(|device| { - let _ = device.config_scroll_set_method(libinput::ScrollMethod::Edge); - }), - ScrollMethod::OnButtonDown => Box::new(|device| { - let _ = - device.config_scroll_set_method(libinput::ScrollMethod::OnButtonDown); - }), - } - } - Setting::NaturalScroll(enable) => Box::new(move |device| { - let _ = device.config_scroll_set_natural_scroll_enabled(enable); - }), - Setting::TapButtonMap(map) => { - let map = TapButtonMap::try_from(map).unwrap_or(TapButtonMap::Unspecified); - - match map { - TapButtonMap::Unspecified => { - return Err(Status::invalid_argument("unspecified tap button map")); - } - TapButtonMap::LeftRightMiddle => Box::new(|device| { - let _ = device - .config_tap_set_button_map(libinput::TapButtonMap::LeftRightMiddle); - }), - TapButtonMap::LeftMiddleRight => Box::new(|device| { - let _ = device - .config_tap_set_button_map(libinput::TapButtonMap::LeftMiddleRight); - }), - } - } - Setting::TapDrag(enable) => Box::new(move |device| { - let _ = device.config_tap_set_drag_enabled(enable); - }), - Setting::TapDragLock(enable) => Box::new(move |device| { - let _ = device.config_tap_set_drag_lock_enabled(enable); - }), - Setting::Tap(enable) => Box::new(move |device| { - let _ = device.config_tap_set_enabled(enable); - }), - }; - - let f = Box::new(move |state: &mut State| { - for device in state.input_state.libinput_devices.iter_mut() { - apply_setting(device); - } - - state - .input_state - .grpc_libinput_settings - .insert(discriminant, apply_setting); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } -} - -pub struct ProcessService { - pub sender: StateFnSender, -} - -#[tonic::async_trait] -impl pinnacle_api_defs::pinnacle::process::v0alpha1::process_service_server::ProcessService - for ProcessService -{ - type SpawnStream = ResponseStream; - - async fn spawn( - &self, - request: Request, - ) -> Result, Status> { - tracing::debug!("ProcessService.spawn"); - let request = request.into_inner(); - - let once = request.once(); - let has_callback = request.has_callback(); - let mut command = request.args.into_iter(); - let arg0 = command - .next() - .ok_or_else(|| Status::invalid_argument("no args specified"))?; - - let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); - - let f = Box::new(move |state: &mut State| { - if once { - state - .system_processes - .refresh_processes_specifics(ProcessRefreshKind::new()); - - let compositor_pid = std::process::id(); - let already_running = - state - .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 { - return; - } - } - - let Ok(mut child) = tokio::process::Command::new(OsString::from(arg0.clone())) - .envs( - [("WAYLAND_DISPLAY", state.socket_name.clone())] - .into_iter() - .chain(state.xdisplay.map(|xdisp| ("DISPLAY", format!(":{xdisp}")))), - ) - .stdin(match has_callback { - true => Stdio::piped(), - false => Stdio::null(), - }) - .stdout(match has_callback { - true => Stdio::piped(), - false => Stdio::null(), - }) - .stderr(match has_callback { - true => Stdio::piped(), - false => Stdio::null(), - }) - .args(command) - .spawn() - else { - tracing::warn!("Tried to run {arg0}, but it doesn't exist",); - return; - }; - - if !has_callback { - return; - } - - let stdout = child.stdout.take(); - let stderr = child.stderr.take(); - - if let Some(stdout) = stdout { - let sender = sender.clone(); - - let mut reader = tokio::io::BufReader::new(stdout).lines(); - - tokio::spawn(async move { - while let Ok(Some(line)) = reader.next_line().await { - let response: Result<_, Status> = Ok(SpawnResponse { - stdout: Some(line), - ..Default::default() - }); - - // TODO: handle error - match sender.send(response) { - Ok(_) => (), - Err(err) => { - tracing::error!(err = ?err); - break; - } - } - } - }); - } - - if let Some(stderr) = stderr { - let sender = sender.clone(); - - let mut reader = tokio::io::BufReader::new(stderr).lines(); - - tokio::spawn(async move { - while let Ok(Some(line)) = reader.next_line().await { - let response: Result<_, Status> = Ok(SpawnResponse { - stderr: Some(line), - ..Default::default() - }); - - // TODO: handle error - match sender.send(response) { - Ok(_) => (), - Err(err) => { - tracing::error!(err = ?err); - break; - } - } - } - }); - } - - tokio::spawn(async move { - match child.wait().await { - Ok(exit_status) => { - let response = Ok(SpawnResponse { - exit_code: exit_status.code(), - exit_message: Some(exit_status.to_string()), - ..Default::default() - }); - // TODO: handle error - let _ = sender.send(response); - } - Err(err) => tracing::warn!("child wait() err: {err}"), - } - }); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); - - Ok(Response::new(Box::pin(receiver_stream))) - } - - async fn set_env(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let key = request - .key - .ok_or_else(|| Status::invalid_argument("no key specified"))?; - let value = request - .value - .ok_or_else(|| Status::invalid_argument("no value specified"))?; - - if key.is_empty() { - return Err(Status::invalid_argument("key was empty")); - } - - if key.contains(['\0', '=']) { - return Err(Status::invalid_argument("key contained NUL or =")); - } - - if value.contains('\0') { - return Err(Status::invalid_argument("value contained NUL")); - } - - std::env::set_var(key, value); - - Ok(Response::new(())) - } -} - -pub struct TagService { - pub sender: StateFnSender, -} - -#[tonic::async_trait] -impl pinnacle_api_defs::pinnacle::tag::v0alpha1::tag_service_server::TagService for TagService { - async fn set_active(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let tag_id = TagId::Some( - request - .tag_id - .ok_or_else(|| Status::invalid_argument("no tag specified"))?, - ); - - let set_or_toggle = match request.set_or_toggle { - Some( - pinnacle_api_defs::pinnacle::tag::v0alpha1::set_active_request::SetOrToggle::Set( - set, - ), - ) => Some(set), - Some( - pinnacle_api_defs::pinnacle::tag::v0alpha1::set_active_request::SetOrToggle::Toggle( - _, - ), - ) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; - - let f = Box::new(move |state: &mut State| { - let Some(tag) = tag_id.tag(state) else { - return; - }; - match set_or_toggle { - Some(set) => tag.set_active(set), - None => tag.set_active(!tag.active()), - } - - let Some(output) = tag.output(state) else { - return; - }; - - state.update_windows(&output); - state.update_focus(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn switch_to(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let tag_id = TagId::Some( - request - .tag_id - .ok_or_else(|| Status::invalid_argument("no tag specified"))?, - ); - - let f = Box::new(move |state: &mut State| { - let Some(tag) = tag_id.tag(state) else { return }; - let Some(output) = tag.output(state) else { return }; - - output.with_state(|state| { - for op_tag in state.tags.iter_mut() { - op_tag.set_active(false); - } - tag.set_active(true); - }); - - state.update_windows(&output); - state.update_focus(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn add(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let output_name = OutputName( - request - .output_name - .ok_or_else(|| Status::invalid_argument("no output specified"))?, - ); - - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::(); - - let f = Box::new(move |state: &mut State| { - let new_tags = request - .tag_names - .into_iter() - .map(Tag::new) - .collect::>(); - - let tag_ids = new_tags - .iter() - .map(|tag| tag.id()) - .map(|id| match id { - TagId::None => unreachable!(), - TagId::Some(id) => id, - }) - .collect::>(); - - let _ = sender.send(AddResponse { tag_ids }); - - if let Some(saved_state) = state.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 { - state.config.connector_saved_states.insert( - output_name.clone(), - crate::config::ConnectorSavedState { - tags: new_tags.clone(), - ..Default::default() - }, - ); - } - - let Some(output) = state - .space - .outputs() - .find(|output| output.name() == output_name.0) - else { - return; - }; - - output.with_state(|state| { - state.tags.extend(new_tags.clone()); - tracing::debug!("tags added, are now {:?}", state.tags); - }); - - for tag in new_tags { - for window in state.windows.iter() { - window.with_state(|state| { - for win_tag in state.tags.iter_mut() { - if win_tag.id() == tag.id() { - *win_tag = tag.clone(); - } - } - }); - } - } - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let response = receiver - .recv() - .await - .ok_or_else(|| Status::internal("internal state was not running"))?; - - Ok(Response::new(response)) - } - - // TODO: test - async fn remove(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let tag_ids = request.tag_ids.into_iter().map(TagId::Some); - - let f = Box::new(move |state: &mut State| { - let tags_to_remove = tag_ids.flat_map(|id| id.tag(state)).collect::>(); - - for output in state.space.outputs().cloned().collect::>() { - // TODO: seriously, convert state.tags into a hashset - output.with_state(|state| { - for tag_to_remove in tags_to_remove.iter() { - state.tags.retain(|tag| tag != tag_to_remove); - } - }); - - state.update_windows(&output); - state.schedule_render(&output); - } - - for conn_saved_state in state.config.connector_saved_states.values_mut() { - for tag_to_remove in tags_to_remove.iter() { - conn_saved_state.tags.retain(|tag| tag != tag_to_remove); - } - } - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_layout(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let tag_id = TagId::Some( - request - .tag_id - .ok_or_else(|| Status::invalid_argument("no tag specified"))?, - ); - - use pinnacle_api_defs::pinnacle::tag::v0alpha1::set_layout_request::Layout; - - // TODO: from impl - let layout = match request.layout() { - Layout::Unspecified => return Err(Status::invalid_argument("unspecified layout")), - Layout::MasterStack => crate::layout::Layout::MasterStack, - Layout::Dwindle => crate::layout::Layout::Dwindle, - Layout::Spiral => crate::layout::Layout::Spiral, - Layout::CornerTopLeft => crate::layout::Layout::CornerTopLeft, - Layout::CornerTopRight => crate::layout::Layout::CornerTopRight, - Layout::CornerBottomLeft => crate::layout::Layout::CornerBottomLeft, - Layout::CornerBottomRight => crate::layout::Layout::CornerBottomRight, - }; - - let f = Box::new(move |state: &mut State| { - let Some(tag) = tag_id.tag(state) else { return }; - - tag.set_layout(layout); - - let Some(output) = tag.output(state) else { return }; - - state.update_windows(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn get( - &self, - _request: Request, - ) -> Result, Status> { - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< - pinnacle_api_defs::pinnacle::tag::v0alpha1::GetResponse, - >(); - - let f = Box::new(move |state: &mut State| { - let tag_ids = state - .space - .outputs() - .flat_map(|op| op.with_state(|state| state.tags.clone())) - .map(|tag| tag.id()) - .map(|id| match id { - TagId::None => unreachable!(), - TagId::Some(id) => id, - }) - .collect::>(); - - let _ = - sender.send(pinnacle_api_defs::pinnacle::tag::v0alpha1::GetResponse { tag_ids }); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let response = receiver - .recv() - .await - .ok_or_else(|| Status::internal("internal state was not running"))?; - - Ok(Response::new(response)) - } - - async fn get_properties( - &self, - request: Request, - ) -> Result, Status> - { - let request = request.into_inner(); - - let tag_id = TagId::Some( - request - .tag_id - .ok_or_else(|| Status::invalid_argument("no tag specified"))?, - ); - - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< - pinnacle_api_defs::pinnacle::tag::v0alpha1::GetPropertiesResponse, - >(); - - let f = Box::new(move |state: &mut State| { - let tag = tag_id.tag(state); - - let output_name = tag - .as_ref() - .and_then(|tag| tag.output(state)) - .map(|output| output.name()); - let active = tag.as_ref().map(|tag| tag.active()); - let name = tag.as_ref().map(|tag| tag.name()); - - let _ = sender.send( - pinnacle_api_defs::pinnacle::tag::v0alpha1::GetPropertiesResponse { - active, - name, - output_name, - }, - ); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let response = receiver - .recv() - .await - .ok_or_else(|| Status::internal("internal state was not running"))?; - - Ok(Response::new(response)) - } -} - -pub struct OutputService { - pub sender: StateFnSender, -} - -#[tonic::async_trait] -impl pinnacle_api_defs::pinnacle::output::v0alpha1::output_service_server::OutputService - for OutputService -{ - type ConnectForAllStream = ResponseStream; - - async fn set_location( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let output_name = OutputName( - request - .output_name - .ok_or_else(|| Status::invalid_argument("no output specified"))?, - ); - - let x = request.x; - let y = request.y; - - let f = Box::new(move |state: &mut State| { - if let Some(saved_state) = state.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 { - state.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(state) 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)); - state.space.map_output(&output, loc); - tracing::debug!("Mapping output {} to {loc:?}", output.name()); - state.update_windows(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - // TODO: remove this and integrate it into a signal/event system - async fn connect_for_all( - &self, - _request: Request, - ) -> Result, Status> { - tracing::trace!("OutputService.connect_for_all"); - let (sender, receiver) = - tokio::sync::mpsc::unbounded_channel::>(); - - let f = Box::new(move |state: &mut State| { - // for output in state.space.outputs() { - // let _ = sender.send(Ok(ConnectForAllResponse { - // output_name: Some(output.name()), - // })); - // tracing::debug!(name = output.name(), "sent connect_for_all"); - // } - - state.config.grpc_output_callback_senders.push(sender); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let receiver_stream = tokio_stream::wrappers::UnboundedReceiverStream::new(receiver); - - Ok(Response::new(Box::pin(receiver_stream))) - } - - async fn get( - &self, - _request: Request, - ) -> Result, Status> { - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< - pinnacle_api_defs::pinnacle::output::v0alpha1::GetResponse, - >(); - - let f = Box::new(move |state: &mut State| { - let output_names = state - .space - .outputs() - .map(|output| output.name()) - .collect::>(); - - let _ = sender - .send(pinnacle_api_defs::pinnacle::output::v0alpha1::GetResponse { output_names }); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let response = receiver - .recv() - .await - .ok_or_else(|| Status::internal("internal state was not running"))?; - - Ok(Response::new(response)) - } - - async fn get_properties( - &self, - request: Request, - ) -> Result< - Response, - Status, - > { - let request = request.into_inner(); - - let output_name = OutputName( - request - .output_name - .ok_or_else(|| Status::invalid_argument("no output specified"))?, - ); - - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< - pinnacle_api_defs::pinnacle::output::v0alpha1::GetPropertiesResponse, - >(); - - let f = Box::new(move |state: &mut State| { - let output = output_name.output(state); - - let pixel_width = output - .as_ref() - .and_then(|output| output.current_mode().map(|mode| mode.size.w as u32)); - - let pixel_height = output - .as_ref() - .and_then(|output| output.current_mode().map(|mode| mode.size.h as u32)); - - let refresh_rate = output - .as_ref() - .and_then(|output| output.current_mode().map(|mode| mode.refresh as u32)); - - let model = output - .as_ref() - .map(|output| output.physical_properties().model); - - let physical_width = output - .as_ref() - .map(|output| output.physical_properties().size.w as u32); - - let physical_height = output - .as_ref() - .map(|output| output.physical_properties().size.h as u32); - - let make = output - .as_ref() - .map(|output| output.physical_properties().make); - - let x = output.as_ref().map(|output| output.current_location().x); - - let y = output.as_ref().map(|output| output.current_location().y); - - let focused = state - .focus_state - .focused_output - .as_ref() - .and_then(|foc_op| output.as_ref().map(|op| op == foc_op)); - - let tag_ids = output - .as_ref() - .map(|output| { - output.with_state(|state| { - state - .tags - .iter() - .map(|tag| match tag.id() { - TagId::None => unreachable!(), - TagId::Some(id) => id, - }) - .collect::>() - }) - }) - .unwrap_or_default(); - - let _ = sender.send( - pinnacle_api_defs::pinnacle::output::v0alpha1::GetPropertiesResponse { - make, - model, - x, - y, - pixel_width, - pixel_height, - refresh_rate, - physical_width, - physical_height, - focused, - tag_ids, - }, - ); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let response = receiver - .recv() - .await - .ok_or_else(|| Status::internal("internal state was not running"))?; - - Ok(Response::new(response)) - } -} - -pub struct WindowService { - pub sender: StateFnSender, -} - -#[tonic::async_trait] -impl pinnacle_api_defs::pinnacle::window::v0alpha1::window_service_server::WindowService - for WindowService -{ - async fn close(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let f = Box::new(move |state: &mut State| { - let Some(window) = window_id.window(state) else { return }; - - match window { - WindowElement::Wayland(window) => window.toplevel().send_close(), - WindowElement::X11(surface) => surface.close().expect("failed to close x11 win"), - WindowElement::X11OverrideRedirect(_) => { - tracing::warn!("tried to close override redirect window"); - } - _ => unreachable!(), - } - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_geometry( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let geometry = request.geometry.unwrap_or_default(); - let x = geometry.x; - let y = geometry.y; - let width = geometry.width; - let height = geometry.height; - - let f = Box::new(move |state: &mut State| { - let Some(window) = window_id.window(state) else { return }; - - // TODO: with no x or y, defaults unmapped windows to 0, 0 - let mut window_loc = state - .space - .element_location(&window) - .unwrap_or((x.unwrap_or_default(), y.unwrap_or_default()).into()); - window_loc.x = x.unwrap_or(window_loc.x); - window_loc.y = y.unwrap_or(window_loc.y); - - let mut window_size = window.geometry().size; - window_size.w = width.unwrap_or(window_size.w); - window_size.h = height.unwrap_or(window_size.h); - - let rect = Rectangle::from_loc_and_size(window_loc, window_size); - // window.change_geometry(rect); - window.with_state(|state| { - use crate::window::window_state::FloatingOrTiled; - state.floating_or_tiled = match state.floating_or_tiled { - FloatingOrTiled::Floating(_) => FloatingOrTiled::Floating(rect), - FloatingOrTiled::Tiled(_) => FloatingOrTiled::Tiled(Some(rect)), - } - }); - - for output in state.space.outputs_for_element(&window) { - state.update_windows(&output); - state.schedule_render(&output); - } - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_fullscreen( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let set_or_toggle = match request.set_or_toggle { - Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_fullscreen_request::SetOrToggle::Set(set)) => { - Some(set) - } - Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_fullscreen_request::SetOrToggle::Toggle(_)) => { - None - } - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; - - let f = Box::new(move |state: &mut State| { - let Some(window) = window_id.window(state) else { - return; - }; - match set_or_toggle { - Some(set) => { - let is_fullscreen = - window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()); - if set != is_fullscreen { - window.toggle_fullscreen(); - } - } - None => window.toggle_fullscreen(), - } - - let Some(output) = window.output(state) else { - return; - }; - - state.update_windows(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_maximized( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let set_or_toggle = match request.set_or_toggle { - Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_maximized_request::SetOrToggle::Set(set)) => { - Some(set) - } - Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_maximized_request::SetOrToggle::Toggle(_)) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; - - let f = Box::new(move |state: &mut State| { - let Some(window) = window_id.window(state) else { - return; - }; - match set_or_toggle { - Some(set) => { - let is_maximized = - window.with_state(|state| state.fullscreen_or_maximized.is_maximized()); - if set != is_maximized { - window.toggle_maximized(); - } - } - None => window.toggle_maximized(), - } - - let Some(output) = window.output(state) else { - return; - }; - - state.update_windows(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_floating( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let set_or_toggle = match request.set_or_toggle { - Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_floating_request::SetOrToggle::Set(set)) => { - Some(set) - } - Some(pinnacle_api_defs::pinnacle::window::v0alpha1::set_floating_request::SetOrToggle::Toggle(_)) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; - - let f = Box::new(move |state: &mut State| { - let Some(window) = window_id.window(state) else { - return; - }; - match set_or_toggle { - Some(set) => { - let is_floating = - window.with_state(|state| state.floating_or_tiled.is_floating()); - if set != is_floating { - window.toggle_floating(); - } - } - None => window.toggle_floating(), - } - - let Some(output) = window.output(state) else { - return; - }; - - state.update_windows(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn move_to_tag( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let tag_id = TagId::Some( - request - .tag_id - .ok_or_else(|| Status::invalid_argument("no tag specified"))?, - ); - - let f = Box::new(move |state: &mut State| { - let Some(window) = window_id.window(state) else { return }; - let Some(tag) = tag_id.tag(state) else { return }; - window.with_state(|state| { - state.tags = vec![tag.clone()]; - }); - let Some(output) = tag.output(state) else { return }; - state.update_windows(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn set_tag(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let tag_id = TagId::Some( - request - .tag_id - .ok_or_else(|| Status::invalid_argument("no tag specified"))?, - ); - - let set_or_toggle = match request.set_or_toggle { - Some( - pinnacle_api_defs::pinnacle::window::v0alpha1::set_tag_request::SetOrToggle::Set( - set, - ), - ) => Some(set), - Some( - pinnacle_api_defs::pinnacle::window::v0alpha1::set_tag_request::SetOrToggle::Toggle( - _, - ), - ) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; - - let f = Box::new(move |state: &mut State| { - let Some(window) = window_id.window(state) else { return }; - let Some(tag) = tag_id.tag(state) else { return }; - - // TODO: turn state.tags into a hashset - match set_or_toggle { - Some(set) => { - if set { - window.with_state(|state| { - state.tags.retain(|tg| tg != &tag); - state.tags.push(tag.clone()); - }) - } else { - window.with_state(|state| { - state.tags.retain(|tg| tg != &tag); - }) - } - } - None => window.with_state(|state| { - if !state.tags.contains(&tag) { - state.tags.push(tag.clone()); - } else { - state.tags.retain(|tg| tg != &tag); - } - }), - } - - let Some(output) = tag.output(state) else { return }; - state.update_windows(&output); - state.schedule_render(&output); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn move_grab(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let button = request - .button - .ok_or_else(|| Status::invalid_argument("no button specified"))?; - - let f = Box::new(move |state: &mut State| { - let Some((FocusTarget::Window(window), _)) = - state.focus_target_under(state.pointer_location) - else { - return; - }; - let Some(wl_surf) = window.wl_surface() else { return }; - let seat = state.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( - state, - &wl_surf, - &seat, - SERIAL_COUNTER.next_serial(), - button, - ); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn resize_grab( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let button = request - .button - .ok_or_else(|| Status::invalid_argument("no button specified"))?; - - let f = Box::new(move |state: &mut State| { - let pointer_loc = state.pointer_location; - let Some((FocusTarget::Window(window), window_loc)) = - state.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) => - { - server::xdg_toplevel::ResizeEdge::TopLeft - } - Point { x, y, .. } - if (half_width..=full_width).contains(&x) - && (window_y..=half_height).contains(&y) => - { - server::xdg_toplevel::ResizeEdge::TopRight - } - Point { x, y, .. } - if (window_x..=half_width).contains(&x) - && (half_height..=full_height).contains(&y) => - { - server::xdg_toplevel::ResizeEdge::BottomLeft - } - Point { x, y, .. } - if (half_width..=full_width).contains(&x) - && (half_height..=full_height).contains(&y) => - { - server::xdg_toplevel::ResizeEdge::BottomRight - } - _ => server::xdg_toplevel::ResizeEdge::None, - }; - - crate::grab::resize_grab::resize_request_server( - state, - &wl_surf, - &state.seat.clone(), - SERIAL_COUNTER.next_serial(), - edges.into(), - button, - ); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } - - async fn get( - &self, - _request: Request, - ) -> Result, Status> { - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< - pinnacle_api_defs::pinnacle::window::v0alpha1::GetResponse, - >(); - - let f = Box::new(move |state: &mut State| { - let window_ids = state - .windows - .iter() - .map(|win| { - win.with_state(|state| match state.id { - WindowId::None => unreachable!(), - WindowId::Some(id) => id, - }) - }) - .collect::>(); - - let _ = sender - .send(pinnacle_api_defs::pinnacle::window::v0alpha1::GetResponse { window_ids }); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let response = receiver - .recv() - .await - .ok_or_else(|| Status::internal("internal state was not running"))?; - - Ok(Response::new(response)) - } - - async fn get_properties( - &self, - request: Request, - ) -> Result< - Response, - Status, - > { - let request = request.into_inner(); - - let window_id = WindowId::Some( - request - .window_id - .ok_or_else(|| Status::invalid_argument("no window specified"))?, - ); - - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::< - pinnacle_api_defs::pinnacle::window::v0alpha1::GetPropertiesResponse, - >(); - - let f = Box::new(move |state: &mut State| { - let window = window_id.window(state); - - let width = window.as_ref().map(|win| win.geometry().size.w); - - let height = window.as_ref().map(|win| win.geometry().size.h); - - let x = window - .as_ref() - .and_then(|win| state.space.element_location(win)) - .map(|loc| loc.x); - - let y = window - .as_ref() - .and_then(|win| state.space.element_location(win)) - .map(|loc| loc.y); - - let geometry = if width.is_none() && height.is_none() && x.is_none() && y.is_none() { - None - } else { - Some(Geometry { - x, - y, - width, - height, - }) - }; - - 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::() - .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(state)?; - state.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)) - .map(|fs_or_max| match fs_or_max { - // TODO: from impl - crate::window::window_state::FullscreenOrMaximized::Neither => { - FullscreenOrMaximized::Neither - } - crate::window::window_state::FullscreenOrMaximized::Fullscreen => { - FullscreenOrMaximized::Fullscreen - } - crate::window::window_state::FullscreenOrMaximized::Maximized => { - FullscreenOrMaximized::Maximized - } - } as i32); - - let tag_ids = window - .as_ref() - .map(|win| { - win.with_state(|state| { - state - .tags - .iter() - .map(|tag| match tag.id() { - TagId::Some(id) => id, - TagId::None => unreachable!(), - }) - .collect::>() - }) - }) - .unwrap_or_default(); - - let _ = sender.send( - pinnacle_api_defs::pinnacle::window::v0alpha1::GetPropertiesResponse { - geometry, - class, - title, - focused, - floating, - fullscreen_or_maximized, - tag_ids, - }, - ); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - let response = receiver - .recv() - .await - .ok_or_else(|| Status::internal("internal state was not running"))?; - - Ok(Response::new(response)) - } - - async fn add_window_rule( - &self, - request: Request, - ) -> Result, Status> { - let request = request.into_inner(); - - let cond = request - .cond - .ok_or_else(|| Status::invalid_argument("no condition specified"))? - .into(); - - let rule = request - .rule - .ok_or_else(|| Status::invalid_argument("no rule specified"))? - .into(); - - let f = Box::new(move |state: &mut State| { - state.config.window_rules.push((cond, rule)); - }); - - self.sender - .send(f) - .map_err(|_| Status::internal("internal state was not running"))?; - - Ok(Response::new(())) - } -} - -impl From for crate::window::rules::WindowRuleCondition { - fn from(cond: WindowRuleCondition) -> Self { - let cond_any = match cond.any.is_empty() { - true => None, - false => Some( - cond.any - .into_iter() - .map(crate::window::rules::WindowRuleCondition::from) - .collect::>(), - ), - }; - - let cond_all = match cond.all.is_empty() { - true => None, - false => Some( - cond.all - .into_iter() - .map(crate::window::rules::WindowRuleCondition::from) - .collect::>(), - ), - }; - - let class = match cond.classes.is_empty() { - true => None, - false => Some(cond.classes), - }; - - let title = match cond.titles.is_empty() { - true => None, - false => Some(cond.titles), - }; - - let tag = match cond.tags.is_empty() { - true => None, - false => Some(cond.tags.into_iter().map(TagId::Some).collect::>()), - }; - - crate::window::rules::WindowRuleCondition { - cond_any, - cond_all, - class, - title, - tag, - } - } -} - -impl From for crate::window::rules::WindowRule { - fn from(rule: WindowRule) -> Self { - let fullscreen_or_maximized = match rule.fullscreen_or_maximized() { - FullscreenOrMaximized::Unspecified => None, - FullscreenOrMaximized::Neither => { - Some(crate::window::window_state::FullscreenOrMaximized::Neither) - } - FullscreenOrMaximized::Fullscreen => { - Some(crate::window::window_state::FullscreenOrMaximized::Fullscreen) - } - FullscreenOrMaximized::Maximized => { - Some(crate::window::window_state::FullscreenOrMaximized::Maximized) - } - }; - let output = rule.output.map(OutputName); - let tags = match rule.tags.is_empty() { - true => None, - false => Some(rule.tags.into_iter().map(TagId::Some).collect::>()), - }; - let floating_or_tiled = rule.floating.map(|floating| match floating { - true => crate::window::rules::FloatingOrTiled::Floating, - false => crate::window::rules::FloatingOrTiled::Tiled, - }); - let size = rule.width.and_then(|w| { - rule.height.and_then(|h| { - Some(( - NonZeroU32::try_from(w as u32).ok()?, - NonZeroU32::try_from(h as u32).ok()?, - )) - }) - }); - let location = rule.x.and_then(|x| rule.y.map(|y| (x, y))); - - crate::window::rules::WindowRule { - output, - tags, - floating_or_tiled, - fullscreen_or_maximized, - size, - location, - } - } -} diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 32a9b76..c94a8fb 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -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()), + })); + } } } diff --git a/src/config.rs b/src/config.rs index 1f87588..50f246c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, - pub key: Key, + modifiers: Vec, + key: Key, +} + +#[derive(serde::Deserialize, Debug, Clone, Copy)] +enum Modifier { + Shift, + Ctrl, + Alt, + Super, +} + +// TODO: refactor metaconfig input +impl From> for ModifierMask { + fn from(mods: Vec) -> 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, - pub grpc_output_callback_senders: - Vec>>, + pub output_callback_senders: Vec>>, /// Saved states when outputs are disconnected pub connector_saved_states: HashMap, } @@ -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; })); diff --git a/src/input.rs b/src/input.rs index 7c3b2ab..b45e13b 100644 --- a/src/input.rs +++ b/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, + pub reload_keybind: Option<(ModifierMask, Keysym)>, + pub kill_keybind: Option<(ModifierMask, Keysym)>, /// All libinput devices that have been connected pub libinput_devices: Vec, - pub grpc_keybinds: + pub keybinds: HashMap<(ModifierMask, Keysym), UnboundedSender>>, - pub grpc_mousebinds: HashMap< + pub mousebinds: HashMap< (ModifierMask, u32, set_mousebind_request::MouseEdge), UnboundedSender>, >, #[allow(clippy::type_complexity)] - pub grpc_libinput_settings: - HashMap, Box>, + pub libinput_settings: HashMap, Box>, } 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>), + CallCallback(UnboundedSender>), 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::::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 {})); } diff --git a/src/input/libinput.rs b/src/input/libinput.rs index 325cb54..f46b4fd 100644 --- a/src/input/libinput.rs +++ b/src/input/libinput.rs @@ -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(&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) { @@ -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); } diff --git a/src/main.rs b/src/main.rs index 8edef8b..c690d90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; diff --git a/src/state.rs b/src/state.rs index 224e28e..b61c23b 100644 --- a/src/state.rs +++ b/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::(); - 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::(&display_handle), input_state: InputState::new(), - api_state: ApiState { - stream: None, - socket_token: None, - tx_channel, - }, focus_state: FocusState::new(), config: Config::default(),