Run cargo format
This commit is contained in:
parent
3b7bff5bd7
commit
a34a6a2ed4
20 changed files with 625 additions and 392 deletions
|
@ -1,6 +1,6 @@
|
||||||
# Rofi Then Light
|
# Rofi Then Light
|
||||||
|
|
||||||
> In the beginning there was Rofi. Rofi was Fast. And Fast was Rofi.
|
> In the beginning there was Rofi. Rofi was good.
|
||||||
>
|
>
|
||||||
> Then Light.
|
> Then Light.
|
||||||
|
|
||||||
|
|
15
default.toml
15
default.toml
|
@ -6,9 +6,22 @@ log_level = "trace"
|
||||||
"t" = "terminal"
|
"t" = "terminal"
|
||||||
|
|
||||||
[sources.primary]
|
[sources.primary]
|
||||||
matcher = "Prefix"
|
matcher = "Flex"
|
||||||
source = "Windows"
|
source = "Windows"
|
||||||
|
|
||||||
[sources.additional]
|
[sources.additional]
|
||||||
matcher = "Flex"
|
matcher = "Flex"
|
||||||
source = ["Apps", "Shell", "Test"]
|
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]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub mod shared;
|
|
||||||
pub mod roftl;
|
pub mod roftl;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
pub mod shared;
|
||||||
|
|
||||||
use super::matcher;
|
use super::matcher;
|
||||||
use super::sources;
|
use super::sources;
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::RwLock;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use super::matcher::PrefixMatcher;
|
use super::matcher::PrefixMatcher;
|
||||||
|
|
||||||
|
use super::shared::Entry;
|
||||||
use super::shared::Matcher;
|
use super::shared::Matcher;
|
||||||
use super::shared::Source;
|
use super::shared::Source;
|
||||||
use super::shared::Entry;
|
|
||||||
|
|
||||||
|
|
||||||
pub type SourceRef = Box<dyn Source>;
|
pub type SourceRef = Box<dyn Source>;
|
||||||
pub type MatcherRef = Box<dyn Matcher>;
|
pub type MatcherRef = Box<dyn Matcher>;
|
||||||
|
@ -26,7 +25,7 @@ pub struct Roftl {
|
||||||
primary_matcher: MatcherRef,
|
primary_matcher: MatcherRef,
|
||||||
entries: Vec<Entry>,
|
entries: Vec<Entry>,
|
||||||
narrow_map: Vec<usize>,
|
narrow_map: Vec<usize>,
|
||||||
completions: HashMap<String, String>
|
completions: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Roftl {
|
impl Roftl {
|
||||||
|
@ -39,18 +38,23 @@ impl Roftl {
|
||||||
primary_matcher: PrefixMatcher::new(),
|
primary_matcher: PrefixMatcher::new(),
|
||||||
entries: vec![],
|
entries: vec![],
|
||||||
narrow_map: vec![],
|
narrow_map: vec![],
|
||||||
completions: HashMap::default()
|
completions: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_primary_source(mut self, source: SourceRef) -> Self {
|
pub fn with_primary_source(mut self, source: SourceRef) -> Self {
|
||||||
self.primary_source = Some(source);
|
self.primary_source = Some(source);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_source(self, source: SourceRef) -> Self {
|
pub fn add_source(self, source: SourceRef) -> Self {
|
||||||
if self.sources.read().unwrap().par_iter().any(|s| s.name().eq(source.name())) {
|
if self
|
||||||
|
.sources
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.par_iter()
|
||||||
|
.any(|s| s.name().eq(source.name()))
|
||||||
|
{
|
||||||
panic! {"Source with name '{}' already exists", source.name()}
|
panic! {"Source with name '{}' already exists", source.name()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +83,8 @@ impl Roftl {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source_primary(&mut self) {
|
fn source_primary(&mut self) {
|
||||||
self.entries = self.primary_source
|
self.entries = self
|
||||||
|
.primary_source
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.flat_map(|s| s.entries())
|
.flat_map(|s| s.entries())
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -93,12 +98,18 @@ impl Roftl {
|
||||||
|
|
||||||
let sources = self.sources.clone();
|
let sources = self.sources.clone();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let entries: Vec<Entry> = sources.write().unwrap()
|
let entries: Vec<Entry> = sources
|
||||||
.par_iter_mut()
|
.write()
|
||||||
.flat_map(|s| s.entries())
|
.unwrap()
|
||||||
.collect();
|
.par_iter_mut()
|
||||||
|
.flat_map(|s| s.entries())
|
||||||
|
.collect();
|
||||||
|
|
||||||
debug!("Sourced {} additional entries from {} sources", entries.len(), sources.read().unwrap().len());
|
debug!(
|
||||||
|
"Sourced {} additional entries from {} sources",
|
||||||
|
entries.len(),
|
||||||
|
sources.read().unwrap().len()
|
||||||
|
);
|
||||||
tx.send(entries).unwrap();
|
tx.send(entries).unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -110,7 +121,7 @@ impl Roftl {
|
||||||
debug!("Processing {} additional entries", entries.len());
|
debug!("Processing {} additional entries", entries.len());
|
||||||
self.entries.append(&mut entries);
|
self.entries.append(&mut entries);
|
||||||
} else {
|
} else {
|
||||||
debug!("No additional entries for processing (yet)");
|
debug!("No additional entries for processing (yet)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,20 +129,26 @@ impl Roftl {
|
||||||
// and not self is captured by the filter_map lambda
|
// and not self is captured by the filter_map lambda
|
||||||
let completions = &self.completions;
|
let completions = &self.completions;
|
||||||
let primary_matcher = &self.primary_matcher;
|
let primary_matcher = &self.primary_matcher;
|
||||||
let mut scored_entries: Vec<(f64, usize, &Entry, Vec<usize>)> = self.entries
|
let mut scored_entries: Vec<(f64, usize, &Entry, Vec<usize>)> = self
|
||||||
|
.entries
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(idx, entry)| {
|
.filter_map(|(idx, entry)| {
|
||||||
if entry.source != "window" { return None }
|
if entry.source != "window" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if input.is_empty() { return Some((0.0, idx, entry, vec![])) }
|
if input.is_empty() {
|
||||||
|
return Some((0.0, idx, entry, vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
let input = completions.get(input)
|
let input = completions
|
||||||
|
.get(input)
|
||||||
.map(|s| s.as_str()) // &String -> &str
|
.map(|s| s.as_str()) // &String -> &str
|
||||||
.unwrap_or(input);
|
.unwrap_or(input);
|
||||||
|
|
||||||
let match_result = primary_matcher.try_match(&entry.name, input);
|
let match_result = primary_matcher.try_match(&entry.name, input);
|
||||||
return match_result.map(|(score, indices)| (score, idx, entry, indices) )
|
return match_result.map(|(score, indices)| (score, idx, entry, indices));
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -141,20 +158,22 @@ impl Roftl {
|
||||||
let input = input.strip_prefix(",").unwrap_or(input);
|
let input = input.strip_prefix(",").unwrap_or(input);
|
||||||
let matcher = &self.matcher;
|
let matcher = &self.matcher;
|
||||||
|
|
||||||
scored_entries = self.entries
|
scored_entries = self
|
||||||
|
.entries
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(idx, entry)| {
|
.filter_map(|(idx, entry)| {
|
||||||
if input.is_empty() { return Some((0.0, idx, entry, vec![])) }
|
if input.is_empty() {
|
||||||
|
return Some((0.0, idx, entry, vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
let match_result = matcher.try_match(&entry.name, input);
|
let match_result = matcher.try_match(&entry.name, input);
|
||||||
return match_result.map(|(score, indices)| (score, idx, entry, indices) )
|
return match_result.map(|(score, indices)| (score, idx, entry, indices));
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
scored_entries
|
scored_entries.par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap());
|
||||||
.par_sort_by(|e1, e2| e1.0.partial_cmp(&e2.0).unwrap());
|
|
||||||
|
|
||||||
self.narrow_map.clear();
|
self.narrow_map.clear();
|
||||||
for se in &scored_entries {
|
for se in &scored_entries {
|
||||||
|
@ -171,7 +190,13 @@ impl Roftl {
|
||||||
let (entry, source) = self.find_selection(selection_id);
|
let (entry, source) = self.find_selection(selection_id);
|
||||||
match source {
|
match source {
|
||||||
usize::MAX => self.primary_source.as_ref().unwrap().actions(entry),
|
usize::MAX => self.primary_source.as_ref().unwrap().actions(entry),
|
||||||
_ => self.sources.read().unwrap().get(source).unwrap().actions(entry)
|
_ => self
|
||||||
|
.sources
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(source)
|
||||||
|
.unwrap()
|
||||||
|
.actions(entry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,23 +204,38 @@ impl Roftl {
|
||||||
let (entry, source) = self.find_selection(selection_id);
|
let (entry, source) = self.find_selection(selection_id);
|
||||||
match source {
|
match source {
|
||||||
usize::MAX => self.primary_source.as_ref().unwrap().exec_default(entry),
|
usize::MAX => self.primary_source.as_ref().unwrap().exec_default(entry),
|
||||||
_ => self.sources.read().unwrap().get(source).unwrap().exec_default(entry)
|
_ => self
|
||||||
|
.sources
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(source)
|
||||||
|
.unwrap()
|
||||||
|
.exec_default(entry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_action(&self, selection_id: usize, action: u8) {
|
pub fn exec_action(&self, selection_id: usize, action: u8) {
|
||||||
let (entry, source) = self.find_selection(selection_id);
|
let (entry, source) = self.find_selection(selection_id);
|
||||||
match source {
|
match source {
|
||||||
usize::MAX => self.primary_source.as_ref().unwrap().exec_action(entry, action),
|
usize::MAX => self
|
||||||
_ => self.sources.read().unwrap().get(source).unwrap().exec_action(entry, action)
|
.primary_source
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.exec_action(entry, action),
|
||||||
|
_ => self
|
||||||
|
.sources
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.get(source)
|
||||||
|
.unwrap()
|
||||||
|
.exec_action(entry, action),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_selection(&self, selection_id: usize) -> (&Entry, usize)
|
fn find_selection(&self, selection_id: usize) -> (&Entry, usize) {
|
||||||
{
|
|
||||||
let entry_id = self.narrow_map[selection_id];
|
let entry_id = self.narrow_map[selection_id];
|
||||||
let entry = &self.entries[entry_id];
|
let entry = &self.entries[entry_id];
|
||||||
debug!{"Got entry {:?} for id {} ", entry, entry_id};
|
debug! {"Got entry {:?} for id {} ", entry, entry_id};
|
||||||
|
|
||||||
match self.primary_source {
|
match self.primary_source {
|
||||||
Some(ref s) if s.name().eq(entry.source) => return (entry, usize::MAX),
|
Some(ref s) if s.name().eq(entry.source) => return (entry, usize::MAX),
|
||||||
|
|
|
@ -1,17 +1,37 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use config::ConfigError;
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
use config::ConfigError;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Color(f64, f64, f64, f64);
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ColorScheme {
|
||||||
|
base: Color,
|
||||||
|
border: Color,
|
||||||
|
highlight: Color,
|
||||||
|
|
||||||
|
divider: Color,
|
||||||
|
|
||||||
|
text: Color,
|
||||||
|
text_highlight: Color,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
|
color_scheme: ColorScheme,
|
||||||
|
font: (String, i32),
|
||||||
border: f64,
|
border: f64,
|
||||||
divider: f64
|
divider: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub enum Matcher {
|
pub enum Matcher {
|
||||||
Prefix, Fuse, Flex, Skim
|
Prefix,
|
||||||
|
Fuse,
|
||||||
|
Flex,
|
||||||
|
Skim,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Matcher {
|
impl Matcher {
|
||||||
|
@ -20,14 +40,17 @@ impl Matcher {
|
||||||
Matcher::Prefix => super::matcher::PrefixMatcher::new(),
|
Matcher::Prefix => super::matcher::PrefixMatcher::new(),
|
||||||
Matcher::Fuse => super::matcher::FuseMatcher::new(),
|
Matcher::Fuse => super::matcher::FuseMatcher::new(),
|
||||||
Matcher::Flex => super::matcher::FlxMatcher::new(),
|
Matcher::Flex => super::matcher::FlxMatcher::new(),
|
||||||
Matcher::Skim => super::matcher::SkimMatcher::new()
|
Matcher::Skim => super::matcher::SkimMatcher::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub enum Source {
|
pub enum Source {
|
||||||
Apps, Shell, Windows, Test
|
Apps,
|
||||||
|
Shell,
|
||||||
|
Windows,
|
||||||
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source {
|
impl Source {
|
||||||
|
@ -36,7 +59,7 @@ impl Source {
|
||||||
Source::Apps => super::sources::Apps::new(),
|
Source::Apps => super::sources::Apps::new(),
|
||||||
Source::Shell => super::sources::ShellHost::new(),
|
Source::Shell => super::sources::ShellHost::new(),
|
||||||
Source::Windows => super::sources::Window::new(),
|
Source::Windows => super::sources::Window::new(),
|
||||||
Source::Test => super::sources::TestSource::new("ts1")
|
Source::Test => super::sources::TestSource::new("ts1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +67,7 @@ impl Source {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct SourceConfig<T> {
|
pub struct SourceConfig<T> {
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
source: T
|
source: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -56,16 +79,20 @@ pub struct Sources {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase", remote = "log::Level")]
|
#[serde(rename_all = "lowercase", remote = "log::Level")]
|
||||||
enum LogLevelDef {
|
enum LogLevelDef {
|
||||||
Error, Warn, Info, Debug, Trace,
|
Error,
|
||||||
|
Warn,
|
||||||
|
Info,
|
||||||
|
Debug,
|
||||||
|
Trace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
#[serde(with = "LogLevelDef")]
|
#[serde(with = "LogLevelDef")]
|
||||||
log_level: log::Level,
|
log_level: log::Level,
|
||||||
completions: HashMap<String, String>,
|
completions: HashMap<String, String>,
|
||||||
sources: Sources,
|
sources: Sources,
|
||||||
|
theme: Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
@ -74,10 +101,12 @@ impl Settings {
|
||||||
|
|
||||||
config.merge(config::File::with_name("default"))?;
|
config.merge(config::File::with_name("default"))?;
|
||||||
|
|
||||||
config.merge(config::Environment::new()
|
config.merge(
|
||||||
.prefix("roftl")
|
config::Environment::new()
|
||||||
.separator("_")
|
.prefix("roftl")
|
||||||
.ignore_empty(true))?;
|
.separator("_")
|
||||||
|
.ignore_empty(true),
|
||||||
|
)?;
|
||||||
|
|
||||||
config.try_into()
|
config.try_into()
|
||||||
}
|
}
|
||||||
|
@ -95,7 +124,9 @@ impl Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sources(&self) -> Vec<Box<dyn super::shared::Source>> {
|
pub fn sources(&self) -> Vec<Box<dyn super::shared::Source>> {
|
||||||
self.sources.additional.source
|
self.sources
|
||||||
|
.additional
|
||||||
|
.source
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.init())
|
.map(|s| s.init())
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -9,11 +9,13 @@ pub struct Entry {
|
||||||
pub trait Source: Send + Sync {
|
pub trait Source: Send + Sync {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
fn entries(&mut self) -> Vec<Entry>;
|
fn entries(&mut self) -> Vec<Entry>;
|
||||||
fn actions(&self, _entry: &Entry) -> Vec<String>
|
fn actions(&self, _entry: &Entry) -> Vec<String> {
|
||||||
{ vec![] }
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
fn exec_default(&self, entry: &Entry)
|
fn exec_default(&self, entry: &Entry) {
|
||||||
{ self.exec_action(entry, 0) }
|
self.exec_action(entry, 0)
|
||||||
|
}
|
||||||
|
|
||||||
fn exec_action(&self, entry: &Entry, action: u8);
|
fn exec_action(&self, entry: &Entry, action: u8);
|
||||||
}
|
}
|
||||||
|
|
185
src/main.rs
185
src/main.rs
|
@ -1,25 +1,27 @@
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
use winit::window::{Window, WindowBuilder};
|
use winit::event::{
|
||||||
|
ElementState, Event, KeyboardInput, VirtualKeyCode,
|
||||||
|
WindowEvent::{CloseRequested, ReceivedCharacter},
|
||||||
|
};
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode,
|
use winit::window::{Window, WindowBuilder};
|
||||||
WindowEvent::{CloseRequested, ReceivedCharacter}};
|
|
||||||
|
|
||||||
use self::core::roftl::Roftl;
|
use self::core::roftl::Roftl;
|
||||||
|
|
||||||
mod ui;
|
|
||||||
mod sources;
|
|
||||||
mod matcher;
|
|
||||||
mod core;
|
mod core;
|
||||||
|
mod matcher;
|
||||||
|
mod sources;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let settings = core::settings::Settings::parse()?;
|
let settings = core::settings::Settings::parse()?;
|
||||||
debug!{"Settings {:?}", settings}
|
debug! {"Settings {:?}", settings}
|
||||||
|
|
||||||
debug!{"Set up roftl"};
|
debug! {"Set up roftl"};
|
||||||
let mut roftl = Roftl::new()
|
let mut roftl = Roftl::new()
|
||||||
.with_primary_source(settings.primary_source())
|
.with_primary_source(settings.primary_source())
|
||||||
.with_primary_matcher(settings.primary_matcher())
|
.with_primary_matcher(settings.primary_matcher())
|
||||||
|
@ -30,10 +32,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
roftl = roftl.add_source(source);
|
roftl = roftl.add_source(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!{"Source roftl sources"}
|
debug! {"Source roftl sources"}
|
||||||
roftl.source();
|
roftl.source();
|
||||||
|
|
||||||
debug!{"Build window"}
|
debug! {"Build window"}
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
let window = WindowBuilder::new()
|
let window = WindowBuilder::new()
|
||||||
.with_decorations(false)
|
.with_decorations(false)
|
||||||
|
@ -41,12 +43,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.expect("Could not create window");
|
.expect("Could not create window");
|
||||||
|
|
||||||
debug!{"Window id: {:?}", window.id()}
|
debug! {"Window id: {:?}", window.id()}
|
||||||
|
|
||||||
debug!{"Draw primary state to window"}
|
debug! {"Draw primary state to window"}
|
||||||
ui::draw(&window, "", roftl.narrow(""), 0);
|
ui::draw(&window, "", roftl.narrow(""), 0);
|
||||||
|
|
||||||
debug!{"Start event loop"}
|
debug! {"Start event loop"}
|
||||||
let roftl_loop = RoftlLoop::new(roftl, window);
|
let roftl_loop = RoftlLoop::new(roftl, window);
|
||||||
roftl_loop.run(event_loop);
|
roftl_loop.run(event_loop);
|
||||||
}
|
}
|
||||||
|
@ -66,51 +68,55 @@ struct RoftlLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoftlLoop {
|
impl RoftlLoop {
|
||||||
fn new(roftl: Roftl, window: Window) -> Self
|
fn new(roftl: Roftl, window: Window) -> Self {
|
||||||
{
|
RoftlLoop {
|
||||||
RoftlLoop{input_buffer: String::default(), input_changed: true, selection: 0,
|
input_buffer: String::default(),
|
||||||
mode: Mode::Selection,
|
input_changed: true,
|
||||||
roftl, window}
|
selection: 0,
|
||||||
|
mode: Mode::Selection,
|
||||||
|
roftl,
|
||||||
|
window,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<T>(mut self, event_loop: EventLoop<T>) -> !
|
fn run<T>(mut self, event_loop: EventLoop<T>) -> ! {
|
||||||
{
|
|
||||||
event_loop.run(move |evt, _win, flow| {
|
event_loop.run(move |evt, _win, flow| {
|
||||||
*flow = ControlFlow::Wait;
|
*flow = ControlFlow::Wait;
|
||||||
|
|
||||||
match evt {
|
match evt {
|
||||||
Event::WindowEvent{event: CloseRequested, window_id}
|
Event::WindowEvent {
|
||||||
if window_id == self.window.id() =>
|
event: CloseRequested,
|
||||||
{
|
window_id,
|
||||||
|
} if window_id == self.window.id() => {
|
||||||
*flow = ControlFlow::Exit;
|
*flow = ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::WindowEvent{event: ReceivedCharacter(character), window_id}
|
Event::WindowEvent {
|
||||||
if window_id == self.window.id()
|
event: ReceivedCharacter(character),
|
||||||
&& matches!(self.mode, Mode::Selection) =>
|
window_id,
|
||||||
{
|
} if window_id == self.window.id() && matches!(self.mode, Mode::Selection) => {
|
||||||
*flow = self.process_character(character);
|
*flow = self.process_character(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::WindowEvent{event: ReceivedCharacter(character), window_id}
|
Event::WindowEvent {
|
||||||
if window_id == self.window.id()
|
event: ReceivedCharacter(character),
|
||||||
&& matches!(self.mode, Mode::Actions) =>
|
window_id,
|
||||||
{
|
} if window_id == self.window.id() && matches!(self.mode, Mode::Actions) => {
|
||||||
*flow = self.process_action(character);
|
*flow = self.process_action(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::WindowEvent{event: winit::event::WindowEvent::KeyboardInput{input, ..}, window_id}
|
Event::WindowEvent {
|
||||||
if window_id == self.window.id() =>
|
event: winit::event::WindowEvent::KeyboardInput { input, .. },
|
||||||
{
|
window_id,
|
||||||
|
} if window_id == self.window.id() => {
|
||||||
self.process_input(input);
|
self.process_input(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::RedrawRequested(window_id)
|
Event::RedrawRequested(window_id)
|
||||||
if window_id == self.window.id()
|
if window_id == self.window.id() && matches! {self.mode, Mode::Selection} =>
|
||||||
&& matches!{self.mode, Mode::Selection} =>
|
|
||||||
{
|
{
|
||||||
let results = self.roftl.narrow(&self.input_buffer);
|
let results = self.roftl.narrow(&self.input_buffer);
|
||||||
trace!{"Narrow result {:?}", results}
|
trace! {"Narrow result {:?}", results}
|
||||||
|
|
||||||
// quick switch if only one result
|
// quick switch if only one result
|
||||||
// but make sure that input buffer is filled, otherwise will immediately close
|
// but make sure that input buffer is filled, otherwise will immediately close
|
||||||
|
@ -122,14 +128,17 @@ impl RoftlLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
// correct selection for results
|
// correct selection for results
|
||||||
if results.len() > 0 { self.selection = self.selection % results.len() }
|
if results.len() > 0 {
|
||||||
else { self.selection = 0 }
|
self.selection = self.selection % results.len()
|
||||||
|
} else {
|
||||||
|
self.selection = 0
|
||||||
|
}
|
||||||
|
|
||||||
if self.input_changed {
|
if self.input_changed {
|
||||||
trace!{"Redrawing with input {}", self.input_buffer}
|
trace! {"Redrawing with input {}", self.input_buffer}
|
||||||
ui::draw(&self.window, &self.input_buffer, results, self.selection);
|
ui::draw(&self.window, &self.input_buffer, results, self.selection);
|
||||||
} else {
|
} else {
|
||||||
trace!{"Quick redraw with input {}", self.input_buffer}
|
trace! {"Quick redraw with input {}", self.input_buffer}
|
||||||
ui::redraw_quick(&self.window, results, self.selection);
|
ui::redraw_quick(&self.window, results, self.selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,34 +146,37 @@ impl RoftlLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::RedrawRequested(window_id)
|
Event::RedrawRequested(window_id)
|
||||||
if window_id == self.window.id()
|
if window_id == self.window.id() && matches! {self.mode, Mode::Actions} =>
|
||||||
&& matches!{self.mode, Mode::Actions} =>
|
|
||||||
{
|
{
|
||||||
let actions = self.roftl.actions(self.selection);
|
let actions = self.roftl.actions(self.selection);
|
||||||
debug!{"Actions for current selection: {:?}", actions}
|
debug! {"Actions for current selection: {:?}", actions}
|
||||||
ui::draw_actions(&self.window, actions, &self.input_buffer);
|
ui::draw_actions(&self.window, actions, &self.input_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_input(&mut self, input: KeyboardInput) -> ControlFlow {
|
fn process_input(&mut self, input: KeyboardInput) -> ControlFlow {
|
||||||
if let KeyboardInput { virtual_keycode: Some(code), state, .. } = input {
|
if let KeyboardInput {
|
||||||
|
virtual_keycode: Some(code),
|
||||||
|
state,
|
||||||
|
..
|
||||||
|
} = input
|
||||||
|
{
|
||||||
return match (code, state) {
|
return match (code, state) {
|
||||||
(VirtualKeyCode::Down, ElementState::Released) => {
|
(VirtualKeyCode::Down, ElementState::Released) => {
|
||||||
debug!("Received down");
|
debug!("Received down");
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
},
|
}
|
||||||
(VirtualKeyCode::Up, ElementState::Released) => {
|
(VirtualKeyCode::Up, ElementState::Released) => {
|
||||||
debug!("Received up");
|
debug!("Received up");
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
},
|
}
|
||||||
|
|
||||||
_ => ControlFlow::Wait,
|
_ => ControlFlow::Wait,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
|
@ -172,14 +184,14 @@ impl RoftlLoop {
|
||||||
|
|
||||||
fn process_character(&mut self, character: char) -> ControlFlow {
|
fn process_character(&mut self, character: char) -> ControlFlow {
|
||||||
match character {
|
match character {
|
||||||
'q' | 'Q' => ControlFlow::Exit,
|
'q' | 'Q' => ControlFlow::Exit,
|
||||||
|
|
||||||
// Escape
|
// Escape
|
||||||
c if c == char::from(0x1b) => ControlFlow::Exit,
|
c if c == char::from(0x1b) => ControlFlow::Exit,
|
||||||
|
|
||||||
// Backspace
|
// Backspace
|
||||||
c if c == char::from(0x08) => {
|
c if c == char::from(0x08) => {
|
||||||
trace!{"Retrieved backspace. Pop char from input"}
|
trace! {"Retrieved backspace. Pop char from input"}
|
||||||
self.input_buffer.pop();
|
self.input_buffer.pop();
|
||||||
self.input_changed = true;
|
self.input_changed = true;
|
||||||
self.window.request_redraw();
|
self.window.request_redraw();
|
||||||
|
@ -188,14 +200,14 @@ impl RoftlLoop {
|
||||||
|
|
||||||
// Enter
|
// Enter
|
||||||
c if c == char::from(0x0d) => {
|
c if c == char::from(0x0d) => {
|
||||||
trace!{"Retrieved enter. Trigger primary action"}
|
trace! {"Retrieved enter. Trigger primary action"}
|
||||||
self.roftl.exec_action(self.selection, 0);
|
self.roftl.exec_action(self.selection, 0);
|
||||||
ControlFlow::Exit
|
ControlFlow::Exit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+n
|
// Ctrl+n
|
||||||
c if c == char::from(0x0e) => {
|
c if c == char::from(0x0e) => {
|
||||||
debug!{"Received next"}
|
debug! {"Received next"}
|
||||||
self.selection += 1;
|
self.selection += 1;
|
||||||
self.window.request_redraw();
|
self.window.request_redraw();
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
|
@ -203,30 +215,38 @@ impl RoftlLoop {
|
||||||
|
|
||||||
// Ctrl+p
|
// Ctrl+p
|
||||||
c if c == char::from(0x10) => {
|
c if c == char::from(0x10) => {
|
||||||
debug!{"Received previous"}
|
debug! {"Received previous"}
|
||||||
if self.selection != 0 { self.selection -= 1 }
|
if self.selection != 0 {
|
||||||
|
self.selection -= 1
|
||||||
|
}
|
||||||
self.window.request_redraw();
|
self.window.request_redraw();
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
}
|
}
|
||||||
|
|
||||||
// tab
|
// tab
|
||||||
c if c == char::from(0x09) => {
|
c if c == char::from(0x09) => {
|
||||||
debug!{"Received actions"}
|
debug! {"Received actions"}
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Selection => { self.mode = Mode::Actions; self.input_changed = true },
|
Mode::Selection => {
|
||||||
Mode::Actions => { self.mode = Mode::Selection; self.input_changed = true }
|
self.mode = Mode::Actions;
|
||||||
|
self.input_changed = true
|
||||||
|
}
|
||||||
|
Mode::Actions => {
|
||||||
|
self.mode = Mode::Selection;
|
||||||
|
self.input_changed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.window.request_redraw();
|
self.window.request_redraw();
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
}
|
}
|
||||||
|
|
||||||
c if c.is_control() => {
|
c if c.is_control() => {
|
||||||
debug!{"Got unknown control character {:#x}", u32::from(c)}
|
debug! {"Got unknown control character {:#x}", u32::from(c)}
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
trace!{"Push {} to input buffer", character}
|
trace! {"Push {} to input buffer", character}
|
||||||
self.input_buffer.push(character);
|
self.input_buffer.push(character);
|
||||||
self.input_changed = true;
|
self.input_changed = true;
|
||||||
self.window.request_redraw();
|
self.window.request_redraw();
|
||||||
|
@ -238,47 +258,52 @@ impl RoftlLoop {
|
||||||
fn process_action(&mut self, character: char) -> ControlFlow {
|
fn process_action(&mut self, character: char) -> ControlFlow {
|
||||||
match character {
|
match character {
|
||||||
'1' => {
|
'1' => {
|
||||||
trace!{"Retrieved action selection 1. Trigger primary action"}
|
trace! {"Retrieved action selection 1. Trigger primary action"}
|
||||||
self.roftl.exec_action(self.selection, 0);
|
self.roftl.exec_action(self.selection, 0);
|
||||||
ControlFlow::Exit
|
ControlFlow::Exit
|
||||||
},
|
}
|
||||||
'2' => {
|
'2' => {
|
||||||
trace!{"Retrieved action selection 2. Trigger secondary action"}
|
trace! {"Retrieved action selection 2. Trigger secondary action"}
|
||||||
self.roftl.exec_action(self.selection, 1);
|
self.roftl.exec_action(self.selection, 1);
|
||||||
ControlFlow::Exit
|
ControlFlow::Exit
|
||||||
},
|
}
|
||||||
'3' => {
|
'3' => {
|
||||||
trace!{"Retrieved action selection 3. Trigger tertiary action"}
|
trace! {"Retrieved action selection 3. Trigger tertiary action"}
|
||||||
self.roftl.exec_action(self.selection, 2);
|
self.roftl.exec_action(self.selection, 2);
|
||||||
ControlFlow::Exit
|
ControlFlow::Exit
|
||||||
},
|
}
|
||||||
'4' => {
|
'4' => {
|
||||||
trace!{"Retrieved action selection 4. Trigger quaternary action"}
|
trace! {"Retrieved action selection 4. Trigger quaternary action"}
|
||||||
self.roftl.exec_action(self.selection, 3);
|
self.roftl.exec_action(self.selection, 3);
|
||||||
ControlFlow::Exit
|
ControlFlow::Exit
|
||||||
},
|
}
|
||||||
|
|
||||||
'q' | 'Q' => ControlFlow::Exit,
|
'q' | 'Q' => ControlFlow::Exit,
|
||||||
|
|
||||||
// Escape
|
// Escape
|
||||||
c if c == char::from(0x1b) => ControlFlow::Exit,
|
c if c == char::from(0x1b) => ControlFlow::Exit,
|
||||||
|
|
||||||
// tab
|
// tab
|
||||||
c if c == char::from(0x09) => {
|
c if c == char::from(0x09) => {
|
||||||
debug!{"Received actions"}
|
debug! {"Received actions"}
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Selection => { self.mode = Mode::Actions; self.input_changed = true },
|
Mode::Selection => {
|
||||||
Mode::Actions => { self.mode = Mode::Selection; self.input_changed = true }
|
self.mode = Mode::Actions;
|
||||||
|
self.input_changed = true
|
||||||
|
}
|
||||||
|
Mode::Actions => {
|
||||||
|
self.mode = Mode::Selection;
|
||||||
|
self.input_changed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.window.request_redraw();
|
self.window.request_redraw();
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
},
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
debug!{"Retrieved unknown action selection. Processing character {}", character}
|
debug! {"Retrieved unknown action selection. Processing character {}", character}
|
||||||
ControlFlow::Wait
|
ControlFlow::Wait
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,19 +7,17 @@ pub struct FlxMatcher;
|
||||||
|
|
||||||
impl FlxMatcher {
|
impl FlxMatcher {
|
||||||
pub fn new() -> Box<Self> {
|
pub fn new() -> Box<Self> {
|
||||||
Box::new(FlxMatcher{})
|
Box::new(FlxMatcher {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Matcher for FlxMatcher {
|
impl Matcher for FlxMatcher {
|
||||||
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
|
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
|
||||||
debug!{"Searching {} in {}", needle, haystack}
|
debug! {"Searching {} in {}", needle, haystack}
|
||||||
let res: Option<Score> = flx_rs::score(haystack, needle);
|
let res: Option<Score> = flx_rs::score(haystack, needle);
|
||||||
res.and_then(|score| {
|
res.and_then(|score| {
|
||||||
let s = score.score as f64;
|
let s = score.score as f64;
|
||||||
let i = score.indices.into_iter()
|
let i = score.indices.into_iter().map(|idx| idx as usize).collect();
|
||||||
.map(|idx| idx as usize)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Some((s, i))
|
Some((s, i))
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,18 +5,20 @@ use fuse_rust::Fuse;
|
||||||
use super::core::shared::Matcher;
|
use super::core::shared::Matcher;
|
||||||
|
|
||||||
fn flatten_ranges(ranges: Vec<Range<usize>>) -> Vec<usize> {
|
fn flatten_ranges(ranges: Vec<Range<usize>>) -> Vec<usize> {
|
||||||
ranges.into_iter().flat_map(|range| {range.collect::<Vec<usize>>().into_iter()})
|
ranges
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|range| range.collect::<Vec<usize>>().into_iter())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FuseMatcher {
|
pub struct FuseMatcher {
|
||||||
matcher: Fuse
|
matcher: Fuse,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuseMatcher {
|
impl FuseMatcher {
|
||||||
pub fn new() -> Box<Self> {
|
pub fn new() -> Box<Self> {
|
||||||
Box::new(FuseMatcher{
|
Box::new(FuseMatcher {
|
||||||
matcher: Fuse::default()
|
matcher: Fuse::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,9 +27,7 @@ impl Matcher for FuseMatcher {
|
||||||
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
|
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
|
||||||
match self.matcher.search_text_in_string(needle, haystack) {
|
match self.matcher.search_text_in_string(needle, haystack) {
|
||||||
Some(score) => Some((score.score, flatten_ranges(score.ranges))),
|
Some(score) => Some((score.score, flatten_ranges(score.ranges))),
|
||||||
None => None
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,4 +11,3 @@ pub use fuse::FuseMatcher;
|
||||||
|
|
||||||
mod flx;
|
mod flx;
|
||||||
pub use flx::FlxMatcher;
|
pub use flx::FlxMatcher;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ pub struct PrefixMatcher;
|
||||||
|
|
||||||
impl PrefixMatcher {
|
impl PrefixMatcher {
|
||||||
pub fn new() -> Box<Self> {
|
pub fn new() -> Box<Self> {
|
||||||
Box::new(PrefixMatcher{})
|
Box::new(PrefixMatcher {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
|
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||||
|
|
||||||
use super::core::shared::Matcher;
|
use super::core::shared::Matcher;
|
||||||
|
|
||||||
pub struct SkimMatcher {
|
pub struct SkimMatcher {
|
||||||
matcher: SkimMatcherV2
|
matcher: SkimMatcherV2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SkimMatcher {
|
impl SkimMatcher {
|
||||||
pub fn new() -> Box<Self> {
|
pub fn new() -> Box<Self> {
|
||||||
Box::new(SkimMatcher{
|
Box::new(SkimMatcher {
|
||||||
matcher: SkimMatcherV2::default()
|
matcher: SkimMatcherV2::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ impl SkimMatcher {
|
||||||
impl Matcher for SkimMatcher {
|
impl Matcher for SkimMatcher {
|
||||||
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
|
fn try_match(&self, haystack: &str, needle: &str) -> Option<(f64, Vec<usize>)> {
|
||||||
match self.matcher.fuzzy_indices(haystack, needle) {
|
match self.matcher.fuzzy_indices(haystack, needle) {
|
||||||
Some((score, indices)) => Some(((i64::MAX as f64/score as f64), indices)),
|
Some((score, indices)) => Some(((i64::MAX as f64 / score as f64), indices)),
|
||||||
None => None
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use std::{os::unix::prelude::CommandExt, process::{Command, Stdio}};
|
use std::{
|
||||||
|
os::unix::prelude::CommandExt,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
|
|
||||||
use freedesktop_entry_parser as parser;
|
use freedesktop_entry_parser as parser;
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, trace};
|
||||||
use walkdir::WalkDir;
|
|
||||||
use nix::unistd::{getpid, setpgid};
|
use nix::unistd::{getpid, setpgid};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use super::core::shared::{Entry, Source};
|
use super::core::shared::{Entry, Source};
|
||||||
|
|
||||||
|
@ -11,7 +14,7 @@ use super::core::shared::{Entry, Source};
|
||||||
struct App {
|
struct App {
|
||||||
name: String,
|
name: String,
|
||||||
command: String,
|
command: String,
|
||||||
terminal: bool
|
terminal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_command_string<'a>(exec: &'a str) -> (String, Vec<String>) {
|
pub fn parse_command_string<'a>(exec: &'a str) -> (String, Vec<String>) {
|
||||||
|
@ -27,13 +30,13 @@ pub fn run_bg(mut command: Command) {
|
||||||
command.pre_exec(|| {
|
command.pre_exec(|| {
|
||||||
let pid = getpid();
|
let pid = getpid();
|
||||||
if let Err(_) = setpgid(pid, pid) {
|
if let Err(_) = setpgid(pid, pid) {
|
||||||
error!{"Failed to set pgid of child process with pid {}", pid};
|
error! {"Failed to set pgid of child process with pid {}", pid};
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!{"Running command {:?}", command}
|
debug! {"Running command {:?}", command}
|
||||||
command.spawn().unwrap();
|
command.spawn().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,27 +53,23 @@ impl App {
|
||||||
command.args(&args);
|
command.args(&args);
|
||||||
let _ = run_bg(command);
|
let _ = run_bg(command);
|
||||||
} else {
|
} else {
|
||||||
|
debug!("Running `{} {}`", cmd, args.join(" "));
|
||||||
debug!("Running `{} {}`", cmd, args.join(" "));
|
let mut command = Command::new(&cmd);
|
||||||
let mut command = Command::new(&cmd);
|
command.args(&args);
|
||||||
command.args(&args);
|
let _ = run_bg(command);
|
||||||
let _ = run_bg(command);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&App> for Entry
|
impl From<&App> for Entry {
|
||||||
{
|
|
||||||
fn from(app: &App) -> Self {
|
fn from(app: &App) -> Self {
|
||||||
Entry {
|
Entry {
|
||||||
name: app.name.clone(),
|
name: app.name.clone(),
|
||||||
description: "".into(),
|
description: "".into(),
|
||||||
source: "apps",
|
source: "apps",
|
||||||
identifier: 0
|
identifier: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
@ -78,45 +77,52 @@ struct AppBuilder<'a> {
|
||||||
name: Option<&'a str>,
|
name: Option<&'a str>,
|
||||||
exec: Option<&'a str>,
|
exec: Option<&'a str>,
|
||||||
hidden: Option<&'a str>,
|
hidden: Option<&'a str>,
|
||||||
terminal: Option<&'a str>
|
terminal: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AppBuilder<'a>
|
impl<'a> AppBuilder<'a> {
|
||||||
{
|
fn build(&self) -> Option<App> {
|
||||||
fn build(&self) -> Option<App>
|
if self.name.is_none() {
|
||||||
{
|
return None;
|
||||||
if self.name.is_none() { return None }
|
}
|
||||||
if self.exec.is_none() { return None }
|
if self.exec.is_none() {
|
||||||
if self.hidden.is_some() { return None }
|
return None;
|
||||||
|
}
|
||||||
|
if self.hidden.is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
Some (
|
Some(App {
|
||||||
App {
|
name: self.name.unwrap().into(),
|
||||||
name: self.name.unwrap().into(),
|
command: strip_entry_args(self.exec.unwrap()).into(),
|
||||||
command: strip_entry_args(self.exec.unwrap()).into(),
|
terminal: self
|
||||||
terminal: self.terminal.map_or(false, |term| term.parse().unwrap_or(false))
|
.terminal
|
||||||
})
|
.map_or(false, |term| term.parse().unwrap_or(false)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Apps {
|
pub struct Apps {
|
||||||
entries: Vec<App>
|
entries: Vec<App>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Apps {
|
impl Apps {
|
||||||
pub fn new() -> Box<Apps>
|
pub fn new() -> Box<Apps> {
|
||||||
{
|
Box::new(Apps { entries: vec![] })
|
||||||
Box::new(Apps{entries: vec![]})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_entry_args(exec: &str) -> String {
|
fn strip_entry_args(exec: &str) -> String {
|
||||||
trace!{"Stripping {}", exec}
|
trace! {"Stripping {}", exec}
|
||||||
let iter = exec.split(' ');
|
let iter = exec.split(' ');
|
||||||
let result = iter.filter(|item| !item.starts_with('%'))
|
let result = iter
|
||||||
|
.filter(|item| !item.starts_with('%'))
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.join(" ");
|
.join(" ")
|
||||||
|
.trim_matches('"')
|
||||||
|
.into();
|
||||||
|
|
||||||
trace!{"Stripped into {}", result}
|
trace! {"Stripped into {}", result}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +136,7 @@ impl Source for Apps {
|
||||||
"/usr/share/applications/",
|
"/usr/share/applications/",
|
||||||
"/home/armin/.local/share/applications/",
|
"/home/armin/.local/share/applications/",
|
||||||
"/var/lib/snapd/desktop/applications",
|
"/var/lib/snapd/desktop/applications",
|
||||||
"/var/lib/flatpak/exports/share/applications"
|
"/var/lib/flatpak/exports/share/applications",
|
||||||
];
|
];
|
||||||
|
|
||||||
let desktop_entries: Vec<parser::Entry> = paths
|
let desktop_entries: Vec<parser::Entry> = paths
|
||||||
|
@ -147,41 +153,37 @@ impl Source for Apps {
|
||||||
let mut app_builder = AppBuilder::default();
|
let mut app_builder = AppBuilder::default();
|
||||||
let section = entry.section("Desktop Entry");
|
let section = entry.section("Desktop Entry");
|
||||||
|
|
||||||
for attr in section.attrs()
|
for attr in section.attrs() {
|
||||||
{
|
match attr.name {
|
||||||
match attr.name
|
|
||||||
{
|
|
||||||
"Name" => app_builder.name = attr.value,
|
"Name" => app_builder.name = attr.value,
|
||||||
"Exec" => app_builder.exec = attr.value,
|
"Exec" => app_builder.exec = attr.value,
|
||||||
"NoDisplay" => app_builder.hidden = attr.value,
|
"NoDisplay" => app_builder.hidden = attr.value,
|
||||||
"Hidden" => app_builder.hidden = attr.value,
|
"Hidden" => app_builder.hidden = attr.value,
|
||||||
"Terminal" => app_builder.terminal = attr.value,
|
"Terminal" => app_builder.terminal = attr.value,
|
||||||
_ => ()
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(app) = app_builder.build()
|
if let Some(app) = app_builder.build() {
|
||||||
{
|
|
||||||
let mut entry: Entry = (&app).into();
|
let mut entry: Entry = (&app).into();
|
||||||
entry.identifier = idx;
|
entry.identifier = idx;
|
||||||
idx += 1;
|
idx += 1;
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
self.entries.push(app);
|
self.entries.push(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!{"Got desktop entries: {:?}", entries}
|
trace! {"Got desktop entries: {:?}", entries}
|
||||||
|
|
||||||
entries
|
entries
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_action(&self, entry: &Entry, action: u8) {
|
fn exec_action(&self, entry: &Entry, action: u8) {
|
||||||
debug!{"Got desktop entry {:?} for action", entry}
|
debug! {"Got desktop entry {:?} for action", entry}
|
||||||
debug!{"Desktop entry has id {}", entry.identifier}
|
debug! {"Desktop entry has id {}", entry.identifier}
|
||||||
trace!{"Apps has entries {:?}", self.entries}
|
trace! {"Apps has entries {:?}", self.entries}
|
||||||
let app = self.entries.get(entry.identifier as usize).unwrap();
|
let app = self.entries.get(entry.identifier as usize).unwrap();
|
||||||
debug!{"Doing primary action {} for {}", app.name, app.command}
|
debug! {"Doing primary action {} for {}", app.name, app.command}
|
||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use std::{os::unix::prelude::CommandExt, path::PathBuf, process::{Command, Stdio}};
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use nix::unistd::{getpid, setpgid};
|
use nix::unistd::{getpid, setpgid};
|
||||||
|
use std::{
|
||||||
|
os::unix::prelude::CommandExt,
|
||||||
|
path::PathBuf,
|
||||||
|
process::{Command, Stdio},
|
||||||
|
};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use super::core::shared::{Entry, Source};
|
use super::core::shared::{Entry, Source};
|
||||||
|
@ -11,7 +15,7 @@ pub struct ShellHost {
|
||||||
|
|
||||||
impl ShellHost {
|
impl ShellHost {
|
||||||
pub fn new() -> Box<ShellHost> {
|
pub fn new() -> Box<ShellHost> {
|
||||||
Box::new(ShellHost{ scripts: vec![] })
|
Box::new(ShellHost { scripts: vec![] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,35 +29,34 @@ impl Source for ShellHost {
|
||||||
let script_path = "/home/armin/dev/incubator/roftl/sh";
|
let script_path = "/home/armin/dev/incubator/roftl/sh";
|
||||||
|
|
||||||
// TODO make robust if path does not exist
|
// TODO make robust if path does not exist
|
||||||
let scripts: Vec<PathBuf> = WalkDir::new(script_path).into_iter()
|
let scripts: Vec<PathBuf> = WalkDir::new(script_path)
|
||||||
|
.into_iter()
|
||||||
.map(|e| e.unwrap().path().to_path_buf())
|
.map(|e| e.unwrap().path().to_path_buf())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut entries: Vec<Entry> = vec![];
|
let mut entries: Vec<Entry> = vec![];
|
||||||
for (idx, script) in scripts.iter().enumerate() {
|
for (idx, script) in scripts.iter().enumerate() {
|
||||||
entries.push(
|
entries.push(Entry {
|
||||||
Entry {
|
source: self.name(),
|
||||||
source: self.name(),
|
name: script.file_stem().unwrap().to_str().unwrap().into(),
|
||||||
name: script.file_stem().unwrap().to_str().unwrap().into(),
|
description: "".into(),
|
||||||
description: "".into(),
|
identifier: idx as u64,
|
||||||
identifier: idx as u64
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
self.scripts = scripts;
|
self.scripts = scripts;
|
||||||
entries
|
entries
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_action(&self, entry: &Entry, action: u8) {
|
fn exec_action(&self, entry: &Entry, action: u8) {
|
||||||
|
|
||||||
let script: &PathBuf = self.scripts.get(entry.identifier as usize).unwrap();
|
let script: &PathBuf = self.scripts.get(entry.identifier as usize).unwrap();
|
||||||
debug!{"Got script {:?}", script};
|
debug! {"Got script {:?}", script};
|
||||||
|
|
||||||
let mut command = Command::new("xfce4-terminal");
|
let mut command = Command::new("xfce4-terminal");
|
||||||
let args = vec!["-x", script.to_str().unwrap()];
|
let args = vec!["-x", script.to_str().unwrap()];
|
||||||
command.args(&args);
|
command.args(&args);
|
||||||
|
|
||||||
let _ = run_bg(command);
|
let _ = run_bg(command);
|
||||||
debug!{"Executed {:?}", script};
|
debug! {"Executed {:?}", script};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,12 +66,12 @@ pub fn run_bg(mut command: Command) {
|
||||||
command.pre_exec(|| {
|
command.pre_exec(|| {
|
||||||
let pid = getpid();
|
let pid = getpid();
|
||||||
if let Err(_) = setpgid(pid, pid) {
|
if let Err(_) = setpgid(pid, pid) {
|
||||||
error!{"Failed to set pgid of child process with pid {}", pid};
|
error! {"Failed to set pgid of child process with pid {}", pid};
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!{"Running command {:?}", command}
|
debug! {"Running command {:?}", command}
|
||||||
command.spawn().unwrap();
|
command.spawn().unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,8 @@ impl Source for TestSource {
|
||||||
|
|
||||||
fn actions(&self, _entry: &Entry) -> Vec<String> {
|
fn actions(&self, _entry: &Entry) -> Vec<String> {
|
||||||
["Primary", "Secondary", "Tertiary", "Quaternary"]
|
["Primary", "Secondary", "Tertiary", "Quaternary"]
|
||||||
.iter().map(|s| (*s).into()).collect()
|
.iter()
|
||||||
|
.map(|s| (*s).into())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,15 +8,21 @@ use xcb_util::ffi::ewmh::XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER;
|
||||||
use xcb_util::icccm;
|
use xcb_util::icccm;
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
action_data: HashMap<u64, (u32, i32)>
|
action_data: HashMap<u64, (u32, i32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn new() -> Box<Window> {
|
pub fn new() -> Box<Window> {
|
||||||
Box::new(Window { action_data: HashMap::<u64, (u32,i32)>::new() })
|
Box::new(Window {
|
||||||
|
action_data: HashMap::<u64, (u32, i32)>::new(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_normal_window(&self, con: &ewmh::Connection, window: u32) -> Result<bool, xcb::ReplyError> {
|
fn is_normal_window(
|
||||||
|
&self,
|
||||||
|
con: &ewmh::Connection,
|
||||||
|
window: u32,
|
||||||
|
) -> Result<bool, xcb::ReplyError> {
|
||||||
let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?;
|
let wm_type_reply = ewmh::get_wm_window_type(con, window).get_reply()?;
|
||||||
|
|
||||||
for atom in wm_type_reply.atoms() {
|
for atom in wm_type_reply.atoms() {
|
||||||
|
@ -28,48 +34,95 @@ impl Window {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_category(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> {
|
fn window_category(
|
||||||
Ok(icccm::get_wm_class(&con, window).get_reply()?.class().into())
|
&self,
|
||||||
|
con: &ewmh::Connection,
|
||||||
|
window: u32,
|
||||||
|
) -> Result<String, xcb::ReplyError> {
|
||||||
|
Ok(icccm::get_wm_class(&con, window)
|
||||||
|
.get_reply()?
|
||||||
|
.class()
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> {
|
fn window_title(&self, con: &ewmh::Connection, window: u32) -> Result<String, xcb::ReplyError> {
|
||||||
Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into())
|
Ok(ewmh::get_wm_name(con, window).get_reply()?.string().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn switch_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> {
|
fn switch_window(
|
||||||
|
&self,
|
||||||
|
con: &ewmh::Connection,
|
||||||
|
window: u32,
|
||||||
|
screen: i32,
|
||||||
|
) -> Result<(), xcb::ReplyError> {
|
||||||
let window_desktop = ewmh::get_wm_desktop(con, window).get_reply()?;
|
let window_desktop = ewmh::get_wm_desktop(con, window).get_reply()?;
|
||||||
let active_window = ewmh::get_active_window(con, screen).get_reply()?;
|
let active_window = ewmh::get_active_window(con, screen).get_reply()?;
|
||||||
ewmh::set_current_desktop(con, screen, window_desktop);
|
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::request_change_active_window(
|
||||||
ewmh::set_active_window(con, screen, window).request_check().unwrap();
|
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);
|
xcb::set_input_focus(&con, 0, window, XCB_CURRENT_TIME);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_maximize_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> {
|
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_horz_atom = ewmh::Connection::WM_STATE_MAXIMIZED_HORZ(con);
|
||||||
let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con);
|
let max_vert_atom = ewmh::Connection::WM_STATE_MAXIMIZED_VERT(con);
|
||||||
let action_atom = ewmh::STATE_TOGGLE;
|
let action_atom = ewmh::STATE_TOGGLE;
|
||||||
|
|
||||||
debug!{"Toggle maximize for {}", window}
|
debug! {"Toggle maximize for {}", window}
|
||||||
ewmh::request_change_wm_state(con, screen, window, action_atom, max_horz_atom, max_vert_atom, 0).request_check()?;
|
ewmh::request_change_wm_state(
|
||||||
|
con,
|
||||||
|
screen,
|
||||||
|
window,
|
||||||
|
action_atom,
|
||||||
|
max_horz_atom,
|
||||||
|
max_vert_atom,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.request_check()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_window(&self, con: &ewmh::Connection, window: u32, screen: i32) -> Result<(), xcb::ReplyError> {
|
fn close_window(
|
||||||
debug!{"Toggle maximize for {}", 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()
|
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> {
|
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 hidden_atom = ewmh::Connection::WM_STATE_HIDDEN(con);
|
||||||
let action_atom = ewmh::STATE_TOGGLE;
|
let action_atom = ewmh::STATE_TOGGLE;
|
||||||
|
|
||||||
debug!{"Toggle hidden for {}", window}
|
debug! {"Toggle hidden for {}", window}
|
||||||
ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0).request_check()?;
|
ewmh::request_change_wm_state(con, screen, window, action_atom, hidden_atom, 0, 0)
|
||||||
|
.request_check()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -81,10 +134,15 @@ impl Source for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entries(&mut self) -> Vec<Entry> {
|
fn entries(&mut self) -> Vec<Entry> {
|
||||||
let (xcb_conn, screen_num) = xcb::base::Connection::connect(None).expect("Could not connect to X server");
|
let (xcb_conn, screen_num) =
|
||||||
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
|
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 client_list_reply = xcb_util::ewmh::get_client_list(&ewmh_conn, screen_num)
|
||||||
|
.get_reply()
|
||||||
|
.unwrap();
|
||||||
let windows = client_list_reply.windows();
|
let windows = client_list_reply.windows();
|
||||||
|
|
||||||
let mut entries: Vec<Entry> = vec![];
|
let mut entries: Vec<Entry> = vec![];
|
||||||
|
@ -92,7 +150,7 @@ impl Source for Window {
|
||||||
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_conn, *w) {
|
||||||
Ok(true) => {},
|
Ok(true) => {}
|
||||||
Ok(false) => continue,
|
Ok(false) => continue,
|
||||||
Err(_err) => {}
|
Err(_err) => {}
|
||||||
}
|
}
|
||||||
|
@ -102,10 +160,15 @@ impl Source for Window {
|
||||||
|
|
||||||
debug!("Found window {} - {}", title, category);
|
debug!("Found window {} - {}", title, category);
|
||||||
|
|
||||||
entries.push(Entry{source: self.name(), name: category, description: title, identifier: count});
|
entries.push(Entry {
|
||||||
|
source: self.name(),
|
||||||
|
name: category,
|
||||||
|
description: title,
|
||||||
|
identifier: count,
|
||||||
|
});
|
||||||
self.action_data.insert(count, (*w, screen_num));
|
self.action_data.insert(count, (*w, screen_num));
|
||||||
|
|
||||||
count+=1;
|
count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
entries
|
entries
|
||||||
|
@ -117,36 +180,54 @@ impl Source for Window {
|
||||||
match action {
|
match action {
|
||||||
0 => {
|
0 => {
|
||||||
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
|
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 (xcb_conn, _screen_num) =
|
||||||
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
|
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()
|
self.switch_window(&ewmh_conn, window, screen).unwrap()
|
||||||
},
|
}
|
||||||
1 => {
|
1 => {
|
||||||
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
|
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 (xcb_conn, _screen_num) =
|
||||||
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
|
xcb::base::Connection::connect(None).expect("Could not connect to X server");
|
||||||
self.toggle_maximize_window(&ewmh_conn, window, screen).unwrap();
|
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()
|
self.switch_window(&ewmh_conn, window, screen).unwrap()
|
||||||
},
|
}
|
||||||
2 => {
|
2 => {
|
||||||
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
|
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 (xcb_conn, _screen_num) =
|
||||||
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
|
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();
|
self.toggle_hide_window(&ewmh_conn, window, screen).unwrap();
|
||||||
},
|
}
|
||||||
3 => {
|
3 => {
|
||||||
let (window, screen) = *self.action_data.get(&entry.identifier).unwrap();
|
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 (xcb_conn, _screen_num) =
|
||||||
let ewmh_conn = ewmh::Connection::connect(xcb_conn).map_err(|(e,_)| e).unwrap();
|
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();
|
self.close_window(&ewmh_conn, window, screen).unwrap();
|
||||||
},
|
}
|
||||||
_ => panic!{"Unknown action {:?}", action}
|
_ => panic! {"Unknown action {:?}", action},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actions(&self, _entry: &Entry) -> Vec<String> {
|
fn actions(&self, _entry: &Entry) -> Vec<String> {
|
||||||
vec!["Switch".into(), "Toggle maximize".into(), "Toggle hide".into(), "Close".into()]
|
vec![
|
||||||
|
"Switch".into(),
|
||||||
|
"Toggle maximize".into(),
|
||||||
|
"Toggle hide".into(),
|
||||||
|
"Close".into(),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ use super::core;
|
||||||
|
|
||||||
mod ui;
|
mod ui;
|
||||||
pub use ui::draw;
|
pub use ui::draw;
|
||||||
pub use ui::redraw_quick;
|
|
||||||
pub use ui::draw_actions;
|
pub use ui::draw_actions;
|
||||||
|
pub use ui::redraw_quick;
|
||||||
|
|
||||||
mod painter;
|
mod painter;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
|
@ -9,11 +9,13 @@ pub struct Painter<'a> {
|
||||||
|
|
||||||
impl<'a> Painter<'a> {
|
impl<'a> Painter<'a> {
|
||||||
pub fn new(context: &'a cairo::Context) -> Self {
|
pub fn new(context: &'a cairo::Context) -> Self {
|
||||||
Painter{context, theme: Theme::default()}
|
Painter {
|
||||||
|
context,
|
||||||
|
theme: Theme::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn background(&self)
|
pub fn background(&self) {
|
||||||
{
|
|
||||||
let ctx = self.context;
|
let ctx = self.context;
|
||||||
|
|
||||||
// reset clip so that we can draw the entire background of the surface
|
// reset clip so that we can draw the entire background of the surface
|
||||||
|
@ -25,42 +27,47 @@ impl<'a> Painter<'a> {
|
||||||
ctx.paint().expect("Could not paint background");
|
ctx.paint().expect("Could not paint background");
|
||||||
|
|
||||||
// draw the border
|
// draw the border
|
||||||
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap();
|
let (x1, y1, x2, y2) = ctx.clip_extents().unwrap();
|
||||||
debug!{"Border width: {}", self.theme.border(ctx)};
|
debug! {"Border width: {}", self.theme.border(ctx)};
|
||||||
let border = self.theme.border(ctx)/2.0;
|
let border = self.theme.border(ctx) / 2.0;
|
||||||
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border);
|
let (x1, y1, x2, y2) = (x1 + border, y1 + border, x2 - x1 - border, y2 - y1 - border);
|
||||||
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2};
|
debug! {"Border rect: {},{},{},{}", x1, y1, x2, y2};
|
||||||
ctx.rectangle(x1,y1,x2-x1,y2-y1);
|
ctx.rectangle(x1, y1, x2 - x1, y2 - y1);
|
||||||
ctx.theme_border(&self.theme);
|
ctx.theme_border(&self.theme);
|
||||||
ctx.stroke().unwrap();
|
ctx.stroke().unwrap();
|
||||||
|
|
||||||
self.border_clip();
|
self.border_clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn border_clip(&self)
|
pub fn border_clip(&self) {
|
||||||
{
|
|
||||||
let ctx = self.context;
|
let ctx = self.context;
|
||||||
|
|
||||||
ctx.reset_clip();
|
ctx.reset_clip();
|
||||||
// clip any future content so the border stays alive
|
// clip any future content so the border stays alive
|
||||||
let (x1,y1,x2,y2) = ctx.clip_extents().unwrap(); // the path of the clip
|
let (x1, y1, x2, y2) = ctx.clip_extents().unwrap(); // the path of the clip
|
||||||
// region, i.e. the surface
|
// region, i.e. the surface
|
||||||
|
|
||||||
debug!{"Border extent: {},{},{},{}", x1, y1, x2, y2};
|
debug! {"Border extent: {},{},{},{}", x1, y1, x2, y2};
|
||||||
let border = self.theme.border(ctx); // width of the border, note
|
let border = self.theme.border(ctx); // width of the border, note
|
||||||
// that half of the border is outside
|
// that half of the border is outside
|
||||||
// the clip_extents
|
// the clip_extents
|
||||||
|
|
||||||
// let (x1,y1,x2,y2) = (x1+border, y1+border, x2-border, y2-border);
|
// let (x1,y1,x2,y2) = (x1+border, y1+border, x2-border, y2-border);
|
||||||
debug!{"Border rect: {},{},{},{}", x1, y1, x2, y2};
|
debug! {"Border rect: {},{},{},{}", x1, y1, x2, y2};
|
||||||
let (x1,y1,x2,y2) = (x1+border,y1+border,x2-x1-border,y2-y1-border-border);
|
let (x1, y1, x2, y2) = (
|
||||||
debug!{"Border clip: {},{},{},{}", x1, y1, x2, y2};
|
x1 + border,
|
||||||
ctx.rectangle(x1,y1,x2,y2);
|
y1 + border,
|
||||||
|
x2 - x1 - border,
|
||||||
|
y2 - y1 - border - border,
|
||||||
|
);
|
||||||
|
debug! {"Border clip: {},{},{},{}", x1, y1, x2, y2};
|
||||||
|
ctx.rectangle(x1, y1, x2, y2);
|
||||||
ctx.clip();
|
ctx.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_box<T>(&self, x: T, y: T, input: &'a str)
|
pub fn input_box<T>(&self, x: T, y: T, input: &'a str)
|
||||||
where T: Into<f64>
|
where
|
||||||
|
T: Into<f64>,
|
||||||
{
|
{
|
||||||
let ctx = self.context;
|
let ctx = self.context;
|
||||||
let (x, y): (f64, f64) = (x.into(), y.into());
|
let (x, y): (f64, f64) = (x.into(), y.into());
|
||||||
|
@ -75,28 +82,40 @@ impl<'a> Painter<'a> {
|
||||||
ctx.theme_text(&input, false, &[], &self.theme);
|
ctx.theme_text(&input, false, &[], &self.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn result_box<T>(&self, x: T, y: T, source: &'a str, result: &'a str, indices: &[usize], selected: bool)
|
pub fn result_box<T>(
|
||||||
where T: Into<f64>
|
&self,
|
||||||
|
x: T,
|
||||||
|
y: T,
|
||||||
|
source: &'a str,
|
||||||
|
result: &'a str,
|
||||||
|
indices: &[usize],
|
||||||
|
selected: bool,
|
||||||
|
) where
|
||||||
|
T: Into<f64>,
|
||||||
{
|
{
|
||||||
let ctx = self.context;
|
let ctx = self.context;
|
||||||
let (x, y): (f64, f64) = (x.into(), y.into());
|
let (x, y): (f64, f64) = (x.into(), y.into());
|
||||||
|
|
||||||
// draw background box
|
// draw background box
|
||||||
ctx.rectangle(x.into(), y.into(), self.clip_width(), 1.0);
|
ctx.rectangle(x.into(), y.into(), self.clip_width(), 1.0);
|
||||||
if selected { ctx.theme_color_highlight(&self.theme) }
|
if selected {
|
||||||
else { ctx.theme_color_base(&self.theme) };
|
ctx.theme_color_highlight(&self.theme)
|
||||||
|
} else {
|
||||||
|
ctx.theme_color_base(&self.theme)
|
||||||
|
};
|
||||||
ctx.set_operator(cairo::Operator::Source);
|
ctx.set_operator(cairo::Operator::Source);
|
||||||
ctx.fill().unwrap();
|
ctx.fill().unwrap();
|
||||||
|
|
||||||
// draw text
|
// draw text
|
||||||
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);
|
||||||
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, selected, indices, &self.theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn divider<T>(&self, x: T, y: T)
|
pub fn divider<T>(&self, x: T, y: T)
|
||||||
where T: Into<f64>
|
where
|
||||||
|
T: Into<f64>,
|
||||||
{
|
{
|
||||||
let ctx = self.context;
|
let ctx = self.context;
|
||||||
let (x, y): (f64, f64) = (x.into(), y.into());
|
let (x, y): (f64, f64) = (x.into(), y.into());
|
||||||
|
@ -106,21 +125,24 @@ impl<'a> Painter<'a> {
|
||||||
ctx.line_to(self.clip_width(), y);
|
ctx.line_to(self.clip_width(), y);
|
||||||
ctx.stroke().unwrap();
|
ctx.stroke().unwrap();
|
||||||
|
|
||||||
// ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width);
|
// ctx.rectangle(x, y+dash_width, self.clip_width(), self.clip_height()-dash_width);
|
||||||
// ctx.clip();
|
// ctx.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_to_user(&self, point: f64) -> f64 {
|
fn device_to_user(&self, point: f64) -> f64 {
|
||||||
self.context.device_to_user_distance(point, point).unwrap().0
|
self.context
|
||||||
|
.device_to_user_distance(point, point)
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clip_width(&self) -> f64 {
|
fn clip_width(&self) -> f64 {
|
||||||
let (x1,_y1,x2,_y2) = self.context.clip_extents().unwrap();
|
let (x1, _y1, x2, _y2) = self.context.clip_extents().unwrap();
|
||||||
x2 - x1
|
x2 - x1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clip_height(&self) -> f64 {
|
fn clip_height(&self) -> f64 {
|
||||||
let (_x1,y1,_x2,y2) = self.context.clip_extents().unwrap();
|
let (_x1, y1, _x2, y2) = self.context.clip_extents().unwrap();
|
||||||
y2 - y1
|
y2 - y1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ struct ColorScheme {
|
||||||
divider: Color,
|
divider: Color,
|
||||||
|
|
||||||
text: Color,
|
text: Color,
|
||||||
text_highlight: Color
|
text_highlight: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
|
@ -22,7 +22,7 @@ pub struct Theme {
|
||||||
font: Font,
|
font: Font,
|
||||||
|
|
||||||
border: f64,
|
border: f64,
|
||||||
divider: f64
|
divider: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
@ -41,7 +41,7 @@ impl Default for Theme {
|
||||||
divider: Color(0.23, 0.05, 0.11, 1.0),
|
divider: Color(0.23, 0.05, 0.11, 1.0),
|
||||||
|
|
||||||
text: Color(0.87, 0.95, 0.77, 1.0),
|
text: Color(0.87, 0.95, 0.77, 1.0),
|
||||||
text_highlight: Color(0.6, 0.8, 0.4, 1.0)
|
text_highlight: Color(0.6, 0.8, 0.4, 1.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
Theme {
|
Theme {
|
||||||
|
@ -49,7 +49,7 @@ impl Default for Theme {
|
||||||
font: Font("Sans Regular".into(), 13),
|
font: Font("Sans Regular".into(), 13),
|
||||||
|
|
||||||
border: 2.0,
|
border: 2.0,
|
||||||
divider: 3.0
|
divider: 3.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,24 +63,23 @@ pub trait ThemedContextExt {
|
||||||
|
|
||||||
fn theme_text(&self, text: &str, selected: bool, indices: &[usize], theme: &Theme);
|
fn theme_text(&self, text: &str, selected: bool, indices: &[usize], theme: &Theme);
|
||||||
|
|
||||||
|
|
||||||
fn device_to_user_point(&self, point: f64) -> f64;
|
fn device_to_user_point(&self, point: f64) -> f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemedContextExt for cairo::Context {
|
impl ThemedContextExt for cairo::Context {
|
||||||
fn theme_color_base(&self, theme: &Theme) {
|
fn theme_color_base(&self, theme: &Theme) {
|
||||||
let Color(r,g,b,a) = theme.colors.base;
|
let Color(r, g, b, a) = theme.colors.base;
|
||||||
self.set_source_rgba(r,g,b,a);
|
self.set_source_rgba(r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme_color_highlight(&self, theme: &Theme) {
|
fn theme_color_highlight(&self, theme: &Theme) {
|
||||||
let Color(r,g,b,a) = theme.colors.highlight;
|
let Color(r, g, b, a) = theme.colors.highlight;
|
||||||
self.set_source_rgba(r,g,b,a);
|
self.set_source_rgba(r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme_border(&self, theme: &Theme) {
|
fn theme_border(&self, theme: &Theme) {
|
||||||
let Color(r,g,b,a) = theme.colors.border;
|
let Color(r, g, b, a) = theme.colors.border;
|
||||||
self.set_source_rgba(r,g,b,a);
|
self.set_source_rgba(r, g, b, a);
|
||||||
let border_width = self.device_to_user_point(theme.border);
|
let border_width = self.device_to_user_point(theme.border);
|
||||||
self.set_line_width(border_width);
|
self.set_line_width(border_width);
|
||||||
}
|
}
|
||||||
|
@ -91,7 +90,7 @@ impl ThemedContextExt for cairo::Context {
|
||||||
|
|
||||||
self.set_dash(&[self.device_to_user_point(3.0)], 0.0);
|
self.set_dash(&[self.device_to_user_point(3.0)], 0.0);
|
||||||
|
|
||||||
let Color(r,g,b,a) = theme.colors.divider;
|
let Color(r, g, b, a) = theme.colors.divider;
|
||||||
self.set_source_rgba(r, g, b, a);
|
self.set_source_rgba(r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,16 +101,19 @@ impl ThemedContextExt for cairo::Context {
|
||||||
// gets totally confused by active cairo transformations
|
// gets totally confused by active cairo transformations
|
||||||
// which then messes with font size, size of attributes, etc
|
// which then messes with font size, size of attributes, etc
|
||||||
|
|
||||||
let Color(r,g,b,a) = if selected { theme.colors.text_highlight }
|
let Color(r, g, b, a) = if selected {
|
||||||
else { theme.colors.text };
|
theme.colors.text_highlight
|
||||||
self.set_source_rgba(r,g,b,a);
|
} else {
|
||||||
|
theme.colors.text
|
||||||
|
};
|
||||||
|
self.set_source_rgba(r, g, b, a);
|
||||||
|
|
||||||
let layout = pangocairo::create_layout(self).unwrap();
|
let layout = pangocairo::create_layout(self).unwrap();
|
||||||
pangocairo::update_layout(self, &layout);
|
pangocairo::update_layout(self, &layout);
|
||||||
|
|
||||||
let attrlist = AttrList::new();
|
let attrlist = AttrList::new();
|
||||||
for i in indices {
|
for i in indices {
|
||||||
let (start, end) = ((*i as u32), (*i as u32)+1);
|
let (start, end) = ((*i as u32), (*i as u32) + 1);
|
||||||
let mut attr = Attribute::new_underline(pangocairo::pango::Underline::Single);
|
let mut attr = Attribute::new_underline(pangocairo::pango::Underline::Single);
|
||||||
attr.set_start_index(start);
|
attr.set_start_index(start);
|
||||||
attr.set_end_index(end);
|
attr.set_end_index(end);
|
||||||
|
@ -121,18 +123,18 @@ impl ThemedContextExt for cairo::Context {
|
||||||
|
|
||||||
let mut font = FontDescription::default();
|
let mut font = FontDescription::default();
|
||||||
font.set_family(&theme.font.0);
|
font.set_family(&theme.font.0);
|
||||||
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_spacing(0);
|
layout.set_spacing(0);
|
||||||
|
|
||||||
layout.set_text(text);
|
layout.set_text(text);
|
||||||
|
|
||||||
let (_width,height) = layout.size();
|
let (_width, height) = layout.size();
|
||||||
let device_height = (height as f64)/(pango::SCALE as f64);
|
let device_height = (height as f64) / (pango::SCALE as f64);
|
||||||
|
|
||||||
self.save().unwrap();
|
self.save().unwrap();
|
||||||
self.scale(30.0,30.0);
|
self.scale(30.0, 30.0);
|
||||||
let cairo_user_height = self.device_to_user_point(device_height);
|
let cairo_user_height = self.device_to_user_point(device_height);
|
||||||
let offset = (1.0 - cairo_user_height) / 2.0;
|
let offset = (1.0 - cairo_user_height) / 2.0;
|
||||||
self.rel_move_to(0.0, offset);
|
self.rel_move_to(0.0, offset);
|
||||||
|
@ -145,7 +147,7 @@ impl ThemedContextExt for cairo::Context {
|
||||||
|
|
||||||
fn device_to_user_point(&self, point: f64) -> f64 {
|
fn device_to_user_point(&self, point: f64) -> f64 {
|
||||||
self.device_to_user(point, point)
|
self.device_to_user(point, point)
|
||||||
.map(|(x,y)| if x>=y {x} else {y})
|
.map(|(x, y)| if x >= y { x } else { y })
|
||||||
.expect("Could not convert device to user space")
|
.expect("Could not convert device to user space")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
166
src/ui/ui.rs
166
src/ui/ui.rs
|
@ -5,26 +5,103 @@ use log::debug;
|
||||||
use winit::{platform::unix::WindowExtUnix, window::Window};
|
use winit::{platform::unix::WindowExtUnix, window::Window};
|
||||||
|
|
||||||
use super::core::shared::Entry;
|
use super::core::shared::Entry;
|
||||||
|
|
||||||
use super::painter;
|
use super::painter;
|
||||||
|
|
||||||
|
pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
|
||||||
|
let context = make_context(&window);
|
||||||
|
|
||||||
|
context.scale(30.0, 30.0);
|
||||||
|
|
||||||
|
let painter = painter::Painter::new(&context);
|
||||||
|
|
||||||
|
painter.background();
|
||||||
|
painter.input_box(0, 0, input);
|
||||||
|
painter.divider(0, 1);
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redraw_quick(window: &Window, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
|
||||||
|
let context = make_context(&window);
|
||||||
|
|
||||||
|
context.scale(30.0, 30.0);
|
||||||
|
|
||||||
|
let painter = painter::Painter::new(&context);
|
||||||
|
painter.border_clip();
|
||||||
|
|
||||||
|
result.iter().enumerate().for_each(|(i, r)| {
|
||||||
|
let e = r.0;
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
if i == result.len() - 1 {
|
||||||
|
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &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)
|
||||||
|
}
|
||||||
|
if i == (selection + 1) {
|
||||||
|
painter.result_box(0, 1 + (i as u32), &e.source, &e.name, &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)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_actions(window: &Window, actions: Vec<String>, input: &str) {
|
||||||
|
let context = make_context(&window);
|
||||||
|
|
||||||
|
context.scale(30.0, 30.0);
|
||||||
|
|
||||||
|
let painter = painter::Painter::new(&context);
|
||||||
|
|
||||||
|
painter.background();
|
||||||
|
painter.input_box(0, 0, input);
|
||||||
|
painter.divider(0, 1);
|
||||||
|
|
||||||
|
actions.iter().enumerate().for_each(|(i, r)| {
|
||||||
|
painter.result_box(
|
||||||
|
0,
|
||||||
|
1 + (i as u32),
|
||||||
|
&usize::to_string(&(i + 1)),
|
||||||
|
r,
|
||||||
|
&[],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn get_visual_type<T>(mut connection: T, window_id: u32) -> xcb::Visualtype
|
fn get_visual_type<T>(mut connection: T, window_id: u32) -> xcb::Visualtype
|
||||||
where T: BorrowMut<*mut c_void>
|
where
|
||||||
|
T: BorrowMut<*mut c_void>,
|
||||||
{
|
{
|
||||||
let xcb_conn = unsafe { xcb::Connection::from_raw_conn(*connection.borrow_mut() as *mut xcb::ffi::xcb_connection_t) };
|
let xcb_conn = unsafe {
|
||||||
|
xcb::Connection::from_raw_conn(*connection.borrow_mut() as *mut xcb::ffi::xcb_connection_t)
|
||||||
|
};
|
||||||
|
|
||||||
let window_visualid = xcb::get_window_attributes(&xcb_conn, window_id)
|
let window_visualid = xcb::get_window_attributes(&xcb_conn, window_id)
|
||||||
.get_reply()
|
.get_reply()
|
||||||
.expect("Could not fetch attributes for window")
|
.expect("Could not fetch attributes for window")
|
||||||
.visual();
|
.visual();
|
||||||
|
|
||||||
debug!{"Found visualid {} for window", window_visualid}
|
debug! {"Found visualid {} for window", window_visualid}
|
||||||
|
|
||||||
debug!{"Trying to map visualid to visualtype {}", window_visualid}
|
debug! {"Trying to map visualid to visualtype {}", window_visualid}
|
||||||
let visualtype = xcb_conn.get_setup().roots()
|
let visualtype = xcb_conn
|
||||||
|
.get_setup()
|
||||||
|
.roots()
|
||||||
.flat_map(|screen| screen.allowed_depths())
|
.flat_map(|screen| screen.allowed_depths())
|
||||||
.flat_map(|depth| depth.visuals())
|
.flat_map(|depth| depth.visuals())
|
||||||
.find(|visualtype| {visualtype.visual_id() == window_visualid})
|
.find(|visualtype| visualtype.visual_id() == window_visualid)
|
||||||
.expect("Could not match visualid to visualtype");
|
.expect("Could not match visualid to visualtype");
|
||||||
|
|
||||||
// xcb::Connection calls disconnect on the underlying xcb_connection_t
|
// xcb::Connection calls disconnect on the underlying xcb_connection_t
|
||||||
|
@ -37,20 +114,18 @@ where T: BorrowMut<*mut c_void>
|
||||||
visualtype
|
visualtype
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_context(window: &Window) -> cairo::Context
|
fn make_context(window: &Window) -> cairo::Context {
|
||||||
{
|
|
||||||
let winit_xcb_conn = window
|
let winit_xcb_conn = window
|
||||||
.xcb_connection()
|
.xcb_connection()
|
||||||
.expect("Could not get connection from window");
|
.expect("Could not get connection from window");
|
||||||
|
|
||||||
let xlib_window_id = window
|
let xlib_window_id = window.xlib_window().expect("Could not get xlib window");
|
||||||
.xlib_window()
|
|
||||||
.expect("Could not get xlib window");
|
|
||||||
|
|
||||||
let visual_type = unsafe {
|
let visual_type = unsafe {
|
||||||
let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32).base;
|
let mut visual_type = get_visual_type(winit_xcb_conn, xlib_window_id as u32).base;
|
||||||
cairo::XCBVisualType::from_raw_none(
|
cairo::XCBVisualType::from_raw_none(
|
||||||
&mut visual_type as *mut xcb::ffi::xcb_visualtype_t as *mut cairo::ffi::xcb_visualtype_t
|
&mut visual_type as *mut xcb::ffi::xcb_visualtype_t
|
||||||
|
as *mut cairo::ffi::xcb_visualtype_t,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,69 +141,8 @@ fn make_context(window: &Window) -> cairo::Context
|
||||||
&visual_type,
|
&visual_type,
|
||||||
window.inner_size().width as i32,
|
window.inner_size().width as i32,
|
||||||
window.inner_size().height as i32,
|
window.inner_size().height as i32,
|
||||||
).expect("Could not create drawing surface");
|
)
|
||||||
|
.expect("Could not create drawing surface");
|
||||||
|
|
||||||
cairo::Context::new(&surface).expect("Could not create drawing context")
|
cairo::Context::new(&surface).expect("Could not create drawing context")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(window: &Window, input: &str, result: Vec<(&Entry, Vec<usize>)>, selection: usize)
|
|
||||||
{
|
|
||||||
let context = make_context(&window);
|
|
||||||
|
|
||||||
context.scale(30.0, 30.0);
|
|
||||||
|
|
||||||
let painter = painter::Painter::new(&context);
|
|
||||||
|
|
||||||
painter.background();
|
|
||||||
painter.input_box(0, 0, input);
|
|
||||||
painter.divider(0, 1);
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redraw_quick(window: &Window, result: Vec<(&Entry, Vec<usize>)>, selection: usize) {
|
|
||||||
let context = make_context(&window);
|
|
||||||
|
|
||||||
context.scale(30.0, 30.0);
|
|
||||||
|
|
||||||
let painter = painter::Painter::new(&context);
|
|
||||||
painter.border_clip();
|
|
||||||
|
|
||||||
result.iter().enumerate()
|
|
||||||
.for_each(|(i, r)| {
|
|
||||||
let e = r.0;
|
|
||||||
// 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) }
|
|
||||||
if i == result.len()-1 { painter.result_box(0, 1+(i as u32), &e.source, &e.name, &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) }
|
|
||||||
if i == (selection+1) { painter.result_box(0, 1+(i as u32), &e.source, &e.name, &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) }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_actions(window: &Window, actions: Vec<String>, input: &str)
|
|
||||||
{
|
|
||||||
let context = make_context(&window);
|
|
||||||
|
|
||||||
context.scale(30.0, 30.0);
|
|
||||||
|
|
||||||
let painter = painter::Painter::new(&context);
|
|
||||||
|
|
||||||
painter.background();
|
|
||||||
painter.input_box(0, 0, input);
|
|
||||||
painter.divider(0, 1);
|
|
||||||
|
|
||||||
actions.iter().enumerate()
|
|
||||||
.for_each(|(i, r)| {
|
|
||||||
painter.result_box(0, 1+(i as u32), &usize::to_string(&(i+1)), r, &[], false);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue