Initial commit

This commit is contained in:
Kenny Levinsen
2023-01-11 00:28:54 +01:00
commit 8e80c0a69c
21 changed files with 4262 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+1161
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -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
View File
@@ -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(())
}
}
+71
View File
@@ -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
View File
@@ -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
View File
@@ -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")
}
}
+19
View File
@@ -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 */
+136
View File
@@ -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);
}
}
}
+158
View File
@@ -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
View File
@@ -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);
}
}
_ => (),
}
}
}
+112
View File
@@ -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()
}
+1
View File
@@ -0,0 +1 @@
pub mod desktop;
+98
View File
@@ -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
}
}
+144
View File
@@ -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)
}
}
}
}
+76
View File
@@ -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
}
}
+72
View File
@@ -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
}
}
+566
View File
@@ -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());
}
}
+39
View File
@@ -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
}
}
+17
View File
@@ -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,
};
+166
View File
@@ -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,
}
}
}