Source windows from x11 server

This commit is contained in:
Armin Friedl 2021-10-03 23:07:19 +02:00
parent 5938bad12f
commit fd54aeece2
8 changed files with 299 additions and 165 deletions

71
Cargo.lock generated
View file

@ -36,9 +36,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.69" version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -120,23 +120,21 @@ dependencies = [
[[package]] [[package]]
name = "fltk" name = "fltk"
version = "1.1.12" version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf356f493316a45a5b90b8d5db97c0fd9850b87ab19cc7f88b53ca5d01fd710" checksum = "c30618861047220f552e9e7dba957bd9c0953f23d96e27447ca4a934f05870a2"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"fltk-derive", "fltk-derive",
"fltk-sys", "fltk-sys",
"lazy_static",
"objc",
"raw-window-handle", "raw-window-handle",
] ]
[[package]] [[package]]
name = "fltk-derive" name = "fltk-derive"
version = "1.1.12" version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57ddbb2cb93484b56da41b80c1466bca2620b68b61847d5e7f999bda0782ab78" checksum = "36c1111ff05b7d2552093f23dd9db1e0e380f9c6c0b57d69e21811eb4057d011"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@ -144,12 +142,11 @@ dependencies = [
[[package]] [[package]]
name = "fltk-sys" name = "fltk-sys"
version = "1.1.12" version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe424d107ab6ef0a4b756295c259bb8cec6e610548d55481457309884d526d4" checksum = "0ccc3328371cbe9d01b89488e815c39c5680843379606aa74b1b169c1f54a489"
dependencies = [ dependencies = [
"cmake", "cmake",
"libc",
] ]
[[package]] [[package]]
@ -175,9 +172,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.100" version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]] [[package]]
name = "log" name = "log"
@ -188,15 +185,6 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.1" version = "2.4.1"
@ -222,20 +210,11 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.28" version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@ -308,6 +287,8 @@ dependencies = [
"fltk", "fltk",
"log", "log",
"rayon", "rayon",
"xcb",
"xcb-util",
] ]
[[package]] [[package]]
@ -318,9 +299,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.75" version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -372,3 +353,23 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 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",
]

View file

@ -9,4 +9,7 @@ edition = "2018"
fltk = "1.1" fltk = "1.1"
log = "0.4" log = "0.4"
env_logger = "0.9.0" env_logger = "0.9.0"
rayon = "1.5.1" rayon = "1.5.1"
xcb = "0.9.0"
xcb-util = {version = "0.3.0", features = ["ewmh", "icccm"]}

View file

