diff --git a/Cargo.toml b/Cargo.toml index b2e6fee..4d349bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ serde = { version = "1.0", features = ["derive"] } # read and parse configuration config = "0.13" +shellexpand = "2.1" # dependencies for building the `window` surface winit = { git = "https://github.com/rust-windowing/winit" } diff --git a/default.toml b/default.toml deleted file mode 100644 index 1ef885e..0000000 --- a/default.toml +++ /dev/null @@ -1,28 +0,0 @@ -log_level = "trace" - -[completions] -"e" = "emacs" -"f" = "firefox" -"t" = "xfce4-terminal" -"j" = "jetbrains" - -[sources.primary] -matcher = "Prefix" -source = "Windows" - -[sources.additional] -matcher = "Fuse" -source = ["Apps", "Shell", "Test"] - -[theme] -font = ["Sans Regular", 13] -border = 2 -divider = 3 - -[theme.color_scheme] -base = [0.13, 0.05, 0.23, 0.9] -border = [0.23, 0.05, 0.11, 1.0] -highlight = [0.12, 0.04, 0.08, 0.9] -divider = [0.23, 0.05, 0.11, 1.0] -text = [0.87, 0.95, 0.77, 1.0] -text_highlight = [0.6, 0.8, 0.4, 1.0] diff --git a/src/core/settings.rs b/src/core/settings.rs index 82da992..8b0e9c7 100644 --- a/src/core/settings.rs +++ b/src/core/settings.rs @@ -1,8 +1,10 @@ -use config::Config; +use config::builder::DefaultState; use config::ConfigError; +use config::{Config, ConfigBuilder}; use serde::Deserialize; use std::collections::HashMap; use std::convert::TryInto; +use std::path::Path; #[derive(Debug, Deserialize)] pub struct Color(f64, f64, f64, f64); @@ -98,16 +100,27 @@ pub struct Settings { impl Settings { pub fn parse() -> Result { - let mut config: Config = Config::builder() - .add_source(config::File::with_name("default")) - .add_source( - config::Environment::with_prefix("roftl") - .ignore_empty(true) - .separator("_"), - ) - .build()?; + let mut config: ConfigBuilder = Config::builder(); - config.try_deserialize() + let xdg_path_expanded = shellexpand::tilde("~/.config/roftl/config.toml"); + let xdg_config_path = Path::new(xdg_path_expanded.as_ref()); + let cwd_config_path = Path::new("default.toml"); + + if xdg_config_path.exists() { + config = config.add_source(config::File::from(xdg_config_path)); + } + + if cwd_config_path.exists() { + config = config.add_source(config::File::from(cwd_config_path)); + } + + config = config.add_source( + config::Environment::with_prefix("roftl") + .ignore_empty(true) + .separator("_"), + ); + + config.build()?.try_deserialize() } pub fn primary_matcher(&self) -> Box { diff --git a/src/sources/windows.rs b/src/sources/windows.rs index 1527ede..de2befb 100644 --- a/src/sources/windows.rs +++ b/src/sources/windows.rs @@ -3,10 +3,8 @@ use std::collections::HashMap; use super::core::shared::{Entry, Source}; use log::debug; 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 { @@ -21,11 +19,15 @@ impl Window { } fn is_normal_window(&self, con: &ewmh::Connection, window: xcb::x::Window) -> bool { - let wm_type_request = ewmh::proto::GetWmWindowType(window); + let wm_type_request = ewmh::GetWmWindowType(window); let wm_types = con .wait_for_reply(con.send_request(&wm_type_request)) .window_types; + if wm_types.is_empty() { + return true; + } + for atom in wm_types { if atom == con.atoms._NET_WM_WINDOW_TYPE_NORMAL { return true; @@ -35,84 +37,28 @@ impl Window { false } - // fn window_category( - // &self, - // con: &ewmh::Connection, - // window: u32, - // ) -> Result { - // Ok(icccm::get_wm_class(&con, window) - // .get_reply()? - // .class() - // .into()) - // } + fn window_category(&self, con: &icccm::Connection, window: xcb::x::Window) -> String { + let wm_category_request = con.send_request(&icccm::GetWmClass::new(window)); + let wm_category = con.wait_for_reply(wm_category_request); + wm_category.class + } fn window_title(&self, con: &ewmh::Connection, window: xcb::x::Window) -> String { - let wm_name_request = ewmh::proto::GetWmName(window); + let wm_name_request = ewmh::GetWmName(window); let wm_name = con.wait_for_reply(con.send_request(&wm_name_request)); wm_name.name } fn switch_window(&self, con: &ewmh::Connection, window: u32) { - let change_active_window = ewmh::proto::SendActiveWindow::new( + let change_active_window = ewmh::SendActiveWindow::new( con, unsafe { xcb::x::Window::new(window.into()) }, 1, xcb::x::CURRENT_TIME, Option::None, ); - con.send_request(&change_active_window); + con.send_and_check_request(&change_active_window).unwrap(); } - - // 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 { @@ -123,29 +69,30 @@ impl Source for Window { fn entries(&mut self) -> Vec { let (xcb_conn, screen_num) = xcb::Connection::connect(None).expect("Could not connect to X server"); - let ewmh_conn = ewmh::Connection::connect(&xcb_conn); + let ewmh_con = ewmh::Connection::connect(&xcb_conn); + let icccm_con = icccm::Connection::connect(&xcb_conn); - 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 client_list_reply = ewmh_con.send_request(&xcb_wm::ewmh::GetClientList); + let windows = ewmh_con.wait_for_reply(client_list_reply).clients; let mut entries: Vec = vec![]; let mut count: u64 = 0; for w in windows { - match self.is_normal_window(&ewmh_conn, w) { + match self.is_normal_window(&ewmh_con, w) { true => {} false => continue, } - let title = self.window_title(&ewmh_conn, w); - let category: String = "Test".into(); //self.window_category(&ewmh_conn, *w).unwrap(); + let title = self.window_title(&ewmh_con, w); + let category: String = self.window_category(&icccm_con, w); debug!("Found window {} - {}", title, category); entries.push(Entry { source: self.name(), - name: title.clone(), - description: title.clone(), + name: category, + description: title, identifier: count, }); self.action_data @@ -157,8 +104,17 @@ impl Source for Window { entries } + fn actions(&self, _entry: &Entry) -> Vec { + vec![ + "Switch".into(), + "Toggle maximize".into(), + "Toggle hide".into(), + "Close".into(), + ] + } + fn exec_action(&self, entry: &Entry, action: u8) { - debug!("Doing action {:?} for entry {}", action, entry.name); + debug!("Executing action {:?} for entry {}", action, entry.name); match action { 0 => { @@ -169,47 +125,7 @@ impl Source for Window { 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}, } } - - fn actions(&self, _entry: &Entry) -> Vec { - vec![ - "Switch".into(), - "Toggle maximize".into(), - "Toggle hide".into(), - "Close".into(), - ] - } } diff --git a/src/ui/painter.rs b/src/ui/painter.rs index 9c5a70d..8f7f4d0 100644 --- a/src/ui/painter.rs +++ b/src/ui/painter.rs @@ -87,7 +87,7 @@ impl<'a> Painter<'a> { x: T, y: T, source: &'a str, - result: &'a str, + result: [&str; 2], indices: &[usize], selected: bool, ) where @@ -107,10 +107,17 @@ impl<'a> Painter<'a> { ctx.fill().unwrap(); // draw text + //source ctx.move_to(x + 0.2, y); ctx.theme_text(&source, selected, &[], &self.theme); + + // category ctx.move_to(x + 3.5, y); - ctx.theme_text(&result, selected, indices, &self.theme); + ctx.theme_text(result[0], selected, indices, &self.theme); + + // title + ctx.move_to(x + 3.5 + 10.0, y); + ctx.theme_text(result[1], selected, indices, &self.theme); } pub fn divider(&self, x: T, y: T) diff --git a/src/ui/theme.rs b/src/ui/theme.rs index db37bbd..14ca444 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -1,4 +1,4 @@ -use pangocairo::pango::{self, AttrInt, AttrList, Attribute, FontDescription}; +use pangocairo::pango::{self, AttrInt, AttrList, Attribute, EllipsizeMode, FontDescription}; // (r,g,b,a) #[derive(Clone, Copy)] @@ -126,7 +126,9 @@ impl ThemedContextExt for cairo::Context { font.set_size(theme.font.1 * pango::SCALE); layout.set_font_description(Some(&font)); + layout.set_width(200 * pango::SCALE); layout.set_spacing(0); + layout.set_ellipsize(EllipsizeMode::End); layout.set_text(text); diff --git a/src/ui/ui.rs b/src/ui/ui.rs index a0c2594..e6b3703 100644 --- a/src/ui/ui.rs +++ b/src/ui/ui.rs @@ -22,7 +22,8 @@ pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec)>, sel result.iter().enumerate().for_each(|(i, r)| { let e = r.0; - painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, selection == i); + let result = [e.name.as_str(), e.description.as_str()]; + painter.result_box(0, 1 + (i as u32), &e.source, result, &r.1, selection == i); }); } @@ -36,25 +37,27 @@ pub fn redraw_quick(window: &Window, result: Vec<(&Entry, Vec)>, selectio result.iter().enumerate().for_each(|(i, r)| { let e = r.0; + let result = [e.name.as_str(), e.description.as_str()]; + // clear first and last as these may produce artifacts otherwise if i == 0 { - painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false) + painter.result_box(0, 1 + (i as u32), &e.source, result, &r.1, false) } if i == result.len() - 1 { - painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false) + painter.result_box(0, 1 + (i as u32), &e.source, result, &r.1, false) } // clear boxes around selection as these could be the old selection if selection > 0 && i == (selection - 1) { - painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false) + painter.result_box(0, 1 + (i as u32), &e.source, result, &r.1, false) } if i == (selection + 1) { - painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, false) + painter.result_box(0, 1 + (i as u32), &e.source, result, &r.1, false) } // mark selection, note that this negates any unmarking above in case they are the same if i == selection { - painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &r.1, true) + painter.result_box(0, 1 + (i as u32), &e.source, result, &r.1, true) } }); } @@ -75,7 +78,7 @@ pub fn draw_actions(window: &Window, actions: Vec, input: &str) { 0, 1 + (i as u32), &usize::to_string(&(i + 1)), - r, + [r.as_str(), ""], &[], false, ); diff --git a/src/window.rs b/src/window.rs index cb475f9..0c7f369 100644 --- a/src/window.rs +++ b/src/window.rs @@ -22,7 +22,7 @@ use winit::window::{Window, WindowBuilder}; use xcb::ffi::xcb_connection_t; use xcb::x::{GrabMode, PropMode}; use xcb::XidNew; -use xcb_wm::ewmh; +use xcb_wm::{ewmh, icccm}; pub fn build_window() -> Result<(Window, EventLoop<()>)> { trace! {"Initialize `winit` event loop"} @@ -68,25 +68,24 @@ fn postprocess_window(window: &Window) -> Result<()> { mem::ManuallyDrop::new(con) }; - let ewmh_con = xcb_wm::ewmh::Connection::connect(&xcb_con); - let icccm_con = xcb_wm::icccm::Connection::connect(&xcb_con); + let ewmh_con = ewmh::Connection::connect(&xcb_con); + let icccm_con = icccm::Connection::connect(&xcb_con); trace! {"Unmap window"} let req = xcb::x::UnmapWindow { window: xcb_window }; xcb_con.send_and_check_request(&req)?; trace! {"Hide window from taskbar"} - let window_state = xcb_wm::ewmh::proto::SendWmState::new( + ewmh_con.send_and_check_request(&ewmh::SendWmState::new( &ewmh_con, xcb_window, - PropMode::Append, + xcb::x::PropMode::Append, [ ewmh_con.atoms._NET_WM_STATE, ewmh_con.atoms._NET_WM_STATE_SKIP_TASKBAR, ], 1, - ); - ewmh_con.send_request(&window_state); + ))?; trace! {"Center window on screen"} let screen_pixel = window @@ -101,7 +100,7 @@ fn postprocess_window(window: &Window) -> Result<()> { .position(); // Configure the window to the center with a size of 800x600 pixels. - let window_width = 800u32; + let window_width = 1000u32; let window_height = 300u32; let x = screen_position.x as i32 + (screen_pixel.width as i32 / 2 - window_width as i32 / 2); let y = screen_position.y as i32 + (screen_pixel.height as i32 / 2 - window_height as i32 / 2); @@ -129,5 +128,6 @@ fn postprocess_window(window: &Window) -> Result<()> { }; xcb_con.send_request(&req); + xcb_con.flush()?; Ok(()) }