keybinds without input-only windows (#13)

Reviewed-on: InfoshockTech/xcrab#13
Co-authored-by: Akshat Deshpande <akshatd18@outlook.com>
Co-committed-by: Akshat Deshpande <akshatd18@outlook.com>
This commit is contained in:
Akshat Deshpande 2022-08-01 22:56:09 -05:00 committed by Yash Karandikar
parent b54a52f64b
commit a3a4b21981
8 changed files with 130 additions and 185 deletions

1
.gitattributes vendored
View file

@ -1 +0,0 @@
* text=auto

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
/.idea

64
Cargo.lock generated
View file

@ -40,9 +40,9 @@ dependencies = [
[[package]]
name = "async-task"
version = "4.2.0"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
[[package]]
name = "atomic-waker"
@ -99,15 +99,15 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.9.1"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc"
checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835"
[[package]]
name = "bytes"
version = "1.1.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cache-padded"
@ -129,9 +129,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "concurrent-queue"
version = "1.2.2"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c"
dependencies = [
"cache-padded",
]
@ -144,15 +144,15 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "event-listener"
version = "2.5.2"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "fastrand"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [
"instant",
]
@ -290,9 +290,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "parking"
@ -344,9 +344,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.40"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b"
dependencies = [
"unicode-ident",
]
@ -362,9 +362,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.2.13"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
@ -377,18 +377,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.137"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.137"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
dependencies = [
"proc-macro2",
"quote",
@ -406,9 +406,12 @@ dependencies = [
[[package]]
name = "slab"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]]
name = "slotmap"
@ -421,9 +424,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.8.1"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]]
name = "socket2"
@ -463,10 +466,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.19.2"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
dependencies = [
"autocfg",
"bytes",
"libc",
"memchr",
@ -503,9 +507,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
[[package]]
name = "version_check"

View file

@ -6,4 +6,4 @@ A window manager written in [Rust](https://rust-lang.org).
## Contributing
Please contribute, we don't know what the fuck we are doing. How we even got to this point is beyond us.
Please contribute, we don't know what the fuck we are doing. How we even got to this point is beyond us.

View file

@ -18,6 +18,7 @@
use std::fmt::{Debug, Display};
use breadx::{
auto::xproto::{GrabKeyRequest, GrabMode, KeyButMask, Keycode, Keysym, ModMask},
keyboard::KeyboardState,
prelude::{AsyncDisplay, AsyncDisplayXprotoExt, MapState},
traits::DisplayBase,
@ -27,7 +28,7 @@ use breadx::{
use lazy_static::lazy_static;
use tokio::sync::mpsc;
use tokio::sync::mpsc::unbounded_channel;
mod config;
mod msg_listener;
@ -35,6 +36,8 @@ mod x11;
use x11::client::{may_not_exist, XcrabWindowManager};
use std::collections::HashMap;
#[non_exhaustive]
pub enum XcrabError {
Bread(BreadError),
@ -76,7 +79,6 @@ impl From<String> for XcrabError {
}
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");
@ -91,9 +93,9 @@ impl Display for XcrabError {
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::ClientDoesntExist => Display::fmt("client didn't exist", f)?,
}
Self::Custom(fe) => Display::fmt(fe, f)?,
};
Ok(())
}
@ -117,7 +119,7 @@ async fn main() -> Result<()> {
// listen for substructure redirects to intercept events like window creation
root.set_event_mask_async(
&mut conn,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY | EventMask::KEY_PRESS,
)
.await?;
@ -137,8 +139,68 @@ 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 mut mask = ModMask::new(false, false, true, false, false, false, false, false, false);
let mut keyboard_state = KeyboardState::new_async(&mut conn).await?;
let keymap = x11::client::keymap(&mut keyboard_state);
let mut request_key = *keymap.get(&120).ok_or_else(|| {
XcrabError::Custom("At least one letter could not be found in the keymap".to_string())
})?;
for &binds in CONFIG.binds.keys() {
for keysym in 97..122_u32 {
let keycode = keymap.get(&keysym).ok_or_else(|| {
XcrabError::Custom(
"At least one letter could not be found in the keymap".to_string(),
)
})?;
let iter_char = keyboard_state
.process_keycode(*keycode, KeyButMask::default())
.ok_or_else(|| {
XcrabError::Custom(
"The keycode returned from the keymap could not be processed".to_string(),
)
})?
.as_char()
.ok_or_else(|| {
XcrabError::Custom("The processed Key could not be cast as a char".to_string())
})?;
if iter_char == binds.key {
request_key = *keycode;
mask.inner = binds.mods.inner;
}
}
}
mask.set_Two(true);
conn.exchange_request_async(GrabKeyRequest {
req_type: 33,
owner_events: false,
length: 4,
grab_window: root,
modifiers: mask,
key: request_key,
pointer_mode: GrabMode::Async,
keyboard_mode: GrabMode::Async,
})
.await?;
mask.set_Two(false);
conn.exchange_request_async(GrabKeyRequest {
req_type: 33,
owner_events: false,
length: 4,
grab_window: root,
modifiers: mask,
key: request_key,
pointer_mode: GrabMode::Async,
keyboard_mode: GrabMode::Async,
})
.await?;
let (send, mut recv) = unbounded_channel();
let (result_send, result_recv) = unbounded_channel();
tokio::spawn(msg_listener::listener_task(
CONFIG.msg.clone().unwrap_or_default().socket_path,
@ -146,25 +208,18 @@ async fn main() -> Result<()> {
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?,
Ok(ev) = conn.wait_for_event_async() => process_event(ev, &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
#[allow(clippy::too_many_lines)]
async fn process_event<Dpy: AsyncDisplay + ?Sized>(
ev: Event,
manager: &mut XcrabWindowManager,
@ -172,20 +227,6 @@ async fn process_event<Dpy: AsyncDisplay + ?Sized>(
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 +262,22 @@ async fn process_event<Dpy: AsyncDisplay + ?Sized>(
manager.remove_client(conn, ev.window).await?;
}
}
Event::ButtonPress(mut ev) => {
if ev.detail == 1 && manager.has_client(ev.event) {
Event::ButtonPress(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) => {
Event::KeyPress(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 {
if bind.key == c {
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(())

View file

@ -12,7 +12,6 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::Result;
use serde::Deserialize;
use std::path::PathBuf;
@ -40,11 +39,8 @@ impl Default for XcrabMsgConfig {
fn load_file_inner() -> Result<XcrabConfig> {
let home_dir = get_home();
let contents = std::fs::read_to_string(format!("{}/.config/xcrab/config.toml", home_dir))?;
let config: XcrabConfig = toml::from_str(&contents)?;
Ok(config)
}

View file

@ -49,6 +49,7 @@ async fn main() -> Result<()> {
let path = conf.msg.socket_path;
let stream = UnixStream::connect(path).await?;
let (mut read, mut write) = stream.into_split();
write.write_all(msg.as_bytes()).await?;
@ -58,7 +59,6 @@ async fn main() -> Result<()> {
let mut buf = String::new();
read.read_to_string(&mut buf).await?;
if !buf.is_empty() {
return Err(CustomError(buf).into());
}

View file

@ -13,15 +13,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use breadx::{
auto::xproto::{ClientMessageEvent, InputFocus, SetInputFocusRequest},
client_message_data::ClientMessageData,
prelude::{AsByteSequence, AsyncDisplayXprotoExt, PropertyType, SetMode},
AsyncDisplay, AsyncDisplayExt, Atom, BreadError, ConfigureWindowParameters, ErrorCode, Event,
EventMask, Window, WindowParameters, XidType,
};
use breadx::{auto::xproto::{ClientMessageEvent, InputFocus, SetInputFocusRequest}, client_message_data::ClientMessageData, prelude::{AsByteSequence, AsyncDisplayXprotoExt, PropertyType, SetMode}, AsyncDisplay, AsyncDisplayExt, Atom, BreadError, ConfigureWindowParameters, ErrorCode, Event, EventMask, Window, WindowParameters, XidType, KeyboardState};
use slotmap::{new_key_type, SlotMap};
use std::{collections::HashMap, future::Future, pin::Pin, slice};
use breadx::auto::xproto::{KeyButMask, Keycode, Keysym};
use crate::{Result, XcrabError, CONFIG};
@ -242,10 +237,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?;
@ -640,7 +632,6 @@ pub fn may_not_exist(res: breadx::Result) -> breadx::Result {
pub struct FramedWindow {
pub frame: Window,
pub win: Window,
pub input: Window,
}
impl FramedWindow {
@ -700,27 +691,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<Dpy: AsyncDisplay + ?Sized>(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 +712,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 +823,6 @@ async fn frame<Dpy: AsyncDisplay + ?Sized>(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 +833,23 @@ async fn frame<Dpy: AsyncDisplay + ?Sized>(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 })
}
pub fn keymap(state: &mut KeyboardState) -> HashMap<Keysym, Keycode> {
let mut map: HashMap<Keysym, Keycode> = HashMap::new();
for keycode in 8..255_u8 {
let key = state.process_keycode(keycode, KeyButMask::default());
let keysyms = state.lookup_keysyms(keycode);
if key != None {
for keysym in keysyms {
map.insert(*keysym, keycode);
}
}
}
map
}