Files
wldash/src/widgets/launcher.rs
T

462 lines
14 KiB
Rust
Raw Normal View History

2019-09-06 21:54:04 +02:00
use crate::buffer::Buffer;
use crate::cmd::Cmd;
use crate::color::Color;
use crate::desktop::{load_desktop_files, Desktop};
2020-06-03 21:06:06 +02:00
use crate::draw::Font;
use crate::{
fonts::FontRef,
widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget},
};
2019-09-06 21:54:04 +02:00
use std::cell::RefCell;
use std::cmp::Ordering;
2020-01-08 11:31:56 +01:00
use std::collections::HashMap;
2019-09-06 21:54:04 +02:00
use std::process::Command;
use std::sync::mpsc::Sender;
use fuzzy_matcher::skim::{fuzzy_indices, fuzzy_match};
use smithay_client_toolkit::keyboard::keysyms;
2020-06-03 21:06:06 +02:00
pub struct Launcher<'a> {
2020-07-17 01:30:59 +02:00
cursor: usize,
2019-09-06 21:54:04 +02:00
options: Vec<Desktop>,
term_opener: String,
app_opener: String,
url_opener: String,
matches: Vec<Desktop>,
input: String,
result: Option<String>,
offset: usize,
2020-06-03 21:06:06 +02:00
font: RefCell<Font<'a>>,
2019-09-06 21:54:04 +02:00
font_size: u32,
length: u32,
dirty: bool,
tx: Sender<Cmd>,
}
2020-06-03 21:06:06 +02:00
impl<'a> Launcher<'a> {
2019-09-06 22:48:50 +02:00
pub fn new(
2020-06-03 21:06:06 +02:00
font: FontRef,
2019-09-06 22:48:50 +02:00
font_size: f32,
length: u32,
listener: Sender<Cmd>,
app: String,
term: String,
url: String,
) -> Box<Launcher> {
2019-09-06 21:54:04 +02:00
Box::new(Launcher {
2020-07-17 01:30:59 +02:00
cursor: 0,
2019-09-06 21:54:04 +02:00
options: load_desktop_files(),
term_opener: term,
app_opener: app,
url_opener: url,
matches: vec![],
input: "".to_string(),
result: None,
offset: 0,
2020-06-03 21:06:06 +02:00
font: RefCell::new(Font::new(font, font_size)),
2019-09-06 21:54:04 +02:00
font_size: font_size as u32,
2020-04-22 01:31:22 +02:00
length,
2019-09-06 21:54:04 +02:00
dirty: true,
tx: listener,
})
}
fn draw_launcher(
&self,
buf: &mut Buffer,
bg: &Color,
width: u32,
) -> Result<(), ::std::io::Error> {
2020-04-22 01:31:22 +02:00
let mut x_off = if !self.input.is_empty() {
let c = if self.matches.is_empty() {
2019-09-06 21:54:04 +02:00
Color::new(1.0, 0.5, 0.5, 1.0)
} else {
Color::new(1.0, 1.0, 1.0, 1.0)
};
2020-07-17 01:30:59 +02:00
2019-09-06 21:54:04 +02:00
let dim = self
.font
.borrow_mut()
.auto_draw_text_with_cursor(buf, bg, &c, &self.input, self.cursor)?;
2019-09-06 21:54:04 +02:00
dim.0 + self.font_size / 4
} else {
0
};
let mut width_remaining: i32 = (width - x_off) as i32;
2019-09-06 21:54:04 +02:00
for (idx, m) in self.matches.iter().enumerate() {
let mut b = match buf.offset((x_off, 0)) {
Ok(b) => b,
Err(_) => break,
};
2020-04-22 01:31:22 +02:00
let size = if idx == self.offset && !self.input.is_empty() {
2019-09-06 21:54:04 +02:00
let (_, indices) =
2020-01-02 17:19:37 +00:00
fuzzy_indices(&m.name.to_lowercase(), &self.input.to_lowercase())
2020-01-08 11:31:56 +01:00
.unwrap_or((0, vec![]));
2019-09-06 21:54:04 +02:00
let mut colors = Vec::with_capacity(m.name.len());
for pos in 0..m.name.len() {
if indices.contains(&pos) {
colors.push(Color::new(1.0, 1.0, 1.0, 1.0));
} else {
colors.push(Color::new(0.75, 0.75, 0.75, 1.0));
}
}
self.font
.borrow_mut()
.auto_draw_text_individual_colors(&mut b, bg, &colors, &m.name)?
} else {
self.font.borrow_mut().auto_draw_text(
&mut b,
bg,
&Color::new(0.5, 0.5, 0.5, 1.0),
&m.name,
)?
};
2019-09-25 20:34:21 +02:00
x_off += size.0 + self.font_size / 2;
width_remaining -= (size.0 + self.font_size / 2) as i32;
2019-09-06 21:54:04 +02:00
if width_remaining < 0 {
break;
}
}
Ok(())
}
fn draw_calc(&self, buf: &mut Buffer, bg: &Color) -> Result<(), ::std::io::Error> {
let x_off = self
.font
.borrow_mut()
.auto_draw_text(buf, bg, &Color::new(1.0, 1.0, 0.0, 1.0), "=")?
.0
+ self.font_size / 4;
2020-04-22 01:31:22 +02:00
let x_off = if !self.input.is_empty() {
2019-09-06 21:54:04 +02:00
let dim = self.font.borrow_mut().auto_draw_text(
&mut buf.offset((x_off, 0))?,
bg,
&Color::new(1.0, 1.0, 1.0, 1.0),
&self.input[1..],
)?;
x_off + dim.0 + self.font_size / 4
} else {
0
};
if let Some(result) = &self.result {
self.font.borrow_mut().auto_draw_text(
&mut buf.offset((x_off, 0))?,
bg,
&Color::new(0.75, 0.75, 0.75, 1.0),
&format!(" = {:}", result),
)?;
}
Ok(())
}
fn draw_shell(&self, buf: &mut Buffer, bg: &Color) -> Result<(), ::std::io::Error> {
let x_off = self
.font
.borrow_mut()
.auto_draw_text(
&mut buf.offset((0, 0))?,
bg,
&Color::new(1.0, 1.0, 0.0, 1.0),
"!",
)?
.0
+ self.font_size / 4;
2020-04-22 01:31:22 +02:00
if !self.input.is_empty() {
2019-09-06 21:54:04 +02:00
self.font.borrow_mut().auto_draw_text(
&mut buf.offset((x_off, 0))?,
bg,
&Color::new(1.0, 1.0, 1.0, 1.0),
&self.input[1..],
)?;
};
Ok(())
}
}
fn calc(s: &str) -> Result<String, String> {
rcalc_lib::parse::eval(s, &mut rcalc_lib::parse::CalcState::new())
2020-04-22 01:31:22 +02:00
.map(|x| format!("{}", x))
.map_err(|x| format!("{}", x))
2019-09-06 21:54:04 +02:00
}
fn wlcopy(s: &str) -> Result<(), String> {
let mut child = std::process::Command::new("wl-copy")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.arg(s)
.spawn()
.map_err(|_| "wl-copy not available".to_string())?;
child
.wait()
.map_err(|_| "unable to run wl-copy".to_string())?;
Ok(())
}
2020-01-02 18:38:23 +00:00
struct Matcher {
matches: HashMap<Desktop, i64>,
}
impl Matcher {
fn new() -> Self {
Self {
matches: HashMap::new(),
}
}
fn try_match(&mut self, dtop: Desktop, val: &str, input: &str, prio: f32) {
if let Some(ma) = fuzzy_match(val, input) {
let ma = ((ma as f32) * prio) as i64;
if let Some(ma_old) = self.matches.get(&dtop) {
// Skip over new matches for the same program that are worse
// than the one we already have.
if ma_old > &ma {
2020-01-08 11:31:56 +01:00
return;
2020-01-02 18:38:23 +00:00
}
}
self.matches.insert(dtop, ma);
}
}
fn matches(&self) -> Vec<Desktop> {
2020-01-08 11:31:56 +01:00
let mut m = self
.matches
.iter()
2020-04-22 01:31:22 +02:00
.map(|(key, ma)| (*ma, key.clone()))
2020-01-08 11:31:56 +01:00
.collect::<Vec<(i64, Desktop)>>();
2020-01-02 18:38:23 +00:00
m.sort_by(|(ma1, d1), (ma2, d2)| {
if ma1 > ma2 {
Ordering::Less
} else if ma1 < ma2 {
Ordering::Greater
} else if d1.name.len() < d2.name.len() {
Ordering::Less
} else if d1.name.len() > d2.name.len() {
Ordering::Greater
} else {
d1.cmp(d2)
}
});
m.into_iter().map(|(_, x)| x).collect()
}
}
2020-06-03 21:06:06 +02:00
impl<'a> Widget for Launcher<'a> {
2019-10-05 21:41:09 +02:00
fn wait(&mut self, _: &mut WaitContext) {}
2019-10-02 14:05:35 +02:00
fn enter(&mut self) {}
fn leave(&mut self) {
self.input = "".to_string();
self.offset = 0;
self.result = None;
self.dirty = true;
}
2019-09-06 21:54:04 +02:00
fn size(&self) -> (u32, u32) {
(self.length, self.font_size)
}
fn draw(
&mut self,
ctx: &mut DrawContext,
pos: (u32, u32),
expansion: (u32, u32),
2019-09-06 21:54:04 +02:00
) -> Result<DrawReport, ::std::io::Error> {
if self.length == 0 {
self.length = expansion.0;
}
let (width, height) = (self.length, self.font_size);
2019-09-06 21:54:04 +02:00
if !self.dirty && !ctx.force {
return Ok(DrawReport::empty(width, height));
}
self.dirty = false;
match self.input.chars().next() {
Some('=') => {
if self.input.len() > 1 {
match calc(&self.input.chars().skip(1).collect::<String>()) {
Ok(v) => self.result = Some(v),
Err(_) => self.result = None,
}
}
}
Some('!') => (),
_ => {
2020-01-02 18:38:23 +00:00
let mut matcher = Matcher::new();
2020-01-02 17:19:37 +00:00
for desktop in self.options.iter() {
2020-01-02 18:38:23 +00:00
matcher.try_match(
desktop.clone(),
&desktop.name.to_lowercase(),
&self.input.to_lowercase(),
1.0,
);
2020-01-02 17:19:37 +00:00
for keyword in desktop.keywords.iter() {
2020-01-02 18:38:23 +00:00
matcher.try_match(
desktop.clone(),
&keyword.to_lowercase(),
&self.input.to_lowercase(),
0.5,
);
2020-01-02 17:19:37 +00:00
}
}
2020-01-02 18:38:23 +00:00
self.matches = matcher.matches();
2019-09-06 21:54:04 +02:00
}
};
let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?;
buf.memset(ctx.bg);
match self.input.chars().next() {
Some('=') => self.draw_calc(buf, ctx.bg),
Some('!') => self.draw_shell(buf, ctx.bg),
_ => self.draw_launcher(buf, ctx.bg, width),
2019-09-06 21:54:04 +02:00
}?;
Ok(DrawReport {
2020-04-22 01:31:22 +02:00
width,
height,
2019-09-06 21:54:04 +02:00
damage: vec![buf.get_signed_bounds()],
full_damage: false,
})
}
fn keyboard_input(
&mut self,
key: u32,
2019-10-02 14:05:35 +02:00
modifiers: ModifiersState,
2019-09-06 21:54:04 +02:00
_: KeyState,
interpreted: Option<String>,
) {
match key {
2019-10-02 14:05:35 +02:00
keysyms::XKB_KEY_u if modifiers.ctrl => self.leave(),
2019-09-06 21:54:04 +02:00
keysyms::XKB_KEY_BackSpace => {
2020-07-17 01:30:59 +02:00
if !self.input.is_empty() && self.cursor > 0 {
self.cursor -= 1;
self.input.remove(self.cursor);
2019-09-06 21:54:04 +02:00
self.offset = 0;
self.result = None;
self.dirty = true
}
}
2020-07-17 01:30:59 +02:00
keysyms::XKB_KEY_Delete => {
if !self.input.is_empty() && self.cursor < self.input.len() {
self.input.remove(self.cursor);
self.dirty = true;
}
}
2019-09-06 21:54:04 +02:00
keysyms::XKB_KEY_Return => {
match self.input.chars().next() {
2020-04-22 01:31:22 +02:00
Some('=') => {
if let Some(ref v) = self.result {
2019-09-06 21:54:04 +02:00
let _ = wlcopy(&v);
}
2020-04-22 01:31:22 +02:00
}
2019-09-06 21:54:04 +02:00
Some('!') => {
2020-07-17 01:30:59 +02:00
self.cursor = 0;
2019-09-06 21:54:04 +02:00
let _ = Command::new("sh")
.arg("-c")
.arg(self.input.chars().skip(1).collect::<String>())
.spawn();
self.tx.send(Cmd::Exit).unwrap();
}
_ => {
2020-07-17 01:30:59 +02:00
self.cursor = 0;
2019-09-06 21:54:04 +02:00
if self.matches.len() > self.offset {
let d = &self.matches[self.offset];
if let Some(exec) = &d.exec {
let exec = exec
.replace("%f", "")
.replace("%F", "")
.replace("%u", "")
.replace("%U", "");
let prefix = if d.term {
&self.term_opener
} else {
&self.app_opener
};
2020-04-22 01:31:22 +02:00
let lexed = if !prefix.is_empty() {
2019-09-06 21:54:04 +02:00
let mut prefix = shlex::split(prefix).unwrap();
prefix.push(exec);
prefix
} else {
shlex::split(&exec).unwrap()
};
2020-04-22 01:31:22 +02:00
if !lexed.is_empty() {
2019-09-06 21:54:04 +02:00
let _ =
Command::new(lexed[0].clone()).args(&lexed[1..]).spawn();
self.tx.send(Cmd::Exit).unwrap();
}
}
if let Some(url) = &d.url {
2020-04-22 01:31:22 +02:00
if !self.url_opener.is_empty() {
2019-09-06 21:54:04 +02:00
let mut lexed = shlex::split(&self.url_opener).unwrap();
lexed.push(url.to_string());
2020-04-22 01:31:22 +02:00
if !lexed.is_empty() {
2019-09-06 21:54:04 +02:00
let _ = Command::new(lexed[0].clone())
.args(&lexed[1..])
.spawn();
self.tx.send(Cmd::Exit).unwrap();
}
}
}
}
}
};
}
2020-07-17 01:01:29 +02:00
keysyms::XKB_KEY_Tab => {
2020-04-22 01:31:22 +02:00
if !self.matches.is_empty() && self.offset < self.matches.len() - 1 {
2019-09-06 21:54:04 +02:00
self.offset += 1;
self.dirty = true;
}
}
2020-07-17 01:01:29 +02:00
keysyms::XKB_KEY_ISO_Left_Tab => {
2020-04-22 01:31:22 +02:00
if !self.matches.is_empty() && self.offset > 0 {
2019-09-06 21:54:04 +02:00
self.offset -= 1;
self.dirty = true;
}
}
2020-07-17 01:30:59 +02:00
keysyms::XKB_KEY_Left => {
if self.cursor > 0 {
self.cursor -= 1;
self.dirty = true;
}
}
keysyms::XKB_KEY_Right => {
if self.cursor < self.input.len() {
self.cursor += 1;
self.dirty = true;
}
}
2020-04-22 01:31:22 +02:00
_ => {
println!("{}", key);
2020-04-22 01:31:22 +02:00
if let Some(v) = interpreted {
2020-07-17 01:30:59 +02:00
self.input.insert_str(self.cursor, &v);
self.cursor += 1;
2019-09-06 21:54:04 +02:00
self.offset = 0;
self.result = None;
self.dirty = true;
}
2020-04-22 01:31:22 +02:00
}
2019-09-06 21:54:04 +02:00
}
}
fn mouse_click(&mut self, _: u32, _: (u32, u32)) {}
fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {}
}