Read config from xdg directory

This commit is contained in:
Armin Friedl 2022-06-19 08:52:07 +02:00
parent 39a888c7ba
commit c5ca08f556
8 changed files with 86 additions and 172 deletions

View file

@ -19,6 +19,7 @@ serde = { version = "1.0", features = ["derive"] }
# read and parse configuration # read and parse configuration
config = "0.13" config = "0.13"
shellexpand = "2.1"
# dependencies for building the `window` surface # dependencies for building the `window` surface
winit = { git = "https://github.com/rust-windowing/winit" } winit = { git = "https://github.com/rust-windowing/winit" }

View file

@ -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]

View file

@ -1,8 +1,10 @@
use config::Config; use config::builder::DefaultState;
use config::ConfigError; use config::ConfigError;
use config::{Config, ConfigBuilder};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use std::path::Path;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Color(f64, f64, f64, f64); pub struct Color(f64, f64, f64, f64);
@ -98,16 +100,27 @@ pub struct Settings {
impl Settings { impl Settings {
pub fn parse() -> Result<Self, ConfigError> { pub fn parse() -> Result<Self, ConfigError> {
let mut config: Config = Config::builder() let mut config: ConfigBuilder<DefaultState> = Config::builder();
.add_source(config::File::with_name("default"))
.add_source( 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") config::Environment::with_prefix("roftl")
.ignore_empty(true) .ignore_empty(true)
.separator("_"), .separator("_"),
) );
.build()?;
config.try_deserialize() config.build()?.try_deserialize()
} }
pub fn primary_matcher(&self) -> Box<dyn super::shared::Matcher> { pub fn primary_matcher(&self) -> Box<dyn super::shared::Matcher> {

View file

@ -3,10 +3,8 @@ use std::collections::HashMap;
use super::core::shared::{Entry, Source}; use super::core::shared::{Entry, Source};
use log::debug; use log::debug;
use winit::platform::unix::x11::ffi::Connection; use winit::platform::unix::x11::ffi::Connection;
use xcb::x::{InputFocus, CURRENT_TIME};
use xcb::{Xid, XidNew}; use xcb::{Xid, XidNew};
use xcb_wm::ewmh; use xcb_wm::ewmh;
use xcb_wm::ewmh::proto::{GetActiveWindow, GetWmDesktop};
use xcb_wm::icccm; use xcb_wm::icccm;
pub struct Window { pub struct Window {
@ -21,11 +19,15 @@ impl Window {
} }
fn is_normal_window(&self, con: &ewmh::Connection, window: xcb::x::Window) -> bool { 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 let wm_types = con
.wait_for_reply(con.send_request(&wm_type_request)) .wait_for_reply(con.send_request(&wm_type_request))
.window_types; .window_types;
if wm_types.is_empty() {
return true;
}
for atom in wm_types { for atom in wm_types {
if atom == con.atoms._NET_WM_WINDOW_TYPE_NORMAL { if atom == con.atoms._NET_WM_WINDOW_TYPE_NORMAL {
return true; return true;
@ -35,84 +37,28 @@ impl Window {
false false
} }
// fn window_category( fn window_category(&self, con: &icccm::Connection, window: xcb::x::Window) -> String {
// &self, let wm_category_request = con.send_request(&icccm::GetWmClass::new(window));
// con: &ewmh::Connection, let wm_category = con.wait_for_reply(wm_category_request);
// window: u32, wm_category.class
// ) -> 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 { 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)); let wm_name = con.wait_for_reply(con.send_request(&wm_name_request));
wm_name.name wm_name.name
} }
fn switch_window(&self, con: &ewmh::Connection, window: u32) { 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, con,
unsafe { xcb::x::Window::new(window.into()) }, unsafe { xcb::x::Window::new(window.into()) },
1, 1,
xcb::x::CURRENT_TIME, xcb::x::CURRENT_TIME,
Option::None, 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 { impl Source for Window {
@ -123,29 +69,30 @@ 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::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_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 client_list_reply = ewmh_con.send_request(&xcb_wm::ewmh::GetClientList);
let windows = ewmh_conn.wait_for_reply(client_list_reply).clients; let windows = ewmh_con.wait_for_reply(client_list_reply).clients;
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_con, w) {
true => {} true => {}
false => continue, false => continue,
} }
let title = self.window_title(&ewmh_conn, w); let title = self.window_title(&ewmh_con, w);
let category: String = "Test".into(); //self.window_category(&ewmh_conn, *w).unwrap(); let category: String = self.window_category(&icccm_con, w);
debug!("Found window {} - {}", title, category); debug!("Found window {} - {}", title, category);
entries.push(Entry { entries.push(Entry {
source: self.name(), source: self.name(),
name: title.clone(), name: category,
description: title.clone(), description: title,
identifier: count, identifier: count,
}); });
self.action_data self.action_data
@ -157,8 +104,17 @@ impl Source for Window {
entries entries
} }
fn actions(&self, _entry: &Entry) -> Vec<String> {
vec![
"Switch".into(),
"Toggle maximize".into(),
"Toggle hide".into(),
"Close".into(),
]
}
fn exec_action(&self, entry: &Entry, action: u8) { 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 { match action {
0 => { 0 => {
@ -169,47 +125,7 @@ impl Source for Window {
self.switch_window(&ewmh_conn, window); self.switch_window(&ewmh_conn, window);
xcb_conn.flush().unwrap(); 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}, _ => panic! {"Unknown action {:?}", action},
} }
} }
fn actions(&self, _entry: &Entry) -> Vec<String> {
vec![
"Switch".into(),
"Toggle maximize".into(),
"Toggle hide".into(),
"Close".into(),
]
}
} }

View file

@ -87,7 +87,7 @@ impl<'a> Painter<'a> {
x: T, x: T,
y: T, y: T,
source: &'a str, source: &'a str,
result: &'a str, result: [&str; 2],
indices: &[usize], indices: &[usize],
selected: bool, selected: bool,
) where ) where
@ -107,10 +107,17 @@ impl<'a> Painter<'a> {
ctx.fill().unwrap(); ctx.fill().unwrap();
// draw text // draw text
//source
ctx.move_to(x + 0.2, y); ctx.move_to(x + 0.2, y);
ctx.theme_text(&source, selected, &[], &self.theme); ctx.theme_text(&source, selected, &[], &self.theme);
// category
ctx.move_to(x + 3.5, y); 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<T>(&self, x: T, y: T) pub fn divider<T>(&self, x: T, y: T)

View file

@ -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) // (r,g,b,a)
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -126,7 +126,9 @@ impl ThemedContextExt for cairo::Context {
font.set_size(theme.font.1 * pango::SCALE); font.set_size(theme.font.1 * pango::SCALE);
layout.set_font_description(Some(&font)); layout.set_font_description(Some(&font));
layout.set_width(200 * pango::SCALE);
layout.set_spacing(0); layout.set_spacing(0);
layout.set_ellipsize(EllipsizeMode::End);
layout.set_text(text); layout.set_text(text);

View file

@ -22,7 +22,8 @@ pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, sel
result.iter().enumerate().for_each(|(i, r)| { result.iter().enumerate().for_each(|(i, r)| {
let e = r.0; 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<usize>)>, selectio
result.iter().enumerate().for_each(|(i, r)| { result.iter().enumerate().for_each(|(i, r)| {
let e = r.0; let e = r.0;
let result = [e.name.as_str(), e.description.as_str()];
// clear first and last as these may produce artifacts otherwise // clear first and last as these may produce artifacts otherwise
if i == 0 { 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 { 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 // clear boxes around selection as these could be the old selection
if selection > 0 && i == (selection - 1) { 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) { 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 // mark selection, note that this negates any unmarking above in case they are the same
if i == selection { 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<String>, input: &str) {
0, 0,
1 + (i as u32), 1 + (i as u32),
&usize::to_string(&(i + 1)), &usize::to_string(&(i + 1)),
r, [r.as_str(), ""],
&[], &[],
false, false,
); );

View file

@ -22,7 +22,7 @@ use winit::window::{Window, WindowBuilder};
use xcb::ffi::xcb_connection_t; use xcb::ffi::xcb_connection_t;
use xcb::x::{GrabMode, PropMode}; use xcb::x::{GrabMode, PropMode};
use xcb::XidNew; use xcb::XidNew;
use xcb_wm::ewmh; use xcb_wm::{ewmh, icccm};
pub fn build_window() -> Result<(Window, EventLoop<()>)> { pub fn build_window() -> Result<(Window, EventLoop<()>)> {
trace! {"Initialize `winit` event loop"} trace! {"Initialize `winit` event loop"}
@ -68,25 +68,24 @@ fn postprocess_window(window: &Window) -> Result<()> {
mem::ManuallyDrop::new(con) mem::ManuallyDrop::new(con)
}; };
let ewmh_con = xcb_wm::ewmh::Connection::connect(&xcb_con); let ewmh_con = ewmh::Connection::connect(&xcb_con);
let icccm_con = xcb_wm::icccm::Connection::connect(&xcb_con); let icccm_con = icccm::Connection::connect(&xcb_con);
trace! {"Unmap window"} trace! {"Unmap window"}
let req = xcb::x::UnmapWindow { window: xcb_window }; let req = xcb::x::UnmapWindow { window: xcb_window };
xcb_con.send_and_check_request(&req)?; xcb_con.send_and_check_request(&req)?;
trace! {"Hide window from taskbar"} 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, &ewmh_con,
xcb_window, xcb_window,
PropMode::Append, xcb::x::PropMode::Append,
[ [
ewmh_con.atoms._NET_WM_STATE, ewmh_con.atoms._NET_WM_STATE,
ewmh_con.atoms._NET_WM_STATE_SKIP_TASKBAR, ewmh_con.atoms._NET_WM_STATE_SKIP_TASKBAR,
], ],
1, 1,
); ))?;
ewmh_con.send_request(&window_state);
trace! {"Center window on screen"} trace! {"Center window on screen"}
let screen_pixel = window let screen_pixel = window
@ -101,7 +100,7 @@ fn postprocess_window(window: &Window) -> Result<()> {
.position(); .position();
// Configure the window to the center with a size of 800x600 pixels. // 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 window_height = 300u32;
let x = screen_position.x as i32 + (screen_pixel.width as i32 / 2 - window_width as i32 / 2); 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); 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.send_request(&req);
xcb_con.flush()?;
Ok(()) Ok(())
} }