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]]
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",
]

View file

@ -10,3 +10,6 @@ fltk = "1.1"
log = "0.4"
env_logger = "0.9.0"
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 ui;
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)
}
}
mod sources;
fn main() -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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) => {

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 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<Entry>;
fn name(&self) -> &'static str;
fn entries(&mut self) -> Vec<Entry>;
fn action(&self, entry: &Entry, action: Action);
}
#[derive(Default)]
pub struct Roftl {
sources: Vec<Box<dyn Source>>,
entries: Vec<Entry>,
narrow_map: Mutex<HashMap<usize, usize>>
}
impl Roftl {
pub fn new() -> Roftl {
Roftl {
sources: vec![],
entries: vec![],
}
pub fn add_source(mut self, source: Box<dyn Source>) -> 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<dyn Source>) -> &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);
}
}

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()
}
}

102
src/ui.rs
View file

@ -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::<Event>,
pub events: app::Receiver<Event>,
send: app::Sender<Event>,
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<RoftlApp, FltkError> {
app::set_font(Font::Screen);
let app = app::App::default();
@ -83,28 +61,64 @@ pub fn draw() -> Result<RoftlApp, FltkError> {
.with_type(PackType::Vertical)
.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::<Event>();
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,
})
}