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