Use xcb-wm and bump xcb to 1.1.1

This commit is contained in:
Armin Friedl 2022-06-03 22:44:32 +02:00
parent 0ae84e89bc
commit e37563eb7a
7 changed files with 285 additions and 2104 deletions

1870
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,23 +3,33 @@ name = "roftl"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features]
default = ["x11"]
# TODO ui module should be independent of windowing framework if possible, still xcb feature is apparently used in cairo
# which is used in ui to paint to the window surface
x11 = ["dep:xcb", "dep:xcb-wm", "cairo-rs/xcb", "cairo-sys-rs/xcb", "xcb-wm?/ewmh", "xcb-wm?/icccm"]
[dependencies] [dependencies]
# globally useful dependencies
log = "0.4" log = "0.4"
env_logger = "0.9" env_logger = "0.9"
rayon = "1.5" rayon = "1.5"
anyhow = "1.0" anyhow = "1.0"
config = "0.13"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
# ui # read and parse configuration
config = "0.13"
# dependencies for building the `window` surface
winit = { git = "https://github.com/rust-windowing/winit" } winit = { git = "https://github.com/rust-windowing/winit" }
cairo-rs = {version = "0.15", features = ["xcb"]} ## `window` dependencies specific for x11. Only enabled if compiled with `x11` feature on [default]
cairo-sys-rs = {version = "0.15", features = ["xcb"]} xcb = { version = "1.1.1", optional = true }
xcb-wm = { git = "https://github.com/arminfriedl/xcb-wm", optional = true, features=["icccm", "ewmh"] }
# dependencies for drawing on the `window` surface (`ui` module)
cairo-rs = "0.15.11"
cairo-sys-rs = "0.15.1"
pangocairo = "0.15" pangocairo = "0.15"
xcb = "0.10"
xcb-util = {version = "0.4", features = ["ewmh", "icccm"]}
# matcher # matcher
fuzzy-matcher = "0.3" fuzzy-matcher = "0.3"

View file

@ -6,8 +6,7 @@ use winit::event::{
WindowEvent::{CloseRequested, ReceivedCharacter}, WindowEvent::{CloseRequested, ReceivedCharacter},
}; };
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::platform::unix::x11::util::WindowType; use winit::platform::unix::WindowExtUnix;
use winit::platform::unix::{WindowBuilderExtUnix, WindowExtUnix, XWindowType};
use winit::window::{Window, WindowBuilder}; use winit::window::{Window, WindowBuilder};
use self::core::roftl::Roftl; use self::core::roftl::Roftl;
@ -16,6 +15,7 @@ mod core;
mod matcher; mod matcher;
mod sources; mod sources;
mod ui; mod ui;
mod window;
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
env_logger::init(); env_logger::init();
@ -37,67 +37,12 @@ fn main() -> Result<(), Box<dyn Error>> {
debug! {"Source roftl sources"} debug! {"Source roftl sources"}
roftl.source(); roftl.source();
debug! {"Build window"} let (window, event_loop) = window::build_window()?;
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_decorations(false)
.with_transparent(true)
.with_title("roftl")
.with_visible(true)
.with_always_on_top(true)
.build(&event_loop)
.expect("Could not create window");
debug! {"Hide window from taskbar"}
let screen = window
.xlib_screen_id()
.expect("Could not get screen id of window");
let xcb_conn = unsafe {
xcb::Connection::from_raw_conn(
window.xcb_connection().unwrap() as *mut xcb::ffi::xcb_connection_t
)
};
let ewmh_conn = xcb_util::ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
let xcb_window: xcb::xproto::Window =
window.xlib_window().expect("Cannot get xlib window") as u32;
xcb_util::ewmh::request_change_wm_state(
&ewmh_conn,
screen,
xcb_window,
xcb_util::ewmh::STATE_ADD,
ewmh_conn.WM_STATE(),
ewmh_conn.WM_STATE_SKIP_TASKBAR(),
xcb_util::ewmh::CLIENT_SOURCE_TYPE_NORMAL,
);
debug! {"Grab keyboard, disable tabbing out"}
let xcb_conn = unsafe {
xcb::Connection::from_raw_conn(
window.xcb_connection().unwrap() as *mut xcb::ffi::xcb_connection_t
)
};
xcb::xproto::grab_keyboard(
&xcb_conn,
true,
xcb_window,
xcb::ffi::base::XCB_CURRENT_TIME,
xcb::xproto::GRAB_MODE_ASYNC as u8,
xcb::xproto::GRAB_MODE_ASYNC as u8,
);
debug! {"Window id: {:?}", window.id()}
debug! {"Draw primary state to window"} debug! {"Draw primary state to window"}
ui::draw(&window, "", roftl.narrow(""), 0); ui::draw(&window, "", roftl.narrow(""), 0);
debug! {"Start event loop"} trace! {"Start event loop"}
let roftl_loop = RoftlLoop::new(roftl, window); let roftl_loop = RoftlLoop::new(roftl, window);
roftl_loop.run(event_loop); roftl_loop.run(event_loop);
} }

