revert to 33c1cc80b6
"make destroying focused client actually work"
This commit is contained in:
parent
b54a52f64b
commit
5d14402cc8
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
||||||
* text=auto
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -604,7 +604,6 @@ name = "xcrab"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"breadx",
|
"breadx",
|
||||||
"gluten-keyboard",
|
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"serde",
|
"serde",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
|
|
|
@ -10,7 +10,6 @@ toml = "0.5.9"
|
||||||
serde = { version = "1.0.137", features = ["derive"]}
|
serde = { version = "1.0.137", features = ["derive"]}
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
gluten-keyboard = "0.1.2"
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "xcrab-msg"
|
name = "xcrab-msg"
|
||||||
|
|
|
@ -15,14 +15,8 @@
|
||||||
|
|
||||||
#![allow(dead_code, clippy::module_name_repetitions)]
|
#![allow(dead_code, clippy::module_name_repetitions)]
|
||||||
|
|
||||||
use crate::msg_listener::Action;
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use breadx::auto::xproto::KeyButMask;
|
use serde::Deserialize;
|
||||||
use serde::{
|
|
||||||
de::{Deserializer, Visitor},
|
|
||||||
Deserialize,
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
@ -33,9 +27,6 @@ pub struct XcrabConfig {
|
||||||
gap_size: Option<u16>,
|
gap_size: Option<u16>,
|
||||||
outer_gap_size: Option<u16>,
|
outer_gap_size: Option<u16>,
|
||||||
pub msg: Option<XcrabMsgConfig>,
|
pub msg: Option<XcrabMsgConfig>,
|
||||||
#[allow(clippy::zero_sized_map_values)] // TODO: Action will be expanded in the future
|
|
||||||
#[serde(default)]
|
|
||||||
pub binds: HashMap<Keybind, Action>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
@ -57,7 +48,6 @@ impl Default for XcrabConfig {
|
||||||
gap_size: Some(DEFAULT_GAP_SIZE),
|
gap_size: Some(DEFAULT_GAP_SIZE),
|
||||||
outer_gap_size: None,
|
outer_gap_size: None,
|
||||||
msg: Some(XcrabMsgConfig::default()),
|
msg: Some(XcrabMsgConfig::default()),
|
||||||
binds: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,79 +96,3 @@ pub fn load_file() -> Result<XcrabConfig> {
|
||||||
fn get_home() -> Result<String> {
|
fn get_home() -> Result<String> {
|
||||||
Ok(std::env::var("HOME")?)
|
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<E: serde::de::Error>(self, value: &str) -> std::result::Result<Self::Value, E> {
|
|
||||||
value.parse().map_err(|s| E::custom(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Action {
|
|
||||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
|
|
||||||
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<E: serde::de::Error>(self, value: &str) -> std::result::Result<Self::Value, E> {
|
|
||||||
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<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
|
|
||||||
deserializer.deserialize_str(KeybindVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
131
src/main.rs
131
src/main.rs
|
@ -16,9 +16,9 @@
|
||||||
#![warn(clippy::pedantic)]
|
#![warn(clippy::pedantic)]
|
||||||
|
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use breadx::{
|
use breadx::{
|
||||||
keyboard::KeyboardState,
|
|
||||||
prelude::{AsyncDisplay, AsyncDisplayXprotoExt, MapState},
|
prelude::{AsyncDisplay, AsyncDisplayXprotoExt, MapState},
|
||||||
traits::DisplayBase,
|
traits::DisplayBase,
|
||||||
AsyncDisplayConnection, AsyncDisplayExt, BreadError, ConfigureWindowParameters, Event,
|
AsyncDisplayConnection, AsyncDisplayExt, BreadError, ConfigureWindowParameters, Event,
|
||||||
|
@ -27,7 +27,7 @@ use breadx::{
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc::unbounded_channel;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod msg_listener;
|
mod msg_listener;
|
||||||
|
@ -42,7 +42,6 @@ pub enum XcrabError {
|
||||||
Toml(toml::de::Error),
|
Toml(toml::de::Error),
|
||||||
Var(std::env::VarError),
|
Var(std::env::VarError),
|
||||||
ClientDoesntExist,
|
ClientDoesntExist,
|
||||||
Custom(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BreadError> for XcrabError {
|
impl From<BreadError> for XcrabError {
|
||||||
|
@ -69,31 +68,19 @@ impl From<std::env::VarError> for XcrabError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for XcrabError {
|
|
||||||
fn from(v: String) -> Self {
|
|
||||||
Self::Custom(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
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_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()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for XcrabError {
|
impl Display for XcrabError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Bread(be) => Display::fmt(be, f)?,
|
Self::Bread(be) => Display::fmt(&be, f)?,
|
||||||
Self::Io(ie) => Display::fmt(ie, f)?,
|
Self::Io(ie) => Display::fmt(&ie, f)?,
|
||||||
Self::Toml(te) => Display::fmt(te, f)?,
|
Self::Toml(te) => Display::fmt(&te, f)?,
|
||||||
Self::Var(ve) => Display::fmt(ve, f)?,
|
Self::Var(ve) => Display::fmt(&ve, f)?,
|
||||||
Self::Custom(fe) => Display::fmt(fe, f)?,
|
|
||||||
Self::ClientDoesntExist => Display::fmt("client didn't exist", f)?,
|
Self::ClientDoesntExist => Display::fmt("client didn't exist", f)?,
|
||||||
}
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -137,55 +124,30 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
conn.ungrab_server_async().await?;
|
conn.ungrab_server_async().await?;
|
||||||
|
|
||||||
let (send, mut recv) = mpsc::unbounded_channel();
|
let (send, mut recv) = unbounded_channel();
|
||||||
let (result_send, result_recv) = mpsc::unbounded_channel();
|
|
||||||
|
|
||||||
tokio::spawn(msg_listener::listener_task(
|
tokio::spawn(msg_listener::listener_task(
|
||||||
CONFIG.msg.clone().unwrap_or_default().socket_path,
|
CONFIG.msg.clone().unwrap_or_default().socket_path,
|
||||||
send,
|
send,
|
||||||
result_recv,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut keyboard_state = KeyboardState::new_async(&mut conn).await?;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// biased mode makes select! poll the channel first in order to keep xcrab-msg from being
|
// 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.
|
// starved by x11 events. Probably unnecessary, but better safe than sorry.
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
Some(s) = recv.recv() => msg_listener::on_recv(s, &mut manager, &mut conn, &result_send).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,
|
Ok(ev) = conn.wait_for_event_async() => process_event(ev, &mut manager, &mut conn, root).await?,
|
||||||
&mut manager,
|
|
||||||
&mut conn,
|
|
||||||
root,
|
|
||||||
&mut keyboard_state,
|
|
||||||
).await?,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)] // FIXME: missing help i have no idea how to make this shorter
|
|
||||||
async fn process_event<Dpy: AsyncDisplay + ?Sized>(
|
async fn process_event<Dpy: AsyncDisplay + ?Sized>(
|
||||||
ev: Event,
|
ev: Event,
|
||||||
manager: &mut XcrabWindowManager,
|
manager: &mut XcrabWindowManager,
|
||||||
conn: &mut Dpy,
|
conn: &mut Dpy,
|
||||||
root: Window,
|
root: Window,
|
||||||
keyboard_state: &mut KeyboardState,
|
|
||||||
) -> Result<()> {
|
) -> 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 {
|
match ev {
|
||||||
Event::MapRequest(ev) => {
|
Event::MapRequest(ev) => {
|
||||||
manager.add_client(conn, ev.window).await?;
|
manager.add_client(conn, ev.window).await?;
|
||||||
|
@ -221,77 +183,12 @@ async fn process_event<Dpy: AsyncDisplay + ?Sized>(
|
||||||
manager.remove_client(conn, ev.window).await?;
|
manager.remove_client(conn, ev.window).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::ButtonPress(mut ev) => {
|
Event::ButtonPress(ev) => {
|
||||||
if ev.detail == 1 && manager.has_client(ev.event) {
|
dbg!(&ev);
|
||||||
|
if ev.detail == 1 {
|
||||||
manager.set_focus(conn, ev.event).await?;
|
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(())
|
Ok(())
|
||||||
|
|
|
@ -17,14 +17,12 @@ use crate::Result;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
#[derive(Clone, Debug, Default, Deserialize)]
|
||||||
// Dummy struct for deserializing the message config - we're using the same file for both binaries
|
// Dummy struct for deserializing the message config - we're using the same file for both binaries
|
||||||
pub struct XcrabConfig {
|
pub struct XcrabConfig {
|
||||||
pub msg: XcrabMsgConfig,
|
pub msg: XcrabMsgConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct XcrabMsgConfig {
|
pub struct XcrabMsgConfig {
|
||||||
pub socket_path: PathBuf,
|
pub socket_path: PathBuf,
|
||||||
|
|
|
@ -13,32 +13,12 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#![warn(clippy::pedantic)]
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
|
||||||
use std::error::Error;
|
use tokio::io::AsyncWriteExt;
|
||||||
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::UnixStream;
|
use tokio::net::UnixStream;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
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 {}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
@ -48,20 +28,9 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let path = conf.msg.socket_path;
|
let path = conf.msg.socket_path;
|
||||||
|
|
||||||
let stream = UnixStream::connect(path).await?;
|
let mut stream = UnixStream::connect(path).await?;
|
||||||
let (mut read, mut write) = stream.into_split();
|
|
||||||
|
|
||||||
write.write_all(msg.as_bytes()).await?;
|
stream.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());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,9 @@ use crate::x11::client::XcrabWindowManager;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use breadx::AsyncDisplay;
|
use breadx::AsyncDisplay;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
macro_rules! unwrap_or_continue {
|
macro_rules! unwrap_or_continue {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
|
@ -35,7 +34,6 @@ macro_rules! unwrap_or_continue {
|
||||||
pub async fn listener_task<P: AsRef<Path>>(
|
pub async fn listener_task<P: AsRef<Path>>(
|
||||||
socket_path: P,
|
socket_path: P,
|
||||||
sender: UnboundedSender<String>,
|
sender: UnboundedSender<String>,
|
||||||
mut result_recv: UnboundedReceiver<Result<()>>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let socket_path = socket_path.as_ref();
|
let socket_path = socket_path.as_ref();
|
||||||
if socket_path.exists() {
|
if socket_path.exists() {
|
||||||
|
@ -49,13 +47,6 @@ pub async fn listener_task<P: AsRef<Path>>(
|
||||||
stream.read_to_string(&mut buf).await?;
|
stream.read_to_string(&mut buf).await?;
|
||||||
|
|
||||||
drop(sender.send(buf)); // go back to ms word clippy
|
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<Dpy: AsyncDisplay + ?Sized>(
|
||||||
data: String,
|
data: String,
|
||||||
manager: &mut XcrabWindowManager,
|
manager: &mut XcrabWindowManager,
|
||||||
conn: &mut Dpy,
|
conn: &mut Dpy,
|
||||||
result_sender: &UnboundedSender<Result<()>>,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let res = { data.parse::<Action>() };
|
match &*data {
|
||||||
|
"close" => manager.destroy_focused_client(conn).await?,
|
||||||
if let Ok(ref a) = res {
|
_ => println!("{}", data),
|
||||||
a.eval(manager, conn).await?; // Don't send these errors over the channel, because they're
|
|
||||||
// xcrab errors, not msg errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(result_sender.send(res.map(|_| ())));
|
|
||||||
|
|
||||||
Ok(())
|
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<Self, Self::Err> {
|
|
||||||
#[allow(clippy::enum_glob_use)]
|
|
||||||
use Action::*;
|
|
||||||
let parts: Vec<String> = 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<Dpy: AsyncDisplay + ?Sized>(
|
|
||||||
&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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -137,12 +137,14 @@ enum RectangleContents {
|
||||||
Client(Client),
|
Client(Client),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Pane {
|
struct Pane {
|
||||||
children: Vec<XcrabKey>,
|
children: Vec<XcrabKey>,
|
||||||
directionality: Directionality,
|
directionality: Directionality,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct Client {
|
struct Client {
|
||||||
frame: FramedWindow,
|
frame: FramedWindow,
|
||||||
|
@ -242,10 +244,7 @@ impl XcrabWindowManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(focus) = self.focused {
|
if let Some(focus) = self.focused {
|
||||||
let focused_key = self.clients.get(&focus).unwrap();
|
req.focus = focus;
|
||||||
let focused = self.rects.get(*focused_key).unwrap();
|
|
||||||
let focused_frame = focused.unwrap_client().frame;
|
|
||||||
req.focus = focused_frame.input;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.exchange_request_async(req).await?;
|
conn.exchange_request_async(req).await?;
|
||||||
|
@ -611,17 +610,6 @@ impl XcrabWindowManager {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_focused(&self) -> Option<Window> {
|
|
||||||
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 {
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct FramedWindow {
|
struct FramedWindow {
|
||||||
pub frame: Window,
|
frame: Window,
|
||||||
pub win: Window,
|
win: Window,
|
||||||
pub input: Window,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FramedWindow {
|
impl FramedWindow {
|
||||||
|
@ -700,27 +687,12 @@ impl FramedWindow {
|
||||||
.await,
|
.await,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
self.input
|
|
||||||
.configure_async(
|
|
||||||
conn,
|
|
||||||
ConfigureWindowParameters {
|
|
||||||
x: Some(0),
|
|
||||||
y: Some(0),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
border_width: None,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn map<Dpy: AsyncDisplay + ?Sized>(self, conn: &mut Dpy) -> Result<()> {
|
async fn map<Dpy: AsyncDisplay + ?Sized>(self, conn: &mut Dpy) -> Result<()> {
|
||||||
may_not_exist(self.win.map_async(conn).await)?;
|
may_not_exist(self.win.map_async(conn).await)?;
|
||||||
self.frame.map_async(conn).await?;
|
self.frame.map_async(conn).await?;
|
||||||
self.input.map_async(conn).await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -736,10 +708,6 @@ impl FramedWindow {
|
||||||
// no longer related to us, remove from save set
|
// no longer related to us, remove from save set
|
||||||
may_not_exist(self.win.change_save_set_async(conn, SetMode::Delete).await)?;
|
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?;
|
self.frame.free_async(conn).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -851,21 +819,6 @@ async fn frame<Dpy: AsyncDisplay + ?Sized>(conn: &mut Dpy, win: Window) -> Resul
|
||||||
)
|
)
|
||||||
.await?;
|
.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
|
frame
|
||||||
.set_event_mask_async(
|
.set_event_mask_async(
|
||||||
conn,
|
conn,
|
||||||
|
@ -876,21 +829,9 @@ async fn frame<Dpy: AsyncDisplay + ?Sized>(conn: &mut Dpy, win: Window) -> Resul
|
||||||
win.set_event_mask_async(conn, EventMask::BUTTON_PRESS)
|
win.set_event_mask_async(conn, EventMask::BUTTON_PRESS)
|
||||||
.await?;
|
.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.change_save_set_async(conn, SetMode::Insert).await)?;
|
||||||
|
|
||||||
may_not_exist(win.reparent_async(conn, frame, 0, 0).await)?;
|
may_not_exist(win.reparent_async(conn, frame, 0, 0).await)?;
|
||||||
|
|
||||||
Ok(FramedWindow { frame, win, input })
|
Ok(FramedWindow { frame, win })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue