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"
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]
# globally useful dependencies
log = "0.4"
env_logger = "0.9"
rayon = "1.5"
anyhow = "1.0"
config = "0.13"
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" }
cairo-rs = {version = "0.15", features = ["xcb"]}
cairo-sys-rs = {version = "0.15", features = ["xcb"]}
## `window` dependencies specific for x11. Only enabled if compiled with `x11` feature on [default]
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"
xcb = "0.10"
xcb-util = {version = "0.4", features = ["ewmh", "icccm"]}
# matcher
fuzzy-matcher = "0.3"

View file

@ -6,8 +6,7 @@ use winit::event::{
WindowEvent::{CloseRequested, ReceivedCharacter},
};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::platform::unix::x11::util::WindowType;
use winit::platform::unix::{WindowBuilderExtUnix, WindowExtUnix, XWindowType};
use winit::platform::unix::WindowExtUnix;
use winit::window::{Window, WindowBuilder};
use self::core::roftl::Roftl;
@ -16,6 +15,7 @@ mod core;
mod matcher;
mod sources;
mod ui;
mod window;
fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();
@ -37,67 +37,12 @@ fn main() -> Result<(), Box<dyn Error>> {
debug! {"Source roftl sources"}
roftl.source();
debug! {"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()}
let (window, event_loop) = window::build_window()?;
debug! {"Draw primary state to window"}
ui::draw(&window, "", roftl.narrow(""), 0);
debug! {"Start event loop"}
trace! {"Start event loop"}
let roftl_loop = RoftlLoop::new(roftl, window);
roftl_loop.run(event_loop);
}

View file

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

View file

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

View file

@ -1,11 +1,13 @@
use std::borrow::BorrowMut;
use std::ffi::c_void;
use std::mem;
use log::debug;
use winit::{platform::unix::WindowExtUnix, window::Window};
use xcb::XidNew;
use super::core::shared::Entry;
use super::painter;
use crate::core::shared::Entry;
pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
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
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)
};
let window_visualid = xcb::get_window_attributes(&xcb_conn, window_id)
.get_reply()
let window_attribute_request = unsafe {
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")
.visual();
debug! {"Found visualid {} for window", window_visualid}
debug! {"Trying to map visualid to visualtype {}", window_visualid}
let visualtype = xcb_conn
let visualtype = *xcb_conn
.get_setup()
.roots()
.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 visual_type = unsafe {
let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32).base;
cairo::XCBVisualType::from_raw_none(
&mut visual_type as *mut xcb::ffi::xcb_visualtype_t
as *mut cairo::ffi::xcb_visualtype_t,
)
let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32);
cairo::XCBVisualType::from_raw_none(mem::transmute(&mut visual_type))
};
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(())
}