View file

@ -2,10 +2,12 @@ use std::collections::HashMap;
use super::core::shared::{Entry, Source}; use super::core::shared::{Entry, Source};
use log::debug; use log::debug;
use xcb::ffi::XCB_CURRENT_TIME; use winit::platform::unix::x11::ffi::Connection;
use xcb_util::ewmh; use xcb::x::{InputFocus, CURRENT_TIME};
use xcb_util::ffi::ewmh::XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER; use xcb::{Xid, XidNew};
use xcb_util::icccm; use xcb_wm::ewmh;
use xcb_wm::ewmh::proto::{GetActiveWindow, GetWmDesktop};
use xcb_wm::icccm;
pub struct Window { pub struct Window {
action_data: HashMap<u64, (u32, i32)>, action_data: HashMap<u64, (u32, i32)>,
@ -18,114 +20,99 @@ impl Window {
}) })
} }
fn is_normal_window( fn is_normal_window(&self, con: &ewmh::Connection, window: xcb::x::Window) -> bool {
&self, let wm_type_request = ewmh::proto::GetWmWindowType(window);
con: &ewmh::Connection, let wm_types = con
window: u32, .wait_for_reply(con.send_request(&wm_type_request))
) -> Result<bool, xcb::ReplyError> { .window_types;
let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?;
for atom in wm_type_reply.atoms() { for atom in wm_types {
if *atom == con.WM_WINDOW_TYPE_NORMAL() { if atom == con.atoms._NET_WM_WINDOW_TYPE_NORMAL {
return Ok(true); return true;
} }
} }
Ok(false) false
} }
fn window_category( // fn window_category(
&self, // &self,
con: &ewmh::Connection, // con: &ewmh::Connection,
window: u32, // window: u32,
) -> Result<String, xcb::ReplyError> { // ) -> Result<String, xcb::ReplyError> {
Ok(icccm::get_wm_class(&con, window) // Ok(icccm::get_wm_class(&con, window)
.get_reply()? // .get_reply()?
.class() // .class()
.into()) // .into())
// }
fn window_title(&self, con: &ewmh::Connection, window: xcb::x::Window) -> String {
let wm_name_request = ewmh::proto::GetWmName(window);
let wm_name = con.wait_for_reply(con.send_request(&wm_name_request));
wm_name.name
} }
fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> { fn switch_window(&self, con: &ewmh::Connection, window: u32) {
Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into()) let change_active_window = ewmh::proto::SendActiveWindow::new(
}
fn switch_window(
&self,
con: &ewmh::Connection,
window: u32,
screen: i32,
) -> Result<(), xcb::ReplyError> {
let window_desktop = ewmh::get_wm_desktop(con, window).get_reply()?;
let active_window = ewmh::get_active_window(con, screen).get_reply()?;
ewmh::set_current_desktop(con, screen, window_desktop);
ewmh::request_change_active_window(
con, con,
screen, unsafe { xcb::x::Window::new(window.into()) },
window, 1,
XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER, xcb::x::CURRENT_TIME,
XCB_CURRENT_TIME, Option::None,
active_window, );
) con.send_request(&change_active_window);
.request_check()
.unwrap();
ewmh::set_active_window(con, screen, window)
.request_check()
.unwrap();
xcb::set_input_focus(&con, 0, window, XCB_CURRENT_TIME);
Ok(())
} }
fn toggle_maximize_window( // fn toggle_maximize_window(
&self, // &self,
con: &ewmh::Connection, // con: &ewmh::Connection,
window: u32, // window: u32,
screen: i32, // screen: i32,
) -> Result<(), xcb::ReplyError> { // ) -> Result<(), xcb::ReplyError> {
let max_horz_atom = ewmh::Connection::WM_STATE_MAXIMIZED_HORZ(con); // let max_horz_atom = ewmh::Connection::WM_STATE_MAXIMIZED_HORZ(con);
let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con); // let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con);
let action_atom = ewmh::STATE_TOGGLE; // let action_atom = ewmh::STATE_TOGGLE;
//
debug! {"Toggle maximize for {}", window} // debug! {"Toggle maximize for {}", window}
ewmh::request_change_wm_state( // ewmh::request_change_wm_state(
con, // con,
screen, // screen,
window, // window,
action_atom, // action_atom,
max_horz_atom, // max_horz_atom,
max_vert_atom, // max_vert_atom,
0, // 0,
) // )
.request_check()?; // .request_check()?;
//
Ok(()) // Ok(())
} // }
//
fn close_window( // fn close_window(
&self, // &self,
con: &ewmh::Connection, // con: &ewmh::Connection,
window: u32, // window: u32,
screen: i32, // screen: i32,
) -> Result<(), xcb::ReplyError> { // ) -> Result<(), xcb::ReplyError> {
debug! {"Toggle maximize for {}", window} // debug! {"Toggle maximize for {}", window}
ewmh::request_close_window(con, screen, window, XCB_CURRENT_TIME, 0).request_check() // ewmh::request_close_window(con, screen, window, XCB_CURRENT_TIME, 0).request_check()
} // }
//
fn toggle_hide_window( // fn toggle_hide_window(
&self, // &self,
con: &ewmh::Connection, // con: &ewmh::Connection,
window: u32, // window: u32,
screen: i32, // screen: i32,
) -> Result<(), xcb::ReplyError> { // ) -> Result<(), xcb::ReplyError> {
let hidden_atom = ewmh::Connection::WM_STATE_HIDDEN(con); // let hidden_atom = ewmh::Connection::WM_STATE_HIDDEN(con);
let action_atom = ewmh::STATE_TOGGLE; // let action_atom = ewmh::STATE_TOGGLE;
//
debug! {"Toggle hidden for {}", window} // debug! {"Toggle hidden for {}", window}
ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0) // ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0)
.request_check()?; // .request_check()?;
//
Ok(()) // Ok(())
} // }
} }
impl Source for Window { impl Source for Window {
@ -135,38 +122,34 @@ impl Source for Window {
fn entries(&mut self) -> Vec<Entry> { fn entries(&mut self) -> Vec<Entry> {
let (xcb_conn, screen_num) = let (xcb_conn, screen_num) =
xcb::base::Connection::connect(None).expect("Could not connect to X server"); xcb::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn) let ewmh_conn = ewmh::Connection::connect(&xcb_conn);
.map_err(|(e, _)| e)
.unwrap();
let client_list_reply = xcb_util::ewmh::get_client_list(&ewmh_conn, screen_num) let client_list_reply = ewmh_conn.send_request(&xcb_wm::ewmh::proto::GetClientList);
.get_reply() let windows = ewmh_conn.wait_for_reply(client_list_reply).clients;
.unwrap();
let windows = client_list_reply.windows();
let mut entries: Vec<Entry> = vec![]; let mut entries: Vec<Entry> = vec![];
let mut count: u64 = 0; let mut count: u64 = 0;
for w in windows { for w in windows {
match self.is_normal_window(&ewmh_conn, *w) { match self.is_normal_window(&ewmh_conn, w) {
Ok(true) => {} true => {}
Ok(false) => continue, false => continue,
Err(_err) => {}
} }
let title = self.window_title(&ewmh_conn, *w).unwrap(); let title = self.window_title(&ewmh_conn, w);
let category = self.window_category(&ewmh_conn, *w).unwrap(); let category: String = "Test".into(); //self.window_category(&ewmh_conn, *w).unwrap();
debug!("Found window {} - {}", title, category); debug!("Found window {} - {}", title, category);
entries.push(Entry { entries.push(Entry {
source: self.name(), source: self.name(),
name: category, name: title.clone(),
description: title, description: title.clone(),
identifier: count, identifier: count,
}); });
self.action_data.insert(count, (*w, screen_num)); self.action_data
.insert(count, (w.resource_id(), screen_num));
count += 1; count += 1;
} }
@ -181,43 +164,42 @@ impl Source for Window {
0 => { 0 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap(); let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) = let (xcb_conn, _screen_num) =
xcb::base::Connection::connect(None).expect("Could not connect to X server"); xcb::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn) let ewmh_conn = ewmh::Connection::connect(&xcb_conn);
.map_err(|(e, _)| e) self.switch_window(&ewmh_conn, window);
.unwrap(); xcb_conn.flush().unwrap();
self.switch_window(&ewmh_conn, window, screen).unwrap()
}
1 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) =
xcb::base::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
self.toggle_maximize_window(&ewmh_conn, window, screen)
.unwrap();
self.switch_window(&ewmh_conn, window, screen).unwrap()
}
2 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) =
xcb::base::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
self.toggle_hide_window(&ewmh_conn, window, screen).unwrap();
}
3 => {
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
let (xcb_conn, _screen_num) =
xcb::base::Connection::connect(None).expect("Could not connect to X server");
let ewmh_conn = ewmh::Connection::connect(xcb_conn)
.map_err(|(e, _)| e)
.unwrap();
self.close_window(&ewmh_conn, window, screen).unwrap();
} }
// 1 => {
// let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
// let (xcb_conn, _screen_num) =
// xcb::base::Connection::connect(None).expect("Could not connect to X server");
// let ewmh_conn = ewmh::Connection::connect(xcb_conn)
// .map_err(|(e, _)| e)
// .unwrap();
// self.toggle_maximize_window(&ewmh_conn, window, screen)
// .unwrap();
// self.switch_window(&ewmh_conn, window, screen).unwrap()
// }
// 2 => {
// let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
// let (xcb_conn, _screen_num) =
// xcb::base::Connection::connect(None).expect("Could not connect to X server");
// let ewmh_conn = ewmh::Connection::connect(xcb_conn)
// .map_err(|(e, _)| e)
// .unwrap();
//
// self.toggle_hide_window(&ewmh_conn, window, screen).unwrap();
// }
// 3 => {
// let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
// let (xcb_conn, _screen_num) =
// xcb::base::Connection::connect(None).expect("Could not connect to X server");
// let ewmh_conn = ewmh::Connection::connect(xcb_conn)
// .map_err(|(e, _)| e)
// .unwrap();
//
// self.close_window(&ewmh_conn, window, screen).unwrap();
// }
_ => panic! {"Unknown action {:?}", action}, _ => panic! {"Unknown action {:?}", action},
} }
} }

