From 5d14402cc8b3c0f01770bf1d3e500af3b0db31f0 Mon Sep 17 00:00:00 2001 From: Akshat Deshpande Date: Mon, 25 Jul 2022 19:47:22 -0500 Subject: [PATCH] revert to 33c1cc80b6 "make destroying focused client actually work" --- .gitattributes | 1 - Cargo.lock | 1 - Cargo.toml | 1 - src/config.rs | 88 +---------------------------- src/main.rs | 131 +++++--------------------------------------- src/msg/config.rs | 2 - src/msg/main.rs | 39 ++----------- src/msg_listener.rs | 85 ++-------------------------- src/x11/client.rs | 73 +++--------------------- 9 files changed, 31 insertions(+), 390 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 176a458..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto diff --git a/Cargo.lock b/Cargo.lock index 035c264..0771a76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,7 +604,6 @@ name = "xcrab" version = "0.1.0" dependencies = [ "breadx", - "gluten-keyboard", "lazy_static", "serde", "slotmap", diff --git a/Cargo.toml b/Cargo.toml index 3329ccb..16f2527 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ toml = "0.5.9" serde = { version = "1.0.137", features = ["derive"]} lazy_static = "1.4.0" slotmap = "1.0.6" -gluten-keyboard = "0.1.2" [[bin]] name = "xcrab-msg" diff --git a/src/config.rs b/src/config.rs index b8a63df..74e0f40 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,14 +15,8 @@ #![allow(dead_code, clippy::module_name_repetitions)] -use crate::msg_listener::Action; use crate::Result; -use breadx::auto::xproto::KeyButMask; -use serde::{ - de::{Deserializer, Visitor}, - Deserialize, -}; -use std::collections::HashMap; +use serde::Deserialize; use std::path::PathBuf; #[derive(Clone, Debug, Deserialize)] @@ -33,9 +27,6 @@ pub struct XcrabConfig { gap_size: Option, outer_gap_size: Option, pub msg: Option, - #[allow(clippy::zero_sized_map_values)] // TODO: Action will be expanded in the future - #[serde(default)] - pub binds: HashMap, } #[derive(Clone, Debug, Deserialize)] @@ -57,7 +48,6 @@ impl Default for XcrabConfig { gap_size: Some(DEFAULT_GAP_SIZE), outer_gap_size: None, msg: Some(XcrabMsgConfig::default()), - binds: HashMap::new(), } } } @@ -106,79 +96,3 @@ pub fn load_file() -> Result { fn get_home() -> Result { Ok(std::env::var("HOME")?) } - -struct ActionVisitor; -impl<'de> Visitor<'de> for ActionVisitor { - type Value = Action; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid WM action") - } - - fn visit_str(self, value: &str) -> std::result::Result { - value.parse().map_err(|s| E::custom(s)) - } -} - -impl<'de> Deserialize<'de> for Action { - fn deserialize>(deserializer: D) -> std::result::Result { - deserializer.deserialize_str(ActionVisitor) - } -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] -pub struct Keybind { - pub key: char, - pub mods: KeyButMask, -} - -struct KeybindVisitor; -impl<'de> Visitor<'de> for KeybindVisitor { - type Value = Keybind; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a keybind in the form of 'M-x'") - } - - fn visit_str(self, value: &str) -> std::result::Result { - let mut mask = KeyButMask::default(); - - for part in value.split('-') { - if part.len() > 1 { - return Err(E::custom("parts may only contain one character")); - } - - let c = part - .chars() - .next() - .ok_or_else(|| E::custom("parts must contain at least one character"))?; - - if c.is_ascii_uppercase() { - // FIXME: add more as required - match c { - 'C' => mask.set_control(true), - 'S' => mask.set_shift(true), - 'A' => mask.set_mod1(true), // alt key - 'W' => mask.set_mod4(true), // super key, 'w' for windows because S is taken - _ => return Err(E::custom(format!("no such modifier: {}", c))), - }; - } - } - - // ignores extraneous keys - let c = value - .split('-') - .flat_map(str::chars) - .find(char::is_ascii_lowercase) - .ok_or_else(|| E::custom("must specify one normal key"))? - .to_ascii_uppercase(); - - Ok(Keybind { key: c, mods: mask }) - } -} - -impl<'de> Deserialize<'de> for Keybind { - fn deserialize>(deserializer: D) -> std::result::Result { - deserializer.deserialize_str(KeybindVisitor) - } -} diff --git a/src/main.rs b/src/main.rs index 1a338d6..a28a758 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,9 +16,9 @@ #![warn(clippy::pedantic)] use std::fmt::{Debug, Display}; +use std::ops::Deref; use breadx::{ - keyboard::KeyboardState, prelude::{AsyncDisplay, AsyncDisplayXprotoExt, MapState}, traits::DisplayBase, AsyncDisplayConnection, AsyncDisplayExt, BreadError, ConfigureWindowParameters, Event, @@ -27,7 +27,7 @@ use breadx::{ use lazy_static::lazy_static; -use tokio::sync::mpsc; +use tokio::sync::mpsc::unbounded_channel; mod config; mod msg_listener; @@ -42,7 +42,6 @@ pub enum XcrabError { Toml(toml::de::Error), Var(std::env::VarError), ClientDoesntExist, - Custom(String), } impl From for XcrabError { @@ -69,31 +68,19 @@ impl From for XcrabError { } } -impl From for XcrabError { - fn from(v: String) -> Self { - Self::Custom(v) - } -} - lazy_static! { - // pub static ref CONFIG: config::XcrabConfig = config::load_file().unwrap_or_default(); - pub static ref CONFIG: config::XcrabConfig = config::load_file().unwrap_or_else(|e| { - println!("[CONFIG] Error parsing config: {e}"); - println!("[CONFIG] Falling back to default config"); - config::XcrabConfig::default() - }); + pub static ref CONFIG: config::XcrabConfig = config::load_file().unwrap_or_default(); } impl Display for XcrabError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Bread(be) => Display::fmt(be, f)?, - Self::Io(ie) => Display::fmt(ie, f)?, - Self::Toml(te) => Display::fmt(te, f)?, - Self::Var(ve) => Display::fmt(ve, f)?, - Self::Custom(fe) => Display::fmt(fe, f)?, + Self::Bread(be) => Display::fmt(&be, f)?, + Self::Io(ie) => Display::fmt(&ie, f)?, + Self::Toml(te) => Display::fmt(&te, f)?, + Self::Var(ve) => Display::fmt(&ve, f)?, Self::ClientDoesntExist => Display::fmt("client didn't exist", f)?, - } + }; Ok(()) } @@ -137,55 +124,30 @@ async fn main() -> Result<()> { conn.ungrab_server_async().await?; - let (send, mut recv) = mpsc::unbounded_channel(); - let (result_send, result_recv) = mpsc::unbounded_channel(); + let (send, mut recv) = unbounded_channel(); tokio::spawn(msg_listener::listener_task( CONFIG.msg.clone().unwrap_or_default().socket_path, send, - result_recv, )); - let mut keyboard_state = KeyboardState::new_async(&mut conn).await?; - loop { // biased mode makes select! poll the channel first in order to keep xcrab-msg from being // starved by x11 events. Probably unnecessary, but better safe than sorry. tokio::select! { biased; - Some(s) = recv.recv() => msg_listener::on_recv(s, &mut manager, &mut conn, &result_send).await?, - Ok(ev) = conn.wait_for_event_async() => process_event(ev, - &mut manager, - &mut conn, - root, - &mut keyboard_state, - ).await?, + Some(s) = recv.recv() => msg_listener::on_recv(s, &mut manager, &mut conn).await?, + Ok(ev) = conn.wait_for_event_async() => process_event(ev, &mut manager, &mut conn, root).await?, } } } -#[allow(clippy::too_many_lines)] // FIXME: missing help i have no idea how to make this shorter async fn process_event( ev: Event, manager: &mut XcrabWindowManager, conn: &mut Dpy, root: Window, - keyboard_state: &mut KeyboardState, ) -> Result<()> { - let focused = { - // A new scope is required in order to drop the future stored here, and therefore free the - // immutable borrow of `manager` - let orig = manager - .get_focused() - .await - .map(|w| (w, manager.get_framed_window(w))); - if let Some((w, fut)) = orig { - Some((w, fut.await)) - } else { - None - } - }; - match ev { Event::MapRequest(ev) => { manager.add_client(conn, ev.window).await?; @@ -221,77 +183,12 @@ async fn process_event( manager.remove_client(conn, ev.window).await?; } } - Event::ButtonPress(mut ev) => { - if ev.detail == 1 && manager.has_client(ev.event) { + Event::ButtonPress(ev) => { + dbg!(&ev); + if ev.detail == 1 { manager.set_focus(conn, ev.event).await?; } - if let Some((focused, focused_frame)) = focused { - if ev.event == focused_frame.input { - ev.event = focused; - conn.send_event_async(focused, EventMask::BUTTON_PRESS, Event::ButtonPress(ev)) - .await?; - } - } } - Event::KeyPress(mut ev) => { - if let Some(k) = keyboard_state.process_keycode(ev.detail, ev.state) { - if let Some(c) = k.as_char() { - for (&bind, action) in &CONFIG.binds { - if bind.key == c && bind.mods == ev.state { - action.eval(manager, conn).await?; - } - } - } - } - - // keybind did not match, forward instead - if let Some((focused, _)) = focused { - ev.event = focused; - conn.send_event_async(focused, EventMask::KEY_PRESS, Event::KeyPress(ev)) - .await?; - } - } - Event::KeyRelease(mut ev) => { - if let Some((focused, focused_frame)) = focused { - if ev.event == focused_frame.input { - ev.event = focused; - conn.send_event_async(focused, EventMask::KEY_RELEASE, Event::KeyRelease(ev)) - .await?; - } - } - } - Event::ButtonRelease(mut ev) => { - if let Some((focused, focused_frame)) = focused { - if ev.event == focused_frame.input { - ev.event = focused; - conn.send_event_async( - focused, - EventMask::BUTTON_RELEASE, - Event::ButtonRelease(ev), - ) - .await?; - } - } - } - Event::EnterNotify(mut ev) => { - if let Some((focused, focused_frame)) = focused { - if ev.event == focused_frame.input { - ev.event = focused; - conn.send_event_async(focused, EventMask::ENTER_WINDOW, Event::EnterNotify(ev)) - .await?; - } - } - } - Event::LeaveNotify(mut ev) => { - if let Some((focused, focused_frame)) = focused { - if ev.event == focused_frame.input { - ev.event = focused; - conn.send_event_async(focused, EventMask::LEAVE_WINDOW, Event::LeaveNotify(ev)) - .await?; - } - } - } - _ => {} } Ok(()) diff --git a/src/msg/config.rs b/src/msg/config.rs index d5a0485..82cedbe 100644 --- a/src/msg/config.rs +++ b/src/msg/config.rs @@ -17,14 +17,12 @@ use crate::Result; use serde::Deserialize; use std::path::PathBuf; -#[allow(clippy::module_name_repetitions)] #[derive(Clone, Debug, Default, Deserialize)] // Dummy struct for deserializing the message config - we're using the same file for both binaries pub struct XcrabConfig { pub msg: XcrabMsgConfig, } -#[allow(clippy::module_name_repetitions)] #[derive(Clone, Debug, Deserialize)] pub struct XcrabMsgConfig { pub socket_path: PathBuf, diff --git a/src/msg/main.rs b/src/msg/main.rs index 3d24557..9231125 100644 --- a/src/msg/main.rs +++ b/src/msg/main.rs @@ -13,32 +13,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#![warn(clippy::pedantic)] - mod config; -use std::error::Error; -use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::io::AsyncWriteExt; use tokio::net::UnixStream; -type Result = std::result::Result>; - -struct CustomError(String); - -impl Debug for CustomError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.write_str(&self.0) - } -} - -impl Display for CustomError { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.write_str(&self.0) - } -} - -impl Error for CustomError {} +type Result = std::result::Result>; #[tokio::main] async fn main() -> Result<()> { @@ -48,20 +28,9 @@ async fn main() -> Result<()> { let path = conf.msg.socket_path; - let stream = UnixStream::connect(path).await?; - let (mut read, mut write) = stream.into_split(); + let mut stream = UnixStream::connect(path).await?; - write.write_all(msg.as_bytes()).await?; - drop(write); // Shutdown the writer half so that the write actually goes through - // "Don't cross the streams!"" - - let mut buf = String::new(); - - read.read_to_string(&mut buf).await?; - - if !buf.is_empty() { - return Err(CustomError(buf).into()); - } + stream.write_all(msg.as_bytes()).await?; Ok(()) } diff --git a/src/msg_listener.rs b/src/msg_listener.rs index 7bc5407..0da7523 100644 --- a/src/msg_listener.rs +++ b/src/msg_listener.rs @@ -17,10 +17,9 @@ use crate::x11::client::XcrabWindowManager; use crate::Result; use breadx::AsyncDisplay; use std::path::Path; -use std::str::FromStr; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::io::AsyncReadExt; use tokio::net::UnixListener; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::mpsc::UnboundedSender; macro_rules! unwrap_or_continue { ($e:expr) => { @@ -35,7 +34,6 @@ macro_rules! unwrap_or_continue { pub async fn listener_task>( socket_path: P, sender: UnboundedSender, - mut result_recv: UnboundedReceiver>, ) -> Result<()> { let socket_path = socket_path.as_ref(); if socket_path.exists() { @@ -49,13 +47,6 @@ pub async fn listener_task>( stream.read_to_string(&mut buf).await?; drop(sender.send(buf)); // go back to ms word clippy - - // we can unwrap here because if the channel is closed then something's not right - if let Err(e) = result_recv.recv().await.unwrap() { - stream.write_all(format!("{}", e).as_bytes()).await?; - } else { - stream.write_all(&[]).await?; - } } } @@ -63,76 +54,10 @@ pub async fn on_recv( data: String, manager: &mut XcrabWindowManager, conn: &mut Dpy, - result_sender: &UnboundedSender>, ) -> Result<()> { - let res = { data.parse::() }; - - if let Ok(ref a) = res { - a.eval(manager, conn).await?; // Don't send these errors over the channel, because they're - // xcrab errors, not msg errors + match &*data { + "close" => manager.destroy_focused_client(conn).await?, + _ => println!("{}", data), } - - drop(result_sender.send(res.map(|_| ()))); - Ok(()) } - -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum Action { - Close, -} - -impl FromStr for Action { - type Err = crate::XcrabError; - - fn from_str(s: &str) -> std::result::Result { - #[allow(clippy::enum_glob_use)] - use Action::*; - let parts: Vec = s - .split(' ') - .map(str::to_ascii_lowercase) - .filter(|s| !s.is_empty()) - .collect(); - - if parts.is_empty() { - return Err(String::from("No action provided").into()); - } - - macro_rules! eq_ignore_ascii_case_match { - (($scrutinee:expr) { $($s:literal => $v:expr,)+ else => $else:expr $(,)? }) => { - $( - if $scrutinee.eq_ignore_ascii_case($s) { - $v - } else - )+ { - $else - } - }; - } - - // TODO: When more actions are added (such as focus etc), they will take arguments. In that - // case, they will get passed the rest of `parts`. - eq_ignore_ascii_case_match!((parts[0]) { - "close" => Ok(Close), - else => Err(format!("Unknown action: {}", s).into()), - }) - } -} - -impl Action { - pub async fn eval( - &self, - manager: &mut XcrabWindowManager, - conn: &mut Dpy, - ) -> Result<()> { - #[allow(clippy::enum_glob_use)] - use Action::*; - - match self { - Close => manager.destroy_focused_client(conn).await?, - } - - Ok(()) - } -} diff --git a/src/x11/client.rs b/src/x11/client.rs index 7d937ad..c1a4c74 100644 --- a/src/x11/client.rs +++ b/src/x11/client.rs @@ -137,12 +137,14 @@ enum RectangleContents { Client(Client), } +#[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone)] struct Pane { children: Vec, directionality: Directionality, } +#[allow(clippy::module_name_repetitions)] #[derive(Debug, Clone, Copy)] struct Client { frame: FramedWindow, @@ -242,10 +244,7 @@ impl XcrabWindowManager { }; if let Some(focus) = self.focused { - let focused_key = self.clients.get(&focus).unwrap(); - let focused = self.rects.get(*focused_key).unwrap(); - let focused_frame = focused.unwrap_client().frame; - req.focus = focused_frame.input; + req.focus = focus; } conn.exchange_request_async(req).await?; @@ -611,17 +610,6 @@ impl XcrabWindowManager { Ok(()) } - - pub async fn get_focused(&self) -> Option { - self.focused - } - - pub async fn get_framed_window(&self, window: Window) -> FramedWindow { - let focused_key = self.clients.get(&window).unwrap(); - let focused = self.rects.get(*focused_key).unwrap(); - let focused_frame = focused.unwrap_client().frame; - focused_frame - } } pub fn may_not_exist(res: breadx::Result) -> breadx::Result { @@ -637,10 +625,9 @@ pub fn may_not_exist(res: breadx::Result) -> breadx::Result { } #[derive(Debug, Clone, Copy)] -pub struct FramedWindow { - pub frame: Window, - pub win: Window, - pub input: Window, +struct FramedWindow { + frame: Window, + win: Window, } impl FramedWindow { @@ -700,27 +687,12 @@ impl FramedWindow { .await, )?; - self.input - .configure_async( - conn, - ConfigureWindowParameters { - x: Some(0), - y: Some(0), - width, - height, - border_width: None, - ..Default::default() - }, - ) - .await?; - Ok(()) } async fn map(self, conn: &mut Dpy) -> Result<()> { may_not_exist(self.win.map_async(conn).await)?; self.frame.map_async(conn).await?; - self.input.map_async(conn).await?; Ok(()) } @@ -736,10 +708,6 @@ impl FramedWindow { // no longer related to us, remove from save set may_not_exist(self.win.change_save_set_async(conn, SetMode::Delete).await)?; - self.input.unmap_async(conn).await?; - - self.input.free_async(conn).await?; - self.frame.free_async(conn).await?; Ok(()) @@ -851,21 +819,6 @@ async fn frame(conn: &mut Dpy, win: Window) -> Resul ) .await?; - let input = conn - .create_window_async( - frame, - breadx::WindowClass::InputOnly, - None, - Some(conn.default_visual_id()), - 0, - 0, - geometry.width, - geometry.height, - 0, - WindowParameters::default(), - ) - .await?; - frame .set_event_mask_async( conn, @@ -876,21 +829,9 @@ async fn frame(conn: &mut Dpy, win: Window) -> Resul win.set_event_mask_async(conn, EventMask::BUTTON_PRESS) .await?; - input - .set_event_mask_async( - conn, - EventMask::BUTTON_PRESS - | EventMask::BUTTON_RELEASE - | EventMask::KEY_PRESS - | EventMask::KEY_RELEASE - | EventMask::ENTER_WINDOW - | EventMask::LEAVE_WINDOW, - ) - .await?; - may_not_exist(win.change_save_set_async(conn, SetMode::Insert).await)?; may_not_exist(win.reparent_async(conn, frame, 0, 0).await)?; - Ok(FramedWindow { frame, win, input }) + Ok(FramedWindow { frame, win }) }