@ -3,66 +3,15 @@ use std::error::Error;
mod roftl; mod roftl;
mod ui; mod ui;
mod sources;
use roftl::{Action, Entry, Source};
struct TestSource {
name: String,
entries: Vec<Entry>,
}
impl TestSource {
fn new(s: &str) -> Box<TestSource> {
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<Entry> {
self.entries.clone()
}
fn action(&self, entry: &Entry, action: Action) {
println!("Doing action {:?} for entry {}", action, entry.name)
}
}
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
env_logger::init(); env_logger::init();
let mut roftl = roftl::Roftl::new(); let roftl = roftl::Roftl::default()
.add_source(sources::TestSource::new("ts1"))
roftl .add_source(sources::Window::new())
.add_source(TestSource::new("ts1")) .source();
.add_source(TestSource::new("ts2"));
roftl.source();
let mut app = ui::draw()?; let mut app = ui::draw()?;
@ -73,9 +22,9 @@ fn main() -> Result<(), Box<dyn Error>> {
while app.wait() { while app.wait() {
match app.events.recv() { match app.events.recv() {
Some(ui::Event::CandidateSelect(entry)) => { Some(ui::Event::CandidateSelect(i)) => {
trace! {"Selected: {}", entry.name} println! {"Selected an entry {}", i}
roftl.action(&entry, Action::Primary) roftl.action(i, roftl::Action::Primary)
} }
Some(ui::Event::InputUpdate) => { Some(ui::Event::InputUpdate) => {

View file

@ -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 struct Entry {
pub source: &'static str,
pub name: String, pub name: String,
pub description: String, pub description: String,
pub source: *const dyn Source, pub identifier: u64,
pub id: u32
} }
unsafe impl Sync for Entry {}
unsafe impl Send for Entry {}
#[derive(Debug)] #[derive(Debug)]
pub enum Action { pub enum Action {
Primary, Primary,
} }
pub trait Source: Send + Sync { pub trait Source: Send + Sync {
fn name(&self) -> &str; fn name(&self) -> &'static str;
fn entries(&self) -> Vec<Entry>; fn entries(&mut self) -> Vec<Entry>;
fn action(&self, entry: &Entry, action: Action); fn action(&self, entry: &Entry, action: Action);
} }
#[derive(Default)]
pub struct Roftl { pub struct Roftl {
sources: Vec<Box<dyn Source>>, sources: Vec<Box<dyn Source>>,
entries: Vec<Entry>, entries: Vec<Entry>,
narrow_map: Mutex<HashMap<usize, usize>>
} }
impl Roftl { impl Roftl {
pub fn new() -> Roftl { pub fn add_source(mut self, source: Box<dyn Source>) -> Self {
Roftl { if self.sources.par_iter().any(|s| s.name().eq(source.name())) {
sources: vec![], panic! {"Source with name '{}' already exists", source.name()}
entries: vec![],
} }
}
pub fn add_source(&mut self, source: Box<dyn Source>) -> &mut Roftl {
self.sources.push(source); self.sources.push(source);
self self
} }
pub fn source(&mut self) { pub fn source(mut self) -> Self {
self.entries = self self.entries = self
.sources .sources
.par_iter() .par_iter_mut()
.flat_map_iter(|s| s.entries()) .flat_map_iter(|s| s.entries())
.collect(); .collect();
println!("Entries {:?}", self.entries);
self
} }
pub fn narrow(&self, input: &str) -> Vec<&Entry> { pub fn narrow(&self, input: &str) -> Vec<&Entry> {
self.entries.par_iter() let count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
.filter(|e| e.name.starts_with(input)) 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() .collect()
} }
pub fn action(&self, entry: &Entry, action: Action) { pub fn action(&self, entry_id: i32, action: Action) {
unsafe { println!("NarrowMap {:?}", self.narrow_map.lock().unwrap());
Source::action(&*entry.source, entry, action);
} 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);
} }
} }

6
src/sources/mod.rs Normal file
View file

@ -0,0 +1,6 @@
mod test;
pub use test::TestSource;
mod windows;
pub use windows::Window;

45
src/sources/test.rs Normal file
View file

@ -0,0 +1,45 @@
use crate::roftl::{Entry, Source, Action};
pub struct TestSource {
entries: Vec<Entry>,
}
impl TestSource {
pub fn new(s: &str) -> Box<TestSource> {
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<Entry> {
self.entries.clone()
}
fn action(&self, entry: &Entry, action: Action) {
println!("Doing action {:?} for entry {}", action, entry.name)
}
}

97
src/sources/windows.rs Normal file
View file

@ -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<String>,
action_data: HashMap<u64, (u32, i32)>
}
impl Window {
pub fn new() -> Box<Window> {
Box::new(Window { windows: vec![], action_data: HashMap::<u64, (u32,i32)>::new() })
}
fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result<bool, xcb::GenericError> {
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<String, xcb::GenericError> {
Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into())
}
fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::GenericError> {
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<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();
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<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) => 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()
}
}

106
src/ui.rs
View file

@ -1,58 +1,37 @@
use std::ops::Deref; 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::group::{PackType, ScrollType};
use fltk::output;
use fltk::prelude::*; use fltk::prelude::*;
use fltk::{app, window, input, group}; use fltk::{app, group, input, window};
use fltk::{browser, output};
use super::roftl::Entry; use super::roftl::Entry;
#[derive(Clone)] #[derive(Clone)]
pub enum Event { pub enum Event {
InputUpdate, InputUpdate,
CandidateSelect(Entry) CandidateSelect(i32),
} }
pub struct RoftlApp { pub struct RoftlApp {
pub app: app::App, pub app: app::App,
pub events: app::Receiver::<Event>, pub events: app::Receiver<Event>,
send: app::Sender<Event>, send: app::Sender<Event>,
input: input::Input, input: input::Input,
candidates: group::Pack, browser: browser::HoldBrowser,
scroll: group::Scroll
} }
impl RoftlApp { impl RoftlApp {
pub fn add_candidate(&mut self, entry: Entry) { pub fn add_candidate(&mut self, entry: Entry) {
let mut output = output::Output::default(); let entry_line = format! {"{}\tf{}\t{}", entry.name, entry.description, entry.source};
output.set_value(&entry.name); self.browser.add(&entry_line);
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();
} }
pub fn clear_candidates(&mut self) { pub fn clear_candidates(&mut self) {
for i in 0..self.candidates.children() { self.browser.clear();
self.candidates.child(i).and_then(|c| {Some(WidgetBase::delete(c))});
}
self.scroll.redraw();
} }
pub fn get_input(&self) -> String { pub fn get_input(&self) -> String {
@ -69,7 +48,6 @@ impl Deref for RoftlApp {
} }
pub fn draw() -> Result<RoftlApp, FltkError> { pub fn draw() -> Result<RoftlApp, FltkError> {
app::set_font(Font::Screen); app::set_font(Font::Screen);
let app = app::App::default(); let app = app::App::default();
@ -81,30 +59,66 @@ pub fn draw() -> Result<RoftlApp, FltkError> {
let pack = group::Pack::default() let pack = group::Pack::default()
.with_type(PackType::Vertical) .with_type(PackType::Vertical)
.with_size(400,40); .with_size(400, 40);
let mut input = input::Input::default() let mut input = input::Input::default().with_size(0, 40);
.with_size(0,40);
input.set_trigger(CallbackTrigger::Changed); input.set_trigger(CallbackTrigger::Changed);
let scroll = group::Scroll::default() let mut browser: browser::HoldBrowser =
.with_size(400, 300-40) browser::HoldBrowser::default().with_size(400, 300 - 40);
.with_type(ScrollType::AlwaysOn); 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(); pack.end();
window.end(); window.end();
window.show(); window.show();
let (s, r) = app::channel::<Event>(); let (s, r) = app::channel::<Event>();
input.emit(s.clone(), Event::InputUpdate); 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,
})
}