Merge new implementation

This implementation is a rewrite of wldash, implemented from scratch.
This also means that it is an unrelated history, hence the history now
having two initial commits.
This commit is contained in:
Kenny Levinsen
2025-02-27 16:41:33 +01:00
45 changed files with 5500 additions and 5854 deletions
-1
View File
@@ -1,2 +1 @@
/target /target
**/*.rs.bk
Generated
+546 -420
View File
File diff suppressed because it is too large Load Diff
+26 -36
View File
@@ -1,50 +1,40 @@
[package] [package]
name = "wldash" name = "wldash2"
version = "0.3.0" version = "0.1.0"
authors = ["Kenny Levinsen <kl@kl.wtf>"]
edition = "2021" edition = "2021"
[profile.release] [profile.release]
#debug = true
lto = "fat" lto = "fat"
panic = "abort" panic = "abort"
opt-level = "z"
[features]
default = ["alsa-widget", "pulseaudio-widget", "yaml-cfg", "json-cfg"]
damage_debug = []
yaml-cfg = ["serde_yaml"]
json-cfg = ["serde_json"]
alsa-widget = ["alsa"]
pulseaudio-widget = ["libpulse-binding"]
[dependencies] [dependencies]
byteorder = "1.4" wayland-client = { version = "0.30", features = ["calloop", "log"] }
bitflags = "1.3" wayland-protocols = { version = "0.30", features = ["client", "unstable", "staging"] }
dlib = "0.5" wayland-protocols-wlr = { version = "0.1", features = ["client"] }
rusttype = "^0.9" nix = "0.26" # 0.27 is breaking
chrono = "0.4"
nix = "0.26"
memmap = "0.7" memmap = "0.7"
os_pipe = "1.1" fontconfig = { version = "0.8.0", optional = true }
wayland-client = { version = "0.29"} rusttype = "0.9"
wayland-protocols = { version = "0.29", features = ["client", "unstable_protocols"] } # fontdue = "0.7"
dbus = "0.6" calloop = { version = "0.10" } # update breaks
chrono = "0.4"
walkdir = "2.4"
unicode-segmentation = "1.10"
fuzzy-matcher = "0.3" fuzzy-matcher = "0.3"
lazy_static = "1.4" shlex = "1.3"
xkbcommon = { version = "0.8", features = ["wayland"] }
rcalc_lib = "1.0" rcalc_lib = "1.0"
rust-ini = "0.18"
shlex = "1.1" #alsa = { version = "0.7", optional = true }
timerfd = "1.4.0" dbus = "0.9"
walkdir = "2.3" libpulse-binding = "2.28"
itertools = "0.10"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true } serde_yaml = "0.9"
serde_yaml = { version = "0.9", optional = true } simd-json = "0.13"
libpulse-binding = { version = "2.26", optional = true }
alsa = { version = "0.7.0", optional = true }
fontconfig = "0.6.0"
unicode-segmentation = "1.10.0"
-55
View File
@@ -1,55 +0,0 @@
{
"widget": {
"margin": {
"margins": [20,20,20,20],
"widget": {
"verticalLayout": [{
"horizontalLayout": [{
"margin": {
"margins": [0,88,0,32],
"widget": {
"verticalLayout": [
{"date": {"font_size": 64.0}},
{"clock": {"font_size": 256.0}}
]
}
}
},{
"verticalLayout": [{
"margin": {
"margins": [0,0,0,8],
"widget": {"battery": {"font_size": 24.0,"length": 600}}
}
},{
"margin": {
"margins": [0,0,0,8],
"widget": {"backlight": {"device": "intel_backlight", "font_size": 24.0,"length": 600}}
}
},{
"margin": {
"margins": [0,0,0,8],
"widget": {"pulseAudio": {"font_size": 24.0,"length": 600}}
}
}]
}
]},
{"calendar": {"font_size": 16.0,"sections": 3}},
{"launcher": {
"font_size": 32.0,
"length": 1200,
"app_opener": "swaymsg exec -- ",
"term_opener": "foot --",
"url_opener": "xdg-open "
}}
]}
}
},
"outputMode": "active",
"scale": 1,
"background": {
"red": 0,
"green": 0,
"blue": 0,
"opacity": 0.9
}
}
-50
View File
@@ -1,50 +0,0 @@
outputMode: active
scale: 1
background:
red: 0.0
green: 0.0
blue: 0.0
opacity: 0.9
widget:
margin:
margins: [20, 20, 20, 20]
widget:
verticalLayout:
- horizontalLayout:
- margin:
margins: [0, 88, 0, 32]
widget:
verticalLayout:
- date:
font_size: 64.0
- clock:
font_size: 256.0
- verticalLayout:
- margin:
margins: [0, 0, 0, 8]
widget:
battery:
font_size: 24.0
length: 600
- margin:
margins: [0, 0, 0, 8]
widget:
backlight:
device: "intel_backlight"
font_size: 24.0
length: 600
- margin:
margins: [0, 0, 0, 8]
widget:
pulseAudio:
font_size: 24.0
length: 600
- calendar:
font_size: 16.0
sections: 3
- launcher:
font_size: 32.0
length: 1200
app_opener: "swaymsg exec --"
term_opener: "foot --"
url_opener: "xdg-open "
-600
View File
@@ -1,600 +0,0 @@
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use chrono::{Duration, Local, NaiveDateTime};
use crate::keyboard::{keysyms, map_keyboard, Event as KbEvent, KeyState, ModifiersState};
use wayland_client::protocol::{wl_compositor, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface};
use wayland_client::{Display, EventQueue, GlobalEvent, GlobalManager, Main};
use wayland_protocols::wlr::unstable::layer_shell::v1::client::{
zwlr_layer_shell_v1, zwlr_layer_surface_v1,
};
use crate::buffer::Buffer;
use crate::color::Color;
use crate::widget::{DrawContext, WaitContext, Widget};
use crate::cmd::Cmd;
use crate::doublemempool::DoubleMemPool;
#[derive(Debug)]
pub enum OutputMode {
Active,
All,
}
struct AppInner {
compositor: Option<Main<wl_compositor::WlCompositor>>,
surfaces: Vec<Main<wl_surface::WlSurface>>,
shell_surfaces: Vec<Main<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>>,
configured_surfaces: Arc<Mutex<usize>>,
outputs: Vec<(u32, Main<wl_output::WlOutput>)>,
shell: Option<Main<zwlr_layer_shell_v1::ZwlrLayerShellV1>>,
draw_tx: Sender<Cmd>,
output_mode: OutputMode,
visible: bool,
scale: u32,
}
impl AppInner {
fn new(tx: Sender<Cmd>, output_mode: OutputMode, scale: u32) -> AppInner {
AppInner {
compositor: None,
surfaces: Vec::new(),
shell_surfaces: Vec::new(),
configured_surfaces: Arc::new(Mutex::new(0)),
outputs: Vec::new(),
shell: None,
draw_tx: tx,
output_mode,
visible: true,
scale,
}
}
fn add_shell_surface(
compositor: &wl_compositor::WlCompositor,
shell: &zwlr_layer_shell_v1::ZwlrLayerShellV1,
scale: u32,
configured_surfaces: Arc<Mutex<usize>>,
tx: Sender<Cmd>,
output: Option<&wl_output::WlOutput>,
) -> (
Main<wl_surface::WlSurface>,
Main<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>,
) {
let surface = compositor.create_surface();
let this_is_stupid = AtomicBool::new(false);
let shell_surface = shell.get_layer_surface(
&surface,
output,
zwlr_layer_shell_v1::Layer::Overlay,
"".to_string(),
);
shell_surface.quick_assign(move |layer, event, _| match event {
zwlr_layer_surface_v1::Event::Configure { serial, .. } => {
if this_is_stupid
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
*(configured_surfaces.lock().unwrap()) += 1;
layer.ack_configure(serial);
tx.send(Cmd::ForceDraw).unwrap();
}
}
_ => unreachable!(),
});
shell_surface.set_keyboard_interactivity(zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive);
shell_surface.set_size(1, 1);
surface.set_buffer_scale(scale as i32);
surface.commit();
(surface, shell_surface)
}
fn outputs_changed(&mut self) {
let shell = match self.shell {
Some(ref shell) => shell.to_owned(),
None => return,
};
let compositor = match self.compositor {
Some(ref c) => c.to_owned(),
None => return,
};
for shell_surface in self.shell_surfaces.iter() {
shell_surface.destroy();
}
for surface in self.surfaces.iter() {
surface.destroy();
}
self.configured_surfaces = Arc::new(Mutex::new(0));
if !self.visible {
self.surfaces = Vec::new();
self.shell_surfaces = Vec::new();
return;
}
match self.output_mode {
OutputMode::Active => {
if !self.shell_surfaces.is_empty() {
return;
}
let (surface, shell_surface) = AppInner::add_shell_surface(
&compositor,
&shell,
self.scale,
self.configured_surfaces.clone(),
self.draw_tx.clone(),
None,
);
self.surfaces = vec![surface];
self.shell_surfaces = vec![shell_surface];
}
OutputMode::All => {
let mut surfaces = Vec::new();
let mut shell_surfaces = Vec::new();
for output in self.outputs.iter() {
let (surface, shell_surface) = AppInner::add_shell_surface(
&compositor,
&shell,
self.scale,
self.configured_surfaces.clone(),
self.draw_tx.clone(),
Some(&output.1),
);
surfaces.push(surface);
shell_surfaces.push(shell_surface);
}
self.surfaces = surfaces;
self.shell_surfaces = shell_surfaces;
}
}
self.draw_tx.send(Cmd::ForceDraw).unwrap();
}
fn add_output(&mut self, id: u32, output: Main<wl_output::WlOutput>) {
self.outputs.push((id, output));
self.outputs_changed();
}
fn remove_output(&mut self, id: u32) {
let old_output = self.outputs.iter().find(|(output_id, _)| *output_id == id);
if let Some(output) = old_output {
let new_outputs = self
.outputs
.iter()
.filter(|(output_id, _)| *output_id != id)
.map(|(x, y)| (*x, y.clone()))
.collect();
if output.1.as_ref().version() >= 3 {
output.1.release()
}
self.outputs = new_outputs;
self.outputs_changed();
}
}
fn set_compositor(&mut self, compositor: Option<Main<wl_compositor::WlCompositor>>) {
self.compositor = compositor
}
fn set_shell(&mut self, shell: Option<Main<zwlr_layer_shell_v1::ZwlrLayerShellV1>>) {
self.shell = shell
}
}
struct AppKeyboard {
current: Option<Cmd>,
delay: i32,
rate: i32,
next: Option<NaiveDateTime>,
}
pub struct App<'a> {
pools: DoubleMemPool,
display: Display,
event_queue: EventQueue,
cmd_queue: Arc<Mutex<VecDeque<Cmd>>>,
widget: Option<Box<dyn Widget + Send + 'a>>,
bg: Color,
inner: Arc<Mutex<AppInner>>,
last_damage: Option<Vec<(i32, i32, i32, i32)>>,
last_dim: (u32, u32),
keyboard: Arc<Mutex<AppKeyboard>>,
}
impl<'a> App<'a> {
pub fn redraw(&mut self, force: bool) -> Result<(), ::std::io::Error> {
let widget = match self.widget {
Some(ref mut widget) => widget,
None => return Ok(()),
};
let inner = self.inner.lock().unwrap();
let time = Local::now().naive_local();
if !inner.visible
|| inner.shell_surfaces.len() != *inner.configured_surfaces.lock().unwrap()
|| inner.surfaces.is_empty()
{
// Not ready yet
return Ok(());
}
let (last, pool) = match self.pools.pool() {
Some((last, pool)) => (last, pool),
None => {
self.pools.never_mind();
return Ok(());
}
};
let size = widget.size();
let size_changed = self.last_dim != size;
let force = force | size_changed;
// resize the pool if relevant
pool.resize((4 * size.0 * size.1) as usize)
.expect("Failed to resize the memory pool.");
let mmap = pool.mmap();
let mut buf = Buffer::new(mmap, size);
// Copy old damage
let force = match (force, &self.last_damage) {
(false, Some(d)) => {
let lastmmap = last.mmap();
let last = Buffer::new(lastmmap, size);
if cfg!(feature = "damage_debug") {
buf.memset(&Color::new(0.5, 0.75, 0.75, 1.0));
}
for d in d {
last.copy_to(&mut buf, d.clone());
}
false
}
_ => true,
};
if force {
buf.memset(&self.bg);
}
let report = widget.draw(
&mut DrawContext {
buf: &mut buf,
bg: &self.bg,
time,
force,
},
(0, 0),
size,
)?;
mmap.flush().unwrap();
if !force && !report.full_damage && report.damage.is_empty() {
// Nothing to do
self.pools.never_mind();
return Ok(());
}
// get a buffer and attach it
let new_buffer = pool.buffer(
0,
report.width as i32,
report.height as i32,
4 * size.0 as i32,
wl_shm::Format::Argb8888,
);
if size_changed {
for shell_surface in inner.shell_surfaces.iter() {
shell_surface.set_size(size.0 / inner.scale, size.1 / inner.scale);
}
}
for surface in inner.surfaces.iter() {
surface.attach(Some(&new_buffer), 0, 0);
if cfg!(feature = "damage_debug") || force || report.full_damage {
surface.damage_buffer(0, 0, size.0 as i32, size.1 as i32);
} else {
for d in report.damage.iter() {
surface.damage_buffer(d.0, d.1, d.2, d.3);
}
}
surface.commit();
}
self.last_damage = if force || report.full_damage {
Some(vec![(0, 0, size.0 as i32, size.1 as i32)])
} else {
Some(report.damage)
};
self.last_dim = size;
Ok(())
}
pub fn hide(&mut self) {
let mut inner = self.inner.lock().unwrap();
inner.visible = false;
self.last_dim = (0, 0);
inner.outputs_changed();
}
pub fn show(&mut self) {
let mut inner = self.inner.lock().unwrap();
inner.visible = true;
self.last_dim = (0, 0);
inner.outputs_changed();
}
pub fn cmd_queue(&self) -> Arc<Mutex<VecDeque<Cmd>>> {
self.cmd_queue.clone()
}
pub fn flush_display(&mut self) {
self.display.flush().expect("unable to flush display");
}
pub fn event_queue(&mut self) -> &mut EventQueue {
&mut self.event_queue
}
pub fn get_widget(&mut self) -> &mut Box<dyn Widget + Send + 'a> {
self.widget.as_mut().unwrap()
}
pub fn set_widget(&mut self, w: Box<dyn Widget + Send + 'a>) -> Result<(), ::std::io::Error> {
self.widget = Some(w);
self.redraw(true)
}
pub fn set_keyboard_repeat(&mut self, ctx: &mut WaitContext) {
let kbd = self.keyboard.lock().unwrap();
if let Some(t) = kbd.next {
ctx.set_time(t);
}
}
pub fn key_repeat(&mut self) -> Option<Cmd> {
let time = Local::now().naive_local();
let mut kbd = self.keyboard.lock().unwrap();
if let Some(target) = kbd.next {
if time >= target {
let cmd = kbd.current.as_ref().unwrap().clone();
kbd.next = Some(time + Duration::milliseconds(kbd.rate.into()));
return Some(cmd);
}
}
None
}
pub fn new(tx: Sender<Cmd>, output_mode: OutputMode, bg: Color, scale: u32) -> App<'a> {
let inner = Arc::new(Mutex::new(AppInner::new(tx, output_mode, scale)));
//
// Set up modules
//
let cmd_queue = Arc::new(Mutex::new(VecDeque::new()));
let display = Display::connect_to_env().unwrap();
let mut event_queue = display.create_event_queue();
let display_wrapper = (*display).clone().attach(event_queue.token());
//
// Set up global manager
//
let inner_global = inner.clone();
let manager =
GlobalManager::new_with_cb(&display_wrapper, move |event, registry, _| match event {
GlobalEvent::New {
id,
ref interface,
version,
} => {
if let "wl_output" = &interface[..] {
let output = registry.bind(std::cmp::min(version, 3), id);
inner_global.lock().unwrap().add_output(id, output);
}
}
GlobalEvent::Removed { id, ref interface } => {
if let "wl_output" = &interface[..] {
inner_global.lock().unwrap().remove_output(id);
}
}
});
// double sync to retrieve the global list
// and the globals metadata
event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap();
event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap();
// wl_compositor
let compositor = manager
.instantiate_range(1, 4)
.expect("server didn't advertise `wl_compositor`");
inner.lock().unwrap().set_compositor(Some(compositor));
// wl_shm
let shm: Main<wl_shm::WlShm> = manager
.instantiate_range(1, 1)
.expect("server didn't advertise `wl_shm`");
let pools = DoubleMemPool::new(shm).expect("Failed to create a memory pool !");
//
// Get our seat
//
let seat: Main<wl_seat::WlSeat> = manager.instantiate_range(1, 6).unwrap();
event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap();
//
// Keyboard processing
//
let kbd_clone = cmd_queue.clone();
let modifiers_state = Arc::new(Mutex::new(ModifiersState {
ctrl: false,
alt: false,
shift: false,
caps_lock: false,
logo: false,
num_lock: false,
}));
let keyboard = Arc::new(Mutex::new(AppKeyboard {
current: None,
delay: 0,
rate: 0,
next: None,
}));
let kb2 = keyboard.clone();
map_keyboard(&seat, None, move |event: KbEvent, _, _| match event {
KbEvent::Key {
keysym,
utf8,
state,
..
} => {
if let KeyState::Pressed = state {
match keysym {
keysyms::XKB_KEY_Escape => kbd_clone.lock().unwrap().push_back(Cmd::Exit),
keysyms::XKB_KEY_c if modifiers_state.lock().unwrap().ctrl => {
kbd_clone.lock().unwrap().push_back(Cmd::Exit)
}
v => {
let ev = Cmd::Keyboard {
key: v,
key_state: state,
modifiers_state: *modifiers_state.lock().unwrap(),
interpreted: utf8,
};
let mut kbd = kb2.lock().unwrap();
kbd.current = Some(ev.clone());
kbd.next = if kbd.delay > 0 {
Some(
Local::now().naive_local()
+ Duration::milliseconds(kbd.delay.into()),
)
} else {
None
};
drop(kbd);
kbd_clone.lock().unwrap().push_back(ev);
}
}
} else {
kb2.lock().unwrap().next = None;
}
}
KbEvent::Leave { .. } => {
kb2.lock().unwrap().next = None;
}
KbEvent::RepeatInfo { delay, rate } => {
let mut kbd = kb2.lock().unwrap();
kbd.delay = delay;
kbd.rate = rate;
}
KbEvent::Modifiers { modifiers } => *modifiers_state.lock().unwrap() = modifiers,
_ => (),
})
.expect("could not map keyboard");
//
// Prepare shell so that we can create our shell surface
//
inner
.lock()
.unwrap()
.set_shell(Some(if let Ok(layer) = manager.instantiate_exact(1) {
layer
} else {
panic!("server didn't advertise `zwlr_layer_shell_v1`");
}));
event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap();
//
// Cursor processing
//
let pointer_clone = cmd_queue.clone();
let pointer = seat.get_pointer();
let mut pos: (u32, u32) = (0, 0);
let mut vert_scroll: f64 = 0.0;
let mut horiz_scroll: f64 = 0.0;
let mut btn: u32 = 0;
let mut btn_clicked = false;
pointer.quick_assign(move |_, event, _| match event {
wl_pointer::Event::Enter {
surface_x,
surface_y,
..
} => {
pos = (surface_x as u32, surface_y as u32);
}
wl_pointer::Event::Leave { .. } => {
pos = (0, 0);
}
wl_pointer::Event::Motion {
surface_x,
surface_y,
..
} => {
pos = (surface_x as u32 * scale, surface_y as u32 * scale);
}
wl_pointer::Event::Axis { axis, value, .. } => {
if axis == wl_pointer::Axis::VerticalScroll {
vert_scroll += value;
}
}
wl_pointer::Event::Button { button, state, .. } => {
if let wl_pointer::ButtonState::Released = state {
btn = button;
btn_clicked = true;
}
}
wl_pointer::Event::Frame => {
if vert_scroll != 0.0 || horiz_scroll != 0.0 {
pointer_clone.lock().unwrap().push_back(Cmd::MouseScroll {
scroll: (horiz_scroll, vert_scroll),
pos,
});
vert_scroll = 0.0;
horiz_scroll = 0.0;
}
if btn_clicked {
pointer_clone
.lock()
.unwrap()
.push_back(Cmd::MouseClick { btn, pos });
btn_clicked = false;
}
}
_ => {}
});
display.flush().unwrap();
App {
display,
event_queue,
cmd_queue,
pools,
widget: None,
bg,
inner,
last_damage: None,
last_dim: (0, 0),
keyboard,
}
}
}
+232 -117
View File
@@ -1,22 +1,205 @@
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 memmap::MmapMut;
use crate::color::Color; use wayland_client::{
protocol::{wl_buffer, wl_shm, wl_shm_pool},
Dispatch, QueueHandle,
};
pub struct Buffer<'a> { use crate::color::Color;
buf: &'a mut MmapMut, 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,
pub id: u32,
pub last_damage: Vec<Geometry>,
}
impl ShmBuffer {
fn new<D>(
shm: &wl_shm::WlShm,
qh: &QueueHandle<D>,
width: i32,
height: i32,
stride: i32,
format: wl_shm::Format,
id: u32,
) -> 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,
id,
last_damage: Vec::new(),
})
}
pub fn acquire(&mut self) {
self.refcnt += 1;
}
pub fn release(&mut self) {
self.refcnt -= 1;
}
}
pub struct BufferManager {
pub buffers: Vec<ShmBuffer>,
pub next_id: u32,
}
impl BufferManager {
pub fn new() -> BufferManager {
BufferManager {
buffers: Vec::new(),
next_id: 0,
}
}
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,
self.next_id,
)
.expect("unable to add buffer");
self.next_id += 1;
BufferView::new(&mut buf.mmap, (dimensions.0 as u32, dimensions.1 as u32));
self.buffers.push(buf);
}
}
pub struct BufferView<'a> {
view: &'a mut [u32],
dimensions: (u32, u32), dimensions: (u32, u32),
subdimensions: Option<(u32, u32, u32, u32)>, subdimensions: Option<(u32, u32, u32, u32)>,
} }
impl<'a> Buffer<'a> { impl<'a> BufferView<'a> {
pub fn new(buf: &'a mut MmapMut, dimensions: (u32, u32)) -> Buffer { pub fn new(buf: &'a mut MmapMut, dimensions: (u32, u32)) -> BufferView {
Buffer { let view = unsafe {
buf, std::slice::from_raw_parts_mut(
buf.as_mut_ptr() as *mut u32,
(dimensions.0 * dimensions.1) as usize,
)
};
BufferView {
view,
dimensions, dimensions,
subdimensions: None, subdimensions: None,
} }
} }
#[inline]
pub fn get_bounds(&self) -> (u32, u32, u32, u32) { pub fn get_bounds(&self) -> (u32, u32, u32, u32) {
if let Some(subdim) = self.subdimensions { if let Some(subdim) = self.subdimensions {
subdim subdim
@@ -25,68 +208,20 @@ impl<'a> Buffer<'a> {
} }
} }
pub fn get_signed_bounds(&self) -> (i32, i32, i32, i32) { pub fn subdimensions(&mut self, subdimensions: (u32, u32, u32, u32)) -> BufferView {
if let Some(subdim) = self.subdimensions {
(
subdim.0 as i32,
subdim.1 as i32,
subdim.2 as i32,
subdim.3 as i32,
)
} else {
(0, 0, self.dimensions.0 as i32, self.dimensions.1 as i32)
}
}
pub fn copy_to(&self, other: &mut Buffer, (x, y, width, height): (i32, i32, i32, i32)) {
debug_assert!(self.dimensions == other.dimensions);
debug_assert!(self.subdimensions.is_none() && other.subdimensions.is_none());
if x == 0 && width as u32 == self.dimensions.0 {
// Full-width copy
let offset = y as isize * self.dimensions.0 as isize;
let n = height as usize * self.dimensions.0 as usize;
unsafe {
std::ptr::copy(
(self.buf.as_ptr() as *const u32).offset(offset),
(other.buf.as_mut_ptr() as *mut u32).offset(offset),
n,
);
}
} else {
// Row-by-row copy
for cur_y in y as isize..(y + height) as isize {
let offset = x as isize + cur_y as isize * self.dimensions.0 as isize;
unsafe {
std::ptr::copy(
(self.buf.as_ptr() as *const u32).offset(offset),
(other.buf.as_mut_ptr() as *mut u32).offset(offset),
width as usize,
);
}
}
}
}
pub fn subdimensions(
&mut self,
subdimensions: (u32, u32, u32, u32),
) -> Result<Buffer, ::std::io::Error> {
let bounds = self.get_bounds(); let bounds = self.get_bounds();
if subdimensions.0 + subdimensions.2 >= bounds.2 if cfg!(debug_assertions)
|| subdimensions.1 + subdimensions.3 >= bounds.3 && (subdimensions.0 + subdimensions.2 > bounds.2
|| subdimensions.1 + subdimensions.3 > bounds.3)
{ {
return Err(::std::io::Error::new( panic!(
::std::io::ErrorKind::Other,
format!(
"cannot create subdimensions larger than buffer: {:?} > {:?}", "cannot create subdimensions larger than buffer: {:?} > {:?}",
subdimensions, bounds subdimensions, bounds
), );
));
} }
Ok(Buffer { BufferView {
buf: self.buf, view: self.view,
dimensions: self.dimensions, dimensions: self.dimensions,
subdimensions: Some(( subdimensions: Some((
subdimensions.0 + bounds.0, subdimensions.0 + bounds.0,
@@ -94,23 +229,24 @@ impl<'a> Buffer<'a> {
subdimensions.2, subdimensions.2,
subdimensions.3, subdimensions.3,
)), )),
}) }
} }
pub fn offset(&mut self, offset: (u32, u32)) -> Result<Buffer, ::std::io::Error> { pub fn subgeometry(&mut self, geo: Geometry) -> BufferView {
self.subdimensions((geo.x, geo.y, geo.width, geo.height))
}
pub fn offset(&mut self, offset: (u32, u32)) -> BufferView {
let bounds = self.get_bounds(); let bounds = self.get_bounds();
if offset.0 > bounds.2 || offset.1 > bounds.3 { if cfg!(debug_assertions) && (offset.0 > bounds.2 || offset.1 > bounds.3) {
return Err(::std::io::Error::new( panic!(
::std::io::ErrorKind::Other,
format!(
"cannot create offset outside buffer: {:?} > {:?}", "cannot create offset outside buffer: {:?} > {:?}",
offset, bounds offset, bounds
), );
));
} }
Ok(Buffer { BufferView {
buf: self.buf, view: self.view,
dimensions: self.dimensions, dimensions: self.dimensions,
subdimensions: Some(( subdimensions: Some((
offset.0 + bounds.0, offset.0 + bounds.0,
@@ -118,63 +254,42 @@ impl<'a> Buffer<'a> {
bounds.2 - offset.0, bounds.2 - offset.0,
bounds.3 - offset.1, bounds.3 - offset.1,
)), )),
}) }
} }
pub fn memset(&mut self, c: &Color) { pub fn memset(&mut self, c: Color) {
if let Some(subdim) = self.subdimensions { if let Some(subdim) = self.subdimensions {
unsafe {
let ptr = self.buf.as_mut_ptr();
for y in subdim.1..(subdim.1 + subdim.3) { for y in subdim.1..(subdim.1 + subdim.3) {
for x in subdim.0..(subdim.0 + subdim.2) { let start = (subdim.0 + y * self.dimensions.0) as usize;
*((ptr as *mut u32).offset((x + y * self.dimensions.0) as isize)) = let end = start + subdim.2 as usize;
c.as_argb8888(); self.view[start..end].fill(c.0);
}
}
} }
} else { } else {
unsafe { self.view.fill(c.0);
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)) = c.as_argb8888();
}
}
} }
} }
pub fn put(&mut self, pos: (u32, u32), c: &Color) -> Result<(), ::std::io::Error> { pub fn clear(&mut self) {
let true_pos = if let Some(subdim) = self.subdimensions { if let Some(subdim) = self.subdimensions {
if pos.0 >= subdim.2 || pos.1 >= subdim.3 { for y in subdim.1..(subdim.1 + subdim.3) {
return Err(::std::io::Error::new( let start = (subdim.0 + y * self.dimensions.0) as usize;
::std::io::ErrorKind::Other, let end = start + subdim.2 as usize;
format!( self.view[start..end].fill(0x0);
"put({:?}) is not within subdimensions of buffer ({:?})",
pos, subdim
),
));
} }
(pos.0 + subdim.0, pos.1 + subdim.1)
} else { } else {
if pos.0 >= self.dimensions.0 || pos.1 >= self.dimensions.1 { self.view.fill(0x0);
return Err(::std::io::Error::new( }
::std::io::ErrorKind::Other,
format!(
"put({:?}) is not within dimensions of buffer ({:?})",
pos, self.dimensions
),
));
} }
pos
};
unsafe { #[inline]
let ptr = self pub fn put_raw(&mut self, pos: (u32, u32), c: Color) {
.buf self.view[(pos.0 + pos.1 * self.dimensions.0) as usize] = c.0
.as_mut_ptr() }
.offset(4 * (true_pos.0 + (true_pos.1 * self.dimensions.0)) as isize);
*(ptr as *mut u32) = c.as_argb8888();
};
Ok(()) #[inline]
pub fn put_line_raw(&mut self, pos: (u32, u32), len: u32, c: Color) {
let start = (pos.0 + (pos.1 * self.dimensions.0)) as usize;
let len = len as usize;
self.view[start..start + len].fill(c.0);
} }
} }
-24
View File
@@ -1,24 +0,0 @@
use crate::keyboard::{KeyState, ModifiersState};
#[derive(Clone)]
pub enum Cmd {
Exit,
Draw,
ForceDraw,
ToggleVisible,
MouseClick {
btn: u32,
pos: (u32, u32),
},
MouseScroll {
scroll: (f64, f64),
pos: (u32, u32),
},
KeyboardTest,
Keyboard {
key: u32,
key_state: KeyState,
modifiers_state: ModifiersState,
interpreted: Option<String>,
},
}
+31 -60
View File
@@ -1,71 +1,42 @@
use serde::{Deserialize, Serialize}; #[derive(Copy, Clone)]
pub struct Color(pub u32);
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
pub struct Color {
red: f32,
green: f32,
blue: f32,
opacity: f32,
}
impl Color { impl Color {
pub fn new(red: f32, green: f32, blue: f32, opacity: f32) -> Color { pub const WHITE: Color = Color(0xFFFFFFFF);
let (red, green, blue, opacity) = clamp(red, green, blue, opacity); pub const GREY35: Color = Color(0xFF595959);
Color { pub const GREY50: Color = Color(0xFF7F7F7F);
red, pub const GREY75: Color = Color(0xFFBFBFBF);
green, pub const GREY80: Color = Color(0xFFCCCCCC);
blue, pub const RED: Color = Color(0xFFFF0000);
opacity, pub const YELLOW: Color = Color(0xFFFFFF00);
} pub const LIGHTGREEN: Color = Color(0xFF7FFF7F);
pub const LIGHTORANGE: Color = Color(0xFFFFBF00);
pub const DARKORANGE: Color = Color(0xFFFF7F00);
pub const BUFF: Color = Color(0xFFFFBF7F);
#[allow(unused)]
pub fn new(red: u8, green: u8, blue: u8, opacity: u8) -> Color {
Color((opacity as u32) << 24 | (red as u32) << 16 | (green as u32) << 8 | (blue as u32))
} }
pub fn blend(&self, other: &Color, ratio: f32) -> Color { fn red(self) -> u8 {
let ratio = clamp_f32(ratio, 0.0, 1.0); ((self.0 >> 16) & 0xFF) as u8
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),
} }
fn green(self) -> u8 {
((self.0 >> 8) & 0xFF) as u8
}
fn blue(self) -> u8 {
(self.0 & 0xFF) as u8
} }
#[inline] pub fn alpha(self, alpha: f32) -> Color {
pub fn as_argb8888(&self) -> u32 { unsafe {
((255.0 * self.opacity) as u32 & 0xFF) << 24 Color(
| ((255.0 * self.red) as u32 & 0xFF) << 16 (255. * alpha).to_int_unchecked::<u32>() << 24
| ((255.0 * self.green) as u32 & 0xFF) << 8 | (self.red() as f32 * alpha).to_int_unchecked::<u32>() << 16
| ((255.0 * self.blue) as u32 & 0xFF) | (self.green() as f32 * alpha).to_int_unchecked::<u32>() << 8
} | (self.blue() as f32 * alpha).to_int_unchecked::<u32>(),
}
#[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)
}
+309 -272
View File
@@ -1,29 +1,39 @@
use crate::cmd::Cmd; use serde::{self, Deserialize, Serialize};
use crate::color::Color;
use crate::widget; use std::collections::HashMap;
use crate::{
fonts::{FontMap, FontRef},
widgets,
};
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use std::default::Default; use std::default::Default;
use std::{collections::HashMap, sync::mpsc::Sender}; use std::sync::{Arc, Mutex};
use crate::{
event::Events,
fonts::{find_font, FontMap},
widgets::{
Backlight, Battery, Calendar, Clock, Date, HorizontalLayout, IndexedLayout, Interface,
InvertedHorizontalLayout, InvertedVerticalLayout, Layout, Line, Margin, PulseAudio,
VerticalLayout, Widget as RealWidget,
},
};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "snake_case")]
pub enum OperationMode {
LayerSurface((u32, u32)),
XdgToplevel,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Widget { pub enum Widget {
Margin { Margin {
margins: (u32, u32, u32, u32), margins: (u32, u32, u32, u32),
widget: Box<Widget>, widget: Box<Widget>,
}, },
Fixed {
width: u32,
height: u32,
widget: Box<Widget>,
},
HorizontalLayout(Vec<Widget>), HorizontalLayout(Vec<Widget>),
InvertedHorizontalLayout(Vec<Widget>),
VerticalLayout(Vec<Widget>), VerticalLayout(Vec<Widget>),
InvertedVerticalLayout(Vec<Widget>),
HorizontalLine(u32),
VerticalLine(u32),
Clock { Clock {
font: Option<String>, font: Option<String>,
font_size: f32, font_size: f32,
@@ -36,315 +46,342 @@ pub enum Widget {
font_primary: Option<String>, font_primary: Option<String>,
font_secondary: Option<String>, font_secondary: Option<String>,
font_size: f32, font_size: f32,
sections: u32, sections_x: i32,
sections_y: i32,
}, },
Launcher { Launcher {
font: Option<String>, font: Option<String>,
font_size: f32, font_size: f32,
length: u32, launch_cmd: Option<String>,
#[serde(default)]
app_opener: String,
#[serde(default)]
term_opener: String,
#[serde(default)]
url_opener: String,
}, },
Battery { Battery {
font: Option<String>, font: Option<String>,
font_size: f32, font_size: f32,
length: u32,
}, },
Backlight { Backlight {
#[serde(default)] device: Option<String>,
device: String,
font: Option<String>, font: Option<String>,
font_size: f32, font_size: f32,
length: u32,
}, },
#[cfg(feature = "pulseaudio-widget")]
PulseAudio { PulseAudio {
font: Option<String>, font: Option<String>,
font_size: f32, font_size: f32,
length: u32,
},
#[cfg(feature = "alsa-widget")]
AlsaSound {
font: Option<String>,
font_size: f32,
length: u32,
}, },
} }
impl Widget { impl Default for OperationMode {
pub fn construct<'a>( fn default() -> OperationMode {
self, OperationMode::LayerSurface((1024, 768))
time: NaiveDateTime,
tx: Sender<Cmd>,
fonts: &'a FontMap,
) -> Option<Box<dyn widget::Widget + Send + 'a>> {
match self {
Widget::Margin { margins, widget } => match widget.construct(time, tx, fonts) {
Some(w) => Some(widget::Margin::new(margins, w)),
None => None,
},
Widget::Fixed {
width,
height,
widget,
} => match widget.construct(time, tx, fonts) {
Some(w) => Some(widget::Fixed::new((width, height), w)),
None => None,
},
Widget::HorizontalLayout(widgets) => Some(widget::HorizontalLayout::new(
widgets
.into_iter()
.map(|x| x.construct(time, tx.clone(), fonts))
.filter(|x| x.is_some())
.map(|x| x.unwrap())
.collect(),
)),
Widget::VerticalLayout(widgets) => Some(widget::VerticalLayout::new(
widgets
.into_iter()
.map(|x| x.construct(time, tx.clone(), fonts))
.filter(|x| x.is_some())
.map(|x| x.unwrap())
.collect(),
)),
Widget::Clock { font, font_size } => match widgets::clock::Clock::new(
time,
get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts),
font_size,
) {
Ok(w) => Some(w),
Err(_) => None,
},
Widget::Date { font, font_size } => match widgets::date::Date::new(
time,
get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts),
font_size,
) {
Ok(w) => Some(w),
Err(_) => None,
},
Widget::Calendar {
font_primary,
font_secondary,
font_size,
sections,
} => Some(widgets::calendar::Calendar::new(
time,
get_font(
&font_primary.or_else(|| Some("sans".to_string())).unwrap(),
&fonts,
),
get_font(
&font_secondary.or_else(|| Some("mono".to_string())).unwrap(),
&fonts,
),
font_size,
sections,
)),
Widget::Launcher {
font,
font_size,
length,
app_opener,
term_opener,
url_opener,
} => Some(widgets::launcher::Launcher::new(
get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts),
font_size,
length,
tx,
app_opener,
term_opener,
if url_opener.is_empty() {
"xdg_open ".to_string()
} else {
url_opener
},
)),
Widget::Battery {
font,
font_size,
length,
} => {
match widgets::battery::UpowerBattery::new(
get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts),
font_size,
length,
tx,
) {
Ok(w) => Some(w),
Err(_) => None,
}
}
Widget::Backlight {
device,
font,
font_size,
length,
} => {
let d = if device == "" {
"intel_backlight"
} else {
&device
};
match widgets::backlight::Backlight::new(
d,
get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts),
font_size,
length,
) {
Ok(w) => Some(w),
Err(_) => None,
}
}
#[cfg(feature = "pulseaudio-widget")]
Widget::PulseAudio {
font,
font_size,
length,
} => {
match widgets::audio::PulseAudio::new(
get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts),
font_size,
length,
tx,
) {
Ok(w) => Some(w),
Err(_) => None,
}
}
#[cfg(feature = "alsa-widget")]
Widget::AlsaSound {
font,
font_size,
length,
} => {
match widgets::audio::Alsa::new(
get_font(&font.or_else(|| Some("sans".to_string())).unwrap(), &fonts),
font_size,
length,
) {
Ok(w) => Some(w),
Err(_) => None,
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum OutputMode {
All,
Active,
}
impl Default for OutputMode {
fn default() -> Self {
OutputMode::Active
} }
} }
fn default_fonts() -> HashMap<String, String> { #[derive(Serialize, Deserialize, Clone, Debug)]
let mut map = HashMap::with_capacity(2); #[serde(rename_all = "snake_case")]
map.insert("mono".to_string(), "mono".to_string());
map.insert("sans".to_string(), "sans".to_string());
map
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Config { pub struct Config {
pub output_mode: OutputMode, pub font_paths: Option<HashMap<String, String>>,
pub scale: u32,
pub background: Color,
pub widget: Widget, pub widget: Widget,
pub mode: OperationMode,
#[serde(default = "default_fonts")] pub background: Option<u32>,
pub fonts: HashMap<String, String>, pub server: Option<bool>,
} }
impl Default for Config { impl Config {
fn default() -> Self { pub fn generate_v1() -> Config {
Config { let has_battery = Battery::detect();
widget: Widget::Margin { let has_backlight = Backlight::detect();
margins: (20, 20, 20, 20), let sans = find_font("sans");
widget: Box::new(Widget::VerticalLayout(vec![ let monospace = find_font("monospace");
Widget::HorizontalLayout(vec![
Widget::Margin { let mut bars = Vec::new();
margins: (0, 88, 0, 32), if has_battery {
widget: Box::new(Widget::VerticalLayout(vec![ bars.push(Widget::Margin {
Widget::Date {
font: None,
font_size: 64.0,
},
Widget::Clock {
font: None,
font_size: 256.0,
},
])),
},
Widget::VerticalLayout(vec![
Widget::Margin {
margins: (0, 0, 0, 8), margins: (0, 0, 0, 8),
widget: Box::new(Widget::Battery { widget: Box::new(Widget::Battery {
font: None, font: None,
font_size: 24.0, font_size: 24.,
length: 0,
}), }),
}, });
Widget::Margin { }
if has_backlight {
bars.push(Widget::Margin {
margins: (0, 0, 0, 8), margins: (0, 0, 0, 8),
widget: Box::new(Widget::Backlight { widget: Box::new(Widget::Backlight {
device: "intel_backlight".to_string(), device: None,
font: None, font: None,
font_size: 24.0, font_size: 24.,
length: 0,
}), }),
}, });
#[cfg(feature = "pulseaudio-widget")] }
Widget::Margin { bars.push(Widget::Margin {
margins: (0, 0, 0, 8), margins: (0, 0, 0, 8),
widget: Box::new(Widget::PulseAudio { widget: Box::new(Widget::PulseAudio {
font: None, font: None,
font_size: 24.0, font_size: 24.,
length: 0,
}), }),
});
let widget = Widget::Margin {
margins: (20, 20, 20, 20),
widget: Box::new(Widget::VerticalLayout(vec![
Widget::HorizontalLayout(vec![
Widget::VerticalLayout(vec![
Widget::Date {
font: None,
font_size: 48.,
},
Widget::Clock {
font: None,
font_size: 256.,
}, },
]), ]),
Widget::Margin {
margins: (88, 0, 0, 0),
widget: Box::new(Widget::VerticalLayout(bars)),
},
]), ]),
Widget::Calendar { Widget::Calendar {
font_primary: None, font_primary: None,
font_secondary: None, font_secondary: None,
font_size: 16.0, font_size: 36.,
sections: 3, sections_x: 3,
sections_y: 1,
}, },
Widget::Launcher { Widget::Launcher {
font: None, font: None,
font_size: 32.0, font_size: 32.,
length: 0, launch_cmd: None,
app_opener: "".to_string(),
term_opener: "".to_string(),
url_opener: "".to_string(),
}, },
])), ])),
};
let map = HashMap::from([
("sans".to_string(), sans),
("monospace".to_string(), monospace),
]);
Config {
font_paths: Some(map),
widget: widget,
mode: OperationMode::LayerSurface((1024, 768)),
background: Some(0xFAFAFAFA),
server: None,
}
}
pub fn generate_v2(clay: bool) -> Config {
let has_battery = Battery::detect();
let has_backlight = Backlight::detect();
let sans = find_font("sans");
let monospace = find_font("monospace");
let mut bars = Vec::new();
if has_battery {
bars.push(Widget::Margin {
margins: (16, 8, 8, 0),
widget: Box::new(Widget::Battery {
font: None,
font_size: 24.,
}),
});
}
if has_backlight {
bars.push(Widget::Margin {
margins: (16, 8, 8, 0),
widget: Box::new(Widget::Backlight {
device: None,
font: None,
font_size: 24.,
}),
});
}
bars.push(Widget::Margin {
margins: (16, 8, 8, 0),
widget: Box::new(Widget::PulseAudio {
font: None,
font_size: 24.,
}),
});
let widget = Widget::VerticalLayout(vec![
Widget::HorizontalLayout(vec![
Widget::Margin {
margins: (8, 0, 0, 0),
widget: Box::new(Widget::Clock {
font: None,
font_size: 128.,
}),
}, },
output_mode: Default::default(), Widget::Margin {
scale: 1, margins: (16, 64, 0, 0),
background: Color::new(0.0, 0.0, 0.0, 0.9), widget: Box::new(Widget::Date {
fonts: default_fonts(), font: None,
font_size: 48.,
}),
},
Widget::VerticalLayout(bars),
]),
Widget::HorizontalLine(1),
Widget::InvertedHorizontalLayout(vec![
Widget::Margin {
margins: (16, 8, 16, 0),
widget: Box::new(Widget::Calendar {
font_primary: None,
font_secondary: None,
font_size: 24.,
sections_x: 1,
sections_y: if clay { -1 } else { 3 },
}),
},
Widget::VerticalLine(1),
Widget::Margin {
margins: (8, 0, 0, 0),
widget: Box::new(Widget::Launcher {
font: None,
font_size: 32.,
launch_cmd: None,
}),
},
]),
]);
let map = HashMap::from([
("sans".to_string(), sans),
("monospace".to_string(), monospace),
]);
Config {
font_paths: Some(map),
widget: widget,
mode: if clay {
OperationMode::XdgToplevel
} else {
OperationMode::LayerSurface((1024, 1))
},
background: Some(0xFAFAFAFA),
server: None,
} }
} }
} }
#[inline] fn leak_or_default(v: Option<String>, d: &'static str) -> &'static str {
fn get_font<'a>(name: &str, map: &'a FontMap) -> FontRef<'a> { match v {
match map.get(name) { Some(v) => Box::leak(v.into_boxed_str()),
Some(f) => f, None => d,
None => panic!("Font {} is missing from the config", name), }
}
fn leak_or_none(v: Option<String>) -> Option<&'static str> {
match v {
Some(v) => Some(Box::leak(v.into_boxed_str())),
None => None,
}
}
impl Widget {
pub fn construct_widgets(
self,
v: &mut Vec<Box<dyn RealWidget>>,
mut fm: &mut FontMap,
events: &Arc<Mutex<Events>>,
) {
match self {
Widget::Margin { widget, .. } => widget.construct_widgets(v, fm, events),
Widget::HorizontalLayout(widgets) => widgets
.into_iter()
.for_each(|w| w.construct_widgets(v, fm, events)),
Widget::InvertedHorizontalLayout(widgets) => widgets
.into_iter()
.for_each(|w| w.construct_widgets(v, fm, events)),
Widget::VerticalLayout(widgets) => widgets
.into_iter()
.for_each(|w| w.construct_widgets(v, fm, events)),
Widget::InvertedVerticalLayout(widgets) => widgets
.into_iter()
.for_each(|w| w.construct_widgets(v, fm, events)),
Widget::HorizontalLine(width) => v.push(Box::new(Line::new(width, false))),
Widget::VerticalLine(width) => v.push(Box::new(Line::new(width, true))),
Widget::Clock { font, font_size } => v.push(Box::new(Clock::new(
&mut fm,
leak_or_default(font, "sans"),
font_size,
))),
Widget::Date { font, font_size } => v.push(Box::new(Date::new(
&mut fm,
leak_or_default(font, "sans"),
font_size,
))),
Widget::Calendar {
font_primary,
font_secondary,
font_size,
sections_x,
sections_y,
} => v.push(Box::new(Calendar::new(
&mut fm,
leak_or_default(font_primary, "monospace"),
leak_or_default(font_secondary, "sans"),
font_size,
sections_x,
sections_y,
))),
Widget::Launcher {
font,
font_size,
launch_cmd,
} => v.push(Box::new(Interface::new(
events.clone(),
&mut fm,
leak_or_default(font, "sans"),
font_size,
launch_cmd,
))),
Widget::Battery { font, font_size } => v.push(Box::new(Battery::new(
events.clone(),
&mut fm,
leak_or_default(font, "sans"),
font_size,
))),
Widget::Backlight {
device,
font,
font_size,
} => v.push(Box::new(Backlight::new(
leak_or_none(device),
&mut fm,
leak_or_default(font, "sans"),
font_size,
))),
Widget::PulseAudio { font, font_size } => v.push(Box::new(PulseAudio::new(
events.clone(),
&mut fm,
Box::leak(font.unwrap_or_else(|| "sans".to_string()).into_boxed_str()),
font_size,
))),
}
}
pub fn construct_layout(&self, idx: &mut usize) -> Box<dyn Layout> {
match self {
Widget::Margin { margins, widget } => {
Margin::new(widget.construct_layout(idx), *margins)
}
Widget::HorizontalLayout(widgets) => {
HorizontalLayout::new(widgets.iter().map(|w| w.construct_layout(idx)).collect())
}
Widget::InvertedHorizontalLayout(widgets) => InvertedHorizontalLayout::new(
widgets.iter().map(|w| w.construct_layout(idx)).collect(),
),
Widget::VerticalLayout(widgets) => {
VerticalLayout::new(widgets.iter().map(|w| w.construct_layout(idx)).collect())
}
Widget::InvertedVerticalLayout(widgets) => InvertedVerticalLayout::new(
widgets.iter().map(|w| w.construct_layout(idx)).collect(),
),
_ => IndexedLayout::new({
let i = *idx;
*idx += 1;
i
}),
}
} }
} }
-61
View File
@@ -1,61 +0,0 @@
use serde::{de::DeserializeOwned, Serialize};
use std::io::BufRead;
pub enum ConfigFmt {
#[cfg(feature = "yaml-cfg")]
Yaml,
#[cfg(feature = "json-cfg")]
Json,
}
pub const CONFIG_NAMES: &[&str] = &[
#[cfg(feature = "yaml-cfg")]
"config.yaml",
#[cfg(feature = "json-cfg")]
"config.json",
];
impl ConfigFmt {
#[inline]
pub fn new(ext: &str) -> Option<Self> {
match ext {
#[cfg(feature = "yaml-cfg")]
"yaml" => Some(ConfigFmt::Yaml),
#[cfg(feature = "json-cfg")]
"json" => Some(ConfigFmt::Json),
_ => None,
}
}
#[inline]
pub fn from_reader<B: BufRead, T: DeserializeOwned>(&self, r: B) -> T {
match self {
#[cfg(feature = "yaml-cfg")]
ConfigFmt::Yaml => serde_yaml::from_reader(r).unwrap(),
#[cfg(feature = "json-cfg")]
ConfigFmt::Json => serde_json::from_reader(r).unwrap(),
}
}
#[inline]
pub fn to_string<T: Serialize + ?Sized>(&self, src: &T) -> String {
match self {
#[cfg(feature = "yaml-cfg")]
ConfigFmt::Yaml => serde_yaml::to_string(&src).unwrap(),
#[cfg(feature = "json-cfg")]
ConfigFmt::Json => serde_json::to_string_pretty(&src).unwrap(),
}
}
}
impl Default for ConfigFmt {
fn default() -> Self {
// attributes require curly braces to work
#[cfg(feature = "yaml-cfg")]
{
ConfigFmt::Yaml
}
#[cfg(all(feature = "json-cfg", not(feature = "yaml-cfg")))]
{
ConfigFmt::Json
}
}
}
-57
View File
@@ -1,57 +0,0 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fs::{create_dir_all, File, OpenOptions};
use std::path::PathBuf;
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
pub struct Data {
pub entries: HashMap<String, i64>,
}
impl Data {
pub fn load() -> Result<Data, Box<dyn Error>> {
Ok(serde_yaml::from_reader(Self::read_file()?)?)
}
pub fn save(&self) -> Result<(), Box<dyn Error>> {
Ok(serde_yaml::to_writer(Self::write_file()?, self)?)
}
fn read_file() -> Result<File, Box<dyn Error>> {
Ok(OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open(Self::path()?)?)
}
fn write_file() -> Result<File, Box<dyn Error>> {
Ok(OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(Self::path()?)?)
}
fn path() -> Result<PathBuf, Box<dyn Error>> {
let xdg_cache = match env::var_os("XDG_CACHE_HOME") {
Some(s) => s
.into_string()
.map_err(|_| "Unable to resolve $XDG_CACHE_HOME")?,
None => format!(
"{}/.cache",
env::var_os("HOME")
.ok_or("Unable to resolve $HOME")?
.into_string()
.map_err(|_| "Unable to resolve $HOME")?
),
};
let cache_dir = PathBuf::from(xdg_cache).join("wldash");
create_dir_all(&cache_dir)?;
Ok(cache_dir.join("data.yaml"))
}
}
-113
View File
@@ -1,113 +0,0 @@
extern crate ini;
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()
}
-261
View File
@@ -1,261 +0,0 @@
use std::{
cell::RefCell,
ffi::CStr,
fs::File,
io,
os::unix::io::{FromRawFd, RawFd},
rc::Rc,
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},
Main,
};
/// A wrapper handling an SHM memory pool backed by a shared memory file
///
/// This wrapper handles for you the creation of the shared memory file and its synchronization
/// with the protocol.
///
/// Mempool internally tracks the release of the buffers by the compositor. As such, creating a buffer
/// that is not commited to a surface (and then never released by the server) would cause the Mempool
/// to be stuck believing it is still in use.
///
/// Mempool will also handle the destruction of buffers and as such the `destroy()` method should not
/// be used on buffers created from Mempool.
///
/// Overwriting the contents of the memory pool before it is completely freed may cause graphical
/// glitches due to the possible corruption of data while the compositor is reading it.
///
/// Mempool requires a callback that will be called when the pool becomes free, this
/// happens when all the pools buffers are released by the server.
pub struct MemPool {
file: File,
len: usize,
pool: Main<wl_shm_pool::WlShmPool>,
buffer_count: Rc<RefCell<u32>>,
mmap: MmapMut,
callback: Rc<RefCell<dyn FnMut(wayland_client::DispatchData)>>,
}
impl MemPool {
/// Create a new memory pool associated with given shm
pub fn new<F>(shm: Main<wl_shm::WlShm>, callback: F) -> io::Result<MemPool>
where
F: FnMut(wayland_client::DispatchData) + 'static,
{
let mem_fd = create_shm_fd()?;
let mem_file = unsafe { File::from_raw_fd(mem_fd) };
mem_file.set_len(128)?;
let pool = shm.create_pool(mem_fd, 128);
let mmap = unsafe { MmapMut::map_mut(&mem_file).unwrap() };
Ok(MemPool {
file: mem_file,
len: 128,
pool,
buffer_count: Rc::new(RefCell::new(0)),
mmap,
callback: Rc::new(RefCell::new(callback)),
})
}
/// Resize the memory pool
///
/// This affect the size as it is seen by the wayland server. Even
/// if you extend the temporary file size by writing to it, you need to
/// call this method otherwise the server won't see the new size.
///
/// Memory pools can only be extented, as such this method will do nothing
/// if the requested new size is smaller than the current size.
///
/// This method allows you to ensure the underlying pool is large enough to
/// hold what you want to write to it.
pub fn resize(&mut self, newsize: usize) -> io::Result<()> {
if newsize > self.len {
self.file.set_len(newsize as u64)?;
self.pool.resize(newsize as i32);
self.len = newsize;
self.mmap = unsafe { MmapMut::map_mut(&self.file).unwrap() };
}
Ok(())
}
/// Create a new buffer to this pool
///
/// The parameters are:
///
/// - `offset`: the offset (in bytes) from the beginning of the pool at which this
/// buffer starts
/// - `width`: the width of this buffer (in pixels)
/// - `height`: the height of this buffer (in pixels)
/// - `stride`: distance (in bytes) between the beginning of a row and the next one
/// - `format`: the encoding format of the pixels. Using a format that was not
/// advertised to the `wl_shm` global by the server is a protocol error and will
/// terminate your connection
pub fn buffer(
&self,
offset: i32,
width: i32,
height: i32,
stride: i32,
format: wl_shm::Format,
) -> wl_buffer::WlBuffer {
*self.buffer_count.borrow_mut() += 1;
let my_buffer_count = self.buffer_count.clone();
let my_callback = self.callback.clone();
let buffer = self
.pool
.create_buffer(offset, width, height, stride, format);
buffer.quick_assign(move |buffer, event, dispatch_data| match event {
wl_buffer::Event::Release => {
buffer.destroy();
let new_count = {
// borrow the buffer_count for as short as possible, in case
// the user wants to create a new buffer from the callback
let mut my_buffer_count = my_buffer_count.borrow_mut();
*my_buffer_count -= 1;
*my_buffer_count
};
if new_count == 0 {
(&mut *my_callback.borrow_mut())(dispatch_data);
}
}
_ => unreachable!(),
});
(*buffer).clone().detach()
}
/// Uses the memmap crate to map the underlying shared memory file
pub fn mmap(&mut self) -> &mut MmapMut {
&mut self.mmap
}
/// Returns true if the pool contains buffers that are currently in use by the server
pub fn is_used(&self) -> bool {
*self.buffer_count.borrow() != 0
}
}
impl Drop for MemPool {
fn drop(&mut self) {
self.pool.destroy();
}
}
impl io::Write for MemPool {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
io::Write::write(&mut self.file, buf)
}
fn flush(&mut self) -> io::Result<()> {
io::Write::flush(&mut self.file)
}
}
impl io::Seek for MemPool {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.file, pos)
}
}
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"smithay-client-toolkit\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!(
"/smithay-client-toolkit-{}",
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!(
"/smithay-client-toolkit-{}",
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
);
continue;
}
Err(Errno::EINTR) => continue,
Err(errno) => return Err(io::Error::from(errno)),
}
}
}
pub struct DoubleMemPool {
pool1: MemPool,
pool2: MemPool,
switch: bool,
}
impl DoubleMemPool {
pub fn new(shm: Main<wl_shm::WlShm>) -> ::std::io::Result<DoubleMemPool> {
Ok(DoubleMemPool {
pool1: MemPool::new(shm.clone(), move |_| {})?,
pool2: MemPool::new(shm.clone(), move |_| {})?,
switch: false,
})
}
pub fn pool(&mut self) -> Option<(&mut MemPool, &mut MemPool)> {
let switch = self.switch;
self.switch = !self.switch;
let (last, cur) = if !switch {
(&mut self.pool2, &mut self.pool1)
} else {
(&mut self.pool1, &mut self.pool2)
};
if cur.is_used() {
None
} else {
Some((last, cur))
}
}
pub fn never_mind(&mut self) {
self.switch = !self.switch;
}
}
+61 -54
View File
@@ -1,4 +1,4 @@
use crate::buffer::Buffer; use crate::buffer::BufferView;
use crate::{color::Color, fonts::FontRef}; use crate::{color::Color, fonts::FontRef};
use rusttype::{point, Scale}; use rusttype::{point, Scale};
@@ -21,7 +21,6 @@ impl CachedGlyph {
if let Some(bounding_box) = glyph.pixel_bounding_box() { if let Some(bounding_box) = glyph.pixel_bounding_box() {
let origin = (bounding_box.min.x, bounding_box.min.y); let origin = (bounding_box.min.x, bounding_box.min.y);
let dimensions = ( let dimensions = (
(bounding_box.max.x - bounding_box.min.x) as u32, (bounding_box.max.x - bounding_box.min.x) as u32,
(bounding_box.max.y - bounding_box.min.y) as u32, (bounding_box.max.y - bounding_box.min.y) as u32,
@@ -47,19 +46,26 @@ impl CachedGlyph {
} }
} }
fn draw(&self, buf: &mut Buffer, pos: (i32, i32), bg: &Color, c: &Color) { fn draw(&self, buf: &mut BufferView, pos: (i32, i32), c: Color) {
let bounds = buf.get_bounds();
if pos.0 as u32 + self.dimensions.0 > bounds.2 {
return;
}
let offset = (bounds.0 as i32, bounds.1 as i32);
let width = self.dimensions.0 as i32 - 1;
let mut x = 0; let mut x = 0;
let mut y = 0; let mut y = 0;
for v in &self.render { for v in &self.render {
let _ = buf.put( buf.put_raw(
( (
(x + pos.0 + self.origin.0) as u32, (x + pos.0 + self.origin.0 + offset.0) as u32,
(y + pos.1 + self.origin.1) as u32, (y + pos.1 + self.origin.1 + offset.1) as u32,
), ),
&bg.blend(&c, *v), c.alpha(*v),
); );
if x == self.dimensions.0 as i32 - 1 { if x == width {
y += 1; y += 1;
x = 0; x = 0;
} else { } else {
@@ -84,6 +90,12 @@ impl<'a> Font<'a> {
} }
} }
pub fn height(&self) -> f32 {
let scale = Scale::uniform(self.size);
let m = self.font.v_metrics(scale);
m.ascent - m.descent
}
pub fn add_str_to_cache(&mut self, s: &str) { pub fn add_str_to_cache(&mut self, s: &str) {
for ch in s.chars() { for ch in s.chars() {
if self.glyphs.get(&ch).is_none() { if self.glyphs.get(&ch).is_none() {
@@ -95,9 +107,8 @@ impl<'a> Font<'a> {
pub fn draw_text( pub fn draw_text(
&self, &self,
buf: &mut Buffer, buf: &mut BufferView,
bg: &Color, c: Color,
c: &Color,
s: &str, s: &str,
) -> Result<(u32, u32), ::std::io::Error> { ) -> Result<(u32, u32), ::std::io::Error> {
let mut x_off = 0; let mut x_off = 0;
@@ -119,7 +130,7 @@ impl<'a> Font<'a> {
} }
} }
for glyph in glyphs { for glyph in glyphs {
glyph.draw(buf, (x_off, -off), bg, c); glyph.draw(buf, (x_off, -off), c);
x_off += glyph.advance; x_off += glyph.advance;
} }
@@ -128,9 +139,8 @@ impl<'a> Font<'a> {
pub fn draw_text_with_cursor( pub fn draw_text_with_cursor(
&self, &self,
buf: &mut Buffer, buf: &mut BufferView,
bg: &Color, c: Color,
c: &Color,
s: &str, s: &str,
cursor: usize, cursor: usize,
) -> Result<(u32, u32), ::std::io::Error> { ) -> Result<(u32, u32), ::std::io::Error> {
@@ -153,12 +163,12 @@ impl<'a> Font<'a> {
} }
} }
let height = buf.get_bounds().3; let height = self.size.ceil() as u32;
for (i, glyph) in glyphs.iter().enumerate() { for (i, glyph) in glyphs.iter().enumerate() {
if i == cursor { if i == cursor {
self.draw_cursor(buf, c, x_off as u32, height)?; self.draw_cursor(buf, c, x_off as u32, height)?;
} }
glyph.draw(buf, (x_off, -off), bg, c); glyph.draw(buf, (x_off, -off), c);
x_off += glyph.advance; x_off += glyph.advance;
} }
if cursor == glyphs.len() { if cursor == glyphs.len() {
@@ -168,16 +178,20 @@ impl<'a> Font<'a> {
Ok((x_off as u32, self.size as u32)) Ok((x_off as u32, self.size as u32))
} }
pub fn draw_cursor( fn draw_cursor(
&self, &self,
buf: &mut Buffer, buf: &mut BufferView,
c: &Color, c: Color,
offset: u32, offset: u32,
height: u32, height: u32,
) -> Result<(), ::std::io::Error> { ) -> Result<(), ::std::io::Error> {
// draw cursor // draw cursor
for i in 1..height { let bounds = buf.get_bounds();
buf.put((offset, i), c)? if offset + 1 > bounds.2 {
return Ok(());
}
for i in bounds.1 + 1..height + bounds.1 {
buf.put_raw((bounds.0 + offset, i), c);
} }
Ok(()) Ok(())
} }
@@ -205,32 +219,29 @@ impl<'a> Font<'a> {
pub fn auto_draw_text( pub fn auto_draw_text(
&mut self, &mut self,
buf: &mut Buffer, buf: &mut BufferView,
bg: &Color, c: Color,
c: &Color,
s: &str, s: &str,
) -> Result<(u32, u32), ::std::io::Error> { ) -> Result<(u32, u32), ::std::io::Error> {
self.add_str_to_cache(s); self.add_str_to_cache(s);
self.draw_text(buf, bg, c, s) self.draw_text(buf, c, s)
} }
pub fn auto_draw_text_with_cursor( pub fn auto_draw_text_with_cursor(
&mut self, &mut self,
buf: &mut Buffer, buf: &mut BufferView,
bg: &Color, c: Color,
c: &Color,
s: &str, s: &str,
cursor: usize, cursor: usize,
) -> Result<(u32, u32), ::std::io::Error> { ) -> Result<(u32, u32), ::std::io::Error> {
self.add_str_to_cache(s); self.add_str_to_cache(s);
self.draw_text_with_cursor(buf, bg, c, s, cursor) self.draw_text_with_cursor(buf, c, s, cursor)
} }
pub fn draw_text_fixed_width( pub fn draw_text_fixed_width(
&self, &self,
buf: &mut Buffer, buf: &mut BufferView,
bg: &Color, c: Color,
c: &Color,
distances: &[u32], distances: &[u32],
s: &str, s: &str,
) -> Result<(u32, u32), ::std::io::Error> { ) -> Result<(u32, u32), ::std::io::Error> {
@@ -253,7 +264,7 @@ impl<'a> Font<'a> {
} }
} }
for (idx, glyph) in glyphs.into_iter().enumerate() { for (idx, glyph) in glyphs.into_iter().enumerate() {
glyph.draw(buf, (x_off, -off), bg, c); glyph.draw(buf, (x_off, -off), c);
x_off += distances[idx] as i32; x_off += distances[idx] as i32;
} }
@@ -262,8 +273,7 @@ impl<'a> Font<'a> {
pub fn draw_text_individual_colors( pub fn draw_text_individual_colors(
&self, &self,
buf: &mut Buffer, buf: &mut BufferView,
bg: &Color,
color: &[Color], color: &[Color],
s: &str, s: &str,
) -> Result<(u32, u32), ::std::io::Error> { ) -> Result<(u32, u32), ::std::io::Error> {
@@ -286,7 +296,7 @@ impl<'a> Font<'a> {
} }
} }
for (idx, glyph) in glyphs.into_iter().enumerate() { for (idx, glyph) in glyphs.into_iter().enumerate() {
glyph.draw(buf, (x_off, -off), bg, &color[idx]); glyph.draw(buf, (x_off, -off), color[idx]);
x_off += glyph.advance; x_off += glyph.advance;
} }
@@ -295,32 +305,30 @@ impl<'a> Font<'a> {
pub fn auto_draw_text_individual_colors( pub fn auto_draw_text_individual_colors(
&mut self, &mut self,
buf: &mut Buffer, buf: &mut BufferView,
bg: &Color,
color: &[Color], color: &[Color],
s: &str, s: &str,
) -> Result<(u32, u32), ::std::io::Error> { ) -> Result<(u32, u32), ::std::io::Error> {
self.add_str_to_cache(s); self.add_str_to_cache(s);
self.draw_text_individual_colors(buf, bg, color, s) self.draw_text_individual_colors(buf, color, s)
} }
} }
pub fn draw_box(buf: &mut Buffer, c: &Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> { pub fn draw_box(buf: &mut BufferView, c: Color, dim: (u32, u32)) -> Result<(), ::std::io::Error> {
for x in 0..dim.0 { let bounds = buf.get_bounds();
let _ = buf.put((x, 0), c); buf.put_line_raw((bounds.0, bounds.1), dim.0, c);
let _ = buf.put((x, dim.1 - 1), c); buf.put_line_raw((bounds.0, bounds.1 + dim.1 - 1), dim.0, c);
} for y in bounds.1..dim.1 + bounds.1 {
for y in 0..dim.1 { buf.put_raw((bounds.0, y), c);
buf.put((0, y), c)?; buf.put_raw((bounds.0 + dim.0 - 1, y), c);
buf.put((dim.0 - 1, y), c)?;
} }
Ok(()) Ok(())
} }
pub fn draw_bar( pub fn draw_bar(
buf: &mut Buffer, buf: &mut BufferView,
color: &Color, color: Color,
length: u32, length: u32,
height: u32, height: u32,
fill: f32, fill: f32,
@@ -329,10 +337,9 @@ pub fn draw_bar(
if fill_pos > length { if fill_pos > length {
fill_pos = length; fill_pos = length;
} }
for y in 0..height { let bounds = buf.get_bounds();
for x in 0..fill_pos { for y in bounds.1..height + bounds.1 {
let _ = buf.put((x, y), color); buf.put_line_raw((bounds.0, y), fill_pos, color);
}
} }
Ok(()) Ok(())
+61
View File
@@ -0,0 +1,61 @@
use std::sync::{Arc, Mutex};
use calloop::ping::Ping;
use crate::keyboard::KeyEvent;
#[derive(Copy, Clone, Debug)]
pub enum PointerButton {
Left,
Right,
Middle,
ScrollVertical(f64),
ScrollHorizontal(f64),
}
#[derive(Debug)]
pub struct PointerEvent {
pub button: PointerButton,
pub pos: (u32, u32),
}
#[derive(Debug)]
pub enum Event {
NewMinute,
PowerUpdate,
LauncherUpdate,
AudioUpdate,
KeyEvent(KeyEvent),
PointerEvent(PointerEvent),
TokenUpdate(String),
FocusLost,
}
pub struct Events {
dirty: bool,
events: Vec<Event>,
ping: Ping,
}
impl Events {
pub fn new(ping: Ping) -> Arc<Mutex<Events>> {
Arc::new(Mutex::new(Events {
dirty: false,
events: Vec::new(),
ping,
}))
}
pub fn add_event(&mut self, ev: Event) {
self.events.push(ev);
if !self.dirty {
self.dirty = true;
self.ping.ping();
}
}
pub fn flush(&mut self) -> Vec<Event> {
self.dirty = false;
self.events.drain(..).collect()
}
}
+128 -16
View File
@@ -1,30 +1,29 @@
//! Utility module for fonts //! Utility module for fonts
use fontconfig::Fontconfig as FontConfig; use crate::draw;
use rusttype::Font; use rusttype::Font;
use std::{ use std::{
collections::HashMap, cell::RefCell, collections::HashMap, fs::File, hash, io::Read, mem, path::Path, rc::Rc, thread,
fs::File,
io::Read,
path::{Path, PathBuf},
}; };
/// FontMap is used to store different font configurations
pub type FontMap<'a> = HashMap<String, rusttype::Font<'a>>;
/// FontRef is used to store Fonts on widgets. /// FontRef is used to store Fonts on widgets.
pub type FontRef<'a> = &'a rusttype::Font<'a>; pub type FontRef<'a> = &'a rusttype::Font<'a>;
/// FontSeeker is a marker struct that is used to look up fonts #[cfg(feature = "fontconfig")]
pub(crate) struct FontSeeker; pub(crate) fn find_font(name: &str) -> String {
use fontconfig::Fontconfig as FontConfig;
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(); let fc = FontConfig::new().unwrap();
fc.find(name, None).unwrap().path fc.find(name, None)
.unwrap()
.path
.to_str()
.unwrap()
.to_string()
} }
#[cfg(not(feature = "fontconfig"))]
pub(crate) fn find_font(_name: &str) -> String {
panic!("fontconfig not enabled so font search not available");
} }
/// FontLoader is a marker struct that is used to load files /// FontLoader is a marker struct that is used to load files
@@ -45,3 +44,116 @@ impl FontLoader {
Font::try_from_vec(data) 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>>,
font_paths: HashMap<&'static str, String>,
required_fonts: HashMap<&'static str, (&'static str, Vec<(f32, &'static str)>)>,
}
impl FontMap {
pub fn new() -> FontMap {
FontMap {
fonts: HashMap::new(),
font_paths: HashMap::new(),
required_fonts: HashMap::new(),
}
}
pub fn queue_font(&mut self, font_name: &'static str, size: f32, preload: &'static str) {
match self.required_fonts.get_mut(font_name) {
Some(v) => v.1.push((size, preload)),
None => {
self.required_fonts
.insert(font_name, (font_name, vec![(size, preload)]));
}
}
}
pub fn add_font_path(&mut self, font_name: &'static str, font_path: String) {
self.font_paths.insert(font_name, font_path);
}
pub fn load_fonts(&mut self) {
for (font_name, v) in self.required_fonts.iter() {
let path = match self.font_paths.get(font_name) {
Some(res) => res,
_ => {
let s = find_font(font_name);
self.font_paths.insert(font_name, s);
self.font_paths.get(font_name).unwrap()
}
};
let fontref = Box::leak(Box::new(
FontLoader::from_path(path).expect("unable to load font"),
));
for (size, preload) in &v.1 {
if let Some(font) = self.fonts.get_mut(&(font_name, ComparableF32(*size))) {
font.add_str_to_cache(preload);
} else {
let mut font = draw::Font::new(fontref, *size);
font.add_str_to_cache(preload);
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")
}
}
pub enum MaybeFontMap {
Waiting(thread::JoinHandle<FontMap>),
Ready(Rc<RefCell<FontMap>>),
Invalid,
}
impl MaybeFontMap {
pub fn unwrap(&self) -> Rc<RefCell<FontMap>> {
match self {
MaybeFontMap::Ready(f) => f.clone(),
_ => panic!("fontmap not yet ready"),
}
}
pub 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!(),
}
}
}
}
-269
View File
@@ -1,269 +0,0 @@
#![allow(dead_code, non_camel_case_types, clippy::identity_op)]
#![cfg_attr(rustfmt, rustfmt_skip)]
use std::os::raw::{c_char, c_int, c_void, c_uint};
pub const XKB_MOD_NAME_SHIFT : &[u8] = b"Shift\0";
pub const XKB_MOD_NAME_CAPS : &[u8] = b"Lock\0";
pub const XKB_MOD_NAME_CTRL : &[u8] = b"Control\0";
pub const XKB_MOD_NAME_ALT : &[u8] = b"Mod1\0";
pub const XKB_MOD_NAME_NUM : &[u8] = b"Mod2\0";
pub const XKB_MOD_NAME_LOGO : &[u8] = b"Mod4\0";
pub const XKB_LED_NAME_CAPS : &[u8] = b"Caps Lock\0";
pub const XKB_LED_NAME_NUM : &[u8] = b"Num Lock\0";
pub const XKB_LED_NAME_SCROLL : &[u8] = b"Scroll Lock\0";
pub struct xkb_context;
pub struct xkb_keymap;
pub struct xkb_state;
pub struct xkb_compose_table;
pub struct xkb_compose_state;
pub type xkb_keycode_t = u32;
pub type xkb_keysym_t = u32;
pub type xkb_layout_index_t = u32;
pub type xkb_layout_mask_t = u32;
pub type xkb_level_index_t = u32;
pub type xkb_mod_index_t = u32;
pub type xkb_mod_mask_t = u32;
pub type xkb_led_index_t = u32;
pub type xkb_led_mask_t = u32;
pub const XKB_KEYCODE_INVALID :u32 = 0xffff_ffff;
pub const XKB_LAYOUT_INVALID :u32 = 0xffff_ffff;
pub const XKB_LEVEL_INVALID :u32 = 0xffff_ffff;
pub const XKB_MOD_INVALID :u32 = 0xffff_ffff;
pub const XKB_LED_INVALID :u32 = 0xffff_ffff;
pub const XKB_KEYCODE_MAX :u32 = 0xffff_fffe;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct xkb_rule_names {
pub rules: *const c_char,
pub model: *const c_char ,
pub layout: *const c_char,
pub variant: *const c_char,
pub options: *const c_char,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_keysym_flags {
/** Do not apply any flags. */
XKB_KEYSYM_NO_FLAGS = 0,
/** Find keysym by case-insensitive search. */
XKB_KEYSYM_CASE_INSENSITIVE = (1 << 0)
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_context_flags {
/** Do not apply any context flags. */
XKB_CONTEXT_NO_FLAGS = 0,
/** Create this context with an empty include path. */
XKB_CONTEXT_NO_DEFAULT_INCLUDES = (1 << 0),
/**
* Don't take RMLVO names from the environment.
* @since 0.3.0
*/
XKB_CONTEXT_NO_ENVIRONMENT_NAMES = (1 << 1)
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_log_level {
/** Log critical internal errors only. */
XKB_LOG_LEVEL_CRITICAL = 10,
/** Log all errors. */
XKB_LOG_LEVEL_ERROR = 20,
/** Log warnings and errors. */
XKB_LOG_LEVEL_WARNING = 30,
/** Log information, warnings, and errors. */
XKB_LOG_LEVEL_INFO = 40,
/** Log everything. */
XKB_LOG_LEVEL_DEBUG = 50
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_keymap_compile_flags {
/** Do not apply any flags. */
XKB_KEYMAP_COMPILE_NO_FLAGS = 0,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_keymap_format {
/** Cannot be used for creation */
XKB_KEYMAP_USE_ORIGINAL_FORMAT = 0,
/** The current/classic XKB text format, as generated by xkbcomp -xkb. */
XKB_KEYMAP_FORMAT_TEXT_V1 = 1,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_key_direction {
/** The key was released. */
XKB_KEY_UP,
/** The key was pressed. */
XKB_KEY_DOWN
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_compose_compile_flags {
XKB_COMPOSE_COMPILE_NO_FLAGS = 0
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_compose_format {
XKB_COMPOSE_FORMAT_TEXT_V1 = 1
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_compose_state_flags {
XKB_COMPOSE_STATE_NO_FLAGS = 0
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_compose_status {
XKB_COMPOSE_NOTHING,
XKB_COMPOSE_COMPOSING,
XKB_COMPOSE_COMPOSED,
XKB_COMPOSE_CANCELLED
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum xkb_compose_feed_result {
XKB_COMPOSE_FEED_IGNORED,
XKB_COMPOSE_FEED_ACCEPTED
}
bitflags::bitflags!(
pub struct xkb_state_component: u32 {
/** Depressed modifiers, i.e. a key is physically holding them. */
const XKB_STATE_MODS_DEPRESSED = (1 << 0);
/** Latched modifiers, i.e. will be unset after the next non-modifier
* key press. */
const XKB_STATE_MODS_LATCHED = (1 << 1);
/** Locked modifiers, i.e. will be unset after the key provoking the
* lock has been pressed again. */
const XKB_STATE_MODS_LOCKED = (1 << 2);
/** Effective modifiers, i.e. currently active and affect key
* processing (derived from the other state components).
* Use this unless you explictly care how the state came about. */
const XKB_STATE_MODS_EFFECTIVE = (1 << 3);
/** Depressed layout, i.e. a key is physically holding it. */
const XKB_STATE_LAYOUT_DEPRESSED = (1 << 4);
/** Latched layout, i.e. will be unset after the next non-modifier
* key press. */
const XKB_STATE_LAYOUT_LATCHED = (1 << 5);
/** Locked layout, i.e. will be unset after the key provoking the lock
* has been pressed again. */
const XKB_STATE_LAYOUT_LOCKED = (1 << 6);
/** Effective layout, i.e. currently active and affects key processing
* (derived from the other state components).
* Use this unless you explictly care how the state came about. */
const XKB_STATE_LAYOUT_EFFECTIVE = (1 << 7);
/** LEDs (derived from the other state components). */
const XKB_STATE_LEDS = (1 << 8);
}
);
dlopen_external_library!(XkbCommon,
functions:
fn xkb_keysym_get_name(xkb_keysym_t, *mut c_char, usize) -> c_int,
fn xkb_keysym_from_name(*const c_char, xkb_keysym_flags) -> xkb_keysym_t,
fn xkb_keysym_to_utf8(xkb_keysym_t, *mut c_char, usize) -> c_int,
fn xkb_keysym_to_utf32(xkb_keysym_t) -> u32,
fn xkb_context_new(xkb_context_flags) -> *mut xkb_context,
fn xkb_context_ref(*mut xkb_context) -> *mut xkb_context,
fn xkb_context_unref(*mut xkb_context) -> (),
fn xkb_context_set_user_data(*mut xkb_context, *mut c_void) -> (),
fn xkb_context_get_user_data(*mut xkb_context) -> *mut c_void,
fn xkb_context_include_path_append(*mut xkb_context, *const c_char) -> c_int,
fn xkb_context_include_path_append_default(*mut xkb_context) -> c_int,
fn xkb_context_include_path_reset_defaults(*mut xkb_context) -> c_int,
fn xkb_context_include_path_clear(*mut xkb_context) -> (),
fn xkb_context_num_include_paths(*mut xkb_context) -> c_uint,
fn xkb_context_include_path_get(*mut xkb_context, c_uint) -> *const c_char,
fn xkb_context_set_log_level(*mut xkb_context, xkb_log_level) -> (),
fn xkb_context_get_log_level(*mut xkb_context) -> xkb_log_level,
fn xkb_context_set_log_verbosity(*mut xkb_context, c_int) -> (),
fn xkb_context_get_log_verbosity(*mut xkb_context) -> c_int,
fn xkb_keymap_new_from_names(*mut xkb_context,
*const xkb_rule_names,
xkb_keymap_compile_flags
) -> *mut xkb_keymap,
fn xkb_keymap_new_from_string(*mut xkb_context,
*const c_char,
xkb_keymap_format,
xkb_keymap_compile_flags
) -> *mut xkb_keymap,
fn xkb_keymap_new_from_buffer(*mut xkb_context,
*const c_char,
usize,
xkb_keymap_format,
xkb_keymap_compile_flags
) -> *mut xkb_keymap,
fn xkb_keymap_ref(*mut xkb_keymap) -> *mut xkb_keymap,
fn xkb_keymap_unref(*mut xkb_keymap) -> (),
fn xkb_keymap_get_as_string(*mut xkb_keymap, xkb_keymap_format) -> *const c_char,
fn xkb_keymap_key_repeats(*mut xkb_keymap, xkb_keycode_t) -> c_int,
fn xkb_state_new(*mut xkb_keymap) -> *mut xkb_state,
fn xkb_state_ref(*mut xkb_state) -> *mut xkb_state,
fn xkb_state_unref(*mut xkb_state) -> (),
fn xkb_state_update_mask(*mut xkb_state,
xkb_mod_mask_t,
xkb_mod_mask_t,
xkb_mod_mask_t,
xkb_layout_index_t,
xkb_layout_index_t,
xkb_layout_index_t
) -> xkb_state_component,
fn xkb_state_update_key(*mut xkb_state,
xkb_keycode_t,
xkb_key_direction
) -> xkb_state_component,
fn xkb_state_key_get_syms(*mut xkb_state,
xkb_keycode_t,
*const *mut xkb_keysym_t
) -> c_int,
fn xkb_state_key_get_utf8(*mut xkb_state,
xkb_keycode_t,
*mut c_char,
usize
) -> c_int,
fn xkb_state_key_get_utf32(*mut xkb_state, xkb_keycode_t) -> u32,
fn xkb_state_key_get_one_sym(*mut xkb_state, xkb_keycode_t) -> xkb_keysym_t,
fn xkb_state_mod_name_is_active(*mut xkb_state, *const c_char, xkb_state_component) -> c_int,
fn xkb_compose_table_new_from_locale(*mut xkb_context, *const c_char, xkb_compose_compile_flags) -> *mut xkb_compose_table,
fn xkb_compose_table_unref(*mut xkb_compose_table) -> (),
fn xkb_compose_state_new(*mut xkb_compose_table, xkb_compose_state_flags) -> *mut xkb_compose_state,
fn xkb_compose_state_unref(*mut xkb_compose_state) -> (),
fn xkb_compose_state_feed(*mut xkb_compose_state, xkb_keysym_t) -> xkb_compose_feed_result,
fn xkb_compose_state_reset(*mut xkb_compose_state) -> (),
fn xkb_compose_state_get_status(*mut xkb_compose_state) -> xkb_compose_status,
fn xkb_compose_state_get_utf8(*mut xkb_compose_state, *mut c_char, usize) -> c_int,
fn xkb_compose_state_get_one_sym(*mut xkb_compose_state) -> xkb_keysym_t,
);
lazy_static::lazy_static!(
pub static ref XKBCOMMON_OPTION: Option<XkbCommon> = {
unsafe {
XkbCommon::open("libxkbcommon.so.0")
.or_else(|_| XkbCommon::open("libxkbcommon.so"))
.ok()
}
};
pub static ref XKBCOMMON_HANDLE: &'static XkbCommon = {
XKBCOMMON_OPTION.as_ref().expect("Library libxkbcommon.so could not be loaded.")
};
);
-17
View File
@@ -1,17 +0,0 @@
#![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_Right: u32 = 0xff53; /* Move right, right 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 */
+80 -264
View File
@@ -12,263 +12,99 @@
//! calloop event loop. Not doing so will prevent key repetition to work //! calloop event loop. Not doing so will prevent key repetition to work
//! (but the rest of the functionnality will not be affected). //! (but the rest of the functionnality will not be affected).
use std::{cell::RefCell, os::unix::io::RawFd, rc::Rc}; use std::{
default::Default,
use byteorder::{ByteOrder, NativeEndian}; os::unix::io::{AsRawFd, OwnedFd},
pub use wayland_client::protocol::wl_keyboard::KeyState;
use wayland_client::{
protocol::{wl_keyboard, wl_seat, wl_surface},
Attached,
}; };
mod ffi; use wayland_client::{protocol::wl_keyboard, WEnum};
pub mod keysyms; use xkbcommon::xkb;
pub use xkbcommon::xkb::keysyms;
mod repeat;
mod state; mod state;
pub use self::repeat::{KeyRepeatSource, RepeatMessage};
use self::state::KbState; use self::state::KbState;
pub use self::state::{ModifiersState, RMLVO}; pub use self::state::ModifiersState;
#[derive(Debug)] #[derive(Debug, Clone)]
/// An error that occurred while trying to initialize a mapped keyboard pub struct KeyEvent {
pub enum Error { pub rawkey: u32,
/// libxkbcommon is not available pub keysym: u32,
XKBNotFound, pub state: WEnum<wl_keyboard::KeyState>,
/// Provided RMLVO specified a keymap that would not be loaded pub modifiers: ModifiersState,
BadNames, pub utf8: Option<String>,
pub repeats: bool,
} }
/// Events received from a mapped keyboard pub struct KeymapDescription {
pub enum Event<'a> { fd: OwnedFd,
/// The keyboard focus has entered a surface
Enter {
/// serial number of the event
serial: u32,
/// surface that was entered
surface: wl_surface::WlSurface,
/// raw values of the currently pressed keys
rawkeys: &'a [u32],
/// interpreted symbols of the currently pressed keys
keysyms: &'a [u32],
},
/// The keyboard focus has left a surface
Leave {
/// serial number of the event
serial: u32,
/// surface that was left
surface: wl_surface::WlSurface,
},
/// The key modifiers have changed state
Modifiers {
/// current state of the modifiers
modifiers: ModifiersState,
},
/// A key event occurred
Key {
/// serial number of the event
serial: u32,
/// time at which the keypress occurred
time: u32,
/// raw value of the key
rawkey: u32,
/// interpreted symbol of the key
keysym: u32,
/// new state of the key
state: KeyState,
/// utf8 interpretation of the entered text
///
/// will always be `None` on key release events
utf8: Option<String>,
repeats: bool,
},
RepeatInfo {
rate: i32,
delay: i32,
},
}
/// Implement a keyboard for keymap translation with key repetition
///
/// This requires you to provide a callback to receive the events after they
/// have been interpreted with the keymap.
///
/// The keymap will be loaded from the provided RMLVO rules, or from the compositor
/// provided keymap if `None`.
///
/// Returns an error if xkbcommon could not be initialized, the RMLVO specification
/// contained invalid values, or if the provided seat does not have keyboard capability.
///
/// **Note:** This adapter does not handle key repetition. See `map_keyboard_repeat` for that.
pub fn map_keyboard<F>(
seat: &Attached<wl_seat::WlSeat>,
rmlvo: Option<RMLVO>,
callback: F,
) -> Result<wl_keyboard::WlKeyboard, Error>
where
F: FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>) + 'static,
{
let keyboard = seat.get_keyboard();
let state = Rc::new(RefCell::new(
rmlvo
.map(KbState::from_rmlvo)
.unwrap_or_else(KbState::new)?,
));
let callback = Rc::new(RefCell::new(callback));
// prepare the handler
let mut kbd_handler = KbdHandler { callback, state };
keyboard.quick_assign(move |keyboard, event, data| {
kbd_handler.event(keyboard.detach(), event, data)
});
Ok(keyboard.detach())
}
/*
* Classic handling
*/
type KbdCallback = dyn FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>);
struct KbdHandler {
state: Rc<RefCell<KbState>>,
callback: Rc<RefCell<KbdCallback>>,
}
impl KbdHandler {
fn event(
&mut self,
kbd: wl_keyboard::WlKeyboard,
event: wl_keyboard::Event,
dispatch_data: wayland_client::DispatchData,
) {
use wl_keyboard::Event;
match event {
Event::Keymap { format, fd, size } => self.keymap(kbd, format, fd, size),
Event::Enter {
serial,
surface,
keys,
} => self.enter(kbd, serial, surface, keys, dispatch_data),
Event::Leave { serial, surface } => self.leave(kbd, serial, surface, dispatch_data),
Event::Key {
serial,
time,
key,
state,
} => self.key(kbd, serial, time, key, state, dispatch_data),
Event::Modifiers {
mods_depressed,
mods_latched,
mods_locked,
group,
..
} => self.modifiers(
kbd,
mods_depressed,
mods_latched,
mods_locked,
group,
dispatch_data,
),
Event::RepeatInfo { rate, delay } => self.repeat_info(kbd, rate, delay, dispatch_data),
_ => {}
}
}
fn keymap(
&mut self,
_: wl_keyboard::WlKeyboard,
format: wl_keyboard::KeymapFormat,
fd: RawFd,
size: u32, size: u32,
) {
let mut state = self.state.borrow_mut();
if state.locked() {
// state is locked, ignore keymap updates
return;
} }
if state.ready() {
// new keymap, we first deinit to free resources impl KeymapDescription {
unsafe { fn new(fd: OwnedFd, size: u32) -> KeymapDescription {
state.de_init(); KeymapDescription { fd: fd, size: size }
} }
} }
impl Clone for KeymapDescription {
fn clone(&self) -> Self {
KeymapDescription::new(self.fd.try_clone().expect("unable to clone fd"), self.size)
}
}
pub struct Keyboard {
state: Option<KbState>,
desc: Option<KeymapDescription>,
}
impl Keyboard {
pub fn new() -> Keyboard {
Keyboard {
state: None,
desc: None,
}
}
pub fn keymap(&mut self, format: WEnum<wl_keyboard::KeymapFormat>, fd: OwnedFd, size: u32) {
match format { match format {
wl_keyboard::KeymapFormat::XkbV1 => unsafe { WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) => {
state.init_with_fd(fd, size as usize); self.desc = Some(KeymapDescription::new(fd, size));
}, }
wl_keyboard::KeymapFormat::NoKeymap => { WEnum::Value(wl_keyboard::KeymapFormat::NoKeymap) => {
// TODO: how to handle this (hopefully never occuring) case? // TODO: how to handle this (hopefully never occuring) case?
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn enter( pub fn resolve(&mut self) {
&mut self, if self.state.is_some() {
object: wl_keyboard::WlKeyboard, return;
serial: u32, }
surface: wl_surface::WlSurface,
keys: Vec<u8>, if let Some(desc) = self.desc.take() {
dispatch_data: wayland_client::DispatchData, self.state = Some(
) { KbState::new_from_fd(desc.fd, desc.size as usize).expect("unable to load keymap"),
let mut state = self.state.borrow_mut();
let rawkeys = keys
.chunks_exact(4)
.map(NativeEndian::read_u32)
.collect::<Vec<_>>();
let keys: Vec<u32> = rawkeys.iter().map(|k| state.get_one_sym_raw(*k)).collect();
(&mut *self.callback.borrow_mut())(
Event::Enter {
serial,
surface,
rawkeys: &rawkeys,
keysyms: &keys,
},
object,
dispatch_data,
); );
} }
fn leave(
&mut self,
object: wl_keyboard::WlKeyboard,
serial: u32,
surface: wl_surface::WlSurface,
dispatch_data: wayland_client::DispatchData,
) {
(&mut *self.callback.borrow_mut())(Event::Leave { serial, surface }, object, dispatch_data);
} }
fn key( pub fn key(&mut self, key: u32, key_state: WEnum<wl_keyboard::KeyState>) -> KeyEvent {
&mut self, if let Some(state) = &mut self.state {
object: wl_keyboard::WlKeyboard,
serial: u32,
time: u32,
key: u32,
key_state: wl_keyboard::KeyState,
dispatch_data: wayland_client::DispatchData,
) {
let (sym, utf8, repeats) = { let (sym, utf8, repeats) = {
let mut state = self.state.borrow_mut();
// Get the values to generate a key event // Get the values to generate a key event
let sym = state.get_one_sym_raw(key); let sym = state.get_one_sym_raw(key);
let utf8 = if key_state == wl_keyboard::KeyState::Pressed { let utf8 = if key_state == WEnum::Value(wl_keyboard::KeyState::Pressed) {
match state.compose_feed(sym) { match state.compose_feed(sym) {
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => { Some(xkb::compose::FeedResult::Accepted) => {
if let Some(status) = state.compose_status() { if let Some(status) = state.compose_status() {
match status { match status {
ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => { xkb::compose::Status::Composed => state.compose_get_utf8(),
state.compose_get_utf8() xkb::compose::Status::Nothing => state.get_utf8_raw(key),
}
ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => {
state.get_utf8_raw(key)
}
_ => None, _ => None,
} }
} else { } else {
@@ -287,58 +123,38 @@ impl KbdHandler {
} else { } else {
None None
}; };
let repeats = unsafe { state.key_repeats(key + 8) }; let repeats = state.key_repeats(xkb::Keycode::new(key + 8));
(sym, utf8, repeats) (sym, utf8, repeats)
}; };
KeyEvent {
(&mut *self.callback.borrow_mut())(
Event::Key {
serial,
time,
rawkey: key, rawkey: key,
keysym: sym, keysym: sym,
state: key_state, state: key_state,
modifiers: state.mods_state(),
utf8, utf8,
repeats, repeats,
}, }
object, } else {
dispatch_data, KeyEvent {
); rawkey: key,
keysym: 0,
state: key_state,
modifiers: Default::default(),
utf8: None,
repeats: false,
}
}
} }
fn modifiers( pub fn modifiers(
&mut self, &mut self,
object: wl_keyboard::WlKeyboard,
mods_depressed: u32, mods_depressed: u32,
mods_latched: u32, mods_latched: u32,
mods_locked: u32, mods_locked: u32,
group: u32, group: u32,
dispatch_data: wayland_client::DispatchData,
) { ) {
{ if let Some(state) = &mut self.state {
let mut state = self.state.borrow_mut();
state.update_modifiers(mods_depressed, mods_latched, mods_locked, group); state.update_modifiers(mods_depressed, mods_latched, mods_locked, group);
(&mut *self.callback.borrow_mut())(
Event::Modifiers {
modifiers: state.mods_state(),
},
object,
dispatch_data,
);
} }
} }
fn repeat_info(
&mut self,
object: wl_keyboard::WlKeyboard,
rate: i32,
delay: i32,
dispatch_data: wayland_client::DispatchData,
) {
(&mut *self.callback.borrow_mut())(
Event::RepeatInfo { rate, delay },
object,
dispatch_data,
);
}
} }
+173
View File
@@ -0,0 +1,173 @@
use std::time::Duration;
use calloop::{
channel::{self, Channel},
timer::{TimeoutAction, Timer},
EventSource, Poll, PostAction, Readiness, Token, TokenFactory,
};
use wayland_client::{protocol::wl_keyboard, WEnum};
use super::KeyEvent;
#[derive(Debug)]
pub enum RepeatMessage {
StopRepeat,
KeyEvent(KeyEvent),
RepeatInfo((u32, u32)),
}
#[derive(Debug)]
pub struct KeyRepeatSource {
channel: Channel<RepeatMessage>,
timer: Timer,
rate: Duration,
delay: Duration,
disabled: bool,
key: Option<KeyEvent>,
}
impl KeyRepeatSource {
pub fn new(ch: channel::Channel<RepeatMessage>) -> KeyRepeatSource {
KeyRepeatSource {
channel: ch,
timer: Timer::immediate(),
delay: Duration::ZERO,
rate: Duration::ZERO,
disabled: true,
key: None,
}
}
}
impl EventSource for KeyRepeatSource {
type Event = KeyEvent;
type Metadata = ();
type Ret = ();
type Error = calloop::Error;
fn process_events<F>(
&mut self,
readiness: Readiness,
token: Token,
mut callback: F,
) -> calloop::Result<PostAction>
where
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
{
let mut removed = false;
let timer = &mut self.timer;
let rate = &mut self.rate;
let delay = &mut self.delay;
let key = &mut self.key;
let disabled = &mut self.disabled;
let mut reregister = false;
let now = std::time::Instant::now();
// Check if the key repeat should stop
let channel_pa = self
.channel
.process_events(readiness, token, |event, _| match event {
channel::Event::Msg(message) => match message {
RepeatMessage::StopRepeat => {
key.take();
}
RepeatMessage::KeyEvent(event) => match event.state {
WEnum::Value(wl_keyboard::KeyState::Pressed) => {
key.replace(event);
reregister = true;
timer.set_deadline(now + *delay);
}
WEnum::Value(wl_keyboard::KeyState::Released) => match key {
Some(k) if k.keysym == event.keysym => {
key.take();
}
_ => (),
},
_ => (),
},
RepeatMessage::RepeatInfo((new_rate, new_delay)) => {
*rate = Duration::from_micros(1_000_000 / new_rate as u64);
*delay = Duration::from_millis(new_delay as u64);
*disabled = false;
if key.is_some() {
timer.set_deadline(now + *delay);
}
}
},
channel::Event::Closed => {
removed = true;
}
})
.map_err(|err| calloop::Error::OtherError(Box::new(err)))?;
// Keyboard was destroyed
if removed {
return Ok(PostAction::Remove);
}
// Re-register the timer to start it again
if reregister {
return Ok(PostAction::Reregister);
}
let timer_pa = timer.process_events(readiness, token, |deadline, _| {
if self.disabled || key.is_none() {
return TimeoutAction::Drop;
}
if deadline - now > std::time::Duration::from_millis(5) {
// We have not yet reached the deadline within our tolerance
return TimeoutAction::ToInstant(deadline);
}
// Invoke the event
callback(key.as_ref().unwrap().clone(), &mut ());
let mut next_deadline = deadline;
if now - deadline > *rate * 2 {
// We're way behind, let's just set a new time
println!("KeyRepeatSource::process_events: More than two repeat cycles behind, resetting");
next_deadline = now + *rate;
} else {
while next_deadline < now {
next_deadline += *rate;
}
}
TimeoutAction::ToInstant(next_deadline)
})?;
// Only disable or remove if both want to, otherwise continue or re-register
Ok(match (timer_pa, channel_pa) {
(PostAction::Disable, PostAction::Disable) => PostAction::Disable,
(PostAction::Remove, PostAction::Remove) => PostAction::Remove,
(PostAction::Reregister, _) | (_, PostAction::Reregister) => PostAction::Reregister,
_ => PostAction::Continue,
})
}
fn register(
&mut self,
poll: &mut Poll,
token_factory: &mut TokenFactory,
) -> calloop::Result<()> {
self.channel.register(poll, token_factory)?;
self.timer.register(poll, token_factory)
}
fn reregister(
&mut self,
poll: &mut Poll,
token_factory: &mut TokenFactory,
) -> calloop::Result<()> {
self.channel.reregister(poll, token_factory)?;
self.timer.reregister(poll, token_factory)
}
fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
self.channel.unregister(poll)?;
self.timer.unregister(poll)
}
}
+68 -305
View File
@@ -1,48 +1,14 @@
use std::{ use std::{env, os::unix::io::OwnedFd};
env,
ffi::CString,
fs::File,
os::raw::c_char,
os::unix::{
ffi::OsStringExt,
io::{FromRawFd, RawFd},
},
ptr,
};
use memmap::MmapOptions; use xkbcommon::xkb;
use super::ffi::{self, xkb_state_component, XKBCOMMON_HANDLE as XKBH};
use super::Error;
pub struct KbState { pub struct KbState {
xkb_context: *mut ffi::xkb_context, xkb_context: xkb::Context,
xkb_keymap: *mut ffi::xkb_keymap, xkb_keymap: xkb::Keymap,
xkb_state: *mut ffi::xkb_state, xkb_state: xkb::State,
xkb_compose_table: *mut ffi::xkb_compose_table, xkb_compose_table: Option<xkb::compose::Table>,
xkb_compose_state: *mut ffi::xkb_compose_state, xkb_compose_state: Option<xkb::compose::State>,
mods_state: ModifiersState, mods_state: ModifiersState,
locked: bool,
}
/// The RMLVO description of a keymap
///
/// All fields are optional, and the system default
/// will be used if set to `None`.
pub struct RMLVO {
/// The rules file to use
pub rules: Option<String>,
/// The keyboard model by which to interpret keycodes and LEDs
pub model: Option<String>,
/// A comma separated list of layouts (languages) to include in the keymap
pub layout: Option<String>,
/// A comma separated list of variants, one per layout, which may modify or
/// augment the respective layout in various ways
pub variant: Option<String>,
/// A comma separated list of options, through which the user specifies
/// non-layout related preferences, like which key combinations are
/// used for switching layouts, or which key is the Compose key.
pub options: Option<String>,
} }
/// Represents the current state of the keyboard modifiers /// Represents the current state of the keyboard modifiers
@@ -74,52 +40,19 @@ impl ModifiersState {
ModifiersState::default() ModifiersState::default()
} }
fn update_with(&mut self, state: *mut ffi::xkb_state) { fn update_with(&mut self, state: &xkb::State) {
self.ctrl = unsafe { self.ctrl = state.mod_name_is_active("Control", xkb::STATE_MODS_EFFECTIVE);
(XKBH.xkb_state_mod_name_is_active)( self.alt = state.mod_name_is_active("Mod1", xkb::STATE_MODS_EFFECTIVE);
state, self.shift = state.mod_name_is_active("Shift", xkb::STATE_MODS_EFFECTIVE);
ffi::XKB_MOD_NAME_CTRL.as_ptr() as *const c_char, self.caps_lock = state.mod_name_is_active("Lock", xkb::STATE_MODS_EFFECTIVE);
xkb_state_component::XKB_STATE_MODS_EFFECTIVE, self.num_lock = state.mod_name_is_active("Mod2", xkb::STATE_MODS_EFFECTIVE);
) > 0 self.logo = state.mod_name_is_active("Mod4", xkb::STATE_MODS_EFFECTIVE);
};
self.alt = unsafe {
(XKBH.xkb_state_mod_name_is_active)(
state,
ffi::XKB_MOD_NAME_ALT.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
};
self.shift = unsafe {
(XKBH.xkb_state_mod_name_is_active)(
state,
ffi::XKB_MOD_NAME_SHIFT.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
};
self.caps_lock = unsafe {
(XKBH.xkb_state_mod_name_is_active)(
state,
ffi::XKB_MOD_NAME_CAPS.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
};
self.logo = unsafe {
(XKBH.xkb_state_mod_name_is_active)(
state,
ffi::XKB_MOD_NAME_LOGO.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
};
self.num_lock = unsafe {
(XKBH.xkb_state_mod_name_is_active)(
state,
ffi::XKB_MOD_NAME_NUM.as_ptr() as *const c_char,
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
) > 0
};
} }
} }
// Safety: No.
unsafe impl Send for KbState {}
impl KbState { impl KbState {
pub(crate) fn update_modifiers( pub(crate) fn update_modifiers(
&mut self, &mut self,
@@ -128,157 +61,74 @@ impl KbState {
mods_locked: u32, mods_locked: u32,
group: u32, group: u32,
) { ) {
if !self.ready() { let mask =
return; self.xkb_state
} .update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
let mask = unsafe { if (mask & xkb::STATE_MODS_EFFECTIVE) > 0 {
(XKBH.xkb_state_update_mask)(
self.xkb_state,
mods_depressed,
mods_latched,
mods_locked,
0,
0,
group,
)
};
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
// effective value of mods have changed, we need to update our state // effective value of mods have changed, we need to update our state
self.mods_state.update_with(self.xkb_state); self.mods_state.update_with(&self.xkb_state);
} }
} }
pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 {
if !self.ready() { self.xkb_state
return 0; .key_get_one_sym(xkb::Keycode::new(keycode + 8))
} .raw()
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode + 8) }
} }
pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option<String> { pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option<String> {
if !self.ready() { Some(self.xkb_state.key_get_utf8(xkb::Keycode::new(keycode + 8)))
return None;
}
let size = unsafe {
(XKBH.xkb_state_key_get_utf8)(self.xkb_state, keycode + 8, ptr::null_mut(), 0)
} + 1;
if size <= 1 {
return None;
};
let mut buffer = Vec::with_capacity(size as usize);
unsafe {
buffer.set_len(size as usize);
(XKBH.xkb_state_key_get_utf8)(
self.xkb_state,
keycode + 8,
buffer.as_mut_ptr() as *mut _,
size as usize,
);
};
// remove the final `\0`
buffer.pop();
// libxkbcommon will always provide valid UTF8
Some(unsafe { String::from_utf8_unchecked(buffer) })
} }
pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> { pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option<xkb::compose::FeedResult> {
if !self.ready() || self.xkb_compose_state.is_null() { match &mut self.xkb_compose_state {
return None; Some(compose_state) => Some(compose_state.feed(xkb::Keysym::new(keysym))),
None => None,
} }
Some(unsafe { (XKBH.xkb_compose_state_feed)(self.xkb_compose_state, keysym) })
} }
pub(crate) fn compose_status(&mut self) -> Option<ffi::xkb_compose_status> { pub(crate) fn compose_status(&mut self) -> Option<xkb::compose::Status> {
if !self.ready() || self.xkb_compose_state.is_null() { match &mut self.xkb_compose_state {
return None; Some(compose_state) => Some(compose_state.status()),
None => None,
} }
Some(unsafe { (XKBH.xkb_compose_state_get_status)(self.xkb_compose_state) })
} }
pub(crate) fn compose_get_utf8(&mut self) -> Option<String> { pub(crate) fn compose_get_utf8(&mut self) -> Option<String> {
if !self.ready() || self.xkb_compose_state.is_null() { match &mut self.xkb_compose_state {
return None; Some(compose_state) => compose_state.utf8(),
None => None,
} }
let size = unsafe {
(XKBH.xkb_compose_state_get_utf8)(self.xkb_compose_state, ptr::null_mut(), 0)
} + 1;
if size <= 1 {
return None;
};
let mut buffer = Vec::with_capacity(size as usize);
unsafe {
buffer.set_len(size as usize);
(XKBH.xkb_compose_state_get_utf8)(
self.xkb_compose_state,
buffer.as_mut_ptr() as *mut _,
size as usize,
);
};
// remove the final `\0`
buffer.pop();
// libxkbcommon will always provide valid UTF8
Some(unsafe { String::from_utf8_unchecked(buffer) })
} }
pub(crate) fn new() -> Result<KbState, Error> { pub(crate) fn new_from_fd(fd: OwnedFd, size: usize) -> Result<KbState, std::io::Error> {
let xkbh = match ffi::XKBCOMMON_OPTION.as_ref() { let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
Some(h) => h, let xkb_keymap = unsafe {
None => return Err(Error::XKBNotFound), xkb::Keymap::new_from_fd(
&xkb_context,
fd,
size,
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
)?
.unwrap()
}; };
let xkb_context = let xkb_state = xkb::State::new(&xkb_keymap);
unsafe { (xkbh.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
if xkb_context.is_null() {
return Err(Error::XKBNotFound);
}
let mut me = KbState { let mut me = KbState {
xkb_context, xkb_context,
xkb_keymap: ptr::null_mut(), xkb_keymap,
xkb_state: ptr::null_mut(), xkb_state,
xkb_compose_table: ptr::null_mut(), xkb_compose_table: None,
xkb_compose_state: ptr::null_mut(), xkb_compose_state: None,
mods_state: ModifiersState::new(), mods_state: ModifiersState::new(),
locked: false,
}; };
unsafe {
me.init_compose(); me.init_compose();
}
Ok(me) Ok(me)
} }
pub(crate) fn from_rmlvo(rmlvo: RMLVO) -> Result<KbState, Error> { pub(crate) fn init_compose(&mut self) {
fn to_cstring(s: Option<String>) -> Result<Option<CString>, Error> {
s.map_or(Ok(None), |s| CString::new(s).map(Option::Some))
.map_err(|_| Error::BadNames)
}
let mut state = KbState::new()?;
let rules = to_cstring(rmlvo.rules)?;
let model = to_cstring(rmlvo.model)?;
let layout = to_cstring(rmlvo.layout)?;
let variant = to_cstring(rmlvo.variant)?;
let options = to_cstring(rmlvo.options)?;
let xkb_names = ffi::xkb_rule_names {
rules: rules.map_or(ptr::null(), |s| s.as_ptr()),
model: model.map_or(ptr::null(), |s| s.as_ptr()),
layout: layout.map_or(ptr::null(), |s| s.as_ptr()),
variant: variant.map_or(ptr::null(), |s| s.as_ptr()),
options: options.map_or(ptr::null(), |s| s.as_ptr()),
};
unsafe {
state.init_with_rmlvo(xkb_names)?;
}
state.locked = true;
Ok(state)
}
pub(crate) unsafe fn init_compose(&mut self) {
let locale = env::var_os("LC_ALL") let locale = env::var_os("LC_ALL")
.and_then(|v| if v.is_empty() { None } else { Some(v) }) .and_then(|v| if v.is_empty() { None } else { Some(v) })
.or_else(|| env::var_os("LC_CTYPE")) .or_else(|| env::var_os("LC_CTYPE"))
@@ -286,99 +136,24 @@ impl KbState {
.or_else(|| env::var_os("LANG")) .or_else(|| env::var_os("LANG"))
.and_then(|v| if v.is_empty() { None } else { Some(v) }) .and_then(|v| if v.is_empty() { None } else { Some(v) })
.unwrap_or_else(|| "C".into()); .unwrap_or_else(|| "C".into());
let locale = CString::new(locale.into_vec()).unwrap();
let compose_table = (XKBH.xkb_compose_table_new_from_locale)( let compose_table = match xkb::compose::Table::new_from_locale(
self.xkb_context, &self.xkb_context,
locale.as_ptr(), &locale,
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, xkb::compose::COMPILE_NO_FLAGS,
); ) {
Ok(t) => t,
Err(_) => return,
};
if compose_table.is_null() { let compose_state = xkb::compose::State::new(&compose_table, xkb::compose::STATE_NO_FLAGS);
// init of compose table failed, continue without compose
return; self.xkb_compose_table = Some(compose_table);
self.xkb_compose_state = Some(compose_state);
} }
let compose_state = (XKBH.xkb_compose_state_new)( pub(crate) fn key_repeats(&mut self, keycode: xkb::Keycode) -> bool {
compose_table, self.xkb_keymap.key_repeats(keycode)
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
);
if compose_state.is_null() {
// init of compose state failed, continue without compose
(XKBH.xkb_compose_table_unref)(compose_table);
return;
}
self.xkb_compose_table = compose_table;
self.xkb_compose_state = compose_state;
}
pub(crate) unsafe fn post_init(&mut self, xkb_keymap: *mut ffi::xkb_keymap) {
let xkb_state = (XKBH.xkb_state_new)(xkb_keymap);
self.xkb_keymap = xkb_keymap;
self.xkb_state = xkb_state;
self.mods_state.update_with(xkb_state);
}
pub(crate) unsafe fn de_init(&mut self) {
(XKBH.xkb_state_unref)(self.xkb_state);
self.xkb_state = ptr::null_mut();
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
self.xkb_keymap = ptr::null_mut();
}
pub(crate) unsafe fn init_with_fd(&mut self, fd: RawFd, size: usize) {
let map = MmapOptions::new()
.len(size)
.map(&File::from_raw_fd(fd))
.unwrap();
let xkb_keymap = (XKBH.xkb_keymap_new_from_string)(
self.xkb_context,
map.as_ptr() as *const _,
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
if xkb_keymap.is_null() {
panic!("Received invalid keymap from compositor.");
}
self.post_init(xkb_keymap);
}
pub(crate) unsafe fn init_with_rmlvo(
&mut self,
names: ffi::xkb_rule_names,
) -> Result<(), Error> {
let xkb_keymap = (XKBH.xkb_keymap_new_from_names)(
self.xkb_context,
&names,
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
);
if xkb_keymap.is_null() {
return Err(Error::BadNames);
}
self.post_init(xkb_keymap);
Ok(())
}
pub(crate) unsafe fn key_repeats(&mut self, xkb_keycode_t: ffi::xkb_keycode_t) -> bool {
(XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, xkb_keycode_t) == 1
}
#[inline]
pub(crate) fn ready(&self) -> bool {
!self.xkb_state.is_null()
}
#[inline]
pub(crate) fn locked(&self) -> bool {
self.locked
} }
#[inline] #[inline]
@@ -386,15 +161,3 @@ impl KbState {
self.mods_state self.mods_state
} }
} }
impl Drop for KbState {
fn drop(&mut self) {
unsafe {
(XKBH.xkb_compose_state_unref)(self.xkb_compose_state);
(XKBH.xkb_compose_table_unref)(self.xkb_compose_table);
(XKBH.xkb_state_unref)(self.xkb_state);
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
(XKBH.xkb_context_unref)(self.xkb_context);
}
}
}
+310 -346
View File
@@ -1,389 +1,353 @@
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Write};
use std::os::unix::io::AsRawFd;
use std::os::unix::net::{UnixListener, UnixStream};
use std::{collections::HashMap, sync::mpsc::channel};
use chrono::{Duration, Local};
use nix::poll::{poll, PollFd, PollFlags};
use os_pipe::pipe;
use timerfd::{SetTimeFlags, TimerFd, TimerState};
mod app;
mod buffer; mod buffer;
mod cmd;
mod color; mod color;
mod config; mod config;
mod configfmt;
mod data;
mod desktop;
mod doublemempool;
mod draw; mod draw;
mod event;
mod fonts; mod fonts;
mod keyboard; mod keyboard;
mod widget; mod state;
mod utils;
mod widgets; mod widgets;
#[macro_use] use wayland_client::{Connection, QueueHandle, WaylandSource};
extern crate dlib;
use app::{App, OutputMode}; use calloop::{
use cmd::Cmd; timer::{TimeoutAction, Timer},
EventLoop,
};
use chrono::{Duration, Local, Timelike};
use buffer::BufferView;
use config::Config; use config::Config;
use configfmt::ConfigFmt; use event::{Event, Events};
use fonts::{FontLoader, FontMap, FontSeeker}; use fonts::{FontMap, MaybeFontMap};
use widget::WaitContext; use keyboard::{KeyRepeatSource, RepeatMessage};
use state::State;
use utils::{
desktop::{load_desktop_files, write_desktop_cache},
xdg,
};
use widgets::{Geometry, Widget};
enum Mode { use std::{
Start, env,
Daemonize, fs::File,
StartOrKill, io::{BufRead, BufReader, Write},
ToggleVisible, os::unix::net::{UnixListener, UnixStream},
PrintConfig(ConfigFmt), rc::Rc,
thread,
};
fn print_usage() {
println!(
"
usage: wldash [OPTIONS]
OPTIONS:
--config CONFIG_FILE use the specified config file
(~/.config/wldash/config.yml by default)
--write-default-config [v1|v2|toplevel] generate and write a new config file
(v2 by default)
--desktop-refresh refresh desktop file cache
"
);
} }
fn main() { fn main() {
let mut args = env::args();
// Skip program name
_ = args.next();
let mut config_file = format!("{}/wldash/config.yml", xdg::config_folder());
loop {
match args.next() {
Some(ref s) if s == "--config" => match args.next() {
Some(ref s) => config_file = s.clone(),
None => panic!("missing argument to --config"),
},
Some(ref s) if s == "--write-default-config" => {
let config = match args.next() {
Some(ref s) if s == "v1" => Config::generate_v1(),
Some(ref s) if s == "v2" => Config::generate_v2(false),
Some(ref s) if s == "toplevel" => Config::generate_v2(true),
None => Config::generate_v2(false),
Some(s) => panic!("unknown argument: {}", s),
};
match File::create(config_file) {
Ok(f) => serde_yaml::to_writer(f, &config).unwrap(),
Err(_) => panic!("uh"),
}
std::process::exit(0);
}
Some(ref s) if s == "--desktop-refresh" => {
let v = load_desktop_files();
write_desktop_cache(&v).unwrap();
std::process::exit(0);
}
Some(ref s) if s == "--help" => {
print_usage();
std::process::exit(0);
}
Some(_) => {
print_usage();
std::process::exit(1);
}
None => break,
}
}
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, ());
display.sync(&qhandle, ());
let (ping_sender, ping_source) = calloop::ping::make_ping().unwrap();
let (keyrepeat_sender, keyrepeat_channel) = calloop::channel::channel::<RepeatMessage>();
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| {
let events = state.events.lock().unwrap().flush();
for widget in state.widgets.iter_mut() {
for event in events.iter() {
widget.event(event);
}
}
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;
for widget in state.widgets.iter_mut() {
widget.event(&Event::NewMinute);
}
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 keyrepeat_source = KeyRepeatSource::new(keyrepeat_channel);
handle
.insert_source(keyrepeat_source, |event, _metadata, state| {
let ev = Event::KeyEvent(event);
for widget in state.widgets.iter_mut() {
widget.event(&ev);
}
state.dirty = true;
})
.expect("Failed to insert keyrepeat source!");
let socket_path = match env::var("XDG_RUNTIME_DIR") { let socket_path = match env::var("XDG_RUNTIME_DIR") {
Ok(dir) => dir + "/wldash", Ok(dir) => dir + "/wldash",
Err(_) => "/tmp/wldash".to_string(), Err(_) => "/tmp/wldash".to_string(),
}; };
let config_home = match env::var("XDG_CONFIG_HOME") {
Ok(dir) => dir + "/wldash", let events = Events::new(ping_sender);
Err(_) => match env::var("HOME") {
Ok(home) => home + "/.config/wldash", let mut fm = FontMap::new();
_ => panic!("unable to find user folder"),
}, let config: Config = match File::open(config_file) {
Ok(f) => serde_yaml::from_reader(f).unwrap(),
Err(_) => panic!("configuration file missing"),
}; };
// From all existing files take the first readable one and write it's extension to `ext` if let Some(true) = config.server {
let mut ext = [0x0; 8];
let file = configfmt::CONFIG_NAMES
.iter()
.map(|name| std::path::Path::new(&config_home).join(name))
.filter_map(|path| {
match File::open(&path) {
Ok(file) => {
let e = path.extension().and_then(|e| e.to_str())?;
let len = e.len();
let from = len.saturating_sub(8); // the longest possible extension
ext[0..len - from].copy_from_slice(&e.as_bytes()[from..]);
Some(file)
}
Err(_) => None,
}
})
.next();
let fmt = std::str::from_utf8(&ext)
.ok()
.and_then(ConfigFmt::new)
.unwrap_or_default();
let config: Config = file
.map(|f| fmt.from_reader(BufReader::new(f)))
.unwrap_or_default();
let scale = config.scale;
let fonts: FontMap = {
let load_font = |font_name| {
let path = FontSeeker::from_string(font_name);
FontLoader::from_path(&path).expect(&format!("Loading {} failed", path.display()))
};
config
.fonts
.iter()
.map(|(key, val)| (key.clone(), load_font(val)))
.collect::<HashMap<_, _>>()
};
let mut args = env::args();
let _ = args.next();
let mode = match args.next() {
Some(arg) => match arg.as_str() {
"start" => Mode::Daemonize,
"start-or-kill" => Mode::StartOrKill,
"toggle-visible" => Mode::ToggleVisible,
"print-config" => Mode::PrintConfig(fmt),
s => {
let p = "print-config-";
let l = p.len();
let fmt = if s.starts_with(p) {
ConfigFmt::new(&s[l..])
} else {
None
};
if let Some(fmt) = fmt {
Mode::PrintConfig(fmt)
} else {
eprintln!("unsupported sub-command {}", s);
std::process::exit(1);
}
}
},
None => Mode::Start,
};
if args.next().is_some() {
// total = args.count + 2 (the two we skipped + the rest)
eprintln!("expected 0 or 1 arguments, got {}", args.count() + 2);
std::process::exit(1);
}
let mut daemon = false;
match mode {
Mode::ToggleVisible => {
if let Ok(mut socket) = UnixStream::connect(socket_path) {
socket.write_all(b"toggle_visible\n").unwrap();
return;
};
eprintln!("wldash is not running");
std::process::exit(1);
}
Mode::StartOrKill => {
if let Ok(mut socket) = UnixStream::connect(socket_path.clone()) { if let Ok(mut socket) = UnixStream::connect(socket_path.clone()) {
socket.write_all(b"kill\n").unwrap(); socket.write_all(b"kill\n").unwrap();
return; return;
}; };
}
Mode::Start => {
if UnixStream::connect(socket_path.clone()).is_ok() {
eprintln!("wldash is already running");
std::process::exit(1);
};
}
Mode::Daemonize => {
if UnixStream::connect(socket_path.clone()).is_ok() {
eprintln!("wldash is already running");
std::process::exit(1);
};
daemon = true;
}
Mode::PrintConfig(fmt) => {
println!("{}", fmt.to_string(&config));
std::process::exit(0);
}
}
let _ = std::fs::remove_file(socket_path.clone()); let _ = std::fs::remove_file(socket_path.clone());
let listener = UnixListener::bind(socket_path.clone()).unwrap(); let _ = thread::Builder::new()
.name("ipc_server".to_string())
let output_mode = match config.output_mode {
config::OutputMode::All => OutputMode::All,
config::OutputMode::Active => OutputMode::Active,
};
let background = config.background;
let (tx_draw, rx_draw) = channel();
let tx_draw_mod = tx_draw.clone();
// Print, write to a file, or send to an HTTP server.
let widget = config
.widget
.construct(Local::now().naive_local(), tx_draw_mod, &fonts)
.expect("no widget configured");
let mut app = App::new(tx_draw, output_mode, background, scale);
if daemon {
app.hide();
} else {
app.show();
}
app.set_widget(widget).unwrap();
let (mut rx_pipe, mut tx_pipe) = pipe().unwrap();
let ipc_pipe = tx_pipe.try_clone().unwrap();
let worker_queue = app.cmd_queue();
let _ = std::thread::Builder::new()
.name("cmd_proxy".to_string())
.spawn(move || loop {
let cmd = rx_draw.recv().unwrap();
worker_queue.lock().unwrap().push_back(cmd);
tx_pipe.write_all(&[0x1]).unwrap();
});
let mut timer = TimerFd::new().unwrap();
let ev_fd = PollFd::new(
app.event_queue().display().get_connection_fd(),
PollFlags::POLLIN,
);
let rx_fd = PollFd::new(rx_pipe.as_raw_fd(), PollFlags::POLLIN);
let tm_fd = PollFd::new(timer.as_raw_fd(), PollFlags::POLLIN);
let ipc_fd = PollFd::new(listener.as_raw_fd(), PollFlags::POLLIN);
app.cmd_queue().lock().unwrap().push_back(Cmd::Draw);
let mut visible = !daemon;
let mut wait_ctx = WaitContext {
fds: Vec::new(),
target_time: None,
};
let q = app.cmd_queue();
loop {
let cmd = q.lock().unwrap().pop_front();
match cmd {
Some(cmd) => match cmd {
Cmd::Draw => {
app.redraw(false).expect("Failed to draw");
app.flush_display();
}
Cmd::KeyboardTest => {
if let Some(cmd) = app.key_repeat() {
q.lock().unwrap().push_back(cmd);
}
}
Cmd::ForceDraw => {
app.redraw(true).expect("Failed to draw");
app.flush_display();
}
Cmd::MouseClick { btn, pos } => {
app.get_widget().mouse_click(btn, pos);
q.lock().unwrap().push_back(Cmd::Draw);
}
Cmd::MouseScroll { scroll, pos } => {
app.get_widget().mouse_scroll(scroll, pos);
q.lock().unwrap().push_back(Cmd::Draw);
}
Cmd::Keyboard {
key,
key_state,
modifiers_state,
interpreted,
} => {
app.get_widget()
.keyboard_input(key, modifiers_state, key_state, interpreted);
q.lock().unwrap().push_back(Cmd::Draw);
}
Cmd::ToggleVisible => {
visible = !visible;
if visible {
app.get_widget().enter();
app.show();
} else {
app.hide();
app.get_widget().leave();
}
app.flush_display();
}
Cmd::Exit => {
if daemon {
visible = false;
app.hide();
app.get_widget().leave();
app.flush_display();
} else {
let _ = std::fs::remove_file(socket_path);
return;
}
}
},
None => {
app.flush_display();
wait_ctx.fds.clear();
wait_ctx.fds.push(ev_fd);
wait_ctx.fds.push(rx_fd);
wait_ctx.fds.push(ipc_fd);
wait_ctx.target_time = None;
app.get_widget().wait(&mut wait_ctx);
app.set_keyboard_repeat(&mut wait_ctx);
if let Some(target_time) = wait_ctx.target_time {
let n = Local::now().naive_local();
let sleep = if target_time > n {
target_time - n
} else {
Duration::seconds(0)
};
timer.set_state(
TimerState::Oneshot(sleep.to_std().unwrap()),
SetTimeFlags::Default,
);
wait_ctx.fds.push(tm_fd);
}
poll(&mut wait_ctx.fds, -1).unwrap();
if wait_ctx.fds[0]
.revents()
.unwrap()
.contains(PollFlags::POLLIN)
{
if let Some(guard) = app.event_queue().prepare_read() {
if let Err(e) = guard.read_events() {
if e.kind() != ::std::io::ErrorKind::WouldBlock {
eprintln!(
"Error while trying to read from the wayland socket: {:?}",
e
);
}
}
}
app.event_queue()
.dispatch_pending(&mut (), |_, _, _| {})
.expect("Failed to dispatch all messages.");
}
if wait_ctx.fds[1]
.revents()
.unwrap()
.contains(PollFlags::POLLIN)
{
let mut v = [0x00];
rx_pipe.read_exact(&mut v).unwrap();
}
if wait_ctx.fds[2]
.revents()
.unwrap()
.contains(PollFlags::POLLIN)
{
if let Ok((stream, _)) = listener.accept() {
let client_queue = q.clone();
let mut client_pipe = ipc_pipe.try_clone().unwrap();
let _ = std::thread::Builder::new()
.name("ipc_client".to_string())
.spawn(move || { .spawn(move || {
let listener = UnixListener::bind(socket_path.clone()).unwrap();
loop {
match listener.accept() {
Ok((stream, _)) => {
let _ = thread::Builder::new().name("ipc_client".to_string()).spawn(
move || {
let r = BufReader::new(stream); let r = BufReader::new(stream);
for line in r.lines() { for line in r.lines() {
match line { match line {
Ok(v) => match v.as_str() { Ok(ref cmd) if cmd == "kill" => {
"kill" => { std::process::exit(0);
client_queue.lock().unwrap().push_back(Cmd::Exit) }
_ => {}
}
} }
"toggle_visible" => client_queue
.lock()
.unwrap()
.push_back(Cmd::ToggleVisible),
v => eprintln!("unknown command: {}", v),
}, },
Err(_) => return, );
}
_ => (),
} }
client_pipe.write_all(&[0x1]).unwrap();
} }
}); });
} }
if let Some(font_paths) = config.font_paths {
for (key, value) in font_paths.into_iter() {
fm.add_font_path(Box::leak(key.into_boxed_str()), value);
}
} }
if wait_ctx.target_time.is_some() let layout = Rc::new(config.widget.construct_layout(&mut 0));
&& wait_ctx.fds[wait_ctx.fds.len() - 1] let mut widgets: Vec<Box<dyn Widget>> = Vec::new();
.revents() config
.unwrap() .widget
.contains(PollFlags::POLLIN) .construct_widgets(&mut widgets, &mut fm, &events);
{
timer.read(); let font_thread = thread::Builder::new()
let mut qq = q.lock().unwrap(); .name("fontloader".to_string())
qq.push_back(Cmd::KeyboardTest); .spawn(move || {
qq.push_back(Cmd::Draw); fm.load_fonts();
fm
})
.unwrap();
let mut state = State::new(
config.mode,
config.background,
widgets,
layout,
MaybeFontMap::Waiting(font_thread),
events,
keyrepeat_sender,
);
// Initial setup
//
while state.running && !state.ready {
event_loop
.dispatch(None, &mut state)
.expect("Could not dispatch event loop");
}
state.check_registry(&qhandle);
while state.running {
event_loop
.dispatch(None, &mut state)
.expect("Could not dispatch event loop");
if !state.configured || !state.dirty || !state.ready {
continue;
}
let mut force = false;
if state.bufmgr.buffers.len() == 0 {
state.add_buffer(&qhandle);
force = true;
}
let mut bufcnt = state.bufmgr.buffers.len();
let buf = match state.bufmgr.next_buffer() {
Some(b) => b,
None => {
if state.bufmgr.buffers.len() >= 3 {
continue;
}
state.add_buffer(&qhandle);
bufcnt = state.bufmgr.buffers.len();
match state.bufmgr.next_buffer() {
Some(b) => b,
None => {
continue;
} }
} }
} }
};
if buf.last_damage.len() == 0 {
for _ in 0..state.widgets.len() {
buf.last_damage.push(Geometry::new());
}
}
state.dirty = false;
buf.acquire();
let mut bufview = BufferView::new(
&mut buf.mmap,
(state.dimensions.0 as u32, state.dimensions.1 as u32),
);
let surface = state.main_surface.wl_surface.as_ref().unwrap().clone();
if force {
for (idx, widget) in state.widgets.iter_mut().enumerate() {
if force || widget.get_dirty() {
let geo = widget.geometry();
let mut subview = bufview.subgeometry(geo);
buf.last_damage[idx] =
widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview);
}
}
surface.damage_buffer(0, 0, 0x7FFFFFFF, 0x7FFFFFFF);
} else {
let mut drew = false;
for (idx, widget) in state.widgets.iter_mut().enumerate() {
if bufcnt > 1 || widget.get_dirty() {
drew = true;
let old_damage = buf.last_damage[idx];
bufview.subgeometry(old_damage).clear();
let geo = widget.geometry();
let mut subview = bufview.subgeometry(geo);
let new_damage =
widget.draw(&mut state.fonts.unwrap().borrow_mut(), &mut subview);
let combined_damage = new_damage.expand(old_damage);
buf.last_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,
);
}
}
if !drew {
buf.release();
conn.flush().unwrap();
continue;
}
}
state.ready = false;
surface.attach(Some(&buf.buffer), 0, 0);
surface.frame(&qhandle, ());
surface.commit();
conn.flush().unwrap();
// Now is a good as time as any to load the keymap
state.keyboard.resolve();
} }
} }
+865
View File
@@ -0,0 +1,865 @@
use std::{
cmp::max,
default::Default,
env,
rc::Rc,
sync::{Arc, Mutex},
};
use wayland_client::{
protocol::{
wl_buffer, wl_callback, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat,
wl_shm, wl_shm_pool, wl_subcompositor, wl_subsurface, wl_surface,
},
Connection, Dispatch, QueueHandle, WEnum,
};
use wayland_protocols::{
wp::{
single_pixel_buffer::v1::client::wp_single_pixel_buffer_manager_v1,
viewporter::client::{wp_viewport, wp_viewporter},
},
xdg::{
activation::v1::client::{xdg_activation_token_v1, xdg_activation_v1},
shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base},
},
};
use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
use crate::{
buffer::BufferManager,
config::OperationMode,
event::{Event, Events, PointerButton, PointerEvent},
fonts::{FontMap, MaybeFontMap},
keyboard::{Keyboard, RepeatMessage},
widgets::{Geometry, Layout, Widget, WidgetUpdater},
};
use calloop::channel::Sender;
#[derive(Default)]
pub struct MainSurface {
pub wl_surface: Option<wl_surface::WlSurface>,
xdg_surface: Option<(xdg_surface::XdgSurface, xdg_toplevel::XdgToplevel)>,
layer_surface: Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>,
}
#[derive(Default)]
pub struct BackgroundSurface {
wl_surface: Option<wl_surface::WlSurface>,
subsurface: Option<wl_subsurface::WlSubsurface>,
viewport: Option<wp_viewport::WpViewport>,
}
#[derive(Default)]
pub struct Protocols {
wl_compositor: Option<wl_compositor::WlCompositor>,
wl_subcompositor: Option<wl_subcompositor::WlSubcompositor>,
wl_shm: Option<wl_shm::WlShm>,
xdg_wm_base: Option<xdg_wm_base::XdgWmBase>,
xdg_activation: Option<xdg_activation_v1::XdgActivationV1>,
zwlr_layer_shell: Option<zwlr_layer_shell_v1::ZwlrLayerShellV1>,
viewporter: Option<wp_viewporter::WpViewporter>,
wp_single_pixel_buffer_manager:
Option<wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1>,
}
pub struct State {
mode: OperationMode,
background: Option<u32>,
pub main_surface: MainSurface,
pub bg_surface: BackgroundSurface,
pub protocols: Protocols,
pub running: bool,
pub ready: bool,
pub dirty: bool,
activated: bool,
pub configured: bool,
pub dimensions: (i32, i32),
pub bufmgr: BufferManager,
pub widgets: Vec<Box<dyn Widget>>,
pub keyboard: Keyboard,
pub keyrepeat_sender: Sender<RepeatMessage>,
pointer: Option<(f64, f64)>,
pub fonts: MaybeFontMap,
pub events: Arc<Mutex<Events>>,
layout: Rc<Box<dyn Layout>>,
}
impl State {
pub fn new(
mode: OperationMode,
background: Option<u32>,
widgets: Vec<Box<dyn Widget>>,
layout: Rc<Box<dyn Layout>>,
fonts: MaybeFontMap,
events: Arc<Mutex<Events>>,
keyrepeat_sender: Sender<RepeatMessage>,
) -> State {
State {
protocols: Default::default(),
bg_surface: Default::default(),
main_surface: Default::default(),
running: true,
dirty: true,
activated: false,
configured: false,
dimensions: (320, 240),
bufmgr: BufferManager::new(),
keyboard: Keyboard::new(),
pointer: None,
keyrepeat_sender,
mode,
background,
widgets,
fonts,
events,
layout,
ready: false,
}
}
pub fn add_buffer(&mut self, qh: &QueueHandle<Self>) {
self.bufmgr.add_buffer(
self.protocols.wl_shm.as_ref().expect("missing wl_shm"),
self.dimensions,
qh,
);
}
pub fn activate(&mut self, qh: &QueueHandle<Self>) {
let key = match env::var("XDG_ACTIVATION_TOKEN") {
Ok(token) => token,
Err(_) => return,
};
match (
&self.main_surface.wl_surface,
&self.protocols.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 minimum_size(&mut self, idx: usize, fonts: &mut FontMap) -> Geometry {
self.widgets[idx].minimum_size(fonts)
}
}
impl State {
pub fn check_registry(&mut self, qh: &QueueHandle<Self>) {
let wl_compositor = self
.protocols
.wl_compositor
.as_ref()
.expect("wl_compositor missing");
let main_surface = wl_compositor.create_surface(qh, ());
let bg_surface = wl_compositor.create_surface(qh, ());
self.main_surface.wl_surface = Some(main_surface.clone());
self.bg_surface.wl_surface = Some(bg_surface.clone());
self.activate(qh);
let wl_subcompositor = self
.protocols
.wl_subcompositor
.as_ref()
.expect("wl_subcompositor missing");
let subsurface = wl_subcompositor.get_subsurface(&bg_surface, &main_surface, qh, ());
subsurface.place_below(&main_surface);
self.bg_surface.subsurface = Some(subsurface.clone());
match self.mode {
OperationMode::XdgToplevel => {
self.fonts.resolve();
let xdg_wm_base = self
.protocols
.xdg_wm_base
.as_ref()
.expect("xdg_wm_base missing");
let xdg_surface = xdg_wm_base.get_xdg_surface(&main_surface, qh, ());
let toplevel = xdg_surface.get_toplevel(qh, ());
toplevel.set_title("wldash".into());
main_surface.commit();
self.main_surface.xdg_surface = Some((xdg_surface, toplevel));
}
OperationMode::LayerSurface(size) => {
self.fonts.resolve();
let layer_shell = self
.protocols
.zwlr_layer_shell
.as_ref()
.expect("zwlr_layer_shell_v1 missing");
let layer_surface = layer_shell.get_layer_surface(
&main_surface,
None,
zwlr_layer_shell_v1::Layer::Overlay,
"launcher".to_string(),
qh,
(),
);
let fonts = self.fonts.unwrap();
let layout = self.layout.clone();
let min_size = layout.minimum_size(&mut fonts.borrow_mut(), self);
layer_surface.set_keyboard_interactivity(
zwlr_layer_surface_v1::KeyboardInteractivity::Exclusive,
);
layer_surface.set_anchor(
zwlr_layer_surface_v1::Anchor::Left
.union(zwlr_layer_surface_v1::Anchor::Bottom)
.union(zwlr_layer_surface_v1::Anchor::Right)
.union(zwlr_layer_surface_v1::Anchor::Top),
);
layer_surface.set_size(max(size.0, min_size.width), max(size.1, min_size.height));
main_surface.commit();
self.main_surface.layer_surface = Some(layer_surface);
}
}
let viewporter = self
.protocols
.viewporter
.as_ref()
.expect("wp_viewporter missing");
let viewport = viewporter.get_viewport(&bg_surface, qh, ());
self.bg_surface.viewport = Some(viewport);
if let Some(background) = self.background {
let wp_single_pixel_buffer_manager = self
.protocols
.wp_single_pixel_buffer_manager
.as_ref()
.expect("wp_single_pixel_buffer_manager_v1 missing");
let buffer =
wp_single_pixel_buffer_manager.create_u32_rgba_buffer(0, 0, 0, background, qh, ());
bg_surface.attach(Some(&buffer), 0, 0);
bg_surface.damage_buffer(0, 0, 1, 1);
}
}
}
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" => {
state.protocols.wl_compositor =
Some(registry.bind::<wl_compositor::WlCompositor, _, _>(name, 5, qh, ()));
}
"wl_subcompositor" => {
state.protocols.wl_subcompositor = Some(
registry.bind::<wl_subcompositor::WlSubcompositor, _, _>(name, 1, qh, ()),
);
}
"wl_shm" => {
state.protocols.wl_shm =
Some(registry.bind::<wl_shm::WlShm, _, _>(name, 1, qh, ()));
}
"wl_seat" => {
registry.bind::<wl_seat::WlSeat, _, _>(name, 8, qh, ());
}
"xdg_wm_base" => {
if let OperationMode::XdgToplevel = state.mode {
state.protocols.xdg_wm_base =
Some(registry.bind::<xdg_wm_base::XdgWmBase, _, _>(name, 2, qh, ()));
}
}
"xdg_activation_v1" => {
state.protocols.xdg_activation = Some(
registry.bind::<xdg_activation_v1::XdgActivationV1, _, _>(name, 1, qh, ()),
);
}
"zwlr_layer_shell_v1" => {
if let OperationMode::LayerSurface(_) = state.mode {
state.protocols.zwlr_layer_shell = Some(
registry.bind::<zwlr_layer_shell_v1::ZwlrLayerShellV1, _, _>(
name,
4,
qh,
(),
),
);
}
}
"wp_viewporter" => {
state.protocols.viewporter =
Some(registry.bind::<wp_viewporter::WpViewporter, _, _>(name, 1, qh, ()));
}
"wp_single_pixel_buffer_manager_v1" => {
state.protocols.wp_single_pixel_buffer_manager = Some(registry.bind::<wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1, _, _>(name, 1, qh, ()));
}
_ => {}
}
}
}
}
impl Dispatch<wl_callback::WlCallback, ()> for State {
fn event(
state: &mut Self,
_: &wl_callback::WlCallback,
event: wl_callback::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match event {
wl_callback::Event::Done { callback_data: _ } => state.ready = true,
_ => {}
}
}
}
impl Dispatch<wl_compositor::WlCompositor, ()> for State {
fn event(
_: &mut Self,
_: &wl_compositor::WlCompositor,
_: wl_compositor::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<wl_subcompositor::WlSubcompositor, ()> for State {
fn event(
_: &mut Self,
_: &wl_subcompositor::WlSubcompositor,
_: wl_subcompositor::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_subsurface::WlSubsurface, ()> for State {
fn event(
_: &mut Self,
_: &wl_subsurface::WlSubsurface,
_: wl_subsurface::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 Dispatch<zwlr_layer_shell_v1::ZwlrLayerShellV1, ()> for State {
fn event(
_: &mut Self,
_: &zwlr_layer_shell_v1::ZwlrLayerShellV1,
_: zwlr_layer_shell_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()> for State {
fn event(
state: &mut Self,
layer_surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
event: zwlr_layer_surface_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match event {
zwlr_layer_surface_v1::Event::Configure {
serial,
mut width,
mut height,
} => {
if (width, height) == (0, 0) {
if state.configured {
return;
}
(width, height) = (640, 480);
}
layer_surface.ack_configure(serial);
state.configured = true;
state.dirty = true;
let dim = (width as i32, height as i32);
if state.configured && state.dimensions == dim {
return;
}
state.bufmgr.clear_buffers();
state.dimensions = dim;
let fonts = state.fonts.unwrap();
let layout = state.layout.clone();
layout.geometry_update(
&mut fonts.borrow_mut(),
&Geometry {
x: 0,
y: 0,
width: width as u32,
height: height as u32,
},
state,
);
let surface = state
.bg_surface
.wl_surface
.as_ref()
.expect("bg surface was not ready");
let viewport = state
.bg_surface
.viewport
.as_ref()
.expect("bg viewport was not ready");
viewport.set_destination(dim.0, dim.1);
surface.commit();
}
zwlr_layer_surface_v1::Event::Closed => {
std::process::exit(0);
}
_ => (),
}
}
}
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 {
mut width,
mut height,
states,
..
} => {
if (width, height) == (0, 0) {
if state.configured {
return;
}
(width, height) = (1280, 720);
}
let activated = states
.iter()
.find(|&st| *st as u32 == xdg_toplevel::State::Activated as u32)
.is_some();
if activated {
state.activated = true;
} else if state.activated && !activated {
let ev = Event::FocusLost;
for widget in state.widgets.iter_mut() {
widget.event(&ev);
}
}
if !state.configured || state.dimensions != (width, height) {
let surface = state
.bg_surface
.wl_surface
.as_ref()
.expect("bg surface was not ready");
let viewport = state
.bg_surface
.viewport
.as_ref()
.expect("bg viewportw as not ready");
viewport.set_destination(width, height);
surface.commit();
state.bufmgr.clear_buffers();
state.dimensions = (width, height);
state.fonts.resolve();
let fonts = state.fonts.unwrap();
let layout = state.layout.clone();
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, ());
}
if capabilities.contains(wl_seat::Capability::Pointer) {
seat.get_pointer(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::Leave { .. } => {
state
.keyrepeat_sender
.send(RepeatMessage::StopRepeat)
.unwrap();
}
wl_keyboard::Event::Key {
key,
state: kbstate,
..
} => {
if key == 1 {
state.running = false;
return;
}
state.keyboard.resolve();
let k = state.keyboard.key(key, kbstate);
let repeats = k.repeats;
let ev = Event::KeyEvent(k);
for widget in state.widgets.iter_mut() {
widget.event(&ev);
}
if repeats {
if let Event::KeyEvent(k) = ev {
state
.keyrepeat_sender
.send(RepeatMessage::KeyEvent(k))
.unwrap();
}
}
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 } => {
// TODO: Keymap loading blocks xdg configure, consider delaying
state.keyboard.keymap(format, fd, size);
}
wl_keyboard::Event::RepeatInfo { rate, delay } => {
state
.keyrepeat_sender
.send(RepeatMessage::RepeatInfo((rate as u32, delay as u32)))
.unwrap();
}
_ => (),
}
}
}
// TODO: Move to dedicated pointer module
const BTN_LEFT: u32 = 0x110;
const BTN_RIGHT: u32 = 0x111;
const BTN_MIDDLE: u32 = 0x112;
impl Dispatch<wl_pointer::WlPointer, ()> for State {
fn event(
state: &mut Self,
_: &wl_pointer::WlPointer,
event: wl_pointer::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match event {
wl_pointer::Event::Enter {
surface_x,
surface_y,
..
} => {
state.pointer = Some((surface_x, surface_y));
}
wl_pointer::Event::Motion {
surface_x,
surface_y,
..
} => {
state.pointer = Some((surface_x, surface_y));
}
wl_pointer::Event::Leave { .. } => state.pointer = None,
wl_pointer::Event::Axis { axis, value, .. } => match state.pointer {
Some((x, y)) => {
let axis = match axis {
WEnum::Value(wl_pointer::Axis::VerticalScroll) => {
PointerButton::ScrollVertical(value)
}
WEnum::Value(wl_pointer::Axis::HorizontalScroll) => {
PointerButton::ScrollHorizontal(value)
}
_ => return,
};
let pos = (x as u32, y as u32);
for widget in state.widgets.iter_mut() {
let geo = widget.geometry();
if geo.contains(pos) {
let pos = (pos.0 - geo.x, pos.1 - geo.y);
let ev = Event::PointerEvent(PointerEvent {
button: axis,
pos: pos,
});
widget.event(&ev);
state.dirty = true;
break;
}
}
}
_ => (),
},
wl_pointer::Event::Button {
button,
state: button_state,
..
} => match (button, button_state, state.pointer) {
(button, WEnum::Value(wl_pointer::ButtonState::Pressed), Some((x, y))) => {
let button = match button {
BTN_LEFT => PointerButton::Left,
BTN_RIGHT => PointerButton::Right,
BTN_MIDDLE => PointerButton::Middle,
_ => return,
};
let pos = (x as u32, y as u32);
for widget in state.widgets.iter_mut() {
let geo = widget.geometry();
if geo.contains(pos) {
let pos = (pos.0 - geo.x, pos.1 - geo.y);
let ev = Event::PointerEvent(PointerEvent {
button: button,
pos: pos,
});
widget.event(&ev);
state.dirty = true;
break;
}
}
}
_ => (),
},
_ => (),
}
}
}
impl Dispatch<wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1, ()> for State {
fn event(
_: &mut Self,
_: &wp_single_pixel_buffer_manager_v1::WpSinglePixelBufferManagerV1,
_: wp_single_pixel_buffer_manager_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<wp_viewporter::WpViewporter, ()> for State {
fn event(
_: &mut Self,
_: &wp_viewporter::WpViewporter,
_: wp_viewporter::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl Dispatch<wp_viewport::WpViewport, ()> for State {
fn event(
_: &mut Self,
_: &wp_viewport::WpViewport,
_: wp_viewport::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
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 } => {
let ev = Event::TokenUpdate(token);
for widget in state.widgets.iter_mut() {
widget.event(&ev);
}
}
_ => (),
}
}
}
+117
View File
@@ -0,0 +1,117 @@
use std::{
cmp::Ordering,
collections::HashSet,
error::Error,
fs::{read_to_string, File},
io::{Error as io_error, ErrorKind},
};
use serde::{self, Deserialize, Serialize};
use simd_json;
use walkdir::WalkDir;
use crate::utils::{inish, xdg};
#[derive(Serialize, Deserialize, Clone, Debug, Eq, Hash)]
#[serde(rename_all = "snake_case")]
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 s = read_to_string(f)?;
let config = inish::parse(&s)?;
match config.get("Desktop Entry") {
Some(section) => Ok(Desktop {
entry_type: section.get("Type").unwrap_or(&"").to_string(),
name: section.get("Name").unwrap_or(&"").to_string(),
term: section.get("Terminal").unwrap_or(&"") == &"true",
no_display: section.get("NoDisplay").unwrap_or(&"") == &"true",
hidden: section.get("Hidden").unwrap_or(&"") == &"true",
exec: section.get("Exec").map(|x| x.to_string()),
url: section.get("URL").map(|x| x.to_string()),
keywords: section
.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",
))),
}
}
}
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_cache() -> Result<Vec<Desktop>, Box<dyn Error>> {
match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) {
Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()),
Err(e) => Err(e.into()),
}
}
pub fn write_desktop_cache(v: &Vec<Desktop>) -> Result<(), Box<dyn Error>> {
match File::create(format!("{}/wldash/desktop.json", xdg::cache_folder())) {
Ok(f) => simd_json::to_writer(f, v).map_err(|e| e.into()),
Err(e) => Err(e.into()),
}
}
pub fn load_desktop_files() -> Vec<Desktop> {
let dirs = xdg::data_folders();
let mut desktop: Vec<Desktop> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for dir in dirs {
for entry in WalkDir::new(format!("{}/applications", dir)) {
if let Ok(entry) = entry {
if let Ok(d) = Desktop::parse(entry.path().to_str().unwrap()) {
if d.hidden
|| d.no_display
|| (d.entry_type != "Application" && d.entry_type != "Link")
{
continue;
}
if !seen.contains(&d.name) {
seen.insert(d.name.to_string());
desktop.push(d);
}
}
}
}
}
desktop
}
+134
View File
@@ -0,0 +1,134 @@
use std::{collections::HashMap, error::Error};
fn parse_field<'a>(line: &'a str) -> Result<(&'a str, &'a str), Box<dyn Error>> {
let split = match line.find('=') {
Some(v) => v,
None => return Err("expected equals sign on line, but found none".into()),
};
let (key, value) = line.split_at(split);
let (key, value) = (key.trim(), value.trim_matches('=').trim());
Ok((key, value))
}
type InishSection<'a> = HashMap<&'a str, &'a str>;
type Inish<'a> = HashMap<&'a str, InishSection<'a>>;
pub fn parse<'a>(s: &'a str) -> Result<Inish<'a>, Box<dyn Error>> {
let mut sections: Inish<'a> = HashMap::new();
let mut current_section = HashMap::new();
let mut current_section_name = "";
for line in s.lines() {
let line = line.trim();
let mut chars = line.chars();
let start = chars.next();
let end = chars.last();
match (start, end) {
(Some('#'), _) => continue,
(Some('['), Some(']')) => {
sections.insert(current_section_name, current_section);
current_section = HashMap::new();
let len = line.bytes().count();
current_section_name = &line[1..len - 1].trim();
}
(Some('['), v) => {
return Err(format!(
"expected Some(']') to terminate section name, but got {:?}",
v
)
.into());
}
_ if line.is_empty() => continue,
_ => {
let (key, value) = parse_field(line)?;
current_section.insert(key, value);
}
}
}
sections.insert(current_section_name, current_section);
Ok(sections)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sections() {
let config = parse(
"
[section_a]
[section_b]
",
)
.expect("config didn't parse");
if !config.contains_key("section_a") {
panic!("named section did not exist");
}
if !config.contains_key("section_b") {
panic!("named section did not exist");
}
if !config.contains_key("") {
panic!("unnamed section did not exist");
}
}
#[test]
fn fields() {
let config = parse(
"
field_outside = 'hello'
[section_a]
field_a = 1234
",
)
.expect("config didn't parse");
let section_a = match config.get("section_a") {
Some(v) => v,
None => panic!("named section did not exist"),
};
assert_eq!(section_a.get("field_a"), Some(&"1234"));
let section_unnamed = match config.get("") {
Some(v) => v,
None => panic!("unnamed section did not exist"),
};
assert_eq!(section_unnamed.get("field_outside"), Some(&"'hello'"));
}
#[test]
fn whitespaces() {
let config = parse(
"
field outside = 'hello'
[ section a ]
field a = 1234 ",
)
.expect("config didn't parse");
let section_a = match config.get("section a") {
Some(v) => v,
None => panic!("named section did not exist"),
};
assert_eq!(section_a.get("field a"), Some(&"1234"));
let section_unnamed = match config.get("") {
Some(v) => v,
None => panic!("unnamed section did not exist"),
};
assert_eq!(section_unnamed.get("field outside"), Some(&"'hello'"));
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod desktop;
mod inish;
pub mod xdg;
+40
View File
@@ -0,0 +1,40 @@
use std::env;
pub fn cache_folder() -> String {
match env::var_os("XDG_CACHE_HOME") {
Some(s) => s.into_string().unwrap(),
None => {
let home = env::var_os("HOME").unwrap().into_string().unwrap();
format!("{}/.cache", home)
}
}
}
pub fn config_folder() -> String {
match env::var_os("XDG_CONFIG_HOME") {
Some(s) => s.into_string().unwrap(),
None => {
let home = env::var_os("HOME").unwrap().into_string().unwrap();
format!("{}/.config", home)
}
}
}
pub fn data_folders() -> Vec<String> {
let xdg_data_home = match env::var_os("XDG_DATA_HOME") {
Some(s) => s.into_string().unwrap(),
None => {
let home = env::var_os("HOME").unwrap().into_string().unwrap();
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(),
};
// Eww.
std::iter::once(xdg_data_home.to_string())
.chain(xdg_data_dirs.split(':').map(|x| x.to_string()))
.collect()
}
-458
View File
@@ -1,458 +0,0 @@
use crate::buffer::Buffer;
use crate::color::Color;
pub use crate::keyboard::{KeyState, ModifiersState};
use chrono::NaiveDateTime;
use nix::poll::PollFd;
pub struct DrawContext<'a> {
pub buf: &'a mut Buffer<'a>,
pub bg: &'a Color,
pub time: NaiveDateTime,
pub force: bool,
}
#[derive(Debug)]
pub struct DrawReport {
pub width: u32,
pub height: u32,
pub damage: Vec<(i32, i32, i32, i32)>,
pub full_damage: bool,
}
impl DrawReport {
pub fn empty(width: u32, height: u32) -> DrawReport {
DrawReport {
width,
height,
damage: Vec::new(),
full_damage: false,
}
}
}
pub struct WaitContext {
pub fds: Vec<PollFd>,
pub target_time: Option<NaiveDateTime>,
}
impl WaitContext {
pub fn set_time(&mut self, new_time: NaiveDateTime) {
if let Some(ot) = self.target_time {
if new_time < ot {
self.target_time = Some(new_time);
}
} else {
self.target_time = Some(new_time);
}
}
}
pub trait Widget {
fn wait(&mut self, ctx: &mut WaitContext);
fn enter(&mut self);
fn leave(&mut self);
fn size(&self) -> (u32, u32);
fn draw(
&mut self,
ctx: &mut DrawContext,
pos: (u32, u32),
expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error>;
fn keyboard_input(
&mut self,
keysym: u32,
modifier_state: ModifiersState,
key_state: KeyState,
interpreted: Option<String>,
);
fn mouse_click(&mut self, button: u32, pos: (u32, u32));
fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32));
}
pub struct VerticalLayout<'a> {
pub children: Vec<Box<dyn Widget + Send + 'a>>,
}
#[allow(dead_code)]
impl<'a> VerticalLayout<'a> {
pub fn new(children: Vec<Box<dyn Widget + Send + 'a>>) -> Box<VerticalLayout> {
Box::new(VerticalLayout { children })
}
}
impl<'a> Widget for VerticalLayout<'a> {
fn wait(&mut self, ctx: &mut WaitContext) {
for child in &mut self.children {
child.wait(ctx);
}
}
fn enter(&mut self) {
for child in &mut self.children {
child.enter();
}
}
fn leave(&mut self) {
for child in &mut self.children {
child.leave();
}
}
fn size(&self) -> (u32, u32) {
let mut width = 0;
let mut height = 0;
for child in &self.children {
let size = child.size();
if size.0 > width {
width = size.0;
}
height += size.1;
}
(width, height)
}
fn draw(
&mut self,
ctx: &mut DrawContext,
pos: (u32, u32),
expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error> {
let mut offset = 0;
let mut width = 0;
let mut damage = Vec::new();
let mut full_damage = false;
for child in &mut self.children {
let mut report = child.draw(
ctx,
(pos.0, offset + pos.1),
(expansion.0, expansion.1 - offset),
)?;
if report.width > width {
width = report.width
}
offset += report.height;
full_damage |= report.full_damage;
damage.append(&mut report.damage);
}
Ok(DrawReport {
width,
height: offset,
damage,
full_damage,
})
}
fn keyboard_input(
&mut self,
keysym: u32,
modifier_state: ModifiersState,
key_state: KeyState,
interpreted: Option<String>,
) {
for child in &mut self.children {
child.keyboard_input(keysym, modifier_state, key_state, interpreted.clone());
}
}
fn mouse_click(&mut self, button: u32, pos: (u32, u32)) {
let mut height = 0;
for child in &mut self.children {
let size = child.size();
if pos.1 >= height && pos.1 < height + size.1 {
let pos = (pos.0, pos.1 - height);
child.mouse_click(button, pos);
return;
}
height += size.1;
}
}
fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) {
let mut height = 0;
for child in &mut self.children {
let size = child.size();
if pos.1 >= height && pos.1 < height + size.1 {
let pos = (pos.0, pos.1 - height);
child.mouse_scroll(scroll, pos);
return;
}
height += size.1;
}
}
}
pub struct HorizontalLayout<'a> {
pub children: Vec<Box<dyn Widget + Send + 'a>>,
}
#[allow(dead_code)]
impl<'a> HorizontalLayout<'a> {
pub fn new(children: Vec<Box<dyn Widget + Send + 'a>>) -> Box<HorizontalLayout> {
Box::new(HorizontalLayout { children })
}
fn height(&self) -> u32 {
let mut height = 0;
for child in &self.children {
let size = child.size();
if size.1 > height {
height = size.1;
}
}
height
}
}
impl<'a> Widget for HorizontalLayout<'a> {
fn wait(&mut self, ctx: &mut WaitContext) {
for child in &mut self.children {
child.wait(ctx);
}
}
fn enter(&mut self) {
for child in &mut self.children {
child.enter();
}
}
fn leave(&mut self) {
for child in &mut self.children {
child.leave();
}
}
fn size(&self) -> (u32, u32) {
let mut width = 0;
let mut height = 0;
for child in &self.children {
let size = child.size();
if size.1 > height {
height = size.1;
}
width += size.0;
}
(width, height)
}
fn draw(
&mut self,
ctx: &mut DrawContext,
pos: (u32, u32),
expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error> {
let mut offset = 0;
let mut height = 0;
let mut damage = Vec::new();
let mut full_damage = false;
for child in &mut self.children {
let mut report = child.draw(
ctx,
(offset + pos.0, pos.1),
(expansion.0 - offset, expansion.1),
)?;
if report.height > height {
height = report.height
}
offset += report.width;
full_damage |= report.full_damage;
damage.append(&mut report.damage);
}
Ok(DrawReport {
width: offset,
height,
damage,
full_damage,
})
}
fn keyboard_input(
&mut self,
keysym: u32,
modifier_state: ModifiersState,
key_state: KeyState,
interpreted: Option<String>,
) {
for child in &mut self.children {
child.keyboard_input(keysym, modifier_state, key_state, interpreted.clone());
}
}
fn mouse_click(&mut self, button: u32, pos: (u32, u32)) {
let mut width = 0;
for child in &mut self.children {
let size = child.size();
if pos.0 >= width && pos.0 < width + size.0 {
let pos = (pos.0 - width, pos.1);
child.mouse_click(button, pos);
return;
}
width += size.0;
}
}
fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) {
let mut width = 0;
for child in &mut self.children {
let size = child.size();
if pos.0 >= width && pos.0 < width + size.0 {
let pos = (pos.0 - width, pos.1);
child.mouse_scroll(scroll, pos);
return;
}
width += size.0;
}
}
}
pub struct Margin<'a> {
pub child: Box<dyn Widget + Send + 'a>,
pub margins: (u32, u32, u32, u32), // left, right, top, bottom
}
#[allow(dead_code)]
impl<'a> Margin<'a> {
pub fn new(margins: (u32, u32, u32, u32), child: Box<dyn Widget + Send + 'a>) -> Box<Margin> {
Box::new(Margin { child, margins })
}
}
impl<'a> Widget for Margin<'a> {
fn wait(&mut self, ctx: &mut WaitContext) {
self.child.wait(ctx)
}
fn enter(&mut self) {
self.child.enter()
}
fn leave(&mut self) {
self.child.leave()
}
fn size(&self) -> (u32, u32) {
let size = self.child.size();
(
size.0 + self.margins.0 + self.margins.1,
size.1 + self.margins.2 + self.margins.3,
)
}
fn draw(
&mut self,
ctx: &mut DrawContext,
pos: (u32, u32),
expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error> {
let expansion = (
expansion.0 - self.margins.0 - self.margins.1,
expansion.1 - self.margins.2 - self.margins.3,
);
let report = self.child.draw(
ctx,
(pos.0 + self.margins.0, pos.1 + self.margins.2),
expansion,
)?;
Ok(DrawReport {
width: report.width + self.margins.0 + self.margins.1,
height: report.height + self.margins.2 + self.margins.3,
damage: report.damage,
full_damage: report.full_damage,
})
}
fn keyboard_input(
&mut self,
keysym: u32,
modifier_state: ModifiersState,
key_state: KeyState,
interpreted: Option<String>,
) {
self.child
.keyboard_input(keysym, modifier_state, key_state, interpreted);
}
fn mouse_click(&mut self, button: u32, pos: (u32, u32)) {
let pos = (
pos.0.saturating_sub(self.margins.0),
pos.1.saturating_sub(self.margins.2),
);
self.child.mouse_click(button, pos);
}
fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) {
let pos = (
pos.0.saturating_sub(self.margins.0),
pos.1.saturating_sub(self.margins.2),
);
self.child.mouse_scroll(scroll, pos);
}
}
pub struct Fixed<'a> {
pub child: Box<dyn Widget + Send + 'a>,
pub size: (u32, u32),
}
#[allow(dead_code)]
impl<'a> Fixed<'a> {
pub fn new(size: (u32, u32), child: Box<dyn Widget + Send + 'a>) -> Box<Fixed> {
Box::new(Fixed { child, size })
}
}
impl<'a> Widget for Fixed<'a> {
fn wait(&mut self, ctx: &mut WaitContext) {
self.child.wait(ctx)
}
fn enter(&mut self) {
self.child.enter()
}
fn leave(&mut self) {
self.child.leave()
}
fn size(&self) -> (u32, u32) {
self.size
}
fn draw(
&mut self,
ctx: &mut DrawContext,
pos: (u32, u32),
expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error> {
let report = self.child.draw(ctx, pos, expansion)?;
Ok(DrawReport {
width: self.size.0,
height: self.size.1,
damage: report.damage,
full_damage: report.full_damage,
})
}
fn keyboard_input(
&mut self,
keysym: u32,
modifier_state: ModifiersState,
key_state: KeyState,
interpreted: Option<String>,
) {
self.child
.keyboard_input(keysym, modifier_state, key_state, interpreted);
}
fn mouse_click(&mut self, button: u32, pos: (u32, u32)) {
self.child.mouse_click(button, pos);
}
fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)) {
self.child.mouse_scroll(scroll, pos);
}
}
+112
View File
@@ -0,0 +1,112 @@
use std::{
thread,
sync::{Arc, Mutex},
};
use crate::{
color::Color,
fonts::FontMap,
event::{Event, Events, PointerButton},
widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
use alsa::mixer::{
Mixer, Selem, SelemChannelId, SelemId,
};
#[inline]
fn alsa_volume_to_f32(volume: i64, min: i64, max: i64) -> f32 {
(volume - min) as f32 / (max - min) as f32
}
#[inline]
fn f32_to_alsa_volume(volume: f32, min: i64, max: i64) -> i64 {
min + ((max - min) as f32 * volume) as i64
}
fn get_master_volume(selem: &mut Selem) -> f32 {
let (min, max) = selem.get_playback_volume_range();
selem
.get_playback_volume(SelemChannelId::mono())
.map(|volume| alsa_volume_to_f32(volume, min, max))
.unwrap()
}
fn get_mute(selem: &mut Selem) -> bool {
let res = selem.get_playback_switch(SelemChannelId::FrontLeft);
false
}
fn set_master_volume(selem: &mut Selem, volume: f32) {
let (min, max) = selem.get_playback_volume_range();
let volume = f32_to_alsa_volume(volume, min, max);
selem.set_playback_volume_all(volume).unwrap();
}
fn start_monitor(inner: Arc<Mutex<Inner>>) {
thread::Builder::new()
.name("audiomon".to_string())
.spawn(move || {
let mixer = Mixer::new("default", true).unwrap();
let id = SelemId::new("Master", 0);
let mut selem = mixer.find_selem(&id).unwrap();
let mut inner = inner.lock().unwrap();
inner.volume = get_master_volume(&mut selem);
inner.mute = get_mute(&mut selem);
})
.unwrap();
}
struct Inner {
volume: f32,
mute: bool,
}
pub struct AlsaAudio {
inner: Arc<Mutex<Inner>>,
}
impl AlsaAudio {
pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
let inner = Arc::new(Mutex::new(Inner{
mute: false,
volume: 0.,
}));
start_monitor(inner.clone());
let dev = AlsaAudio{ inner };
BarWidget::new(Box::new(dev), fm, font, size)
}
}
impl BarWidgetImpl for AlsaAudio {
fn name(&self) -> &'static str {
"audio"
}
fn get_dirty(&self) -> bool {
true
}
fn value(&mut self) -> f32 {
let inner = self.inner.lock().unwrap();
inner.volume
}
fn color(&self) -> Color {
let inner = self.inner.lock().unwrap();
if inner.mute {
Color::DARKORANGE
} else {
Color::WHITE
}
}
fn click(&mut self, pos: f32, btn: PointerButton) {
let inner = self.inner.lock().unwrap();
match btn {
// PointerButton::Left => inner.set_master_volume(pos),
//PointerButton::Right => Some(Change::ToggleMute),
//PointerButton::ScrollVertical(val) => Some(Change::VolumeInc((val / 512.) as f32)),
//PointerButton::ScrollHorizontal(val) => Some(Change::VolumeInc((val / 100.) as f32)),
_ => (),
};
}
}
-115
View File
@@ -1,115 +0,0 @@
use crate::color::Color;
use crate::widget::WaitContext;
use crate::{
fonts::FontRef,
widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
use alsa::mixer::{Mixer, Selem, SelemChannelId, SelemId};
const CARD_NAME: &str = "default";
const SELEM_NAME: &str = "Master";
const SELEM_ID: u32 = 0u32;
#[inline]
fn alsa_error_to_io_error(fail: &'static str, err: &alsa::Error) -> ::std::io::Error {
let kind = ::std::io::ErrorKind::Other;
let func = err.func();
::std::io::Error::new(kind, format!("{}: {}{}", fail, func, err.errno()))
}
#[inline]
fn alsa_volume_to_f32(volume: i64, min: i64, max: i64) -> f32 {
(volume - min) as f32 / (max - min) as f32
}
#[inline]
fn f32_to_alsa_volume(volume: f32, min: i64, max: i64) -> i64 {
min + ((max - min) as f32 * volume) as i64
}
pub struct Alsa {
pub mixer: Mixer,
}
impl Alsa {
pub fn new(font: FontRef, font_size: f32, length: u32) -> ::std::io::Result<Box<BarWidget>> {
let mixer = Mixer::new(CARD_NAME, true)
.map_err(|err| alsa_error_to_io_error("Failed to create ALSA mixer", &err))?;
let mixer = Self { mixer };
mixer.get_master_volume()?; // probing
Ok(BarWidget::new_simple(
font,
font_size,
length,
Box::new(mixer),
))
}
#[inline]
pub fn get_master(&self) -> ::std::io::Result<Selem> {
let master_id = SelemId::new(SELEM_NAME, SELEM_ID);
match self.mixer.find_selem(&master_id) {
Some(master) => Ok(master),
None => {
let kind = ::std::io::ErrorKind::NotFound;
let desc = "`Master` not found";
Err(::std::io::Error::new(kind, desc))
}
}
}
pub fn get_master_volume(&self) -> ::std::io::Result<f32> {
let master = self.get_master()?;
let (min, max) = master.get_playback_volume_range();
master
.get_playback_volume(SelemChannelId::mono())
.map(|volume| alsa_volume_to_f32(volume, min, max))
.map_err(|e| alsa_error_to_io_error("Failed to get `Master` volume", &e))
}
pub fn set_master_volume(&self, volume: f32) -> ::std::io::Result<()> {
let master = self.get_master()?;
let (min, max) = master.get_playback_volume_range();
let volume = f32_to_alsa_volume(volume, min, max);
master
.set_playback_volume_all(volume)
.map_err(|e| alsa_error_to_io_error("Failed to set `Master` volume", &e))
}
pub fn inc_master_volume(&self, diff: f32) -> ::std::io::Result<()> {
let master = self.get_master()?;
let (min, max) = master.get_playback_volume_range();
master
.get_playback_volume(SelemChannelId::mono())
.and_then(|volume| {
let volume = alsa_volume_to_f32(volume, min, max);
let volume = f32_to_alsa_volume(volume + diff, min, max);
master.set_playback_volume_all(volume)
})
.map_err(|e| alsa_error_to_io_error("Failed to inc/dec `Master` volume", &e))
}
}
impl BarWidgetImpl for Alsa {
fn wait(&mut self, _: &mut WaitContext) {}
fn name(&self) -> &str {
"volume"
}
fn value(&self) -> f32 {
match self.get_master_volume() {
Ok(v) => v,
Err(e) => panic!("{}", e),
}
}
fn color(&self) -> Color {
Color::new(1.0, 1.0, 1.0, 1.0)
}
fn inc(&mut self, diff: f32) {
if let Err(e) = self.inc_master_volume(diff) {
panic!("{}", e)
}
}
fn set(&mut self, abs: f32) {
if let Err(e) = self.set_master_volume(abs) {
panic!("{}", e)
}
}
fn toggle(&mut self) {}
}
-9
View File
@@ -1,9 +0,0 @@
#[cfg(feature = "alsa-widget")]
mod alsa_sound;
#[cfg(feature = "alsa-widget")]
pub use alsa_sound::Alsa;
#[cfg(feature = "pulseaudio-widget")]
mod pulse_sound;
#[cfg(feature = "pulseaudio-widget")]
pub use pulse_sound::PulseAudio;
-544
View File
@@ -1,544 +0,0 @@
use crate::cmd::Cmd;
use crate::color::Color;
use crate::widget::WaitContext;
use crate::{
fonts::FontRef,
widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::mpsc::{channel, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use libpulse_binding::callbacks::ListResult;
use libpulse_binding::context::{
introspect::ServerInfo, introspect::SinkInfo, subscribe::Facility, subscribe::InterestMaskSet,
subscribe::Operation as SubscribeOperation, Context, FlagSet, State as PulseState,
};
use libpulse_binding::mainloop::standard::IterateResult;
use libpulse_binding::mainloop::standard::Mainloop;
use libpulse_binding::proplist::{properties, Proplist};
use libpulse_binding::volume::{ChannelVolumes, Volume};
struct PulseAudioConnection {
mainloop: Rc<RefCell<Mainloop>>,
context: Rc<RefCell<Context>>,
}
#[derive(Clone)]
struct PulseAudioSinkInfo {
volume: ChannelVolumes,
mute: bool,
}
struct PulseAudioClient {
sender: Sender<PulseAudioClientRequest>,
default_sink: String,
sinks: HashMap<String, PulseAudioSinkInfo>,
}
enum PulseAudioClientRequest {
GetDefaultDevice(Option<Sender<bool>>),
GetSinkInfoByIndex(Option<Sender<bool>>, u32),
GetSinkInfoByName(Option<Sender<bool>>, String),
SetSinkVolumeByName(Option<Sender<bool>>, String, ChannelVolumes),
SetSinkMuteByName(Option<Sender<bool>>, String, bool),
}
#[derive(Debug)]
struct PulseAudioSoundDeviceInner {
name: Option<String>,
volume: Option<ChannelVolumes>,
volume_avg: f32,
muted: bool,
default_sink: String,
}
struct PulseAudioSoundDevice {
client: Arc<Mutex<PulseAudioClient>>,
inner: Arc<Mutex<PulseAudioSoundDeviceInner>>,
}
impl PulseAudioConnection {
fn new() -> Result<Self, ::std::io::Error> {
let mut proplist = Proplist::new().unwrap();
proplist
.set_str(properties::APPLICATION_NAME, "wldash")
.unwrap();
let mainloop = Rc::new(RefCell::new(Mainloop::new().unwrap()));
let context = Rc::new(RefCell::new(
Context::new_with_proplist(mainloop.borrow().deref(), "wldash_context", &proplist)
.unwrap(),
));
context
.borrow_mut()
.connect(None, FlagSet::NOFLAGS, None)
.map_err(|_e| {
::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to connect to pulseaudio context",
)
})?;
let mut connection = PulseAudioConnection { mainloop, context };
// Wait for context to be ready
loop {
connection.iterate(false)?;
match connection.context.borrow().get_state() {
PulseState::Ready => {
break;
}
PulseState::Failed | PulseState::Terminated => {}
_ => {}
}
}
Ok(connection)
}
fn iterate(&mut self, blocking: bool) -> Result<(), ::std::io::Error> {
match self.mainloop.borrow_mut().iterate(blocking) {
IterateResult::Quit(_) | IterateResult::Err(_) => Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to iterate pulseaudio state",
)),
IterateResult::Success(_) => Ok(()),
}
}
}
impl PulseAudioClient {
fn new<F>(listener: F) -> Result<Arc<Mutex<Self>>, ::std::io::Error>
where
F: Fn(Arc<Mutex<Self>>) -> (),
F: Send + 'static + Clone,
{
let (tx, rx) = channel();
let client = Arc::new(Mutex::new(PulseAudioClient {
sender: tx,
default_sink: "@DEFAULT_SINK@".to_string(),
sinks: HashMap::new(),
}));
let loop_client = client.clone();
let (tx1, rx1) = channel();
let _ = thread::Builder::new()
.name("pa_writer".to_string())
.spawn(move || {
let mut conn = match PulseAudioConnection::new() {
Ok(v) => {
tx1.send(true).unwrap();
v
}
Err(_) => {
tx1.send(false).unwrap();
return;
}
};
// make sure mainloop dispatched everything
for _ in 0..10 {
conn.iterate(false).unwrap();
}
loop {
let cl = loop_client.clone();
let l = listener.clone();
match rx.recv() {
Err(_) => return,
Ok(req) => {
let mut introspector = conn.context.borrow_mut().introspect();
let done = Arc::new(Mutex::new(false));
let done_reader = done.clone();
let s = match req {
PulseAudioClientRequest::GetDefaultDevice(s) => {
introspector.get_server_info(move |info| {
*done.lock().unwrap() = true;
PulseAudioClient::server_info_callback(
cl.clone(),
l.clone(),
info,
);
});
s
}
PulseAudioClientRequest::GetSinkInfoByIndex(s, index) => {
introspector.get_sink_info_by_index(index, move |res| {
*done.lock().unwrap() = true;
PulseAudioClient::sink_info_callback(
cl.clone(),
l.clone(),
res,
);
});
s
}
PulseAudioClientRequest::GetSinkInfoByName(s, name) => {
introspector.get_sink_info_by_name(&name, move |res| {
*done.lock().unwrap() = true;
PulseAudioClient::sink_info_callback(
cl.clone(),
l.clone(),
res,
);
});
s
}
PulseAudioClientRequest::SetSinkVolumeByName(s, name, volumes) => {
introspector.set_sink_volume_by_name(
&name,
&volumes,
Some(Box::new(move |_| {
*done.lock().unwrap() = true;
})),
);
s
}
PulseAudioClientRequest::SetSinkMuteByName(s, name, mute) => {
introspector.set_sink_mute_by_name(
&name,
mute,
Some(Box::new(move |_| {
*done.lock().unwrap() = true;
})),
);
s
}
};
// send request and receive response
loop {
if *done_reader.lock().unwrap() {
break;
}
conn.iterate(true).unwrap();
}
if let Some(s) = s {
let _ = s.send(true);
}
}
}
}
});
// subscribe
let cl2 = client.clone();
let (tx2, rx2) = channel();
let _ = thread::Builder::new()
.name("pa_reader".to_string())
.spawn(move || {
let conn = match PulseAudioConnection::new() {
Ok(v) => {
tx2.send(true).unwrap();
v
}
Err(_) => {
tx2.send(false).unwrap();
return;
}
};
// subcribe for events
conn.context
.borrow_mut()
.set_subscribe_callback(Some(Box::new(move |facility, operation, index| {
cl2.lock()
.unwrap()
.subscribe_callback(facility, operation, index)
})));
conn.context
.borrow_mut()
.subscribe(InterestMaskSet::SERVER | InterestMaskSet::SINK, |_| {});
conn.mainloop.borrow_mut().run().unwrap();
});
let pa_writer_res = rx1.recv().unwrap();
let pa_reader_res = rx2.recv().unwrap();
if !pa_writer_res || !pa_reader_res {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to start pulseaudio thread",
));
}
Ok(client)
}
fn send(&self, request: PulseAudioClientRequest) -> Result<(), ::std::io::Error> {
self.sender.send(request).map_err(|_e| {
::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to send pulseaudio request",
)
})
}
fn server_info_callback<F>(s: Arc<Mutex<Self>>, listener: F, server_info: &ServerInfo)
where
F: Fn(Arc<Mutex<Self>>) -> (),
F: Send + 'static,
{
match server_info.default_sink_name.clone() {
None => {}
Some(default_sink) => {
(*s.lock().unwrap()).default_sink = default_sink.into();
listener(s);
}
}
}
fn sink_info_callback<F>(s: Arc<Mutex<Self>>, listener: F, result: ListResult<&SinkInfo>)
where
F: Fn(Arc<Mutex<Self>>) -> (),
F: Send + 'static,
{
match result {
ListResult::End | ListResult::Error => {}
ListResult::Item(sink_info) => match sink_info.name.clone() {
None => {}
Some(name) => {
let info = PulseAudioSinkInfo {
volume: sink_info.volume,
mute: sink_info.mute,
};
s.lock().unwrap().sinks.insert(name.into(), info);
listener(s);
}
},
}
}
fn subscribe_callback(
&self,
facility: Option<Facility>,
_operation: Option<SubscribeOperation>,
index: u32,
) {
match facility {
None => {}
Some(facility) => match facility {
Facility::Server => {
let _ = self.send(PulseAudioClientRequest::GetDefaultDevice(None));
}
Facility::Sink => {
let _ = self.send(PulseAudioClientRequest::GetSinkInfoByIndex(None, index));
}
_ => {}
},
}
}
}
impl PulseAudioSoundDevice {
fn new<F>(listener: F) -> Result<Self, ::std::io::Error>
where
F: Fn() -> (),
F: Send + 'static + Clone,
{
let inner = Arc::new(Mutex::new(PulseAudioSoundDeviceInner {
name: None,
volume: None,
volume_avg: 0.0,
muted: false,
default_sink: "@DEFAULT_SINK@".to_string(),
}));
let cb_inner = inner.clone();
let client = PulseAudioClient::new(move |client| {
let mut inner = cb_inner.lock().unwrap();
inner.default_sink = client.lock().unwrap().default_sink.clone();
let name = inner
.name
.clone()
.unwrap_or_else(|| inner.default_sink.clone());
let sink_info = match client.lock().unwrap().sinks.get(&name) {
None => return,
Some(sink_info) => (*sink_info).clone(),
};
inner.volume = Some(sink_info.volume);
inner.volume_avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32;
inner.muted = sink_info.mute;
listener();
})?;
let cl = client.clone();
let (tx, rx) = channel();
{
let cl = client.lock().unwrap();
cl.send(PulseAudioClientRequest::GetDefaultDevice(Some(tx)))?;
}
rx.recv().unwrap();
let name = {
let cl = client.lock().unwrap();
cl.default_sink.to_string()
};
(*inner.lock().unwrap()).name = Some(name.clone());
let device = PulseAudioSoundDevice { client: cl, inner };
let (tx, rx) = channel();
{
let cl = client.lock().unwrap();
cl.send(PulseAudioClientRequest::GetSinkInfoByName(Some(tx), name))?;
}
rx.recv().unwrap();
Ok(device)
}
fn muted(&self) -> bool {
let inner = self.inner.lock().unwrap();
inner.muted
}
fn volume(&self) -> f32 {
let inner = self.inner.lock().unwrap();
inner.volume_avg
}
fn inc_volume(&mut self, step: f32) -> Result<(), ::std::io::Error> {
let mut inner = self.inner.lock().unwrap();
let mut volume = match inner.volume {
Some(volume) => volume,
None => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to set volume",
))
}
};
// apply step to volumes
let step = (step * Volume::NORMAL.0 as f32).round() as i32;
if step > 0 {
volume.inc_clamp(Volume(step as u32), Volume::MAX);
} else {
volume.decrease(Volume(-step as u32));
}
let name = inner
.name
.clone()
.unwrap_or_else(|| inner.default_sink.clone());
// update volumes
inner.volume = Some(volume);
inner.volume_avg = volume.avg().0 as f32 / Volume::NORMAL.0 as f32;
self.client
.lock()
.unwrap()
.send(PulseAudioClientRequest::SetSinkVolumeByName(
None, name, volume,
))?;
Ok(())
}
fn set_volume(&mut self, val: f32) -> Result<(), ::std::io::Error> {
let mut inner = self.inner.lock().unwrap();
let mut volume = match inner.volume {
Some(volume) => volume,
None => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to set volume",
))
}
};
// apply step to volumes
volume.scale(Volume((val * Volume::NORMAL.0 as f32).round() as u32));
let name = inner
.name
.clone()
.unwrap_or_else(|| inner.default_sink.clone());
// update volumes
inner.volume = Some(volume);
inner.volume_avg = volume.avg().0 as f32 / Volume::NORMAL.0 as f32;
self.client
.lock()
.unwrap()
.send(PulseAudioClientRequest::SetSinkVolumeByName(
None, name, volume,
))?;
Ok(())
}
fn toggle(&mut self) -> Result<(), ::std::io::Error> {
let mut inner = self.inner.lock().unwrap();
inner.muted = !inner.muted;
self.client
.lock()
.unwrap()
.send(PulseAudioClientRequest::SetSinkMuteByName(
None,
inner
.name
.clone()
.unwrap_or_else(|| inner.default_sink.clone()),
inner.muted,
))?;
Ok(())
}
}
pub struct PulseAudio {
device: PulseAudioSoundDevice,
}
impl PulseAudio {
pub fn new(
font: FontRef,
font_size: f32,
length: u32,
sender: Sender<Cmd>,
) -> Result<Box<BarWidget>, ::std::io::Error> {
BarWidget::new(font, font_size, length, move |dirty| {
let device = PulseAudioSoundDevice::new(move || {
*dirty.lock().unwrap() = true;
sender.send(Cmd::Draw).unwrap();
})?;
Ok(Box::new(PulseAudio { device }))
})
}
}
impl BarWidgetImpl for PulseAudio {
fn wait(&mut self, _: &mut WaitContext) {}
fn name(&self) -> &str {
"volume"
}
fn value(&self) -> f32 {
self.device.volume()
}
fn color(&self) -> Color {
let muted = self.device.muted();
if muted {
Color::new(1.0, 1.0, 0.0, 1.0)
} else {
Color::new(1.0, 1.0, 1.0, 1.0)
}
}
fn inc(&mut self, inc: f32) {
self.device.inc_volume(inc).unwrap();
}
fn set(&mut self, val: f32) {
self.device.set_volume(val).unwrap();
}
fn toggle(&mut self) {
self.device.toggle().unwrap();
}
}
+68 -104
View File
@@ -1,21 +1,17 @@
use crate::color::Color; use std::{
use crate::widget::WaitContext; fs::{self, OpenOptions},
use crate::{ io::{Error, ErrorKind, Write},
fonts::FontRef, path::{Path, PathBuf},
widgets::bar_widget::{BarWidget, BarWidgetImpl},
}; };
use std::fs; use walkdir::WalkDir;
use std::fs::OpenOptions;
use std::io::Write;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
pub struct Backlight { use crate::{
device_path: PathBuf, color::Color,
cur_brightness: u64, event::PointerButton,
max_brightness: u64, fonts::FontMap,
} widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
fn read_file_as_u64(path: &Path) -> Result<u64, Error> { fn read_file_as_u64(path: &Path) -> Result<u64, Error> {
let mut s = fs::read_to_string(path)?; let mut s = fs::read_to_string(path)?;
@@ -29,113 +25,81 @@ fn write_file_as_u64(path: &Path, value: u64) -> Result<(), Error> {
file.write_fmt(format_args!("{}", value)) file.write_fmt(format_args!("{}", value))
} }
pub struct Backlight {
device_path: PathBuf,
cur: u64,
max: u64,
dirty: bool,
}
impl Backlight { impl Backlight {
pub fn update(&mut self) -> Result<(), Error> { fn update(&mut self) {
self.cur_brightness = read_file_as_u64(self.device_path.join("brightness").as_path())?; self.cur = read_file_as_u64(self.device_path.join("brightness").as_path()).unwrap();
self.max_brightness = read_file_as_u64(self.device_path.join("max_brightness").as_path())?; self.max = read_file_as_u64(self.device_path.join("max_brightness").as_path()).unwrap();
Ok(())
} }
pub fn sync(&mut self) -> Result<(), Error> { pub fn new(path: Option<&str>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
write_file_as_u64( let mut device_path = None;
self.device_path.join("brightness").as_path(), if let Some(path) = path {
self.cur_brightness, device_path = Some(Path::new("/sys/class/backlight").to_path_buf().join(path));
)?;
self.update()?;
Ok(())
}
pub fn brightness(&self) -> f32 {
if self.cur_brightness > self.max_brightness {
// what.
return 1.0;
}
self.cur_brightness as f32 / self.max_brightness as f32
}
pub fn set(&mut self, val: f32) -> Result<(), Error> {
self.cur_brightness = (self.max_brightness as f32 * val) as u64;
Ok(())
}
pub fn add(&mut self, diff: f32) -> Result<(), Error> {
let inc = (self.max_brightness as f32 * diff) as i64;
self.cur_brightness = if self.cur_brightness as i64 + inc < 1 {
1
} else if self.cur_brightness as i64 + inc > self.max_brightness as i64 {
self.max_brightness
} else { } else {
(self.cur_brightness as i64 + inc) as u64 for entry in WalkDir::new("/sys/class/backlight") {
if let Ok(entry) = entry {
device_path = Some(entry.path().to_path_buf());
}
}
}
let mut dev = Backlight {
device_path: device_path.expect("did not find backlight device"),
cur: 0,
max: 0,
dirty: true,
}; };
Ok(()) dev.update();
BarWidget::new(Box::new(dev), fm, font, size)
} }
pub fn new<'a>( pub fn detect() -> bool {
path: &str, for entry in WalkDir::new("/sys/class/backlight") {
font: FontRef<'a>, if entry.is_ok() {
font_size: f32, return true;
length: u32, }
) -> Result<Box<BarWidget<'a>>, Error> { }
let mut dev = Backlight { return false;
device_path: Path::new("/sys/class/backlight").to_path_buf().join(path), }
cur_brightness: 0,
max_brightness: 0,
};
dev.update()?; pub fn set(&mut self, brightness: f32) {
let val = (self.max as f32 * brightness) as u64;
Ok(BarWidget::new_simple( let _ = write_file_as_u64(self.device_path.join("brightness").as_path(), val);
font, self.cur = read_file_as_u64(self.device_path.join("brightness").as_path()).unwrap();
font_size,
length,
Box::new(dev),
))
} }
} }
impl BarWidgetImpl for Backlight { impl BarWidgetImpl for Backlight {
fn wait(&mut self, _: &mut WaitContext) {} fn name(&self) -> &'static str {
fn name(&self) -> &str {
"backlight" "backlight"
} }
fn value(&self) -> f32 { fn get_dirty(&self) -> bool {
self.brightness() self.dirty
}
fn value(&mut self) -> f32 {
if self.cur > self.max {
// what.
return 1.0;
}
self.dirty = false;
self.cur as f32 / self.max as f32
} }
fn color(&self) -> Color { fn color(&self) -> Color {
Color::new(1.0, 1.0, 1.0, 1.0) Color::WHITE
} }
fn inc(&mut self, inc: f32) { fn click(&mut self, pos: f32, btn: PointerButton) {
self.add(inc).unwrap(); self.dirty = true;
match self.sync() { match btn {
Ok(val) => val, PointerButton::Left => self.set(pos),
Err(err) => { _ => (),
eprintln!("Error while trying to change brightness: {}", err);
}
}
}
fn set(&mut self, val: f32) {
self.set(val).unwrap();
match self.sync() {
Ok(val) => val,
Err(err) => {
eprintln!("Error while trying to change brightness: {}", err);
}
}
}
fn toggle(&mut self) {
self.cur_brightness = if self.cur_brightness == 1 {
self.max_brightness
} else {
1
}; };
match self.sync() {
Ok(val) => val,
Err(err) => {
eprintln!("Error while trying to change brightness: {}", err);
}
}
} }
} }
+91 -135
View File
@@ -1,163 +1,119 @@
use crate::color::Color; use std::{cmp::min, default::Default};
use crate::draw::{draw_bar, draw_box, Font};
use crate::{ use crate::{
fonts::FontRef, buffer::BufferView,
widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, color::Color,
draw::{draw_bar, draw_box},
event::{Event, PointerButton},
fonts::FontMap,
widgets::{Geometry, Widget},
}; };
use std::sync::{Arc, Mutex};
pub trait BarWidgetImpl { pub trait BarWidgetImpl {
fn wait(&mut self, ctx: &mut WaitContext); fn get_dirty(&self) -> bool {
fn name(&self) -> &str; false
fn value(&self) -> f32; }
fn name(&self) -> &'static str;
fn value(&mut self) -> f32;
fn color(&self) -> Color; fn color(&self) -> Color;
fn inc(&mut self, inc: f32); fn click(&mut self, _pos: f32, _btn: PointerButton) {}
fn set(&mut self, val: f32);
fn toggle(&mut self);
} }
pub struct BarWidget<'a> { pub struct BarWidget {
bar_impl: Box<dyn BarWidgetImpl + Send>, font: &'static str,
font: Font<'a>, size: f32,
font_size: u32, geometry: Geometry,
length: u32, inner_widget: Box<dyn BarWidgetImpl>,
dirty: Arc<Mutex<bool>>,
} }
impl<'a> BarWidget<'a> { impl BarWidget {
pub fn new_simple( pub fn new(
font: FontRef, inner_widget: Box<dyn BarWidgetImpl>,
font_size: f32, fm: &mut FontMap,
length: u32, font: &'static str,
w: Box<dyn BarWidgetImpl + Send>, size: f32,
) -> Box<BarWidget> { ) -> BarWidget {
let mut font = Font::new(font, font_size); fm.queue_font(font, size, inner_widget.name());
font.add_str_to_cache(w.name()); BarWidget {
geometry: Default::default(),
Box::new(BarWidget { inner_widget,
bar_impl: w,
dirty: Arc::new(Mutex::new(true)),
font, font,
font_size: font_size as u32, size,
length,
})
} }
pub fn new<F>(
font: FontRef<'a>,
font_size: f32,
length: u32,
f: F,
) -> Result<Box<BarWidget>, ::std::io::Error>
where
F: FnOnce(Arc<Mutex<bool>>) -> Result<Box<dyn BarWidgetImpl + Send>, ::std::io::Error>,
F: Send + 'a + Clone,
{
let dirty = Arc::new(Mutex::new(true));
let im = f(dirty.clone())?;
let mut font = Font::new(font, font_size);
font.add_str_to_cache(im.name());
Ok(Box::new(BarWidget {
bar_impl: im,
dirty,
font,
font_size: font_size as u32,
length,
}))
} }
} }
impl<'a> Widget for BarWidget<'a> { impl Widget for BarWidget {
fn wait(&mut self, ctx: &mut WaitContext) { fn geometry(&self) -> Geometry {
self.bar_impl.wait(ctx); self.geometry
}
fn enter(&mut self) {}
fn leave(&mut self) {}
fn size(&self) -> (u32, u32) {
(self.length, self.font_size)
}
fn draw(
&mut self,
ctx: &mut DrawContext,
pos: (u32, u32),
expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error> {
if self.length == 0 {
self.length = expansion.0;
}
let (width, height) = (self.length, self.font_size);
{
let mut d = self.dirty.lock().unwrap();
if !*d && !ctx.force {
return Ok(DrawReport::empty(width, height));
}
*d = false;
} }
let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; fn get_dirty(&self) -> bool {
buf.memset(ctx.bg); self.inner_widget.get_dirty()
}
let c = Color::new(1.0, 1.0, 1.0, 1.0); fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
self.font.draw_text(buf, ctx.bg, &c, self.bar_impl.name())?; let font = fonts.get_font(self.font, self.size);
let fg = Color::WHITE;
let c = self.bar_impl.color(); font.draw_text(view, fg, self.inner_widget.name()).unwrap();
let bar_off = 5 * self.font_size; let size = self.size.ceil() as u32;
let mut val = self.bar_impl.value(); let bar_offset = 4 * size;
let val = self.inner_widget.value();
let c = self.inner_widget.color();
draw_bar( draw_bar(
&mut buf.offset((bar_off, 0))?, &mut view.offset((bar_offset, 0)),
&c, c,
width - bar_off, self.geometry.width - bar_offset,
self.font_size, size,
val, val,
)?; )
let mut iter = 1.0; .unwrap();
while val > 1.0 {
let c = &Color::new(0.75 / iter, 0.25 / iter, 0.25 / iter, 1.0);
val -= 1.0;
iter += 1.0;
draw_bar(
&mut buf.offset((bar_off, 0))?,
&c,
width - bar_off,
self.font_size,
val,
)?;
}
draw_box( draw_box(
&mut buf.offset((bar_off, 0))?, &mut view.offset((bar_offset, 0)),
&c, c,
(width - bar_off, self.font_size), (self.geometry.width - bar_offset, size),
)?; )
Ok(DrawReport { .unwrap();
width, self.geometry
height,
damage: vec![buf.get_signed_bounds()],
full_damage: false,
})
} }
fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option<String>) {} fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
fn mouse_click(&mut self, button: u32, (x, _): (u32, u32)) { let w = min(geometry.width, 768);
*self.dirty.lock().unwrap() = true; let mut x = geometry.x;
match button { if geometry.width > w {
272 => { x += geometry.width - w;
let offset = 5 * self.font_size; }
if x > offset { self.geometry = Geometry {
self.bar_impl x: x,
.set(((x - offset) + 1) as f32 / (self.length - offset) as f32); y: geometry.y,
width: w,
height: self.size.ceil() as u32,
};
self.geometry
}
fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry {
Geometry {
x: 0,
y: 0,
width: self.size.ceil() as u32 * 6,
height: self.size.ceil() as u32,
} }
} }
273 => self.bar_impl.toggle(),
x => { fn event(&mut self, event: &Event) {
dbg!(x); match event {
Event::PointerEvent(ev) => {
let offset = 4 * self.size.ceil() as u32;
if ev.pos.0 >= offset {
let val = (ev.pos.0 - offset) as f32 / (self.geometry.width - offset) as f32;
self.inner_widget.click(val, ev.button);
}
}
_ => (),
} }
} }
} }
fn mouse_scroll(&mut self, (_, y): (f64, f64), _: (u32, u32)) {
*self.dirty.lock().unwrap() = true;
self.bar_impl.inc(y as f32 / -800.0);
}
}
+145 -208
View File
@@ -1,240 +1,177 @@
use crate::cmd::Cmd; use std::{
use crate::color::Color; error::Error,
use crate::widget::WaitContext; sync::{Arc, Mutex},
thread,
time::Duration,
};
use dbus::blocking::{
stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged},
LocalConnection,
};
use crate::{ use crate::{
fonts::FontRef, color::Color,
event::{Event, Events},
fonts::FontMap,
widgets::bar_widget::{BarWidget, BarWidgetImpl}, widgets::bar_widget::{BarWidget, BarWidgetImpl},
}; };
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use nix::poll::{PollFd, PollFlags};
fn get_upower_property(
con: &dbus::Connection,
device_path: &str,
property: &str,
) -> Result<dbus::Message, ::std::io::Error> {
let msg = dbus::Message::new_method_call(
"org.freedesktop.UPower",
device_path,
"org.freedesktop.DBus.Properties",
"Get",
)
.map_err(|_| {
::std::io::Error::new(
::std::io::ErrorKind::Other,
"could not send make dbus method call",
)
})?
.append2(
dbus::MessageItem::Str("org.freedesktop.UPower.Device".to_string()),
dbus::MessageItem::Str(property.to_string()),
);
con.send_with_reply_and_block(msg, 1000).map_err(|_| {
::std::io::Error::new(::std::io::ErrorKind::Other, "could not send dbus message")
})
}
// The widget is created on a different thread than where it will be used, so
// we need it to be Send. However, dbus::Connection has a void*, so it's not
// auto-derived. So, we make a small wrapper where we make it Send.
struct DbusConnection(dbus::Connection);
impl std::convert::AsRef<dbus::Connection> for DbusConnection {
fn as_ref(&self) -> &dbus::Connection {
&self.0
}
}
unsafe impl Send for DbusConnection {}
pub struct UpowerBattery {
device_path: String,
sender: Sender<Cmd>,
dirty: Arc<Mutex<bool>>,
state: UpowerBatteryState,
capacity: f64,
con: DbusConnection,
watch: dbus::Watch,
}
enum UpowerBatteryState { enum UpowerBatteryState {
Unknown,
Charging, Charging,
Discharging, Discharging,
Empty, Empty,
Full, FullyCharged,
NotCharging, PendingCharge,
Unknown, PendingDischarge,
} }
impl UpowerBattery { impl UpowerBatteryState {
pub fn from_device( fn from_dbus(val: u64) -> UpowerBatteryState {
dirty: Arc<Mutex<bool>>, match val {
sender: Sender<Cmd>,
device: &str,
) -> Result<Self, ::std::io::Error> {
let con = dbus::Connection::get_private(dbus::BusType::System).map_err(|_| {
::std::io::Error::new(::std::io::ErrorKind::Other, "unable to open dbus")
})?;
let device_path = format!("/org/freedesktop/UPower/devices/{}", device);
let rule = format!(
"type='signal',\
path='{}',\
interface='org.freedesktop.DBus.Properties',\
member='PropertiesChanged'",
&device_path
);
con.add_match(&rule).map_err(|_| {
::std::io::Error::new(
::std::io::ErrorKind::Other,
"unable to add match rule to dbus connection",
)
})?;
let upower_type: dbus::arg::Variant<u32> =
match get_upower_property(&con, &device_path, "Type")?.get1() {
Some(v) => v,
None => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"no such upower device",
));
}
};
// https://upower.freedesktop.org/docs/Device.html#Device:Type
if upower_type.0 != 2 {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"UPower device is not a battery.",
));
}
let capacity: f64 = match get_upower_property(&con, &device_path, "Percentage")?
.get1::<dbus::arg::Variant<f64>>()
{
Some(v) => v.0,
None => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"no such upower device",
));
}
};
let state: UpowerBatteryState = match get_upower_property(&con, &device_path, "State")?
.get1::<dbus::arg::Variant<u32>>()
{
Some(v) => match v.0 {
1 => UpowerBatteryState::Charging, 1 => UpowerBatteryState::Charging,
2 => UpowerBatteryState::Discharging, 2 => UpowerBatteryState::Discharging,
3 => UpowerBatteryState::Empty, 3 => UpowerBatteryState::Empty,
4 => UpowerBatteryState::Full, 4 => UpowerBatteryState::FullyCharged,
5 => UpowerBatteryState::NotCharging, 5 => UpowerBatteryState::PendingCharge,
6 => UpowerBatteryState::Discharging, 6 => UpowerBatteryState::PendingDischarge,
_ => UpowerBatteryState::Unknown, _ => UpowerBatteryState::Unknown,
},
None => {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"no such upower device",
));
} }
}; }
let fds = con.watch_fds();
if fds.len() != 1 {
return Err(::std::io::Error::new(
::std::io::ErrorKind::Other,
"expected 1 watch fd from dbus",
));
} }
Ok(UpowerBattery { struct InnerBattery {
value: f32,
state: UpowerBatteryState,
dirty: bool,
}
fn get_battery_state() -> Result<UpowerBatteryState, Box<dyn Error>> {
let conn = LocalConnection::new_system()?;
let device_path = "/org/freedesktop/UPower/devices/DisplayDevice";
let proxy = conn.with_proxy(
"org.freedesktop.UPower",
device_path, device_path,
con: DbusConnection(con), Duration::from_millis(500),
dirty, );
sender, let state: u32 = proxy.get("org.freedesktop.UPower.Device", "State")?;
capacity, Ok(UpowerBatteryState::from_dbus(state as u64))
state,
watch: fds[0],
})
} }
fn start_monitor(inner: Arc<Mutex<InnerBattery>>, events: Arc<Mutex<Events>>) {
thread::Builder::new()
.name("battmon".to_string())
.spawn(move || {
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 = UpowerBatteryState::from_dbus(state as u64);
}
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 =
UpowerBatteryState::from_dbus(value.0.as_u64().unwrap());
inner.dirty = true;
let mut events = events.lock().unwrap();
events.add_event(Event::PowerUpdate);
}
"Percentage" => {
inner.value = value.0.as_f64().unwrap() as f32 / 100.;
inner.dirty = true;
let mut events = events.lock().unwrap();
events.add_event(Event::PowerUpdate);
}
_ => (),
}
}
true
},
)
.unwrap();
loop {
conn.process(Duration::from_millis(60000)).unwrap();
}
})
.unwrap();
}
pub struct Battery {
inner: Arc<Mutex<InnerBattery>>,
}
impl Battery {
pub fn new( pub fn new(
font: FontRef, events: Arc<Mutex<Events>>,
font_size: f32, fm: &mut FontMap,
length: u32, font: &'static str,
sender: Sender<Cmd>, size: f32,
) -> Result<Box<BarWidget>, ::std::io::Error> { ) -> BarWidget {
BarWidget::new(font, font_size, length, move |dirty| { let battery = Battery {
let d = UpowerBattery::from_device(dirty, sender, "DisplayDevice")?; inner: Arc::new(Mutex::new(InnerBattery {
Ok(Box::new(d)) value: 0.,
}) state: UpowerBatteryState::Unknown,
} dirty: false,
} })),
impl BarWidgetImpl for UpowerBattery {
fn wait(&mut self, ctx: &mut WaitContext) {
for _ in self
.con
.as_ref()
.watch_handle(self.watch.fd(), dbus::WatchEvent::Readable as u32)
{
let capacity = get_upower_property(self.con.as_ref(), &self.device_path, "Percentage")
.unwrap()
.get1::<dbus::arg::Variant<f64>>()
.unwrap()
.0;
let state = match get_upower_property(self.con.as_ref(), &self.device_path, "State")
.unwrap()
.get1::<dbus::arg::Variant<u32>>()
.unwrap()
.0
{
1 => UpowerBatteryState::Charging,
2 => UpowerBatteryState::Discharging,
3 => UpowerBatteryState::Empty,
4 => UpowerBatteryState::Full,
5 => UpowerBatteryState::NotCharging,
6 => UpowerBatteryState::Discharging,
_ => UpowerBatteryState::Unknown,
}; };
self.state = state; start_monitor(battery.inner.clone(), events);
self.capacity = capacity; BarWidget::new(Box::new(battery), fm, font, size)
*self.dirty.lock().unwrap() = true;
self.sender.send(Cmd::Draw).unwrap();
} }
ctx.fds pub fn detect() -> bool {
.push(PollFd::new(self.watch.fd(), PollFlags::POLLIN)); match get_battery_state() {
Ok(_) => true,
Err(_) => false,
} }
fn name(&self) -> &str { }
}
impl BarWidgetImpl for Battery {
fn get_dirty(&self) -> bool {
self.inner.lock().unwrap().dirty
}
fn name(&self) -> &'static str {
"battery" "battery"
} }
fn value(&self) -> f32 { fn value(&mut self) -> f32 {
(self.capacity as f32) / 100.0 let mut inner = self.inner.lock().unwrap();
inner.dirty = false;
inner.value
} }
fn color(&self) -> Color { fn color(&self) -> Color {
match self.state { let inner = self.inner.lock().unwrap();
UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => { match inner.state {
if self.capacity > 10.0 { UpowerBatteryState::Charging | UpowerBatteryState::FullyCharged => Color::LIGHTGREEN,
Color::new(1.0, 1.0, 1.0, 1.0) _ if inner.value > 0.25 => Color::WHITE,
} else { _ if inner.value > 0.1 => Color::DARKORANGE,
Color::new(1.0, 0.5, 0.0, 1.0) _ => Color::RED,
}
}
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)
} }
} }
} }
fn inc(&mut self, _: f32) {}
fn set(&mut self, _: f32) {}
fn toggle(&mut self) {}
}
+275 -204
View File
@@ -1,37 +1,114 @@
use crate::buffer::Buffer; use std::default::Default;
use crate::color::Color;
use crate::draw::Font;
use crate::{ use crate::{
fonts::FontRef, buffer::BufferView,
widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, color::Color,
event::Event,
fonts::FontMap,
widgets::{Geometry, Widget},
}; };
use chrono::{Datelike, NaiveDate, NaiveDateTime}; use chrono::{Datelike, Local, NaiveDate};
pub struct Calendar<'a> { pub struct Calendar {
cur_date: NaiveDate, font: &'static str,
month_font: &'static str,
sections_x: i32,
sections_y: i32,
shown_sections_x: u32,
shown_sections_y: u32,
size: f32,
dirty: bool, dirty: bool,
offset: f64, geometry: Geometry,
sections: u32, year_width: u32,
font_size: u32, date_width: u32,
calendar_cache: Font<'a>, day_width: u32,
month_cache: Font<'a>,
year_cache: Font<'a>,
day_cache: Font<'a>,
} }
impl<'a> Calendar<'a> { const DAY_FACTOR: f32 = 0.6;
fn draw_month( const YEAR_FACTOR: f32 = 0.75;
&self, const MONTH_FACTOR: f32 = 1.25;
buf: &mut Buffer, const DATE_FACTOR: f32 = 1.;
background_color: &Color,
orig: NaiveDate,
time: NaiveDate,
) -> Result<(i32, i32, i32, i32), ::std::io::Error> {
let mut time = time;
let mut y_off = 1;
let mut done = false;
const LINE_HEIGHT: f32 = 0.85;
impl Calendar {
pub fn new(
fm: &mut FontMap,
font: &'static str,
month_font: &'static str,
size: f32,
sections_x: i32,
sections_y: i32,
) -> Calendar {
fm.queue_font(font, size * DAY_FACTOR, "ADEFHIMNORSTUW");
fm.queue_font(font, size * YEAR_FACTOR, "0123456789");
fm.queue_font(font, size * DATE_FACTOR, "0123456789");
fm.queue_font(
month_font,
size * MONTH_FACTOR,
"ADFJMNOSabcehgilmnoprstuvy",
);
Calendar {
font,
month_font,
size,
sections_x,
sections_y,
shown_sections_x: 0,
shown_sections_y: 0,
dirty: true,
geometry: Default::default(),
date_width: 0,
day_width: 0,
year_width: 0,
}
}
fn draw_month(
&mut self,
fonts: &mut FontMap,
mut view: &mut BufferView,
orig: NaiveDate,
mut time: NaiveDate,
) {
let cal_width = 8 * self.date_width + 7 * (self.date_width / 2);
//
// Draw the week day
//
let day_offset = (self.date_width - self.day_width) / 2;
let date_offset = self.date_width + self.date_width / 2;
for idx in 1..8 {
let day_font = fonts.get_font(self.font, self.size * DAY_FACTOR);
let (wk_chr, c) = match idx {
1 => ("MON", Color::WHITE),
2 => ("TUE", Color::WHITE),
3 => ("WED", Color::WHITE),
4 => ("THU", Color::WHITE),
5 => ("FRI", Color::WHITE),
6 => ("SAT", Color::GREY80),
7 => ("SUN", Color::GREY80),
_ => panic!("impossible value"),
};
day_font
.draw_text(
&mut view.offset((
idx * date_offset + day_offset,
(self.size * MONTH_FACTOR).ceil() as u32
+ (self.size * DAY_FACTOR * 0.2).ceil() as u32,
)),
c,
&wk_chr,
)
.unwrap();
}
//
// Draw the month
//
let month_font = fonts.get_font(self.month_font, self.size * MONTH_FACTOR);
let month_str = match time.month() { let month_str = match time.month() {
1 => "January", 1 => "January",
2 => "February", 2 => "February",
@@ -47,234 +124,228 @@ impl<'a> Calendar<'a> {
12 => "December", 12 => "December",
_ => panic!("impossible value"), _ => panic!("impossible value"),
}; };
month_font
.draw_text(&mut view, Color::WHITE, month_str)
.unwrap();
// //
// Draw the of the month // Draw the year
// //
self.month_cache.draw_text(
buf,
background_color,
&Color::new(1.0, 1.0, 1.0, 1.0),
month_str,
)?;
if time.year() != orig.year() { if time.year() != orig.year() {
self.year_cache.draw_text( let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR);
&mut buf.offset((self.font_size * 20, 0))?, year_font
background_color, .draw_text(
&Color::new(0.8, 0.8, 0.8, 1.0), &mut view.offset((cal_width - self.year_width, 0)),
Color::GREY80,
&format!("{:}", time.year()), &format!("{:}", time.year()),
)?; )
} .unwrap();
//
// Draw the week day
//
for idx in 1..8 {
let wk_chr = match idx {
1 => "MON",
2 => "TUE",
3 => "WED",
4 => "THU",
5 => "FRI",
6 => "SAT",
7 => "SUN",
_ => panic!("impossible value"),
};
self.day_cache.draw_text(
&mut buf.offset((
idx * self.font_size * 3 + self.font_size / 5,
y_off * self.font_size * 2 + self.font_size * 4,
))?,
background_color,
&Color::new(1.0, 1.0, 1.0, 1.0),
&wk_chr,
)?;
}
y_off += 1;
while !done {
let mut x_pos = 0;
//
// Find the start of this week
//
let mut wkday = time.weekday();
while wkday != chrono::Weekday::Mon {
x_pos += 1;
wkday = wkday.pred();
} }
let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR);
'row: for y_off in 1.. {
// //
// Draw the week number // Draw the week number
// //
let wk = time.iso_week(); let wk = time.iso_week();
self.calendar_cache.draw_text( cal_font
&mut buf.offset((0, y_off * self.font_size * 2 + self.font_size * 4))?, .draw_text(
background_color, &mut view.offset((
&Color::new(0.75, 0.75, 0.75, 1.0), 0,
y_off * (self.size * LINE_HEIGHT).ceil() as u32
+ (self.size * MONTH_FACTOR).ceil() as u32,
)),
if wk == orig.iso_week() {
Color::WHITE
} else {
Color::GREY75
},
&format!("{:02}", wk.week()), &format!("{:02}", wk.week()),
)?; )
x_pos += 1; .unwrap();
// //
// Draw the dates // Draw the dates
// //
while x_pos < 8 { let x_off = time.weekday().number_from_monday();
for x_pos in x_off..8 {
let c = if time.day() == orig.day() && time.month() == orig.month() { let c = if time.day() == orig.day() && time.month() == orig.month() {
Color::new(1.0, 1.0, 1.0, 1.0) Color::WHITE
} else if x_pos < 6 {
Color::GREY50
} else { } else {
Color::new(0.5, 0.5, 0.5, 1.0) Color::GREY35
}; };
self.calendar_cache.draw_text( cal_font
&mut buf.offset(( .draw_text(
x_pos * self.font_size * 3, &mut view.offset((
y_off * self.font_size * 2 + self.font_size * 4, x_pos * date_offset,
))?, y_off * (self.size * LINE_HEIGHT).ceil() as u32
background_color, + (self.size * MONTH_FACTOR).ceil() as u32,
&c, )),
c,
&format!("{:02}", time.day()), &format!("{:02}", time.day()),
)?; )
.unwrap();
let t = time.with_day(time.day() + 1); let t = time.with_day(time.day() + 1);
if t.is_none() { if t.is_none() {
done = true; break 'row;
break;
} }
time = t.unwrap(); time = t.unwrap();
x_pos += 1;
} }
y_off += 1;
} }
Ok(buf.get_signed_bounds())
} }
} }
impl<'a> Calendar<'a> { impl<'a> Widget for Calendar {
pub fn new( fn get_dirty(&self) -> bool {
time: NaiveDateTime, self.dirty
font_primary: FontRef<'a>, }
font_secondary: FontRef<'a>,
font_size: f32, fn geometry(&self) -> Geometry {
sections: u32, self.geometry
) -> Box<Calendar<'a>> { }
let mut calendar_cache = Font::new(font_secondary, font_size * 2.0);
calendar_cache.add_str_to_cache("0123456789"); fn event(&mut self, event: &Event) {
let mut month_cache = Font::new(font_primary, font_size * 4.0); match event {
month_cache.add_str_to_cache("JanuryFebMchApilJgstSmOoNvD"); Event::NewMinute => {
let mut year_cache = Font::new(font_primary, font_size * 1.5); self.dirty = true;
year_cache.add_str_to_cache("-0123456789"); }
let mut day_cache = Font::new(font_primary, font_size); _ => (),
day_cache.add_str_to_cache("MONTUEWDHFRISA");
Box::new(Calendar {
cur_date: time.date(),
dirty: true,
offset: 0.0,
sections,
font_size: font_size as u32,
calendar_cache,
month_cache,
year_cache,
day_cache,
})
} }
} }
impl<'a> Widget for Calendar<'a> { fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
fn wait(&mut self, _: &mut WaitContext) {} if self.shown_sections_y == 0 || self.shown_sections_x == 0 {
fn enter(&mut self) {} return Geometry {
fn leave(&mut self) {} x: self.geometry.x,
y: self.geometry.y,
fn size(&self) -> (u32, u32) { width: 0,
let cal_width = 7 * self.font_size * 3 + self.font_size * 2; height: 0,
let cal_pad = self.font_size * 3; };
(
cal_width * self.sections + cal_pad * (self.sections - 1),
(self.font_size as f32 * 21.5) as u32,
)
} }
fn draw( let time = Local::now().naive_local().date();
&mut self,
ctx: &mut DrawContext, let cal_height =
pos: (u32, u32), (self.size * MONTH_FACTOR).ceil() as u32 + (self.size * LINE_HEIGHT * 8.).ceil() as u32;
_expansion: (u32, u32), let cal_width = 8 * self.date_width + 7 * (self.date_width / 2);
) -> Result<DrawReport, ::std::io::Error> { let cal_pad = self.date_width * 2;
let (width, height) = self.size();
if ctx.time.date() == self.cur_date && !ctx.force && !self.dirty {
return Ok(DrawReport::empty(width, height));
}
self.dirty = false;
self.cur_date = ctx.time.date();
let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?;
buf.memset(ctx.bg);
let time = ctx.time.date();
let mut t = time.with_day(1).unwrap(); let mut t = time.with_day(1).unwrap();
let o = (self.offset / 100.0) as i32; let cals = self.shown_sections_y * self.shown_sections_x;
if cals >= 3 {
let cals = self.sections - 1;
let pre_cals = cals / 2;
for _ in 0..pre_cals {
t = t.pred_opt().unwrap().with_day(1).unwrap(); t = t.pred_opt().unwrap().with_day(1).unwrap();
} }
if o != 0 { for ydx in 0..self.shown_sections_y {
let mut month = (t.month() - 1) as i32 + o; for idx in 0..self.shown_sections_x {
let mut year = t.year(); self.draw_month(
while month > 11 { fonts,
year += 1; &mut view.offset(((cal_width + cal_pad) * idx, cal_height * ydx)),
month -= 12; time,
} t,
while month < 0 { );
year -= 1;
month += 12;
}
t = t
.with_year(year)
.unwrap()
.with_month((month + 1) as u32)
.unwrap();
}
let cal_pad = 7 * self.font_size * 3 + self.font_size * 5;
for idx in 0..self.sections {
self.draw_month(&mut buf.offset((cal_pad * idx, 0))?, ctx.bg, time, t)?;
t = if t.month() == 12 { t = if t.month() == 12 {
t.with_year(t.year() + 1).unwrap().with_month(1).unwrap() t.with_year(t.year() + 1).unwrap().with_month(1).unwrap()
} else { } else {
t.with_month(t.month() + 1).unwrap() t.with_month(t.month() + 1).unwrap()
}; };
} }
Ok(DrawReport { }
width, self.dirty = false;
height, self.geometry
damage: vec![buf.get_signed_bounds()],
full_damage: false,
})
} }
fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option<String>) {} fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
fn mouse_click(&mut self, _: u32, (x, _): (u32, u32)) { let year_font = fonts.get_font(self.font, self.size * YEAR_FACTOR);
let cal_pad = 7 * self.font_size * 3 + self.font_size * 5; self.year_width = year_font.auto_widest("0123456789").unwrap() * 4;
let cals = self.sections - 1; let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR);
let pre_cals = cals / 2; self.date_width = cal_font.auto_widest("0123456789").unwrap() * 2;
if x < pre_cals * cal_pad { let day_font = fonts.get_font(self.font, self.size * DAY_FACTOR);
self.offset -= 100.0; self.day_width = day_font.auto_widest("ADEFHIMNORSTUW").unwrap() * 3;
} else if x >= (pre_cals + 1) * cal_pad {
self.offset += 100.0; let cal_width = 8 * self.date_width + 7 * (self.date_width / 2);
let cal_height =
(self.size * MONTH_FACTOR).ceil() as u32 + (self.size * LINE_HEIGHT * 8.).ceil() as u32;
let cal_pad = self.date_width * 2;
if geometry.width < cal_width || geometry.height < cal_height {
self.shown_sections_x = 0;
self.shown_sections_y = 0;
self.geometry = Default::default();
return self.geometry;
}
let possible_sections_x = 1 + (geometry.width - cal_width) / (cal_width + cal_pad);
let possible_sections_y = 1 + (geometry.height - cal_height) / cal_height;
self.shown_sections_x =
if self.sections_x > 0 && self.sections_x < possible_sections_x as i32 {
self.sections_x as u32
} else { } else {
self.offset = 0.0; possible_sections_x
};
self.shown_sections_y =
if self.sections_y > 0 && self.sections_y < possible_sections_y as i32 {
self.sections_y as u32
} else {
possible_sections_y
};
self.geometry = Geometry {
x: geometry.x,
y: geometry.y,
width: cal_width * self.shown_sections_x as u32
+ cal_pad * (self.shown_sections_x as u32 - 1),
height: cal_height * self.shown_sections_y as u32,
};
self.geometry
}
fn minimum_size(&mut self, fonts: &mut FontMap) -> Geometry {
let cal_font = fonts.get_font(self.font, self.size * DATE_FACTOR);
let date_width = cal_font.auto_widest("0123456789").unwrap() * 2;
let cal_width = 8 * date_width + 7 * (date_width / 2);
let cal_height =
(self.size * MONTH_FACTOR).ceil() as u32 + (self.size * LINE_HEIGHT * 8.).ceil() as u32;
let cal_pad = date_width * 2;
let sections_x = match self.sections_x {
-1 => 1,
0 => {
return Geometry {
x: 0,
y: 0,
width: 0,
height: 0,
}
}
x => x,
};
let sections_y = match self.sections_y {
-1 => 1,
0 => {
return Geometry {
x: 0,
y: 0,
width: 0,
height: 0,
}
}
y => y,
};
let width = cal_width * sections_x as u32 + cal_pad * (sections_x as u32 - 1);
let height = cal_height * sections_y as u32;
Geometry {
x: 0,
y: 0,
width: width,
height: height,
} }
self.dirty = true;
}
fn mouse_scroll(&mut self, (_, y): (f64, f64), _: (u32, u32)) {
self.offset += y;
self.dirty = true;
} }
} }
+75 -71
View File
@@ -1,92 +1,96 @@
use crate::color::Color; use std::default::Default;
use crate::draw::Font;
use crate::{ use crate::{
fonts::FontRef, buffer::BufferView,
widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, color::Color,
event::Event,
fonts::FontMap,
widgets::{Geometry, Widget},
}; };
use chrono::{Duration, NaiveDateTime, Timelike}; use chrono::{Local, Timelike};
pub struct Clock<'a> { pub struct Clock {
cur_time: NaiveDateTime, font: &'static str,
clock_cache: Font<'a>,
size: f32, size: f32,
digit: u32, dirty: bool,
colon: u32, geometry: Geometry,
digit_width: u32,
colon_width: u32,
} }
impl<'a> Clock<'a> { impl<'a> Clock {
pub fn new(time: NaiveDateTime, font: FontRef, size: f32) -> ::std::io::Result<Box<Clock>> { pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Clock {
let mut clock_cache = Font::new(font, size); fm.queue_font(font, size, "0123456789: ");
clock_cache.add_str_to_cache("0123456789:"); Clock {
font,
let digit = clock_cache.auto_widest("123456789")?;
let colon = clock_cache.auto_widest(":")?;
Ok(Box::new(Clock {
cur_time: time,
clock_cache,
size, size,
digit, dirty: true,
colon, geometry: Default::default(),
})) digit_width: 0,
colon_width: 0,
}
} }
} }
impl<'a> Widget for Clock<'a> { impl<'a> Widget for Clock {
fn wait(&mut self, ctx: &mut WaitContext) { fn get_dirty(&self) -> bool {
let target = (self.cur_time + Duration::seconds(60)) self.dirty
.with_second(0)
.unwrap()
.with_nanosecond(0)
.unwrap();
ctx.set_time(target);
}
fn enter(&mut self) {}
fn leave(&mut self) {}
fn size(&self) -> (u32, u32) {
(self.digit * 4 + self.colon, self.size.ceil() as u32)
} }
fn draw( fn geometry(&self) -> Geometry {
&mut self, self.geometry
ctx: &mut DrawContext,
pos: (u32, u32),
_expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error> {
let (width, height) = self.size();
if !(ctx.time.date() != self.cur_time.date()
|| ctx.time.hour() != self.cur_time.hour()
|| ctx.time.minute() != self.cur_time.minute()
|| ctx.force)
{
return Ok(DrawReport::empty(width, height));
} }
self.cur_time = ctx.time; fn event(&mut self, event: &Event) {
match event {
Event::NewMinute => {
self.dirty = true;
}
_ => (),
}
}
let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
buf.memset(ctx.bg); let time = Local::now().naive_local();
let fg = Color::WHITE;
let digit = self.digit; let digit = self.digit_width;
let colon = self.colon; let colon = self.colon_width;
self.clock_cache.draw_text_fixed_width( let font = fonts.get_font(self.font, self.size);
buf, font.draw_text_fixed_width(
ctx.bg, view,
&Color::new(1.0, 1.0, 1.0, 1.0), fg,
&[digit, digit, colon, digit, digit], &[digit, digit, colon, digit, digit],
&format!("{:02}:{:02}", ctx.time.hour(), ctx.time.minute()), &format!("{:02}:{:02}", time.hour(), time.minute()),
)?; )
.unwrap();
Ok(DrawReport { self.dirty = false;
width, self.geometry
height,
damage: vec![(pos.0 as i32, pos.1 as i32, width as i32, height as i32)],
full_damage: false,
})
} }
fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option<String>) {} fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} let font = fonts.get_font(self.font, self.size);
fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} 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: self.size.ceil() as u32,
};
self.geometry
}
fn minimum_size(&mut self, fonts: &mut FontMap) -> Geometry {
let font = fonts.get_font(self.font, self.size);
let digit_width = font.auto_widest("0123456789").unwrap();
let colon_width = font.auto_widest(":").unwrap();
Geometry {
x: 0,
y: 0,
width: digit_width * 4 + colon_width,
height: self.size.ceil() as u32,
}
}
} }
+71 -74
View File
@@ -1,95 +1,92 @@
use crate::color::Color; use std::default::Default;
use crate::draw::Font;
use crate::{ use crate::{
fonts::FontRef, buffer::BufferView,
widget::{DrawContext, DrawReport, KeyState, ModifiersState, WaitContext, Widget}, color::Color,
event::Event,
fonts::FontMap,
widgets::{Geometry, Widget},
}; };
use chrono::{Datelike, NaiveDateTime}; use chrono::{Datelike, Local};
pub struct Date<'a> { pub struct Date {
cur_time: NaiveDateTime, font: &'static str,
date_cache: Font<'a>,
size: f32, size: f32,
ch_width: u32, dirty: bool,
digit_width: u32, geometry: Geometry,
spacing_width: u32,
} }
impl<'a> Date<'a> { impl Date {
pub fn new(time: NaiveDateTime, font: FontRef, size: f32) -> ::std::io::Result<Box<Date>> { pub fn new(fm: &mut FontMap, font: &'static str, size: f32) -> Date {
let mut date_cache = Font::new(font, size); fm.queue_font(font, size, "0123456789/, adehinortuFMSTW");
let chs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; Date {
let digits = "0123456789,/ "; font,
let spacings = ",/ ";
date_cache.add_str_to_cache(chs);
date_cache.add_str_to_cache(digits);
date_cache.add_str_to_cache(spacings);
let ch_width = date_cache.auto_widest(chs)?;
let digit_width = date_cache.auto_widest(digits)?;
let spacing_width = date_cache.auto_widest(spacings)?;
Ok(Box::new(Date {
cur_time: time,
date_cache,
size, size,
ch_width, dirty: true,
digit_width, geometry: Default::default(),
spacing_width, }
}))
} }
} }
impl<'a> Widget for Date<'a> { impl<'a> Widget for Date {
fn wait(&mut self, _: &mut WaitContext) {} fn get_dirty(&self) -> bool {
fn enter(&mut self) {} self.dirty
fn leave(&mut self) {}
fn size(&self) -> (u32, u32) {
(
(3 * self.ch_width + 8 * self.digit_width + 4 * self.spacing_width) as u32,
self.size.ceil() as u32,
)
} }
fn draw( fn geometry(&self) -> Geometry {
&mut self, self.geometry
ctx: &mut DrawContext,
pos: (u32, u32),
_expansion: (u32, u32),
) -> Result<DrawReport, ::std::io::Error> {
let (width, height) = self.size();
if !(ctx.time.date() != self.cur_time.date() || ctx.force) {
return Ok(DrawReport::empty(width, height));
} }
self.cur_time = ctx.time; fn event(&mut self, event: &Event) {
match event {
Event::NewMinute => {
self.dirty = true;
}
_ => (),
}
}
let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
buf.memset(ctx.bg); let time = Local::now().naive_local();
self.date_cache.draw_text( let fg = Color::WHITE;
buf, let font = fonts.get_font(self.font, self.size);
ctx.bg, font.draw_text(
&Color::new(1.0, 1.0, 1.0, 1.0), view,
fg,
&format!( &format!(
"{:?}, {:02}/{:02}/{:4}", "{}, {:02}/{:02}/{:4}",
ctx.time.weekday(), time.weekday(),
ctx.time.day(), time.day(),
ctx.time.month(), time.month(),
ctx.time.year() time.year()
), ),
)?; )
.unwrap();
Ok(DrawReport { self.dirty = false;
width, self.geometry
height,
damage: vec![(pos.0 as i32, pos.1 as i32, width as i32, height as i32)],
full_damage: false,
})
} }
fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option<String>) {} fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} let width = (3. + 2. + 2. + 2. + 4.) * self.size / 2.;
fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} let font = fonts.get_font(self.font, self.size);
self.geometry = Geometry {
x: geometry.x,
y: geometry.y,
width: width.ceil() as u32,
height: font.height().ceil() as u32,
};
self.geometry
}
fn minimum_size(&mut self, fonts: &mut FontMap) -> Geometry {
let width = (3. + 2. + 2. + 2. + 4.) * self.size / 2.;
let font = fonts.get_font(self.font, self.size);
Geometry {
x: 0,
y: 0,
width: width.ceil() as u32,
height: font.height().ceil() as u32,
}
}
} }
+712 -438
View File
File diff suppressed because it is too large Load Diff
+283
View File
@@ -0,0 +1,283 @@
use std::cmp::max;
use crate::{fonts::FontMap, state::State, widgets::Geometry};
pub trait Layout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry;
fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry;
}
pub struct HorizontalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl HorizontalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(HorizontalLayout { widgets })
}
}
impl Layout for HorizontalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.geometry_update(fonts, &geo, state);
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
}
fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry {
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.minimum_size(fonts, state);
max_width += result.x + result.width;
max_height = max(result.y + result.height, max_height);
}
Geometry {
width: max_width,
height: max_height,
x: 0,
y: 0,
}
}
}
pub struct VerticalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl VerticalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(VerticalLayout { widgets })
}
}
impl Layout for VerticalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.geometry_update(fonts, &geo, state);
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
}
fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry {
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.minimum_size(fonts, state);
max_width = max(result.x + result.width, max_width);
max_height += result.y + result.height;
}
Geometry {
width: max_width,
height: max_height,
x: 0,
y: 0,
}
}
}
pub trait WidgetUpdater {
fn geometry_update(&mut self, idx: usize, fonts: &mut FontMap, geometry: &Geometry)
-> Geometry;
fn minimum_size(&mut self, idx: usize, fonts: &mut FontMap) -> Geometry;
}
pub struct IndexedLayout {
pub widget_idx: usize,
}
impl IndexedLayout {
pub fn new(widget_idx: usize) -> Box<dyn Layout> {
Box::new(IndexedLayout { widget_idx })
}
}
impl Layout for IndexedLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
state.geometry_update(self.widget_idx, fonts, geometry)
}
fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry {
state.minimum_size(self.widget_idx, fonts)
}
}
pub struct InvertedHorizontalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl InvertedHorizontalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(InvertedHorizontalLayout { widgets })
}
}
impl Layout for InvertedHorizontalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_height = 0;
for w in self.widgets.iter() {
let mut temp_geo = geo.clone();
let temp_result = w.geometry_update(fonts, &temp_geo, state);
temp_geo.x = geo.x + (geo.width - temp_result.width);
let result = w.geometry_update(fonts, &temp_geo, state);
geo.width -= result.width;
max_height = max(result.height, max_height);
}
geo.height = max_height;
geo
}
fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry {
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.minimum_size(fonts, state);
max_width += result.x + result.width;
max_height = max(result.y + result.height, max_height);
}
Geometry {
width: max_width,
height: max_height,
x: 0,
y: 0,
}
}
}
pub struct InvertedVerticalLayout {
pub widgets: Vec<Box<dyn Layout>>,
}
impl InvertedVerticalLayout {
pub fn new(widgets: Vec<Box<dyn Layout>>) -> Box<dyn Layout> {
Box::new(InvertedVerticalLayout { widgets })
}
}
impl Layout for InvertedVerticalLayout {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> Geometry {
let mut geo = geometry.clone();
let mut max_width = 0;
for w in self.widgets.iter() {
let mut temp_geo = geo.clone();
let temp_result = w.geometry_update(fonts, &temp_geo, state);
temp_geo.y = geo.y + (geo.height - temp_result.height);
let result = w.geometry_update(fonts, &temp_geo, state);
geo.height -= result.height;
max_width = max(result.width, max_width);
}
geo.width = max_width;
geo
}
fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry {
let mut max_width = 0;
let mut max_height = 0;
for w in self.widgets.iter() {
let result = w.minimum_size(fonts, state);
max_width = max(result.x + result.width, max_width);
max_height += result.y + result.height;
}
Geometry {
width: max_width,
height: max_height,
x: 0,
y: 0,
}
}
}
pub struct Margin {
pub widget: Box<dyn Layout>,
pub margin: (u32, u32, u32, u32),
}
impl Margin {
pub fn new(widget: Box<dyn Layout>, margin: (u32, u32, u32, u32)) -> Box<dyn Layout> {
Box::new(Margin { widget, margin })
}
}
impl Layout for Margin {
fn geometry_update(
&self,
fonts: &mut FontMap,
geometry: &Geometry,
state: &mut State,
) -> 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, state);
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,
}
}
fn minimum_size(&self, fonts: &mut FontMap, state: &mut State) -> Geometry {
let size = self.widget.minimum_size(fonts, state);
Geometry {
width: size.x + size.width + self.margin.0 + self.margin.2,
height: size.y + size.height + self.margin.1 + self.margin.3,
x: 0,
y: 0,
}
}
}
+62
View File
@@ -0,0 +1,62 @@
use std::default::Default;
use crate::{
buffer::BufferView,
color::Color,
fonts::FontMap,
widgets::{Geometry, Widget},
};
pub struct Line {
geometry: Geometry,
thickness: u32,
vertical: bool,
}
impl Line {
pub fn new(thickness: u32, vertical: bool) -> Line {
Line {
geometry: Default::default(),
thickness,
vertical,
}
}
}
impl Widget for Line {
fn geometry(&self) -> Geometry {
self.geometry
}
fn draw(&mut self, _fonts: &mut FontMap, view: &mut BufferView) -> Geometry {
view.memset(Color::WHITE);
self.geometry
}
fn geometry_update(&mut self, _fonts: &mut FontMap, geometry: &Geometry) -> Geometry {
self.geometry = Geometry {
x: geometry.x,
y: geometry.y,
width: if self.vertical {
self.thickness
} else {
geometry.width
},
height: if self.vertical {
geometry.height
} else {
self.thickness
},
};
self.geometry
}
fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry {
Geometry {
x: 0,
y: 0,
width: if self.vertical { self.thickness } else { 1 },
height: if self.vertical { 1 } else { self.thickness },
}
}
}
+26 -9
View File
@@ -1,10 +1,27 @@
pub mod backlight; //mod alsaaudio;
pub mod bar_widget; mod backlight;
pub mod battery; mod bar_widget;
pub mod calendar; mod battery;
pub mod clock; mod calendar;
pub mod date; mod clock;
pub mod launcher; mod date;
mod launcher;
mod layout;
mod line;
mod pulseaudio;
mod widget;
#[cfg(any(feature = "alsa-widget", feature = "pulseaudio-widget"))] //pub use alsaaudio::AlsaAudio;
pub mod audio; pub use backlight::Backlight;
pub use battery::Battery;
pub use calendar::Calendar;
pub use clock::Clock;
pub use date::Date;
pub use launcher::Interface;
pub use layout::{
HorizontalLayout, IndexedLayout, InvertedHorizontalLayout, InvertedVerticalLayout, Layout,
Margin, VerticalLayout, WidgetUpdater,
};
pub use line::Line;
pub use pulseaudio::PulseAudio;
pub use widget::{Geometry, Widget};
+243
View File
@@ -0,0 +1,243 @@
use std::{
cell::RefCell,
error::Error,
os::fd::RawFd,
rc::Rc,
sync::{Arc, Mutex},
thread,
};
use nix::unistd::{pipe, read, write};
use crate::{
color::Color,
event::{Event, Events, PointerButton},
fonts::FontMap,
widgets::bar_widget::{BarWidget, BarWidgetImpl},
};
use libpulse_binding::volume::ChannelVolumes;
fn monitor(
events: Arc<Mutex<Events>>,
inner: Arc<Mutex<InnerAudio>>,
pipe: RawFd,
) -> Result<(), Box<dyn Error>> {
use libpulse_binding::{
callbacks::ListResult,
context::{
subscribe::{Facility, InterestMaskSet},
Context, FlagSet, State,
},
mainloop::{
self, api::Mainloop, events::io::FlagSet as IoFlagSet, standard::IterateResult,
},
proplist::{properties, Proplist},
volume::Volume,
};
let mut proplist = Proplist::new().unwrap();
proplist
.set_str(properties::APPLICATION_NAME, "wldash")
.unwrap();
let mut mainloop = mainloop::standard::Mainloop::new().unwrap();
let context = Rc::new(RefCell::new(
Context::new_with_proplist(&mainloop, "wldash_context", &proplist).unwrap(),
));
context.borrow_mut().connect(None, FlagSet::NOFLAGS, None)?;
loop {
mainloop.iterate(false);
match context.borrow().get_state() {
State::Ready => break,
State::Failed | State::Terminated => panic!("damn"),
_ => (),
}
}
let local_context = context.clone();
let local_inner = inner.clone();
let local_events = events.clone();
context.borrow_mut().set_subscribe_callback(Some(Box::new(
move |facility, _operation, index| match facility {
Some(Facility::Sink) => {
let introspector = local_context.borrow_mut().introspect();
let inner = local_inner.clone();
let events = local_events.clone();
introspector.get_sink_info_by_index(index, move |res| match res {
ListResult::End | ListResult::Error => (),
ListResult::Item(sink_info) => {
if let Some(_) = &sink_info.name {
let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32;
let mut inner = inner.lock().unwrap();
inner.volume = avg;
inner.mute = sink_info.mute;
inner.dirty = true;
drop(inner);
let mut events = events.lock().unwrap();
events.add_event(Event::AudioUpdate);
}
}
});
}
_ => (),
},
)));
context
.borrow_mut()
.subscribe(InterestMaskSet::SINK, |_| {});
let introspector = context.borrow_mut().introspect();
let local_inner = inner.clone();
introspector.get_sink_info_by_name("@DEFAULT_SINK@", move |res| match res {
ListResult::End | ListResult::Error => (),
ListResult::Item(sink_info) => {
if let Some(_) = &sink_info.name {
let avg = sink_info.volume.avg().0 as f32 / Volume::NORMAL.0 as f32;
let mut inner = local_inner.lock().unwrap();
inner.volume = avg;
inner.channel_volume = Some(sink_info.volume);
inner.mute = sink_info.mute;
inner.dirty = true;
drop(inner);
let mut events = events.lock().unwrap();
events.add_event(Event::AudioUpdate);
}
}
});
drop(introspector);
let local_context = context.clone();
let local_inner = inner.clone();
let event_source = mainloop.new_io_event(
pipe as i32,
IoFlagSet::INPUT,
Box::new(move |_, _, _| {
let mut buf = [0; 8];
_ = read(pipe, &mut buf);
let mut inner = local_inner.lock().unwrap();
let mut introspector = local_context.borrow_mut().introspect();
match inner.last_click.take() {
Some(Change::Volume(volume)) => {
// do something
let mut vol = inner.channel_volume.unwrap();
vol.scale(Volume((volume * Volume::NORMAL.0 as f32).round() as u32));
introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None);
}
Some(Change::VolumeInc(value)) => {
let mut vol = inner.channel_volume.unwrap();
// apply step to volumes
let step = (value * Volume::NORMAL.0 as f32).round() as i32;
if step > 0 {
vol.inc_clamp(Volume(step as u32), Volume::NORMAL);
} else {
vol.decrease(Volume(-step as u32));
}
// HACK: It would be better to only update the volume from feedback, but axis
// events fire fast and require the last set volume immediately for reference.
inner.channel_volume = Some(vol);
introspector.set_sink_volume_by_name("@DEFAULT_SINK@", &vol, None);
}
Some(Change::ToggleMute) => {
introspector.set_sink_mute_by_name("@DEFAULT_SINK@", !inner.mute, None);
}
None => (),
}
}),
);
loop {
match mainloop.iterate(true) {
IterateResult::Quit(_) | IterateResult::Err(_) => break,
_ => (),
}
}
// Need to keep the event source alive
_ = event_source;
Ok(())
}
fn start_monitor(events: Arc<Mutex<Events>>, inner: Arc<Mutex<InnerAudio>>, pipe: RawFd) {
thread::Builder::new()
.name("audiomon".to_string())
.spawn(move || monitor(events, inner, pipe).unwrap())
.unwrap();
}
enum Change {
Volume(f32),
VolumeInc(f32),
ToggleMute,
}
struct InnerAudio {
volume: f32,
channel_volume: Option<ChannelVolumes>,
mute: bool,
dirty: bool,
last_click: Option<Change>,
pipe: RawFd,
}
pub struct PulseAudio {
inner: Arc<Mutex<InnerAudio>>,
}
impl PulseAudio {
pub fn new(
events: Arc<Mutex<Events>>,
fm: &mut FontMap,
font: &'static str,
size: f32,
) -> BarWidget {
let (a, b) = pipe().unwrap();
let audio = PulseAudio {
inner: Arc::new(Mutex::new(InnerAudio {
volume: 0.0,
channel_volume: None,
mute: false,
dirty: false,
last_click: None,
pipe: b,
})),
};
start_monitor(events, audio.inner.clone(), a);
BarWidget::new(Box::new(audio), fm, font, size)
}
}
impl BarWidgetImpl for PulseAudio {
fn get_dirty(&self) -> bool {
self.inner.lock().unwrap().dirty
}
fn name(&self) -> &'static str {
"volume"
}
fn value(&mut self) -> f32 {
let mut inner = self.inner.lock().unwrap();
inner.dirty = false;
inner.volume
}
fn color(&self) -> Color {
match self.inner.lock().unwrap().mute {
true => Color::YELLOW,
false => Color::WHITE,
}
}
fn click(&mut self, pos: f32, btn: PointerButton) {
let mut inner = self.inner.lock().unwrap();
inner.last_click = match btn {
PointerButton::Left => Some(Change::Volume(pos)),
PointerButton::Right => Some(Change::ToggleMute),
PointerButton::ScrollVertical(val) => Some(Change::VolumeInc((val / 512.) as f32)),
PointerButton::ScrollHorizontal(val) => Some(Change::VolumeInc((val / 100.) as f32)),
_ => None,
};
_ = write(inner.pipe, &[0]);
}
}
+66
View File
@@ -0,0 +1,66 @@
use std::{
cmp::{max, min},
fmt,
};
use crate::{buffer::BufferView, event::Event, fonts::FontMap};
#[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 fn contains(&self, position: (u32, u32)) -> bool {
position.0 >= self.x
&& position.0 < self.x + self.width
&& position.1 >= self.y
&& position.1 < self.y + self.height
}
}
pub trait Widget {
fn get_dirty(&self) -> bool {
false
}
fn geometry(&self) -> Geometry;
fn geometry_update(&mut self, fonts: &mut FontMap, geometry: &Geometry) -> Geometry;
fn minimum_size(&mut self, _fonts: &mut FontMap) -> Geometry {
Default::default()
}
fn draw(&mut self, fonts: &mut FontMap, view: &mut BufferView) -> Geometry;
fn event(&mut self, _event: &Event) {}
}