diff --git a/Cargo.lock b/Cargo.lock index be2a7de..5219c95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,9 +36,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" [[package]] name = "cfg-if" @@ -120,23 +120,21 @@ dependencies = [ [[package]] name = "fltk" -version = "1.1.12" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf356f493316a45a5b90b8d5db97c0fd9850b87ab19cc7f88b53ca5d01fd710" +checksum = "c30618861047220f552e9e7dba957bd9c0953f23d96e27447ca4a934f05870a2" dependencies = [ "bitflags", "fltk-derive", "fltk-sys", - "lazy_static", - "objc", "raw-window-handle", ] [[package]] name = "fltk-derive" -version = "1.1.12" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ddbb2cb93484b56da41b80c1466bca2620b68b61847d5e7f999bda0782ab78" +checksum = "36c1111ff05b7d2552093f23dd9db1e0e380f9c6c0b57d69e21811eb4057d011" dependencies = [ "quote", "syn", @@ -144,12 +142,11 @@ dependencies = [ [[package]] name = "fltk-sys" -version = "1.1.12" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe424d107ab6ef0a4b756295c259bb8cec6e610548d55481457309884d526d4" +checksum = "0ccc3328371cbe9d01b89488e815c39c5680843379606aa74b1b169c1f54a489" dependencies = [ "cmake", - "libc", ] [[package]] @@ -175,9 +172,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.100" +version = "0.2.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" [[package]] name = "log" @@ -188,15 +185,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "memchr" version = "2.4.1" @@ -222,20 +210,11 @@ dependencies = [ "libc", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ "unicode-xid", ] @@ -308,6 +287,8 @@ dependencies = [ "fltk", "log", "rayon", + "xcb", + "xcb-util", ] [[package]] @@ -318,9 +299,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "syn" -version = "1.0.75" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" dependencies = [ "proc-macro2", "quote", @@ -372,3 +353,23 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xcb" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62056f63138b39116f82a540c983cc11f1c90cd70b3d492a70c25eaa50bd22a6" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "xcb-util" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43893e47f27bf7d81d489feef3a0e34a457e90bc314b7e74ad9bb3980e4c1c48" +dependencies = [ + "libc", + "xcb", +] diff --git a/Cargo.toml b/Cargo.toml index 8353c9d..318edfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,7 @@ edition = "2018" fltk = "1.1" log = "0.4" env_logger = "0.9.0" -rayon = "1.5.1" \ No newline at end of file +rayon = "1.5.1" + +xcb = "0.9.0" +xcb-util = {version = "0.3.0", features = ["ewmh", "icccm"]} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 22dcc36..620350f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,66 +3,15 @@ use std::error::Error; mod roftl; mod ui; - -use roftl::{Action, Entry, Source}; - -struct TestSource { - name: String, - entries: Vec, -} - -impl TestSource { - fn new(s: &str) -> Box { - let mut ts = Box::new(TestSource { - name: "Testsource".into(), - entries: vec![], - }); - - (1..1000).for_each(|i| { - ts.add_entry( - format! {"Test {} {}", i, s}, - format! {"Test {} description", i}, - ); - }); - - ts - } - - fn add_entry(&mut self, name: String, description: String) { - let id = self.entries.len(); - self.entries.push(Entry { - name, - description, - source: self, - id: id as u32, - }); - } -} - -impl Source for TestSource { - fn name(&self) -> &str { - &self.name - } - - fn entries(&self) -> Vec { - self.entries.clone() - } - - fn action(&self, entry: &Entry, action: Action) { - println!("Doing action {:?} for entry {}", action, entry.name) - } -} +mod sources; fn main() -> Result<(), Box> { env_logger::init(); - let mut roftl = roftl::Roftl::new(); - - roftl - .add_source(TestSource::new("ts1")) - .add_source(TestSource::new("ts2")); - - roftl.source(); + let roftl = roftl::Roftl::default() + .add_source(sources::TestSource::new("ts1")) + .add_source(sources::Window::new()) + .source(); let mut app = ui::draw()?; @@ -73,9 +22,9 @@ fn main() -> Result<(), Box> { while app.wait() { match app.events.recv() { - Some(ui::Event::CandidateSelect(entry)) => { - trace! {"Selected: {}", entry.name} - roftl.action(&entry, Action::Primary) + Some(ui::Event::CandidateSelect(i)) => { + println! {"Selected an entry {}", i} + roftl.action(i, roftl::Action::Primary) } Some(ui::Event::InputUpdate) => { diff --git a/src/roftl.rs b/src/roftl.rs index 4a8b0d5..c9fc517 100644 --- a/src/roftl.rs +++ b/src/roftl.rs @@ -1,63 +1,82 @@ -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use std::{collections::HashMap, sync::Mutex}; -#[derive(Clone)] +use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; + +#[derive(Clone, Debug)] pub struct Entry { + pub source: &'static str, pub name: String, pub description: String, - pub source: *const dyn Source, - pub id: u32 + pub identifier: u64, } -unsafe impl Sync for Entry {} -unsafe impl Send for Entry {} - - #[derive(Debug)] pub enum Action { Primary, } pub trait Source: Send + Sync { - fn name(&self) -> &str; - fn entries(&self) -> Vec; + fn name(&self) -> &'static str; + fn entries(&mut self) -> Vec; fn action(&self, entry: &Entry, action: Action); } +#[derive(Default)] pub struct Roftl { sources: Vec>, entries: Vec, + narrow_map: Mutex> } impl Roftl { - pub fn new() -> Roftl { - Roftl { - sources: vec![], - entries: vec![], + pub fn add_source(mut self, source: Box) -> Self { + if self.sources.par_iter().any(|s| s.name().eq(source.name())) { + panic! {"Source with name '{}' already exists", source.name()} } - } - pub fn add_source(&mut self, source: Box) -> &mut Roftl { self.sources.push(source); self } - pub fn source(&mut self) { + pub fn source(mut self) -> Self { self.entries = self .sources - .par_iter() + .par_iter_mut() .flat_map_iter(|s| s.entries()) .collect(); + + println!("Entries {:?}", self.entries); + self } pub fn narrow(&self, input: &str) -> Vec<&Entry> { - self.entries.par_iter() - .filter(|e| e.name.starts_with(input)) + let count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); + self.narrow_map.lock().unwrap().clear(); + + self.entries + .par_iter() + .enumerate() + .filter(|(_idx, e)| e.name.starts_with(input)) + .map(move |(idx, e)| { + self.narrow_map.lock().unwrap().insert(count.load(std::sync::atomic::Ordering::Acquire), idx); + count.fetch_add(1, std::sync::atomic::Ordering::Acquire); + e + }) .collect() } - pub fn action(&self, entry: &Entry, action: Action) { - unsafe { - Source::action(&*entry.source, entry, action); - } + pub fn action(&self, entry_id: i32, action: Action) { + println!("NarrowMap {:?}", self.narrow_map.lock().unwrap()); + + let real_id = *self.narrow_map.lock().unwrap().get(&((entry_id-1) as usize)).unwrap(); + let entry = self.entries.get(real_id).unwrap(); + + let source = self + .sources + .iter() + .find(|&s| s.name().eq(entry.source)) + .unwrap(); + + source.action(entry, action); } } diff --git a/src/sources/mod.rs b/src/sources/mod.rs new file mode 100644 index 0000000..ee10e2f --- /dev/null +++ b/src/sources/mod.rs @@ -0,0 +1,6 @@ +mod test; +pub use test::TestSource; + +mod windows; +pub use windows::Window; + diff --git a/src/sources/test.rs b/src/sources/test.rs new file mode 100644 index 0000000..2bc2463 --- /dev/null +++ b/src/sources/test.rs @@ -0,0 +1,45 @@ +use crate::roftl::{Entry, Source, Action}; + +pub struct TestSource { + entries: Vec, +} + +impl TestSource { + pub fn new(s: &str) -> Box { + let mut ts = Box::new(TestSource { entries: vec![] }); + + (1..2).for_each(|i| { + ts.add_entry( + format! {"Test {} {}", i, s}, + format! {"Test {} description", i}, + ); + }); + + ts + } + + pub fn add_entry(&mut self, name: String, description: String) { + let id = self.entries.len(); + self.entries.push(Entry { + name, + description, + source: self.name(), + identifier: id as u64, + }); + } +} + +impl Source for TestSource { + fn name(&self) -> &'static str { + "TestSource" + } + + fn entries(&mut self) -> Vec { + self.entries.clone() + } + + fn action(&self, entry: &Entry, action: Action) { + println!("Doing action {:?} for entry {}", action, entry.name) + } +} + diff --git a/src/sources/windows.rs b/src/sources/windows.rs new file mode 100644 index 0000000..7f68d42 --- /dev/null +++ b/src/sources/windows.rs @@ -0,0 +1,97 @@ +use std::collections::HashMap; +use std::ptr; + +use crate::roftl::{Entry, Source, Action}; +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; + +pub struct Window { + windows: Vec, + action_data: HashMap +} + +impl Window { + pub fn new() -> Box { + Box::new(Window { windows: vec![], action_data: HashMap::::new() }) + } + + fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result { + let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?; + for atom in wm_type_reply.atoms() { + if *atom == con.WM_WINDOW_TYPE_NORMAL() { + return Ok(true); + } + } + + Err(xcb::GenericError{ptr: ptr::null_mut()}) + } + + fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result { + Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into()) + } + + fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result { + Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into()) + } + + fn switch_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::GenericError> { + 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, 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(()) + } +} + +impl Source for Window { + fn name(&self) -> &'static str { + "window" + } + + fn entries(&mut self) -> Vec { + 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(); + + let client_list_reply = xcb_util::ewmh::get_client_list(&ewmh_conn, screen_num).get_reply().unwrap(); + let windows = client_list_reply.windows(); + + let mut entries: Vec = 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) => continue + } + + let title = self.window_title(&ewmh_conn, *w).unwrap(); + let category = self.window_category(&ewmh_conn, *w).unwrap(); + + println!("Found window {} - {}", title, category); + + entries.push(Entry{source: self.name(), name: category, description: title, identifier: count}); + self.action_data.insert(count, (*w, screen_num)); + + count+=1; + } + + entries + } + + fn action(&self, entry: &Entry, action: Action) { + println!("Doing action {:?} for entry {}", action, entry.name); + + 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() + } +} diff --git a/src/ui.rs b/src/ui.rs index 047ad9a..e2fcf44 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,58 +1,37 @@ use std::ops::Deref; -use fltk::enums::{CallbackTrigger, Font, FrameType}; +use fltk::app::event_key; +use fltk::enums::{CallbackTrigger, Font, FrameType, Key}; use fltk::group::{PackType, ScrollType}; -use fltk::output; use fltk::prelude::*; -use fltk::{app, window, input, group}; +use fltk::{app, group, input, window}; +use fltk::{browser, output}; use super::roftl::Entry; #[derive(Clone)] pub enum Event { InputUpdate, - CandidateSelect(Entry) + CandidateSelect(i32), } pub struct RoftlApp { pub app: app::App, - pub events: app::Receiver::, + pub events: app::Receiver, send: app::Sender, input: input::Input, - candidates: group::Pack, - scroll: group::Scroll + browser: browser::HoldBrowser, } impl RoftlApp { pub fn add_candidate(&mut self, entry: Entry) { - let mut output = output::Output::default(); - output.set_value(&entry.name); - output.set_frame(FrameType::FlatBox); - output.set_size(380,30); - - let s = self.send.clone(); - output.handle(move |_widget, ev: fltk::enums::Event| { - match ev { - fltk::enums::Event::Focus => { - s.send(Event::CandidateSelect(entry.clone())); - true - }, - - _ => false - } - }); - - self.candidates.add(&output); - self.scroll.redraw(); + let entry_line = format! {"{}\tf{}\t{}", entry.name, entry.description, entry.source}; + self.browser.add(&entry_line); } pub fn clear_candidates(&mut self) { - for i in 0..self.candidates.children() { - self.candidates.child(i).and_then(|c| {Some(WidgetBase::delete(c))}); - } - - self.scroll.redraw(); + self.browser.clear(); } pub fn get_input(&self) -> String { @@ -69,7 +48,6 @@ impl Deref for RoftlApp { } pub fn draw() -> Result { - app::set_font(Font::Screen); let app = app::App::default(); @@ -81,30 +59,66 @@ pub fn draw() -> Result { let pack = group::Pack::default() .with_type(PackType::Vertical) - .with_size(400,40); + .with_size(400, 40); - let mut input = input::Input::default() - .with_size(0,40); + let mut input = input::Input::default().with_size(0, 40); input.set_trigger(CallbackTrigger::Changed); - let scroll = group::Scroll::default() - .with_size(400, 300-40) - .with_type(ScrollType::AlwaysOn); + let mut browser: browser::HoldBrowser = + browser::HoldBrowser::default().with_size(400, 300 - 40); + browser.set_has_scrollbar(browser::BrowserScrollbar::None); - let candidates = group::Pack::default() - .with_size(380, 300-40) - .center_of(&scroll); - - candidates.end(); - scroll.end(); pack.end(); window.end(); window.show(); let (s, r) = app::channel::(); + input.emit(s.clone(), Event::InputUpdate); - Ok(RoftlApp{app, events: r, send: s, input, candidates, scroll}) -} + let mut b1 = browser.clone(); + let s1 = s.clone(); + input.handle(move |_input, evt| match evt { + fltk::enums::Event::KeyUp => { + if event_key().eq(&Key::Tab) { + b1.take_focus().unwrap(); + b1.select(1); + s1.send(Event::CandidateSelect(b1.value())); + return true; + } + return false; + } + _ => false, + }); + + let mut b2 = browser.clone(); + let s2 = s.clone(); + browser.handle(move |_input, evt| match evt { + fltk::enums::Event::KeyUp => { + if event_key().eq(&Key::Tab) { + b2.take_focus().unwrap(); + b2.select(b2.value() + 1); + s2.send(Event::CandidateSelect(b2.value())); + return true; + } + return false; + } + + _ => false, + }); + + let s3 = s.clone(); + browser.set_callback(move |b| { + s3.send(Event::CandidateSelect(b.value())); + }); + + Ok(RoftlApp { + app, + events: r, + send: s, + input, + browser, + }) +}