View file

@ -1,5 +1,3 @@
use super::core;
mod ui; mod ui;
pub use ui::draw; pub use ui::draw;
pub use ui::draw_actions; pub use ui::draw_actions;

View file

@ -1,11 +1,13 @@
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem;
use log::debug; use log::debug;
use winit::{platform::unix::WindowExtUnix, window::Window}; use winit::{platform::unix::WindowExtUnix, window::Window};
use xcb::XidNew;
use super::core::shared::Entry;
use super::painter; use super::painter;
use crate::core::shared::Entry;
pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, selection: usize) { pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
let context = make_context(&window); let context = make_context(&window);
@ -80,7 +82,7 @@ pub fn draw_actions(window: &Window, actions: Vec<String>, input: &str) {
}); });
} }
fn get_visual_type<T>(mut connection: T, window_id: u32) -> xcb::Visualtype fn get_visual_type<T>(mut connection: T, window_id: u32) -> xcb::x::Visualtype
where where
T: BorrowMut<*mut c_void>, T: BorrowMut<*mut c_void>,
{ {
@ -88,15 +90,22 @@ where
xcb::Connection::from_raw_conn(*connection.borrow_mut() as *mut xcb::ffi::xcb_connection_t) xcb::Connection::from_raw_conn(*connection.borrow_mut() as *mut xcb::ffi::xcb_connection_t)
}; };
let window_visualid = xcb::get_window_attributes(&xcb_conn, window_id) let window_attribute_request = unsafe {
.get_reply() xcb::x::GetWindowAttributes {
window: xcb::x::Window::new(window_id),
}
};
let window_attribute_cookie = xcb_conn.send_request(&window_attribute_request);
let window_visualid = xcb_conn
.wait_for_reply(window_attribute_cookie)
.expect("Could not fetch attributes for window") .expect("Could not fetch attributes for window")
.visual(); .visual();
debug! {"Found visualid {} for window", window_visualid} debug! {"Found visualid {} for window", window_visualid}
debug! {"Trying to map visualid to visualtype {}", window_visualid} debug! {"Trying to map visualid to visualtype {}", window_visualid}
let visualtype = xcb_conn let visualtype = *xcb_conn
.get_setup() .get_setup()
.roots() .roots()
.flat_map(|screen| screen.allowed_depths()) .flat_map(|screen| screen.allowed_depths())
@ -122,11 +131,8 @@ fn make_context(window: &Window) -> cairo::Context {
let xlib_window_id = window.xlib_window().expect("Could not get xlib window"); let xlib_window_id = window.xlib_window().expect("Could not get xlib window");
let visual_type = unsafe { let visual_type = unsafe {
let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32).base; let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32);
cairo::XCBVisualType::from_raw_none( cairo::XCBVisualType::from_raw_none(mem::transmute(&mut visual_type))
&mut visual_type as *mut xcb::ffi::xcb_visualtype_t
as *mut cairo::ffi::xcb_visualtype_t,
)
}; };
let connection = unsafe { let connection = unsafe {

110
src/window.rs Normal file
View file

@ -0,0 +1,110 @@
//! # Window
//!
//! Build a surface for `roftl`. The surface in turn provides an event loop as
//! well as a canvas for the `ui` module to paint on. Adhering to common
//! terminology we refer to this surface as window.
//!
//! Currently only x11 is implemented. The separation into a separate module is
//! there to make conditional compilation for other frameworks easier to handle.
//!
//! Some of the heavy lifting is done by `winit`. However, `winit` has some
//! assumptions built-in that are not changeable via the `winit` API. Since a
//! `roftl` does not behave like a regular application window we have to
//! directly interact with the underlying windowing framework for fine-tuning.
use std::mem;
use anyhow::{anyhow, Result};
use log::{debug, trace};
use winit::event_loop::EventLoop;
use winit::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
use winit::window::{Window, WindowBuilder};
use xcb::ffi::xcb_connection_t;
use xcb::x::{GrabMode, PropMode};
use xcb::XidNew;
pub fn build_window() -> Result<(Window, EventLoop<()>)> {
trace! {"Initialize `winit` event loop"}
let event_loop = EventLoop::new();
trace! {"Build `winit` base window"}
let window = WindowBuilder::new()
.with_decorations(false)
.with_transparent(true)
.with_title("roftl")
.with_visible(true)
.with_always_on_top(true)
.with_override_redirect(true)
.build(&event_loop)?;
trace! {"Built base window {:?}", window.id()}
trace! {"Post-process window"}
postprocess_window(&window)?;
Ok((window, event_loop))
}
#[cfg(feature = "x11")]
fn postprocess_window(window: &Window) -> Result<()> {
trace! {"Post-process window for x11"}
trace! {"Get low level connection and data"}
let screen_id = window
.xlib_screen_id()
.ok_or(anyhow! {"Could not get X11 screen id for the window"})?;
let xcb_window: xcb::x::Window = unsafe {
xcb::x::Window::new(
window
.xlib_window()
.ok_or(anyhow! {"Cannot get xlib window"})? as u32,
)
};
let xcb_conn_raw = window
.xcb_connection()
.ok_or(anyhow! {"Cannot get raw xcb connection"})?;
let xcb_conn = unsafe {
mem::ManuallyDrop::new(xcb::Connection::from_raw_conn(mem::transmute(xcb_conn_raw)))
};
let req = xcb::x::UnmapWindow { window: xcb_window };
let cookie = xcb_conn.send_request_checked(&req);
xcb_conn.check_request(cookie)?;
trace! {"Hide window from taskbar"}
let xcb_conn = unsafe { xcb::Connection::from_raw_conn(mem::transmute(xcb_conn_raw)) };
let ewmh_conn = xcb_wm::ewmh::Connection::connect(&xcb_conn);
let window_state = xcb_wm::ewmh::proto::SendWmState::new(
&ewmh_conn,
xcb_window,
PropMode::Append,
[
ewmh_conn.atoms._NET_WM_STATE,
ewmh_conn.atoms._NET_WM_STATE_SKIP_TASKBAR,
],
1,
);
ewmh_conn.send_request(&window_state);
let req = xcb::x::MapWindow { window: xcb_window };
xcb_conn.send_request(&req);
trace! {"Grab keyboard"}
let req = xcb::x::GrabKeyboard {
owner_events: true,
grab_window: xcb_window,
time: xcb::x::CURRENT_TIME,
pointer_mode: GrabMode::Async,
keyboard_mode: GrabMode::Async,
};
xcb_conn.send_request(&req);
mem::forget(xcb_conn);
// trace! {"Center window on screen"}
Ok(())
}