Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+1161
File diff suppressed because it is too large
Load Diff
+32
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "wldash2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wayland-client = { version = "0.30.0-beta.13", features = ["calloop", "log"] }
|
||||
wayland-protocols = { version = "0.30.0-beta.13", features = ["client", "unstable", "staging"] }
|
||||
tempfile = "3.3"
|
||||
nix = "0.26"
|
||||
memmap = "0.7"
|
||||
fontconfig = "0.6.0"
|
||||
rusttype = "^0.9"
|
||||
calloop = { version = "0.10" }
|
||||
chrono = "0.4"
|
||||
|
||||
walkdir = "2.3"
|
||||
itertools = "0.10"
|
||||
rust-ini = "0.18"
|
||||
|
||||
bitflags = "1.3"
|
||||
|
||||
unicode-segmentation = "1.10"
|
||||
fuzzy-matcher = "0.3"
|
||||
shlex = "1.1"
|
||||
|
||||
xkbcommon = { version = "0.5", features = ["wayland"] }
|
||||
dbus = "0.9"
|
||||
|
||||
rcalc_lib = "1.0"
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
fs::File,
|
||||
io,
|
||||
os::unix::io::{FromRawFd, RawFd},
|
||||
time::SystemTime,
|
||||
time::UNIX_EPOCH,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use nix::sys::memfd;
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
fcntl,
|
||||
sys::{mman, stat},
|
||||
unistd,
|
||||
};
|
||||
|
||||
use memmap::MmapMut;
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_buffer, wl_shm, wl_shm_pool},
|
||||
Dispatch, QueueHandle,
|
||||
};
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::widgets::Geometry;
|
||||
|
||||
fn create_shm_fd() -> io::Result<RawFd> {
|
||||
// Only try memfd on linux
|
||||
#[cfg(target_os = "linux")]
|
||||
loop {
|
||||
match memfd::memfd_create(
|
||||
CStr::from_bytes_with_nul(b"wldash\0").unwrap(),
|
||||
memfd::MemFdCreateFlag::MFD_CLOEXEC,
|
||||
) {
|
||||
Ok(fd) => return Ok(fd),
|
||||
Err(Errno::EINTR) => continue,
|
||||
Err(Errno::ENOSYS) => break,
|
||||
Err(errno) => return Err(io::Error::from(errno)),
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to using shm_open
|
||||
let sys_time = SystemTime::now();
|
||||
let mut mem_file_handle = format!(
|
||||
"/wldash-{}",
|
||||
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
|
||||
);
|
||||
loop {
|
||||
match mman::shm_open(
|
||||
mem_file_handle.as_str(),
|
||||
fcntl::OFlag::O_CREAT
|
||||
| fcntl::OFlag::O_EXCL
|
||||
| fcntl::OFlag::O_RDWR
|
||||
| fcntl::OFlag::O_CLOEXEC,
|
||||
stat::Mode::S_IRUSR | stat::Mode::S_IWUSR,
|
||||
) {
|
||||
Ok(fd) => match mman::shm_unlink(mem_file_handle.as_str()) {
|
||||
Ok(_) => return Ok(fd),
|
||||
Err(errno) => match unistd::close(fd) {
|
||||
Ok(_) => return Err(io::Error::from(errno)),
|
||||
Err(errno) => return Err(io::Error::from(errno)),
|
||||
},
|
||||
},
|
||||
Err(Errno::EEXIST) => {
|
||||
// If a file with that handle exists then change the handle
|
||||
mem_file_handle = format!(
|
||||
"/wldash-{}",
|
||||
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(Errno::EINTR) => continue,
|
||||
Err(errno) => return Err(io::Error::from(errno)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShmBuffer {
|
||||
pub file: File,
|
||||
pub mmap: MmapMut,
|
||||
pub buffer: wl_buffer::WlBuffer,
|
||||
pub refcnt: u32,
|
||||
}
|
||||
|
||||
impl ShmBuffer {
|
||||
pub fn new<D>(
|
||||
shm: &wl_shm::WlShm,
|
||||
qh: &QueueHandle<D>,
|
||||
width: i32,
|
||||
height: i32,
|
||||
stride: i32,
|
||||
format: wl_shm::Format,
|
||||
) -> io::Result<ShmBuffer>
|
||||
where
|
||||
D: Dispatch<wl_shm_pool::WlShmPool, ()> + Dispatch<wl_buffer::WlBuffer, ()> + 'static,
|
||||
{
|
||||
let mem_fd = create_shm_fd()?;
|
||||
let file = unsafe { File::from_raw_fd(mem_fd) };
|
||||
file.set_len((height * stride) as u64)?;
|
||||
let pool = shm.create_pool(mem_fd, height * stride, &qh, ());
|
||||
let mmap = unsafe { MmapMut::map_mut(&file).unwrap() };
|
||||
let buffer = pool.create_buffer(0, width, height, stride, format, &qh, ());
|
||||
|
||||
Ok(ShmBuffer {
|
||||
refcnt: 0,
|
||||
file,
|
||||
mmap,
|
||||
buffer,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn acquire(&mut self) {
|
||||
self.refcnt += 1;
|
||||
}
|
||||
|
||||
pub fn release(&mut self) {
|
||||
self.refcnt -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BufferManager {
|
||||
pub buffers: Vec<ShmBuffer>,
|
||||
}
|
||||
|
||||
impl BufferManager {
|
||||
pub fn clear_buffers(&mut self) {
|
||||
for buf in self.buffers.iter_mut() {
|
||||
buf.buffer.destroy()
|
||||
}
|
||||
self.buffers.clear()
|
||||
}
|
||||
|
||||
pub fn next_buffer(&mut self) -> Option<&mut ShmBuffer> {
|
||||
for buf in self.buffers.iter_mut() {
|
||||
if buf.refcnt == 0 {
|
||||
return Some(buf);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn add_buffer<D>(
|
||||
&mut self,
|
||||
wl_shm: &wl_shm::WlShm,
|
||||
dimensions: (i32, i32),
|
||||
qh: &QueueHandle<D>,
|
||||
) where
|
||||
D: Dispatch<wl_shm_pool::WlShmPool, ()> + Dispatch<wl_buffer::WlBuffer, ()> + 'static,
|
||||
{
|
||||
let mut buf = ShmBuffer::new(
|
||||
wl_shm,
|
||||
qh,
|
||||
dimensions.0,
|
||||
dimensions.1,
|
||||
dimensions.0 * 4,
|
||||
wl_shm::Format::Argb8888,
|
||||
)
|
||||
.expect("unable to add buffer");
|
||||
BufferView::new(&mut buf.mmap, (dimensions.0 as u32, dimensions.1 as u32));
|
||||
self.buffers.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BufferView<'a> {
|
||||
buf: &'a mut MmapMut,
|
||||
dimensions: (u32, u32),
|
||||
subdimensions: Option<(u32, u32, u32, u32)>,
|
||||
}
|
||||
|
||||
impl<'a> BufferView<'a> {
|
||||
pub fn new(buf: &'a mut MmapMut, dimensions: (u32, u32)) -> BufferView {
|
||||
BufferView {
|
||||
buf,
|
||||
dimensions,
|
||||
subdimensions: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bounds(&self) -> (u32, u32, u32, u32) {
|
||||
if let Some(subdim) = self.subdimensions {
|
||||
subdim
|
||||
} else {
|
||||
(0, 0, self.dimensions.0, self.dimensions.1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subdimensions(
|
||||
&mut self,
|
||||
subdimensions: (u32, u32, u32, u32),
|
||||
) -> Result<BufferView, ::std::io::Error> {
|
||||
let bounds = self.get_bounds();
|
||||
if subdimensions.0 + subdimensions.2 > bounds.2
|
||||
|| subdimensions.1 + subdimensions.3 > bounds.3
|
||||
{
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"cannot create subdimensions larger than buffer: {:?} > {:?}",
|
||||
subdimensions, bounds
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(BufferView {
|
||||
buf: self.buf,
|
||||
dimensions: self.dimensions,
|
||||
subdimensions: Some((
|
||||
subdimensions.0 + bounds.0,
|
||||
subdimensions.1 + bounds.1,
|
||||
subdimensions.2,
|
||||
subdimensions.3,
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn subgeometry(&mut self, geo: Geometry) -> Result<BufferView, ::std::io::Error> {
|
||||
self.subdimensions((geo.x, geo.y, geo.width, geo.height))
|
||||
}
|
||||
|
||||
pub fn offset(&mut self, offset: (u32, u32)) -> Result<BufferView, ::std::io::Error> {
|
||||
let bounds = self.get_bounds();
|
||||
if offset.0 > bounds.2 || offset.1 > bounds.3 {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"cannot create offset outside buffer: {:?} > {:?}",
|
||||
offset, bounds
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(BufferView {
|
||||
buf: self.buf,
|
||||
dimensions: self.dimensions,
|
||||
subdimensions: Some((
|
||||
offset.0 + bounds.0,
|
||||
offset.1 + bounds.1,
|
||||
bounds.2 - offset.0,
|
||||
bounds.3 - offset.1,
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn limit(&mut self, dimensions: (u32, u32)) -> Result<BufferView, ::std::io::Error> {
|
||||
let bounds = self.get_bounds();
|
||||
if dimensions.0 > bounds.2 || dimensions.1 > bounds.3 {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"cannot create offset outside buffer: {:?} > {:?}",
|
||||
dimensions, bounds
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(BufferView {
|
||||
buf: self.buf,
|
||||
dimensions: self.dimensions,
|
||||
subdimensions: Some((bounds.0, bounds.1, dimensions.0, dimensions.1)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn memset(&mut self, c: &Color) {
|
||||
let v = c.as_argb8888();
|
||||
if let Some(subdim) = self.subdimensions {
|
||||
unsafe {
|
||||
let ptr = self.buf.as_mut_ptr();
|
||||
for y in subdim.1..(subdim.1 + subdim.3) {
|
||||
for x in subdim.0..(subdim.0 + subdim.2) {
|
||||
*((ptr as *mut u32).offset((x + y * self.dimensions.0) as isize)) = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
let ptr = self.buf.as_mut_ptr();
|
||||
for p in 0..(self.dimensions.0 * self.dimensions.1) {
|
||||
*((ptr as *mut u32).offset(p as isize)) = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put(&mut self, pos: (u32, u32), c: &Color) -> Result<(), ::std::io::Error> {
|
||||
let true_pos = if let Some(subdim) = self.subdimensions {
|
||||
if pos.0 >= subdim.2 || pos.1 >= subdim.3 {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"put({:?}) is not within subdimensions of buffer ({:?})",
|
||||
pos, subdim
|
||||
),
|
||||
));
|
||||
}
|
||||
(pos.0 + subdim.0, pos.1 + subdim.1)
|
||||
} else {
|
||||
if pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1 {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"put({:?}) is not within dimensions of buffer ({:?})",
|
||||
pos, self.dimensions
|
||||
),
|
||||
));
|
||||
}
|
||||
pos
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let ptr = self
|
||||
.buf
|
||||
.as_mut_ptr()
|
||||
.offset(4 * (true_pos.0 + (true_pos.1 * self.dimensions.0)) as isize);
|
||||
*(ptr as *mut u32) = c.as_argb8888();
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//use serde::{Deserialize, Serialize};
|
||||
|
||||
//#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
|
||||
pub struct Color {
|
||||
red: f32,
|
||||
green: f32,
|
||||
blue: f32,
|
||||
opacity: f32,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn new(red: f32, green: f32, blue: f32, opacity: f32) -> Color {
|
||||
let (red, green, blue, opacity) = clamp(red, green, blue, opacity);
|
||||
Color {
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
opacity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend(&self, other: &Color, ratio: f32) -> Color {
|
||||
let ratio = clamp_f32(ratio, 0.0, 1.0);
|
||||
|
||||
Color {
|
||||
red: blend_f32(self.red, other.red, ratio),
|
||||
green: blend_f32(self.green, other.green, ratio),
|
||||
blue: blend_f32(self.blue, other.blue, ratio),
|
||||
opacity: blend_f32(self.opacity, other.opacity, ratio),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_argb8888(&self) -> u32 {
|
||||
((255.0 * self.opacity) as u32 & 0xFF) << 24
|
||||
| ((255.0 * self.red) as u32 & 0xFF) << 16
|
||||
| ((255.0 * self.green) as u32 & 0xFF) << 8
|
||||
| ((255.0 * self.blue) as u32 & 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) {
|
||||
clamp_naive(r, g, b, o)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp_naive(r: f32, g: f32, b: f32, o: f32) -> (f32, f32, f32, f32) {
|
||||
(
|
||||
clamp_f32(r, 0.0, 1.0),
|
||||
clamp_f32(g, 0.0, 1.0),
|
||||
clamp_f32(b, 0.0, 1.0),
|
||||
clamp_f32(o, 0.0, 1.0),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp_f32(v: f32, a: f32, b: f32) -> f32 {
|
||||
if v > b {
|
||||
b
|
||||
} else if v < a {
|
||||
a
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn blend_f32(a: f32, b: f32, r: f32) -> f32 {
|
||||
a + ((b - a) * r)
|
||||
}
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
use crate::buffer::BufferView;
|
||||
use crate::{color::Color, fonts::FontRef};
|
||||
|
||||
use rusttype::{point, Scale};
|
||||
use std::collections::HashMap;
|
||||
|
||||
struct CachedGlyph {
|
||||
dimensions: (u32, u32),
|
||||
origin: (i32, i32),
|
||||
render: Vec<f32>,
|
||||
advance: i32,
|
||||
}
|
||||
|
||||
impl CachedGlyph {
|
||||
fn new(font: FontRef, size: f32, ch: char) -> CachedGlyph {
|
||||
let scale = Scale::uniform(size);
|
||||
let v_metrics = font.v_metrics(scale);
|
||||
let glyph = font.glyph(ch).scaled(scale);
|
||||
let advance = glyph.h_metrics().advance_width as i32;
|
||||
let glyph = glyph.positioned(point(0.0, v_metrics.ascent));
|
||||
|
||||
if let Some(bounding_box) = glyph.pixel_bounding_box() {
|
||||
let origin = (bounding_box.min.x, bounding_box.min.y);
|
||||
|
||||
let dimensions = (
|
||||
(bounding_box.max.x - bounding_box.min.x) as u32,
|
||||
(bounding_box.max.y - bounding_box.min.y) as u32,
|
||||
);
|
||||
let mut render = vec![0.0; (dimensions.0 * dimensions.1) as usize];
|
||||
glyph.draw(|x, y, o| {
|
||||
let pos = x + (y * dimensions.0);
|
||||
render[pos as usize] = o;
|
||||
});
|
||||
CachedGlyph {
|
||||
origin,
|
||||
dimensions,
|
||||
render,
|
||||
advance,
|
||||
}
|
||||
} else {
|
||||
CachedGlyph {
|
||||
origin: (0, 0),
|
||||
dimensions: (0, 0),
|
||||
render: Vec::new(),
|
||||
advance: (size / 4.0) as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, buf: &mut BufferView, pos: (i32, i32), bg: &Color, c: &Color) {
|
||||
let mut x = 0;
|
||||
let mut y = 0;
|
||||
for v in &self.render {
|
||||
let _ = buf.put(
|
||||
(
|
||||
(x + pos.0 + self.origin.0) as u32,
|
||||
(y + pos.1 + self.origin.1) as u32,
|
||||
),
|
||||
&bg.blend(&c, *v),
|
||||
);
|
||||
|
||||
if x == self.dimensions.0 as i32 - 1 {
|
||||
y += 1;
|
||||
x = 0;
|
||||
} else {
|
||||
x += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Font<'a> {
|
||||
glyphs: HashMap<char, CachedGlyph>,
|
||||
font: FontRef<'a>,
|
||||
size: f32,
|
||||
}
|
||||
|
||||
impl<'a> Font<'a> {
|
||||
pub fn new(font: FontRef<'a>, size: f32) -> Font {
|
||||
Font {
|
||||
glyphs: HashMap::new(),
|
||||
font,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_str_to_cache(&mut self, s: &str) {
|
||||
for ch in s.chars() {
|
||||
if self.glyphs.get(&ch).is_none() {
|
||||
let glyph = CachedGlyph::new(self.font, self.size, ch);
|
||||
self.glyphs.insert(ch, glyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
&self,
|
||||
buf: &mut BufferView,
|
||||
bg: &Color,
|
||||
c: &Color,
|
||||
s: &str,
|
||||
) -> Result<(u32, u32), ::std::io::Error> {
|
||||
let mut x_off = 0;
|
||||
let mut off = 0;
|
||||
let mut glyphs = Vec::with_capacity(s.len());
|
||||
for ch in s.chars() {
|
||||
let glyph = match self.glyphs.get(&ch) {
|
||||
Some(glyph) => glyph,
|
||||
None => {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!("glyph for {:} not in cache", ch),
|
||||
))
|
||||
}
|
||||
};
|
||||
glyphs.push(glyph);
|
||||
if glyph.origin.1 < off {
|
||||
off = glyph.origin.1
|
||||
}
|
||||
}
|
||||
for glyph in glyphs {
|
||||
glyph.draw(buf, (x_off, -off), bg, c);
|
||||
x_off += glyph.advance;
|
||||
}
|
||||
|
||||
Ok((x_off as u32, self.size as u32))
|
||||
}
|
||||
|
||||
pub fn draw_text_with_cursor(
|
||||
&self,
|
||||
buf: &mut BufferView,
|
||||
bg: &Color,
|
||||
c: &Color,
|
||||
s: &str,
|
||||
cursor: usize,
|
||||
) -> Result<(u32, u32), ::std::io::Error> {
|
||||
let mut x_off: i32 = 0;
|
||||
let mut off: i32 = 0;
|
||||
let mut glyphs = Vec::with_capacity(s.len());
|
||||
for ch in s.chars() {
|
||||
let glyph = match self.glyphs.get(&ch) {
|
||||
Some(glyph) => glyph,
|
||||
None => {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!("glyph for {:} not in cache", ch),
|
||||
))
|
||||
}
|
||||
};
|
||||
glyphs.push(glyph);
|
||||
if glyph.origin.1 < off {
|
||||
off = glyph.origin.1
|
||||
}
|
||||
}
|
||||
|
||||
let height = buf.get_bounds().3;
|
||||
for (i, glyph) in glyphs.iter().enumerate() {
|
||||
if i == cursor {
|
||||
self.draw_cursor(buf, c, x_off as u32, height)?;
|
||||
}
|
||||
glyph.draw(buf, (x_off, -off), bg, c);
|
||||
x_off += glyph.advance;
|
||||
}
|
||||
if cursor == glyphs.len() {
|
||||
self.draw_cursor(buf, c, x_off as u32, height)?;
|
||||
}
|
||||
|
||||
Ok((x_off as u32, self.size as u32))
|
||||
}
|
||||
|
||||
pub fn draw_cursor(
|
||||
&self,
|
||||
buf: &mut BufferView,
|
||||
c: &Color,
|
||||
offset: u32,
|
||||
height: u32,
|
||||
) -> Result<(), ::std::io::Error> {
|
||||
// draw cursor
|
||||
for i in 1..height {
|
||||
buf.put((offset, i), c)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn auto_widest(&mut self, s: &str) -> Result<u32, ::std::io::Error> {
|
||||
self.add_str_to_cache(s);
|
||||
let mut max = 0;
|
||||
for ch in s.chars() {
|
||||
let glyph = match self.glyphs.get(&ch) {
|
||||
Some(glyph) => glyph,
|
||||
None => {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!("glyph for {:} not in cache", ch),
|
||||
))
|
||||
}
|
||||
};
|
||||
let width = glyph.advance;
|
||||
if width > max {
|
||||
max = width
|
||||
}
|
||||
}
|
||||
Ok(max as u32)
|
||||
}
|
||||
|
||||
pub fn auto_draw_text(
|
||||
&mut self,
|
||||
buf: &mut BufferView,
|
||||
bg: &Color,
|
||||
c: &Color,
|
||||
s: &str,
|
||||
) -> Result<(u32, u32), ::std::io::Error> {
|
||||
self.add_str_to_cache(s);
|
||||
self.draw_text(buf, bg, c, s)
|
||||
}
|
||||
|
||||
pub fn auto_draw_text_with_cursor(
|
||||
&mut self,
|
||||
buf: &mut BufferView,
|
||||
bg: &Color,
|
||||
c: &Color,
|
||||
s: &str,
|
||||
cursor: usize,
|
||||
) -> Result<(u32, u32), ::std::io::Error> {
|
||||
self.add_str_to_cache(s);
|
||||
self.draw_text_with_cursor(buf, bg, c, s, cursor)
|
||||
}
|
||||
|
||||
pub fn draw_text_fixed_width(
|
||||
&self,
|
||||
buf: &mut BufferView,
|
||||
bg: &Color,
|
||||
c: &Color,
|
||||
distances: &[u32],
|
||||
s: &str,
|
||||
) -> Result<(u32, u32), ::std::io::Error> {
|
||||
let mut x_off = 0;
|
||||
let mut off = 0;
|
||||
let mut glyphs = Vec::with_capacity(s.len());
|
||||
for ch in s.chars() {
|
||||
let glyph = match self.glyphs.get(&ch) {
|
||||
Some(glyph) => glyph,
|
||||
None => {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!("glyph for {:} not in cache", ch),
|
||||
))
|
||||
}
|
||||
};
|
||||
glyphs.push(glyph);
|
||||
if glyph.origin.1 < off {
|
||||
off = glyph.origin.1
|
||||
}
|
||||
}
|
||||
for (idx, glyph) in glyphs.into_iter().enumerate() {
|
||||
glyph.draw(buf, (x_off, -off), bg, c);
|
||||
x_off += distances[idx] as i32;
|
||||
}
|
||||
|
||||
Ok((x_off as u32, self.size as u32))
|
||||
}
|
||||
|
||||
pub fn draw_text_individual_colors(
|
||||
&self,
|
||||
buf: &mut BufferView,
|
||||
bg: &Color,
|
||||
color: &[Color],
|
||||
s: &str,
|
||||
) -> Result<(u32, u32), ::std::io::Error> {
|
||||
let mut x_off = 0;
|
||||
let mut off = 0;
|
||||
let mut glyphs = Vec::with_capacity(s.len());
|
||||
for ch in s.chars() {
|
||||
let glyph = match self.glyphs.get(&ch) {
|
||||
Some(glyph) => glyph,
|
||||
None => {
|
||||
return Err(::std::io::Error::new(
|
||||
::std::io::ErrorKind::Other,
|
||||
format!("glyph for {:} not in cache", ch),
|
||||
))
|
||||
}
|
||||
};
|
||||
glyphs.push(glyph);
|
||||
if glyph.origin.1 < off {
|
||||
off = glyph.origin.1
|
||||
}
|
||||
}
|
||||
for (idx, glyph) in glyphs.into_iter().enumerate() {
|
||||
glyph.draw(buf, (x_off, -off), bg, &color[idx]);
|
||||
x_off += glyph.advance;
|
||||
}
|
||||
|
||||
Ok((x_off as u32, self.size as u32))
|
||||
}
|
||||
|
||||
pub fn auto_draw_text_individual_colors(
|
||||
&mut self,
|
||||
buf: &mut BufferView,
|
||||
bg: &Color,
|
||||
color: &[Color],
|
||||
s: &str,
|
||||
) -> Result<(u32, u32), ::std::io::Error> {
|
||||
self.add_str_to_cache(s);
|
||||
self.draw_text_individual_colors(buf, bg, color, s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_box(buf: &mut BufferView, c: &Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> {
|
||||
for x in 0..dim.0 {
|
||||
let _ = buf.put((x, 0), c);
|
||||
let _ = buf.put((x, dim.1 - 1), c);
|
||||
}
|
||||
for y in 0..dim.1 {
|
||||
buf.put((0, y), c)?;
|
||||
buf.put((dim.0 - 1, y), c)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw_bar(
|
||||
buf: &mut BufferView,
|
||||
color: &Color,
|
||||
length: u32,
|
||||
height: u32,
|
||||
fill: f32,
|
||||
) -> Result<(), ::std::io::Error> {
|
||||
let mut fill_pos = ((length as f32) * fill) as u32;
|
||||
if fill_pos > length {
|
||||
fill_pos = length;
|
||||
}
|
||||
for y in 0..height {
|
||||
for x in 0..fill_pos {
|
||||
let _ = buf.put((x, y), color);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
//! Utility module for fonts
|
||||
|
||||
use crate::draw;
|
||||
use fontconfig::Fontconfig as FontConfig;
|
||||
use rusttype::Font;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
hash,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// FontRef is used to store Fonts on widgets.
|
||||
pub type FontRef<'a> = &'a rusttype::Font<'a>;
|
||||
|
||||
/// FontSeeker is a marker struct that is used to look up fonts
|
||||
pub(crate) struct FontSeeker;
|
||||
|
||||
impl FontSeeker {
|
||||
/// Acts like fc-match.
|
||||
/// Given a string, it matches it to a font file and returns its path.
|
||||
pub(crate) fn from_string(name: &str) -> PathBuf {
|
||||
let fc = FontConfig::new().unwrap();
|
||||
fc.find(name, None).unwrap().path
|
||||
}
|
||||
}
|
||||
|
||||
/// FontLoader is a marker struct that is used to load files
|
||||
pub(crate) struct FontLoader;
|
||||
|
||||
impl FontLoader {
|
||||
/// Given a path, loads it as a Font, which can be rendered to the screen.
|
||||
pub(crate) fn from_path<'a, P>(path: P) -> Option<Font<'a>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut file = File::open(path).expect("Font file not found");
|
||||
let mut data = match file.metadata() {
|
||||
Ok(metadata) => Vec::with_capacity(metadata.len() as usize),
|
||||
Err(_) => vec![],
|
||||
};
|
||||
file.read_to_end(&mut data).unwrap();
|
||||
Font::try_from_vec(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct ComparableF32(f32);
|
||||
impl ComparableF32 {
|
||||
fn key(&self) -> u64 {
|
||||
self.0.to_bits() as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl hash::Hash for ComparableF32 {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: hash::Hasher,
|
||||
{
|
||||
self.key().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ComparableF32 {
|
||||
fn eq(&self, other: &ComparableF32) -> bool {
|
||||
self.key() == other.key()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ComparableF32 {}
|
||||
|
||||
pub struct FontMap {
|
||||
fonts: HashMap<(&'static str, ComparableF32), draw::Font<'static>>,
|
||||
required_fonts: HashMap<&'static str, (&'static str, Vec<f32>)>,
|
||||
}
|
||||
|
||||
impl FontMap {
|
||||
pub fn new() -> FontMap {
|
||||
FontMap {
|
||||
fonts: HashMap::new(),
|
||||
required_fonts: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_font_name(&mut self, font_name: &'static str, size: f32) {
|
||||
match self.required_fonts.get_mut(font_name) {
|
||||
Some(v) => v.1.push(size),
|
||||
None => {
|
||||
self.required_fonts
|
||||
.insert(font_name, (font_name, vec![size]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_font_path(&mut self, font_name: &'static str, font_path: &'static str, size: f32) {
|
||||
match self.required_fonts.get_mut(font_name) {
|
||||
Some(v) => v.1.push(size),
|
||||
None => {
|
||||
self.required_fonts
|
||||
.insert(font_name, (font_path, vec![size]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_fonts(&mut self) {
|
||||
for (font_name, v) in self.required_fonts.iter() {
|
||||
let path = if v.0.starts_with("/") {
|
||||
std::path::PathBuf::from(v.0)
|
||||
} else {
|
||||
FontSeeker::from_string(v.0)
|
||||
};
|
||||
let fontref = Box::leak(Box::new(
|
||||
FontLoader::from_path(path).expect("unable to load font"),
|
||||
));
|
||||
for size in &v.1 {
|
||||
let font = draw::Font::new(fontref, *size);
|
||||
self.fonts.insert((font_name, ComparableF32(*size)), font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_font(&mut self, font_name: &'static str, size: f32) -> &mut draw::Font<'static> {
|
||||
self.fonts
|
||||
.get_mut(&(font_name, ComparableF32(size)))
|
||||
.expect("no font at specified size")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#![allow(missing_docs, non_upper_case_globals, unused_parens, clippy::all)]
|
||||
|
||||
pub const XKB_KEY_BackSpace: u32 = 0xff08; /* Back space, back char */
|
||||
//pub const XKB_KEY_Tab: u32 = 0xff09;
|
||||
pub const XKB_KEY_Return: u32 = 0xff0d; /* Return, enter */
|
||||
//pub const XKB_KEY_Escape: u32 = 0xff1b;
|
||||
pub const XKB_KEY_Delete: u32 = 0xffff; /* Delete, rubout */
|
||||
pub const XKB_KEY_Home: u32 = 0xff50;
|
||||
pub const XKB_KEY_Left: u32 = 0xff51; /* Move left, left arrow */
|
||||
pub const XKB_KEY_Up: u32 = 0xff52; /* Move up, up arrow */
|
||||
pub const XKB_KEY_Right: u32 = 0xff53; /* Move right, right arrow */
|
||||
pub const XKB_KEY_Down: u32 = 0xff54; /* Move down, down arrow */
|
||||
pub const XKB_KEY_End: u32 = 0xff57; /* EOL */
|
||||
|
||||
//pub const XKB_KEY_ISO_Left_Tab: u32 = 0xfe20;
|
||||
pub const XKB_KEY_e: u32 = 0x0065; /* U+0065 LATIN SMALL LETTER E */
|
||||
pub const XKB_KEY_a: u32 = 0x0061; /* U+0061 LATIN SMALL LETTER A */
|
||||
//pub const XKB_KEY_c: u32 = 0x0063; /* U+0063 LATIN SMALL LETTER C */
|
||||
//pub const XKB_KEY_u: u32 = 0x0075; /* U+0075 LATIN SMALL LETTER U */
|
||||
@@ -0,0 +1,136 @@
|
||||
//! Utilities for keymap interpretation of keyboard input
|
||||
//!
|
||||
//! This module provides an implementation for `wl_keyboard`
|
||||
//! objects using `libxkbcommon` to interpret the keyboard input
|
||||
//! given the user keymap.
|
||||
//!
|
||||
//! The entry point of this module is the [`map_keyboard`](fn.map_keyboard.html)
|
||||
//! function which, given a `wl_seat` and a callback, setup keymap interpretation
|
||||
//! and key repetition for the `wl_keyboard` of this seat.
|
||||
//!
|
||||
//! Key repetition relies on an event source, that needs to be inserted in your
|
||||
//! calloop event loop. Not doing so will prevent key repetition to work
|
||||
//! (but the rest of the functionnality will not be affected).
|
||||
|
||||
use std::default::Default;
|
||||
use std::os::unix::io::{AsRawFd, OwnedFd};
|
||||
|
||||
use wayland_client::{protocol::wl_keyboard, WEnum};
|
||||
use xkbcommon::xkb;
|
||||
|
||||
pub mod keysyms;
|
||||
mod state;
|
||||
|
||||
use self::state::KbState;
|
||||
pub use self::state::ModifiersState;
|
||||
|
||||
pub struct KeyEvent {
|
||||
pub serial: u32,
|
||||
pub time: u32,
|
||||
pub rawkey: u32,
|
||||
pub keysym: u32,
|
||||
pub state: WEnum<wl_keyboard::KeyState>,
|
||||
pub modifiers: ModifiersState,
|
||||
pub utf8: Option<String>,
|
||||
pub repeats: bool,
|
||||
}
|
||||
|
||||
pub struct Keyboard {
|
||||
state: Option<KbState>,
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
pub fn new() -> Keyboard {
|
||||
Keyboard { state: None }
|
||||
}
|
||||
|
||||
pub fn keymap(&mut self, format: WEnum<wl_keyboard::KeymapFormat>, fd: OwnedFd, size: u32) {
|
||||
match format {
|
||||
WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) => {
|
||||
self.state = Some(
|
||||
KbState::new_from_fd(fd.as_raw_fd(), size as usize)
|
||||
.expect("unable to load keymap"),
|
||||
);
|
||||
}
|
||||
WEnum::Value(wl_keyboard::KeymapFormat::NoKeymap) => {
|
||||
// TODO: how to handle this (hopefully never occuring) case?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(
|
||||
&mut self,
|
||||
serial: u32,
|
||||
time: u32,
|
||||
key: u32,
|
||||
key_state: WEnum<wl_keyboard::KeyState>,
|
||||
) -> KeyEvent {
|
||||
if let Some(state) = &mut self.state {
|
||||
let (sym, utf8, repeats) = {
|
||||
// Get the values to generate a key event
|
||||
let sym = state.get_one_sym_raw(key);
|
||||
let utf8 = if key_state == WEnum::Value(wl_keyboard::KeyState::Pressed) {
|
||||
match state.compose_feed(sym) {
|
||||
Some(xkb::compose::FeedResult::Accepted) => {
|
||||
if let Some(status) = state.compose_status() {
|
||||
match status {
|
||||
xkb::compose::Status::Composed => state.compose_get_utf8(),
|
||||
xkb::compose::Status::Nothing => state.get_utf8_raw(key),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
state.get_utf8_raw(key)
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
// XKB_COMPOSE_FEED_IGNORED
|
||||
None
|
||||
}
|
||||
None => {
|
||||
// XKB COMPOSE is not initialized
|
||||
state.get_utf8_raw(key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let repeats = state.key_repeats(key + 8);
|
||||
(sym, utf8, repeats)
|
||||
};
|
||||
KeyEvent {
|
||||
rawkey: key,
|
||||
keysym: sym,
|
||||
state: key_state,
|
||||
modifiers: state.mods_state(),
|
||||
serial,
|
||||
time,
|
||||
utf8,
|
||||
repeats,
|
||||
}
|
||||
} else {
|
||||
KeyEvent {
|
||||
rawkey: key,
|
||||
keysym: 0,
|
||||
state: key_state,
|
||||
modifiers: Default::default(),
|
||||
utf8: None,
|
||||
repeats: false,
|
||||
serial,
|
||||
time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
) {
|
||||
if let Some(state) = &mut self.state {
|
||||
state.update_modifiers(mods_depressed, mods_latched, mods_locked, group);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
use std::{env, os::unix::io::RawFd};
|
||||
|
||||
use xkbcommon::xkb;
|
||||
|
||||
pub struct KbState {
|
||||
xkb_context: xkb::Context,
|
||||
xkb_keymap: xkb::Keymap,
|
||||
xkb_state: xkb::State,
|
||||
xkb_compose_table: Option<xkb::compose::Table>,
|
||||
xkb_compose_state: Option<xkb::compose::State>,
|
||||
mods_state: ModifiersState,
|
||||
}
|
||||
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||
///
|
||||
/// For some modifiers, this means that the key is currently pressed, others are toggled
|
||||
/// (like caps lock).
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ModifiersState {
|
||||
/// The "control" key
|
||||
pub ctrl: bool,
|
||||
/// The "alt" key
|
||||
pub alt: bool,
|
||||
/// The "shift" key
|
||||
pub shift: bool,
|
||||
/// The "Caps lock" key
|
||||
pub caps_lock: bool,
|
||||
/// The "logo" key
|
||||
///
|
||||
/// Also known as the "windows" key on most keyboards
|
||||
pub logo: bool,
|
||||
/// The "Num lock" key
|
||||
pub num_lock: bool,
|
||||
}
|
||||
|
||||
impl ModifiersState {
|
||||
fn new() -> ModifiersState {
|
||||
ModifiersState::default()
|
||||
}
|
||||
|
||||
fn update_with(&mut self, state: &xkb::State) {
|
||||
self.ctrl = state.mod_name_is_active("Control", xkb::STATE_MODS_EFFECTIVE);
|
||||
self.alt = state.mod_name_is_active("Mod1", xkb::STATE_MODS_EFFECTIVE);
|
||||
self.shift = state.mod_name_is_active("Shift", xkb::STATE_MODS_EFFECTIVE);
|
||||
self.caps_lock = state.mod_name_is_active("Lock", xkb::STATE_MODS_EFFECTIVE);
|
||||
self.num_lock = state.mod_name_is_active("Mod2", xkb::STATE_MODS_EFFECTIVE);
|
||||
self.logo = state.mod_name_is_active("Mod4", xkb::STATE_MODS_EFFECTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
impl KbState {
|
||||
pub(crate) fn update_modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
) {
|
||||
let mask =
|
||||
self.xkb_state
|
||||
.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
if (mask & xkb::STATE_MODS_EFFECTIVE) > 0 {
|
||||
// effective value of mods have changed, we need to update our state
|
||||
self.mods_state.update_with(&self.xkb_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 {
|
||||
self.xkb_state.key_get_one_sym(keycode + 8)
|
||||
}
|
||||
|
||||
pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option<String> {
|
||||
Some(self.xkb_state.key_get_utf8(keycode + 8))
|
||||
}
|
||||
|
||||
pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option<xkb::compose::FeedResult> {
|
||||
match &mut self.xkb_compose_state {
|
||||
Some(compose_state) => Some(compose_state.feed(keysym)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compose_status(&mut self) -> Option<xkb::compose::Status> {
|
||||
match &mut self.xkb_compose_state {
|
||||
Some(compose_state) => Some(compose_state.status()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compose_get_utf8(&mut self) -> Option<String> {
|
||||
match &mut self.xkb_compose_state {
|
||||
Some(compose_state) => compose_state.utf8(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_fd(fd: RawFd, size: usize) -> Result<KbState, std::io::Error> {
|
||||
let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||
let xkb_keymap = unsafe {
|
||||
xkb::Keymap::new_from_fd(
|
||||
&xkb_context,
|
||||
fd,
|
||||
size,
|
||||
xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||
)?
|
||||
.unwrap()
|
||||
};
|
||||
let xkb_state = xkb::State::new(&xkb_keymap);
|
||||
let mut me = KbState {
|
||||
xkb_context,
|
||||
xkb_keymap,
|
||||
xkb_state,
|
||||
xkb_compose_table: None,
|
||||
xkb_compose_state: None,
|
||||
mods_state: ModifiersState::new(),
|
||||
};
|
||||
|
||||
me.init_compose();
|
||||
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
pub(crate) fn init_compose(&mut self) {
|
||||
let locale = env::var_os("LC_ALL")
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LC_CTYPE"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LANG"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.unwrap_or_else(|| "C".into());
|
||||
|
||||
let compose_table = match xkb::compose::Table::new_from_locale(
|
||||
&self.xkb_context,
|
||||
&locale,
|
||||
xkb::compose::COMPILE_NO_FLAGS,
|
||||
) {
|
||||
Ok(t) => t,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let compose_state = xkb::compose::State::new(&compose_table, xkb::compose::STATE_NO_FLAGS);
|
||||
|
||||
self.xkb_compose_table = Some(compose_table);
|
||||
self.xkb_compose_state = Some(compose_state);
|
||||
}
|
||||
|
||||
pub(crate) fn key_repeats(&mut self, keycode: xkb::Keycode) -> bool {
|
||||
self.xkb_keymap.key_repeats(keycode)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn mods_state(&self) -> ModifiersState {
|
||||
self.mods_state
|
||||
}
|
||||
}
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
mod buffer;
|
||||
mod color;
|
||||
mod draw;
|
||||
mod fonts;
|
||||
mod keyboard;
|
||||
mod utils;
|
||||
mod widgets;
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{
|
||||
wl_buffer, wl_compositor, wl_keyboard, wl_registry, wl_seat, wl_shm, wl_shm_pool,
|
||||
wl_surface,
|
||||
},
|
||||
Connection, Dispatch, QueueHandle, WEnum, WaylandSource,
|
||||
};
|
||||
|
||||
use wayland_protocols::xdg::{
|
||||
activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1},
|
||||
shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base},
|
||||
};
|
||||
|
||||
use calloop::{
|
||||
timer::{TimeoutAction, Timer},
|
||||
EventLoop,
|
||||
};
|
||||
use chrono::{Duration, Local, Timelike};
|
||||
|
||||
use buffer::{BufferManager, BufferView};
|
||||
use color::Color;
|
||||
use fonts::FontMap;
|
||||
use keyboard::Keyboard;
|
||||
use widgets::{
|
||||
Battery, Clock, Date, Geometry, HorizontalLayout, IndexedLayout, Interface, Layout, Line,
|
||||
Margin, VerticalLayout, Widget, WidgetUpdater,
|
||||
};
|
||||
|
||||
use std::{cell::RefCell, env, mem, rc::Rc, thread};
|
||||
|
||||
enum MaybeFontMap {
|
||||
Waiting(thread::JoinHandle<FontMap>),
|
||||
Ready(Rc<RefCell<FontMap>>),
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl MaybeFontMap {
|
||||
fn unwrap(&self) -> Rc<RefCell<FontMap>> {
|
||||
match self {
|
||||
MaybeFontMap::Ready(f) => f.clone(),
|
||||
_ => panic!("fontmap not yet ready"),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve(&mut self) {
|
||||
if matches!(self, MaybeFontMap::Waiting(_)) {
|
||||
let s = mem::replace(self, MaybeFontMap::Invalid);
|
||||
match s {
|
||||
MaybeFontMap::Waiting(handle) => {
|
||||
*self = MaybeFontMap::Ready(Rc::new(RefCell::new(handle.join().unwrap())));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
running: bool,
|
||||
dirty: bool,
|
||||
base_surface: Option<wl_surface::WlSurface>,
|
||||
wm_base: Option<xdg_wm_base::XdgWmBase>,
|
||||
xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>,
|
||||
wl_shm: Option<wl_shm::WlShm>,
|
||||
xdg_activation: Option<xdg_activation_v1::XdgActivationV1>,
|
||||
configured: bool,
|
||||
dimensions: (i32, i32),
|
||||
bufmgr: BufferManager,
|
||||
widgets: Vec<Box<dyn Widget>>,
|
||||
keyboard: Keyboard,
|
||||
fonts: MaybeFontMap,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn add_buffer(&mut self, qh: &QueueHandle<Self>) {
|
||||
self.bufmgr.add_buffer(
|
||||
self.wl_shm.as_ref().expect("missing wl_shm"),
|
||||
self.dimensions,
|
||||
qh,
|
||||
);
|
||||
}
|
||||
|
||||
fn activate(&mut self, qh: &QueueHandle<Self>) {
|
||||
let key = match env::var("XDG_ACTIVATION_TOKEN") {
|
||||
Ok(token) => token,
|
||||
Err(_) => return,
|
||||
};
|
||||
match (&self.base_surface, &self.xdg_activation) {
|
||||
(Some(surface), Some(activation)) => {
|
||||
activation.activate(key, surface);
|
||||
let activation_token = activation.get_activation_token(qh, ());
|
||||
activation_token.commit();
|
||||
env::remove_var("XDG_ACTIVATION_TOKEN");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetUpdater for State {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
) -> Geometry {
|
||||
self.widgets[idx].geometry_update(fonts, geometry)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let font_thread = thread::spawn(move || {
|
||||
let mut fm = FontMap::new();
|
||||
// Hard-coding font paths make things a lot faster
|
||||
fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 96.);
|
||||
fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 40.);
|
||||
fm.queue_font_path("sans", "/usr/share/fonts/noto/NotoSans-Regular.ttf", 24.);
|
||||
fm.queue_font_path(
|
||||
"monospace",
|
||||
"/usr/share/fonts/noto/NotoSansMono-Regular.ttf",
|
||||
32.,
|
||||
);
|
||||
fm.load_fonts();
|
||||
fm
|
||||
});
|
||||
let conn = Connection::connect_to_env().unwrap();
|
||||
|
||||
let event_queue = conn.new_event_queue();
|
||||
let qhandle: QueueHandle<State> = event_queue.handle();
|
||||
|
||||
let display = conn.display();
|
||||
display.get_registry(&qhandle, ());
|
||||
|
||||
let bufmgr = BufferManager {
|
||||
buffers: Vec::new(),
|
||||
};
|
||||
|
||||
let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap();
|
||||
|
||||
let clock = Box::new(Clock::new());
|
||||
let date = Box::new(Date::new());
|
||||
let line = Box::new(Line::new());
|
||||
let launcher = Box::new(Interface::new());
|
||||
let battery = Box::new(Battery::new(ping_sender));
|
||||
|
||||
let mut event_loop: EventLoop<State> =
|
||||
EventLoop::try_new().expect("Failed to initialize the event loop!");
|
||||
|
||||
let handle = event_loop.handle();
|
||||
handle
|
||||
.insert_source(
|
||||
WaylandSource::new(event_queue).expect("Could not create WaylandSource!"),
|
||||
|_event, queue, mut state| queue.dispatch_pending(&mut state),
|
||||
)
|
||||
.expect("Failed to insert event source!");
|
||||
|
||||
handle
|
||||
.insert_source(ping_source, |(), &mut (), state| {
|
||||
state.widgets[2].set_dirty(true);
|
||||
state.dirty = true;
|
||||
})
|
||||
.expect("Failed to insert ping source!");
|
||||
|
||||
let clock_source = Timer::from_duration(std::time::Duration::from_secs(1));
|
||||
handle
|
||||
.insert_source(clock_source, |_event, _metadata, state| {
|
||||
let now = Local::now().naive_local();
|
||||
let target = (now + Duration::seconds(60))
|
||||
.with_second(0)
|
||||
.unwrap()
|
||||
.with_nanosecond(0)
|
||||
.unwrap();
|
||||
let d = target - now;
|
||||
state.widgets[0].set_dirty(true);
|
||||
state.widgets[1].set_dirty(true);
|
||||
state.dirty = true;
|
||||
|
||||
// The timer event source requires us to return a TimeoutAction to
|
||||
// specify if the timer should be rescheduled. In our case we just drop it.
|
||||
TimeoutAction::ToDuration(d.to_std().unwrap())
|
||||
})
|
||||
.expect("Failed to insert event source!");
|
||||
|
||||
let mut state = State {
|
||||
running: true,
|
||||
dirty: true,
|
||||
base_surface: None,
|
||||
wm_base: None,
|
||||
xdg_surface: None,
|
||||
wl_shm: None,
|
||||
xdg_activation: None,
|
||||
configured: false,
|
||||
dimensions: (320, 240),
|
||||
bufmgr: bufmgr,
|
||||
widgets: vec![clock, date, battery, line, launcher],
|
||||
keyboard: Keyboard::new(),
|
||||
fonts: MaybeFontMap::Waiting(font_thread),
|
||||
};
|
||||
|
||||
let mut damage = Vec::new();
|
||||
for _ in 0..state.widgets.len() {
|
||||
damage.push(Geometry::new());
|
||||
}
|
||||
|
||||
while state.running {
|
||||
event_loop
|
||||
.dispatch(None, &mut state)
|
||||
.expect("Could not dispatch event loop");
|
||||
|
||||
if !state.configured || !state.dirty {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut drew = false;
|
||||
let mut force = false;
|
||||
if state.bufmgr.buffers.len() == 0 {
|
||||
state.add_buffer(&qhandle);
|
||||
force = true;
|
||||
}
|
||||
|
||||
// We expect to see the usual shm optimization
|
||||
let buf = match state.bufmgr.next_buffer() {
|
||||
Some(b) => b,
|
||||
None => {
|
||||
// We are still waiting for our buffer
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
state.dirty = false;
|
||||
buf.acquire();
|
||||
let mut bufview = BufferView::new(
|
||||
&mut buf.mmap,
|
||||
(state.dimensions.0 as u32, state.dimensions.1 as u32),
|
||||
);
|
||||
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let surface = state.base_surface.as_ref().unwrap().clone();
|
||||
for (idx, widget) in state.widgets.iter_mut().enumerate() {
|
||||
if force || widget.get_dirty() {
|
||||
widget.set_dirty(false);
|
||||
drew = true;
|
||||
|
||||
let geo = widget.geometry();
|
||||
let old_damage = damage[idx];
|
||||
|
||||
if !force {
|
||||
bufview.subgeometry(old_damage).unwrap().memset(&bg);
|
||||
} else {
|
||||
bufview.subgeometry(geo).unwrap().memset(&bg);
|
||||
}
|
||||
|
||||
let mut subview = bufview.subgeometry(geo).unwrap();
|
||||
let new_damage = widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview);
|
||||
let combined_damage = new_damage.expand(old_damage);
|
||||
damage[idx] = new_damage;
|
||||
surface.damage_buffer(
|
||||
combined_damage.x as i32,
|
||||
combined_damage.y as i32,
|
||||
combined_damage.width as i32,
|
||||
combined_damage.height as i32,
|
||||
);
|
||||
//draw_box(&mut subview, &Color::new(1.0, 0.5, 0.0, 1.0), (geo.width, geo.height));
|
||||
}
|
||||
}
|
||||
if !drew {
|
||||
buf.release();
|
||||
continue;
|
||||
}
|
||||
|
||||
if force {
|
||||
surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF)
|
||||
}
|
||||
surface.attach(Some(&buf.buffer), 0, 0);
|
||||
surface.commit();
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_registry::Event::Global {
|
||||
name, interface, ..
|
||||
} = event
|
||||
{
|
||||
match &interface[..] {
|
||||
"wl_compositor" => {
|
||||
let compositor =
|
||||
registry.bind::<wl_compositor::WlCompositor, _, _>(name, 4, qh, ());
|
||||
let surface = compositor.create_surface(qh, ());
|
||||
state.base_surface = Some(surface);
|
||||
state.activate(qh);
|
||||
|
||||
if state.wm_base.is_some() && state.xdg_surface.is_none() {
|
||||
state.init_xdg_surface(qh);
|
||||
}
|
||||
}
|
||||
"wl_shm" => {
|
||||
state.wl_shm = Some(registry.bind::<wl_shm::WlShm, _, _>(name, 1, qh, ()));
|
||||
}
|
||||
"wl_seat" => {
|
||||
registry.bind::<wl_seat::WlSeat, _, _>(name, 1, qh, ());
|
||||
}
|
||||
"xdg_wm_base" => {
|
||||
let wm_base = registry.bind::<xdg_wm_base::XdgWmBase, _, _>(name, 1, qh, ());
|
||||
state.wm_base = Some(wm_base);
|
||||
|
||||
if state.base_surface.is_some() && state.xdg_surface.is_none() {
|
||||
state.init_xdg_surface(qh);
|
||||
}
|
||||
}
|
||||
"xdg_activation_v1" => {
|
||||
state.xdg_activation = Some(
|
||||
registry.bind::<xdg_activation_v1::XdgActivationV1, _, _>(name, 1, qh, ()),
|
||||
);
|
||||
state.activate(qh);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_compositor::WlCompositor, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &wl_compositor::WlCompositor,
|
||||
_: wl_compositor::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_surface::WlSurface, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &wl_surface::WlSurface,
|
||||
_: wl_surface::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_shm::WlShm, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &wl_shm::WlShm,
|
||||
_: wl_shm::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_shm_pool::WlShmPool, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &wl_shm_pool::WlShmPool,
|
||||
_: wl_shm_pool::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_buffer::WlBuffer, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
buffer: &wl_buffer::WlBuffer,
|
||||
_: wl_buffer::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
for buf in state.bufmgr.buffers.iter_mut() {
|
||||
if &buf.buffer == buffer {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn init_xdg_surface(&mut self, qh: &QueueHandle<State>) {
|
||||
let wm_base = self.wm_base.as_ref().unwrap();
|
||||
let base_surface = self.base_surface.as_ref().unwrap();
|
||||
|
||||
let xdg_surface = wm_base.get_xdg_surface(base_surface, qh, ());
|
||||
let toplevel = xdg_surface.get_toplevel(qh, ());
|
||||
toplevel.set_title("wldash".into());
|
||||
|
||||
base_surface.commit();
|
||||
|
||||
self.xdg_surface = Some((xdg_surface, toplevel));
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
wm_base: &xdg_wm_base::XdgWmBase,
|
||||
event: xdg_wm_base::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let xdg_wm_base::Event::Ping { serial } = event {
|
||||
wm_base.pong(serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<xdg_surface::XdgSurface, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
xdg_surface: &xdg_surface::XdgSurface,
|
||||
event: xdg_surface::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let xdg_surface::Event::Configure { serial, .. } = event {
|
||||
xdg_surface.ack_configure(serial);
|
||||
state.configured = true;
|
||||
state.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &xdg_toplevel::XdgToplevel,
|
||||
event: xdg_toplevel::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
xdg_toplevel::Event::Configure { width, height, .. } => {
|
||||
if (width, height) == (0, 0) {
|
||||
return;
|
||||
}
|
||||
if state.dimensions != (width, height) {
|
||||
state.bufmgr.clear_buffers();
|
||||
state.dimensions = (width, height);
|
||||
let mut layout = VerticalLayout {
|
||||
widgets: vec![
|
||||
Box::new(HorizontalLayout {
|
||||
widgets: vec![
|
||||
Box::new(IndexedLayout { widget_idx: 0 }),
|
||||
Box::new(Margin {
|
||||
widget: Box::new(IndexedLayout { widget_idx: 1 }),
|
||||
margin: (16, 8, 0, 0),
|
||||
}),
|
||||
Box::new(Margin {
|
||||
widget: Box::new(IndexedLayout { widget_idx: 2 }),
|
||||
margin: (16, 8, 8, 0),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Box::new(IndexedLayout { widget_idx: 3 }),
|
||||
Box::new(IndexedLayout { widget_idx: 4 }),
|
||||
],
|
||||
};
|
||||
|
||||
state.fonts.resolve();
|
||||
let fonts = state.fonts.unwrap();
|
||||
layout.geometry_update(
|
||||
&mut fonts.borrow_mut(),
|
||||
&Geometry {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
},
|
||||
state,
|
||||
);
|
||||
}
|
||||
}
|
||||
xdg_toplevel::Event::Close => {
|
||||
state.running = false;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_seat::WlSeat, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
seat: &wl_seat::WlSeat,
|
||||
event: wl_seat::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_seat::Event::Capabilities {
|
||||
capabilities: WEnum::Value(capabilities),
|
||||
} = event
|
||||
{
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) {
|
||||
seat.get_keyboard(qh, ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &wl_keyboard::WlKeyboard,
|
||||
event: wl_keyboard::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
wl_keyboard::Event::Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
state: kbstate,
|
||||
} => {
|
||||
if key == 1 {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
let k = state.keyboard.key(serial, time, key, kbstate);
|
||||
for widget in state.widgets.iter_mut() {
|
||||
widget.keyboard_input(&k);
|
||||
}
|
||||
state.dirty = true;
|
||||
}
|
||||
wl_keyboard::Event::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
..
|
||||
} => {
|
||||
state
|
||||
.keyboard
|
||||
.modifiers(mods_depressed, mods_latched, mods_locked, group);
|
||||
}
|
||||
wl_keyboard::Event::Keymap { format, fd, size } => {
|
||||
state.keyboard.keymap(format, fd, size);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<xdg_activation_v1::XdgActivationV1, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &xdg_activation_v1::XdgActivationV1,
|
||||
_: xdg_activation_v1::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &xdg_activation_token_v1::XdgActivationTokenV1,
|
||||
event: xdg_activation_token_v1::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
xdg_activation_token_v1::Event::Done { token } => {
|
||||
for widget in state.widgets.iter_mut() {
|
||||
widget.token_update(&token);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
use ini::{Ini, ParseOption};
|
||||
use itertools::Itertools;
|
||||
use std::cmp::Ordering;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io::Error as io_error;
|
||||
use std::io::ErrorKind;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash)]
|
||||
pub struct Desktop {
|
||||
pub entry_type: String,
|
||||
pub name: String,
|
||||
pub no_display: bool,
|
||||
pub hidden: bool,
|
||||
pub exec: Option<String>,
|
||||
pub url: Option<String>,
|
||||
pub term: bool,
|
||||
pub keywords: Vec<String>,
|
||||
}
|
||||
|
||||
impl Desktop {
|
||||
fn parse(f: &str) -> Result<Desktop, Box<dyn Error>> {
|
||||
let file = Ini::load_from_file_opt(
|
||||
f,
|
||||
ParseOption {
|
||||
enabled_quote: false,
|
||||
enabled_escape: false,
|
||||
},
|
||||
)?;
|
||||
match file.section(Some("Desktop Entry")) {
|
||||
Some(desktop) => Ok(Desktop {
|
||||
entry_type: desktop.get("Type").unwrap_or(&"".to_string()).to_string(),
|
||||
name: desktop.get("Name").unwrap_or(&"".to_string()).to_string(),
|
||||
term: desktop.get("Terminal").unwrap_or(&"".to_string()) == "true",
|
||||
no_display: desktop.get("NoDisplay").unwrap_or(&"".to_string()) == "true",
|
||||
hidden: desktop.get("Hidden").unwrap_or(&"".to_string()) == "true",
|
||||
exec: desktop.get("Exec").map(|x| x.to_string()),
|
||||
url: desktop.get("URL").map(|x| x.to_string()),
|
||||
keywords: desktop
|
||||
.get("Keywords")
|
||||
.map(|x| {
|
||||
x.split(';')
|
||||
.map(|y| y.trim().to_string())
|
||||
.filter(|z| z != "")
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![]),
|
||||
}),
|
||||
None => Err(Box::new(io_error::new(
|
||||
ErrorKind::NotFound,
|
||||
"no desktop entry in file",
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dir(d: &str) -> Result<Vec<Desktop>, Box<dyn Error>> {
|
||||
let mut files: Vec<Desktop> = Vec::with_capacity(16);
|
||||
for entry in WalkDir::new(d) {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if let Ok(d) = Desktop::parse(path.to_str().unwrap()) {
|
||||
files.push(d)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Desktop {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.name.cmp(&other.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Desktop {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Desktop {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_desktop_files() -> Vec<Desktop> {
|
||||
let home = env::var_os("HOME").unwrap().into_string().unwrap();
|
||||
|
||||
let xdg_data_home = match env::var_os("XDG_DATA_HOME") {
|
||||
Some(s) => s.into_string().unwrap(),
|
||||
None => format!("{}/.local/share", home),
|
||||
};
|
||||
let xdg_data_dirs = match env::var_os("XDG_DATA_DIRS") {
|
||||
Some(s) => s.into_string().unwrap(),
|
||||
None => "/usr/local/share:/usr/share".to_string(),
|
||||
};
|
||||
|
||||
std::iter::once(xdg_data_home.as_str())
|
||||
.chain(xdg_data_dirs.split(':'))
|
||||
.map(|p| Desktop::parse_dir(&format!("{}/applications", p)))
|
||||
.filter_map(Result::ok)
|
||||
.flatten()
|
||||
.filter(|d| {
|
||||
!d.hidden && !d.no_display && (d.entry_type == "Application" || d.entry_type == "Link")
|
||||
})
|
||||
.unique_by(|x| x.name.clone())
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod desktop;
|
||||
@@ -0,0 +1,98 @@
|
||||
use std::{
|
||||
cmp::min,
|
||||
default::Default,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
draw::{draw_bar, draw_box},
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
const BAR_FONT: &str = "sans";
|
||||
const BAR_SIZE: f32 = 24.;
|
||||
|
||||
pub trait BarWidgetImpl {
|
||||
fn get_dirty(&self) -> bool;
|
||||
fn set_dirty(&mut self, dirty: bool);
|
||||
fn update(&mut self);
|
||||
fn name(&self) -> &'static str;
|
||||
fn value(&self) -> f32;
|
||||
fn color(&self) -> Color;
|
||||
}
|
||||
|
||||
pub struct BarWidget {
|
||||
geometry: Geometry,
|
||||
inner_widget: Box<dyn BarWidgetImpl>,
|
||||
}
|
||||
|
||||
impl BarWidget {
|
||||
pub fn new(inner_widget: Box<dyn BarWidgetImpl>) -> BarWidget {
|
||||
BarWidget {
|
||||
geometry: Default::default(),
|
||||
inner_widget,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for BarWidget {
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.inner_widget.get_dirty()
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.inner_widget.set_dirty(dirty)
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let font = fonts.get_font(BAR_FONT, BAR_SIZE);
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
font.auto_draw_text(view, &bg, &fg, self.inner_widget.name())
|
||||
.unwrap();
|
||||
let size = BAR_SIZE.ceil() as u32;
|
||||
let bar_offset = 4 * size;
|
||||
let val = self.inner_widget.value();
|
||||
|
||||
let c = self.inner_widget.color();
|
||||
draw_bar(
|
||||
&mut view.offset((bar_offset, 0)).unwrap(),
|
||||
&c,
|
||||
self.geometry.width - bar_offset,
|
||||
size,
|
||||
val,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
draw_box(
|
||||
&mut view.offset((bar_offset, 0)).unwrap(),
|
||||
&c,
|
||||
(self.geometry.width - bar_offset, size),
|
||||
)
|
||||
.unwrap();
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
let w = min(geometry.width, 768);
|
||||
let mut x = geometry.x;
|
||||
if geometry.width > w {
|
||||
x += geometry.width - w;
|
||||
}
|
||||
self.geometry = Geometry {
|
||||
x: x,
|
||||
y: geometry.y,
|
||||
width: w,
|
||||
height: BAR_SIZE.ceil() as u32,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
use std::{
|
||||
thread,
|
||||
time::Duration,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use calloop::ping::Ping;
|
||||
|
||||
use crate::{
|
||||
color::Color,
|
||||
widgets::bar_widget::{
|
||||
BarWidget,
|
||||
BarWidgetImpl,
|
||||
},
|
||||
};
|
||||
|
||||
enum UpowerBatteryState {
|
||||
Charging,
|
||||
Discharging,
|
||||
Empty,
|
||||
Full,
|
||||
NotCharging,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
struct InnerBattery {
|
||||
value: f32,
|
||||
state: UpowerBatteryState,
|
||||
}
|
||||
|
||||
fn start_monitor(inner: Arc<Mutex<InnerBattery>>, ping: Ping) {
|
||||
thread::spawn(move || {
|
||||
use dbus::blocking::LocalConnection;
|
||||
use dbus::blocking::stdintf::org_freedesktop_dbus::Properties;
|
||||
use dbus::blocking::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
|
||||
let conn = LocalConnection::new_system().unwrap();
|
||||
let device_path = "/org/freedesktop/UPower/devices/DisplayDevice";
|
||||
let proxy = conn.with_proxy("org.freedesktop.UPower", device_path, Duration::from_millis(500));
|
||||
|
||||
let capacity: f64 = proxy.get("org.freedesktop.UPower.Device", "Percentage").expect("unable to get property");
|
||||
let state: u32 = proxy.get("org.freedesktop.UPower.Device", "State").expect("unable to get state");
|
||||
|
||||
{
|
||||
let mut inner = inner.lock().unwrap();
|
||||
inner.value = capacity as f32 / 100.;
|
||||
inner.state = match state {
|
||||
1 => UpowerBatteryState::Charging,
|
||||
2 => UpowerBatteryState::Discharging,
|
||||
3 => UpowerBatteryState::Empty,
|
||||
4 => UpowerBatteryState::Full,
|
||||
5 => UpowerBatteryState::NotCharging,
|
||||
6 => UpowerBatteryState::Discharging,
|
||||
_ => UpowerBatteryState::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
proxy.match_signal(move |c: PropertiesPropertiesChanged, _: &LocalConnection, _: &dbus::Message| {
|
||||
if c.interface_name != "org.freedesktop.UPower.Device" {
|
||||
return true;
|
||||
}
|
||||
let mut inner = inner.lock().unwrap();
|
||||
for (key, value) in c.changed_properties {
|
||||
match key.as_str() {
|
||||
"State" => {
|
||||
inner.state = match value.0.as_u64().unwrap() {
|
||||
1 => UpowerBatteryState::Charging,
|
||||
2 => UpowerBatteryState::Discharging,
|
||||
3 => UpowerBatteryState::Empty,
|
||||
4 => UpowerBatteryState::Full,
|
||||
5 => UpowerBatteryState::NotCharging,
|
||||
6 => UpowerBatteryState::Discharging,
|
||||
_ => UpowerBatteryState::Unknown,
|
||||
};
|
||||
ping.ping();
|
||||
},
|
||||
"Percentage" => {
|
||||
inner.value = value.0.as_f64().unwrap() as f32 / 100.;
|
||||
ping.ping();
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
true
|
||||
}).unwrap();
|
||||
|
||||
loop { conn.process(Duration::from_millis(60000)).unwrap(); }
|
||||
});
|
||||
}
|
||||
|
||||
pub struct Battery {
|
||||
inner: Arc<Mutex<InnerBattery>>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Battery {
|
||||
pub fn new(ping: Ping) -> BarWidget {
|
||||
let battery = Battery{
|
||||
inner: Arc::new(Mutex::new(InnerBattery{
|
||||
value: 0.,
|
||||
state: UpowerBatteryState::Unknown,
|
||||
})),
|
||||
dirty: false,
|
||||
};
|
||||
start_monitor(battery.inner.clone(), ping);
|
||||
BarWidget::new(Box::new(battery))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl BarWidgetImpl for Battery {
|
||||
fn update(&mut self) {}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
"battery"
|
||||
}
|
||||
fn value(&self) -> f32 {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.value
|
||||
}
|
||||
fn color(&self) -> Color {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
match inner.state {
|
||||
UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => {
|
||||
if inner.value > 0.1 {
|
||||
Color::new(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::new(1.0, 0.5, 0.0, 1.0)
|
||||
}
|
||||
}
|
||||
UpowerBatteryState::Charging | UpowerBatteryState::Full => {
|
||||
Color::new(0.5, 1.0, 0.5, 1.0)
|
||||
}
|
||||
UpowerBatteryState::NotCharging | UpowerBatteryState::Empty => {
|
||||
Color::new(1.0, 0.5, 0.5, 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::default::Default;
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
use chrono::{Local, Timelike};
|
||||
|
||||
const CLOCK_FONT: &str = "sans";
|
||||
const CLOCK_SIZE: f32 = 96.;
|
||||
|
||||
pub struct Clock {
|
||||
dirty: bool,
|
||||
geometry: Geometry,
|
||||
digit_width: u32,
|
||||
colon_width: u32,
|
||||
}
|
||||
|
||||
impl<'a> Clock {
|
||||
pub fn new() -> Clock {
|
||||
Clock {
|
||||
dirty: true,
|
||||
geometry: Default::default(),
|
||||
digit_width: 0,
|
||||
colon_width: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Clock {
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty;
|
||||
}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let time = Local::now().naive_local();
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let digit = self.digit_width;
|
||||
let colon = self.colon_width;
|
||||
let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE);
|
||||
font.draw_text_fixed_width(
|
||||
view,
|
||||
&bg,
|
||||
&fg,
|
||||
&[digit, digit, colon, digit, digit],
|
||||
&format!("{:02}:{:02}", time.hour(), time.minute()),
|
||||
)
|
||||
.unwrap();
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
let font = fonts.get_font(CLOCK_FONT, CLOCK_SIZE);
|
||||
self.digit_width = font.auto_widest("0123456789").unwrap();
|
||||
self.colon_width = font.auto_widest(":").unwrap();
|
||||
self.geometry = Geometry {
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
width: self.digit_width * 4 + self.colon_width,
|
||||
height: CLOCK_SIZE.ceil() as u32,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use std::default::Default;
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
use chrono::{Datelike, Local};
|
||||
|
||||
const DATE_FONT: &str = "sans";
|
||||
const DATE_SIZE: f32 = 40.;
|
||||
|
||||
pub struct Date {
|
||||
dirty: bool,
|
||||
geometry: Geometry,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
pub fn new() -> Date {
|
||||
Date {
|
||||
dirty: true,
|
||||
geometry: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Date {
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty;
|
||||
}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let time = Local::now().naive_local();
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
let mut date_line = view.offset((0, 8)).unwrap();
|
||||
let font = fonts.get_font(DATE_FONT, DATE_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut date_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(
|
||||
"{}, {:02}/{:02}/{:4}",
|
||||
time.weekday(),
|
||||
time.day(),
|
||||
time.month(),
|
||||
time.year()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
self.geometry = Geometry {
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
width: 256,
|
||||
height: DATE_SIZE.ceil() as u32,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
use std::cmp::{min, Ordering};
|
||||
use std::collections::HashMap;
|
||||
use std::default::Default;
|
||||
use std::process::{exit, Command};
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
keyboard::{keysyms, KeyEvent},
|
||||
utils::desktop::{load_desktop_files, Desktop},
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||
use fuzzy_matcher::FuzzyMatcher;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wayland_client::{protocol::wl_keyboard, WEnum};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
const LAUNCHER_FONT: &str = "monospace";
|
||||
const LAUNCHER_SIZE: f32 = 32.;
|
||||
|
||||
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) {
|
||||
let fuzzy_matcher = SkimMatcherV2::default();
|
||||
if let Some(ma) = fuzzy_matcher.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 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.matches.insert(dtop, ma);
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(&self) -> Vec<Desktop> {
|
||||
let mut m = self
|
||||
.matches
|
||||
.iter()
|
||||
.map(|(key, ma)| (*ma, key.clone()))
|
||||
.collect::<Vec<(i64, Desktop)>>();
|
||||
|
||||
m.sort_by(|(ma1, d1), (ma2, d2)| {
|
||||
if ma1 > ma2 {
|
||||
Ordering::Less
|
||||
} else if ma1 < ma2 {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
d1.cmp(d2)
|
||||
}
|
||||
});
|
||||
|
||||
m.into_iter().map(|(_, x)| x).collect()
|
||||
}
|
||||
}
|
||||
|
||||
enum PromptMode {
|
||||
Normal,
|
||||
Shell,
|
||||
Calc,
|
||||
}
|
||||
|
||||
struct Prompt {
|
||||
input: String,
|
||||
mode: PromptMode,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
impl Prompt {
|
||||
fn new() -> Prompt {
|
||||
Prompt {
|
||||
input: String::new(),
|
||||
mode: PromptMode::Normal,
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn move_cursor(&mut self, distance: isize) {
|
||||
let new_cursor = self.cursor as isize + distance;
|
||||
if new_cursor < 0 {
|
||||
self.cursor = 0;
|
||||
return;
|
||||
} else if new_cursor > self.input.len() as isize {
|
||||
self.cursor = self.input.len()
|
||||
} else {
|
||||
self.cursor = new_cursor as usize;
|
||||
}
|
||||
}
|
||||
|
||||
fn append(&mut self, v: &str) {
|
||||
if self.input.len() == 0 {
|
||||
match v {
|
||||
"!" => {
|
||||
self.mode = PromptMode::Shell;
|
||||
return;
|
||||
},
|
||||
"=" => {
|
||||
self.mode = PromptMode::Calc;
|
||||
return;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
let indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect();
|
||||
if self.cursor == indices.len() {
|
||||
self.input += &v;
|
||||
} else {
|
||||
let index_at = indices[self.cursor].0;
|
||||
self.input.insert_str(index_at, &v);
|
||||
}
|
||||
self.cursor += 1;
|
||||
}
|
||||
|
||||
fn backspace(&mut self) {
|
||||
let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect();
|
||||
if !indices.is_empty() && self.cursor > 0 {
|
||||
self.cursor -= 1;
|
||||
indices.remove(self.cursor);
|
||||
self.input = indices.iter().fold("".into(), |acc, el| acc + el.1);
|
||||
} else {
|
||||
self.mode = PromptMode::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self) {
|
||||
let mut indices: Vec<(usize, &str)> = self.input.grapheme_indices(true).collect();
|
||||
if !indices.is_empty() && self.cursor < indices.len() {
|
||||
indices.remove(self.cursor);
|
||||
self.input = indices.iter().fold("".into(), |acc, el| acc + el.1);
|
||||
} else {
|
||||
self.mode = PromptMode::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
fn home(&mut self) {
|
||||
self.cursor = 0;
|
||||
}
|
||||
|
||||
fn end(&mut self) {
|
||||
self.cursor = self.input.len();
|
||||
}
|
||||
}
|
||||
|
||||
trait InterfaceWidget {
|
||||
fn trigger(&mut self, intf: &InnerInterface);
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry;
|
||||
fn update(&mut self, intf: &InnerInterface);
|
||||
}
|
||||
|
||||
struct Launcher {
|
||||
next_token: Option<String>,
|
||||
options: Arc<Mutex<Vec<Desktop>>>,
|
||||
matches: Vec<Desktop>,
|
||||
}
|
||||
|
||||
impl Launcher {
|
||||
fn new() -> Launcher {
|
||||
let options = Arc::new(Mutex::new(Vec::new()));
|
||||
{
|
||||
let options = Arc::clone(&options);
|
||||
thread::spawn(move || {
|
||||
let mut options = options.lock().unwrap();
|
||||
*options = load_desktop_files();
|
||||
});
|
||||
}
|
||||
|
||||
Launcher {
|
||||
next_token: None,
|
||||
options: options,
|
||||
matches: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn exec(&self, args: Vec<String>) {
|
||||
let mut cmd = Command::new(args[0].clone());
|
||||
if let Some(token) = &self.next_token {
|
||||
cmd.env("XDG_ACTIVATION_TOKEN", token);
|
||||
}
|
||||
cmd.args(&args[1..]).spawn().unwrap();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
impl InterfaceWidget for Launcher {
|
||||
fn trigger(&mut self, intf: &InnerInterface) {
|
||||
if self.matches.len() > intf.selection {
|
||||
let d = &self.matches[intf.selection];
|
||||
if let Some(exec) = &d.exec {
|
||||
let exec = exec
|
||||
.replace("%f", "")
|
||||
.replace("%F", "")
|
||||
.replace("%u", "")
|
||||
.replace("%U", "");
|
||||
|
||||
let lexed = shlex::split(&exec).unwrap();
|
||||
if !lexed.is_empty() {
|
||||
self.exec(lexed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, intf: &InnerInterface) {
|
||||
let mut matcher = Matcher::new();
|
||||
let options = self.options.lock().unwrap();
|
||||
for desktop in options.iter() {
|
||||
matcher.try_match(
|
||||
desktop.clone(),
|
||||
&desktop.name.to_lowercase(),
|
||||
&intf.prompt.input.to_lowercase(),
|
||||
1.0,
|
||||
);
|
||||
for keyword in desktop.keywords.iter() {
|
||||
matcher.try_match(
|
||||
desktop.clone(),
|
||||
&keyword.to_lowercase(),
|
||||
&intf.prompt.input.to_lowercase(),
|
||||
0.5,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.matches = matcher.matches();
|
||||
}
|
||||
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let line_height = LAUNCHER_SIZE.ceil() as u32;
|
||||
|
||||
// Draw line
|
||||
let mut prompt_offset = intf.geometry.height - line_height;
|
||||
let mut prompt_line = view.offset((0, prompt_offset)).unwrap();
|
||||
let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap();
|
||||
prompt_border.memset(&fg);
|
||||
|
||||
// Draw prompt
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text_with_cursor(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(" {}", &intf.prompt.input),
|
||||
intf.prompt.cursor + 3,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
">",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Draw entries
|
||||
let dimfg = Color::new(0.5, 0.5, 0.5, 1.0);
|
||||
prompt_offset -= 16;
|
||||
|
||||
for (idx, m) in self.matches.iter().enumerate() {
|
||||
if prompt_offset < line_height {
|
||||
break;
|
||||
}
|
||||
prompt_offset -= line_height;
|
||||
let mut line = view.offset((0, prompt_offset)).unwrap();
|
||||
|
||||
if idx == intf.selection {
|
||||
let fuzzy_matcher = SkimMatcherV2::default();
|
||||
let (_, indices) = fuzzy_matcher
|
||||
.fuzzy_indices(&m.name.to_lowercase(), &intf.prompt.input.to_lowercase())
|
||||
.unwrap_or((0, vec![]));
|
||||
|
||||
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, 0.65, 0., 1.0));
|
||||
} else {
|
||||
colors.push(Color::new(0.75, 0.75, 0.75, 1.0));
|
||||
}
|
||||
}
|
||||
font.auto_draw_text_individual_colors(&mut line, &bg, &colors, &m.name)
|
||||
.unwrap();
|
||||
} else {
|
||||
font.auto_draw_text(&mut line, &bg, &dimfg, &m.name)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let content_height = min(intf.geometry.height, (self.matches.len() + 1) as u32 * line_height + 16);
|
||||
Geometry{
|
||||
x: intf.geometry.x,
|
||||
y: intf.geometry.y + intf.geometry.height - content_height,
|
||||
width: intf.geometry.width,
|
||||
height: content_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Shell ();
|
||||
|
||||
impl InterfaceWidget for Shell {
|
||||
fn trigger(&mut self, intf: &InnerInterface) {
|
||||
let args = vec![
|
||||
"/bin/sh".to_string(),
|
||||
"-c".to_string(),
|
||||
intf.prompt.input.clone(),
|
||||
];
|
||||
Command::new(args[0].clone())
|
||||
.args(&args[1..])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
fn update(&mut self, _intf: &InnerInterface) {}
|
||||
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let line_height = LAUNCHER_SIZE.ceil() as u32;
|
||||
|
||||
// Draw line
|
||||
let prompt_offset = intf.geometry.height - line_height;
|
||||
let mut prompt_line = view.offset((0, prompt_offset)).unwrap();
|
||||
let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap();
|
||||
prompt_border.memset(&fg);
|
||||
|
||||
// Draw prompt
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text_with_cursor(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(" {}", &intf.prompt.input),
|
||||
intf.prompt.cursor + 3,
|
||||
)
|
||||
.unwrap();
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&Color::new(1., 0.75, 0.5, 1.),
|
||||
"!",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Geometry{
|
||||
x: intf.geometry.x,
|
||||
y: intf.geometry.y + intf.geometry.height - line_height,
|
||||
width: intf.geometry.width,
|
||||
height: line_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Calc {
|
||||
old: Vec<String>,
|
||||
result: Option<String>,
|
||||
}
|
||||
|
||||
impl InterfaceWidget for Calc {
|
||||
fn trigger(&mut self, intf: &InnerInterface) {
|
||||
if let Some(res) = &self.result {
|
||||
self.old.push(format!("{} = {}", &intf.prompt.input, res));
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, intf: &InnerInterface) {
|
||||
let res = rcalc_lib::parse::eval(&intf.prompt.input, &mut rcalc_lib::parse::CalcState::new())
|
||||
.map(|x| format!("{}", x))
|
||||
.map_err(|x| format!("{}", x));
|
||||
self.result = match res {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
};
|
||||
}
|
||||
|
||||
fn draw(&mut self, intf: &InnerInterface, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
let fg = Color::new(1., 1., 1., 1.);
|
||||
let bg = Color::new(0., 0., 0., 1.);
|
||||
|
||||
let line_height = LAUNCHER_SIZE.ceil() as u32;
|
||||
|
||||
// Draw line
|
||||
let mut prompt_offset = intf.geometry.height - (LAUNCHER_SIZE.ceil() as u32);
|
||||
let mut prompt_line = view.offset((0, prompt_offset)).unwrap();
|
||||
let mut prompt_border = prompt_line.limit((intf.geometry.width, 1)).unwrap();
|
||||
prompt_border.memset(&fg);
|
||||
|
||||
// Draw prompt
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
let res_off = font.auto_draw_text_with_cursor(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&fg,
|
||||
&format!(" {} ", &intf.prompt.input),
|
||||
intf.prompt.cursor + 3,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let font = fonts.get_font(LAUNCHER_FONT, LAUNCHER_SIZE);
|
||||
font.auto_draw_text(
|
||||
&mut prompt_line,
|
||||
&bg,
|
||||
&Color::new(1., 0.75, 0.5, 1.),
|
||||
"=",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(res) = &self.result {
|
||||
let resfg = Color::new(1.0, 0.65, 0., 1.0);
|
||||
let mut result_line = view.offset((res_off.0, prompt_offset)).unwrap();
|
||||
font.auto_draw_text(&mut result_line, &bg, &resfg, &format!(" = {}", &res)).unwrap();
|
||||
}
|
||||
|
||||
for m in self.old.iter().rev() {
|
||||
let dimfg = Color::new(0.5, 0.5, 0.5, 1.0);
|
||||
if prompt_offset < LAUNCHER_SIZE.ceil() as u32 {
|
||||
break;
|
||||
}
|
||||
prompt_offset -= LAUNCHER_SIZE.ceil() as u32;
|
||||
let mut result_line = view.offset((0, prompt_offset)).unwrap();
|
||||
font.auto_draw_text(&mut result_line, &bg, &dimfg, &m).unwrap();
|
||||
}
|
||||
|
||||
let content_height = min(intf.geometry.height, (self.old.len() + 1) as u32 * line_height + 16);
|
||||
Geometry{
|
||||
x: intf.geometry.x,
|
||||
y: intf.geometry.y + intf.geometry.height - content_height,
|
||||
width: intf.geometry.width,
|
||||
height: content_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerInterface {
|
||||
dirty: bool,
|
||||
geometry: Geometry,
|
||||
selection: usize,
|
||||
prompt: Prompt,
|
||||
}
|
||||
|
||||
pub struct Interface {
|
||||
launcher: Launcher,
|
||||
shell: Shell,
|
||||
calc: Calc,
|
||||
inner: InnerInterface,
|
||||
}
|
||||
|
||||
impl Interface {
|
||||
pub fn new() -> Interface {
|
||||
Interface{
|
||||
launcher: Launcher::new(),
|
||||
shell: Shell{},
|
||||
calc: Calc{
|
||||
old: Vec::new(),
|
||||
result: None,
|
||||
},
|
||||
inner: InnerInterface{
|
||||
dirty: false,
|
||||
geometry: Default::default(),
|
||||
selection: 0,
|
||||
prompt: Prompt::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Interface {
|
||||
fn set_dirty(&mut self, dirty: bool) {
|
||||
self.inner.dirty = dirty;
|
||||
}
|
||||
fn get_dirty(&self) -> bool {
|
||||
self.inner.dirty
|
||||
}
|
||||
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.inner.geometry
|
||||
}
|
||||
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
match self.inner.prompt.mode {
|
||||
PromptMode::Shell => self.shell.draw(&self.inner, fonts, view),
|
||||
PromptMode::Normal => self.launcher.draw(&self.inner, fonts, view),
|
||||
PromptMode::Calc => self.calc.draw(&self.inner, fonts, view),
|
||||
}
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
self.inner.geometry = geometry.clone();
|
||||
self.inner.geometry
|
||||
}
|
||||
|
||||
fn keyboard_input(&mut self, event: &KeyEvent) {
|
||||
if event.state != WEnum::Value(wl_keyboard::KeyState::Pressed) {
|
||||
return;
|
||||
}
|
||||
self.inner.dirty = true;
|
||||
let widget = match self.inner.prompt.mode {
|
||||
PromptMode::Shell => &mut self.shell as &mut dyn InterfaceWidget,
|
||||
PromptMode::Normal => &mut self.launcher as &mut dyn InterfaceWidget,
|
||||
PromptMode::Calc => &mut self.calc as &mut dyn InterfaceWidget,
|
||||
};
|
||||
match event.keysym {
|
||||
keysyms::XKB_KEY_a if event.modifiers.ctrl => self.inner.prompt.home(),
|
||||
keysyms::XKB_KEY_e if event.modifiers.ctrl => self.inner.prompt.end(),
|
||||
keysyms::XKB_KEY_Home => self.inner.prompt.home(),
|
||||
keysyms::XKB_KEY_End => self.inner.prompt.end(),
|
||||
keysyms::XKB_KEY_BackSpace => {
|
||||
self.inner.prompt.backspace();
|
||||
self.inner.selection = 0;
|
||||
widget.update(&self.inner);
|
||||
}
|
||||
keysyms::XKB_KEY_Delete => {
|
||||
self.inner.prompt.delete();
|
||||
self.inner.selection = 0;
|
||||
widget.update(&self.inner);
|
||||
}
|
||||
keysyms::XKB_KEY_Return => widget.trigger(&self.inner),
|
||||
keysyms::XKB_KEY_Left => self.inner.prompt.move_cursor(-1),
|
||||
keysyms::XKB_KEY_Right => self.inner.prompt.move_cursor(1),
|
||||
keysyms::XKB_KEY_Up => {
|
||||
self.inner.selection += 1;
|
||||
}
|
||||
keysyms::XKB_KEY_Down => {
|
||||
if self.inner.selection > 0 {
|
||||
self.inner.selection -= 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(utf8) = &event.utf8 {
|
||||
self.inner.prompt.append(&utf8);
|
||||
self.inner.selection = 0;
|
||||
widget.update(&self.inner);
|
||||
} else {
|
||||
self.inner.dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn token_update(&mut self, token: &str) {
|
||||
self.launcher.next_token = Some(token.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use std::default::Default;
|
||||
|
||||
use crate::{
|
||||
buffer::BufferView,
|
||||
color::Color,
|
||||
fonts::FontMap,
|
||||
widgets::{Geometry, Widget},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Line {
|
||||
geometry: Geometry,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new() -> Line {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Line {
|
||||
fn geometry(&self) -> Geometry {
|
||||
self.geometry
|
||||
}
|
||||
fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
|
||||
view.memset(&Color::new(1., 1., 1., 1.));
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
|
||||
self.geometry = Geometry {
|
||||
x: geometry.x,
|
||||
y: geometry.y,
|
||||
width: geometry.width,
|
||||
height: 1,
|
||||
};
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
mod bar_widget;
|
||||
mod battery;
|
||||
mod clock;
|
||||
mod date;
|
||||
mod launcher;
|
||||
mod line;
|
||||
mod widget;
|
||||
|
||||
pub use battery::Battery;
|
||||
pub use clock::Clock;
|
||||
pub use date::Date;
|
||||
pub use launcher::Interface;
|
||||
pub use line::Line;
|
||||
pub use widget::{
|
||||
Geometry, HorizontalLayout, IndexedLayout, Layout, Margin, VerticalLayout, Widget,
|
||||
WidgetUpdater,
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
use std::cmp::{min, max};
|
||||
use std::fmt;
|
||||
|
||||
use crate::{buffer::BufferView, fonts::FontMap, keyboard::KeyEvent};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct Geometry {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for Geometry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"(x: {}, y: {}, width: {}, height: {})",
|
||||
self.x, self.y, self.width, self.height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Geometry {
|
||||
pub fn new() -> Geometry {
|
||||
Geometry{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand(&self, other: Geometry) -> Geometry {
|
||||
let x = min(self.x, other.x);
|
||||
let y = min(self.y, other.y);
|
||||
Geometry {
|
||||
x, y,
|
||||
width: max(self.width + self.x, other.width + other.x) - x,
|
||||
height: max(self.height + self.y, other.height + other.height) - y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Widget {
|
||||
fn get_dirty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn set_dirty(&mut self, _dirty: bool) {}
|
||||
fn geometry(&self) -> Geometry;
|
||||
fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry;
|
||||
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry;
|
||||
fn keyboard_input(&mut self, _event: &KeyEvent) {}
|
||||
fn token_update(&mut self, _token: &str) {}
|
||||
}
|
||||
|
||||
pub trait Layout<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry;
|
||||
}
|
||||
pub struct HorizontalLayout<U> {
|
||||
pub widgets: Vec<Box<dyn Layout<U>>>,
|
||||
}
|
||||
|
||||
impl<U> Layout<U> for HorizontalLayout<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
let mut geo = geometry.clone();
|
||||
let mut max_width = 0;
|
||||
let mut max_height = 0;
|
||||
for w in self.widgets.iter_mut() {
|
||||
let result = w.geometry_update(fonts, &geo, user_data);
|
||||
geo.x = result.x + result.width;
|
||||
geo.width -= result.width;
|
||||
max_width += result.width;
|
||||
max_height = max(result.height, max_height);
|
||||
}
|
||||
geo.width = max_width;
|
||||
geo.height = max_height;
|
||||
geo
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VerticalLayout<U> {
|
||||
pub widgets: Vec<Box<dyn Layout<U>>>,
|
||||
}
|
||||
|
||||
impl<U> Layout<U> for VerticalLayout<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
let mut geo = geometry.clone();
|
||||
let mut max_width = 0;
|
||||
let mut max_height = 0;
|
||||
for w in self.widgets.iter_mut() {
|
||||
let result = w.geometry_update(fonts, &geo, user_data);
|
||||
geo.y = result.y + result.height;
|
||||
geo.height -= result.height;
|
||||
max_width = max(result.width, max_width);
|
||||
max_height += result.height;
|
||||
}
|
||||
geo.width = max_width;
|
||||
geo.height = max_height;
|
||||
geo
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WidgetUpdater {
|
||||
fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry)
|
||||
-> Geometry;
|
||||
}
|
||||
|
||||
pub struct IndexedLayout {
|
||||
pub widget_idx: usize,
|
||||
}
|
||||
|
||||
impl<U: WidgetUpdater> Layout<U> for IndexedLayout {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
user_data.geometry_update(self.widget_idx, fonts, geometry)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Margin<U> {
|
||||
pub widget: Box<dyn Layout<U>>,
|
||||
pub margin: (u32, u32, u32, u32),
|
||||
}
|
||||
|
||||
impl<U> Layout<U> for Margin<U> {
|
||||
fn geometry_update(
|
||||
&mut self,
|
||||
fonts: &mut FontMap,
|
||||
geometry: &Geometry,
|
||||
user_data: &mut U,
|
||||
) -> Geometry {
|
||||
let geo = Geometry{
|
||||
x: geometry.x + self.margin.0,
|
||||
y: geometry.y + self.margin.1,
|
||||
width: geometry.width - self.margin.0 - self.margin.2,
|
||||
height: geometry.height - self.margin.1 - self.margin.3,
|
||||
};
|
||||
|
||||
let out = self.widget.geometry_update(fonts, &geo, user_data);
|
||||
Geometry {
|
||||
x: out.x - self.margin.0,
|
||||
y: out.y - self.margin.1,
|
||||
width: out.width + self.margin.0 + self.margin.2,
|
||||
height: out.height + self.margin.1 + self.margin.3,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user