diff --git a/Cargo.lock b/Cargo.lock index fa9f3fd..31e258d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,11 @@ dependencies = [ "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.4.0" @@ -375,6 +380,14 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "0.6.13" @@ -383,6 +396,14 @@ dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rcalc_lib" version = "0.9.3" @@ -419,6 +440,11 @@ dependencies = [ "stb_truetype 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ryu" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "same-file" version = "1.0.5" @@ -427,6 +453,34 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha-1" version = "0.8.1" @@ -476,6 +530,16 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.42" @@ -501,6 +565,11 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -613,6 +682,8 @@ dependencies = [ "rcalc_lib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "rusttype 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "smithay-client-toolkit 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-client 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -650,6 +721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fuzzy-matcher 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2db8c952765d7250f5fa509db7f6510ceef4be8672affc9bdbeffa2695923f67" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libdbus-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "69382cb3be797cfe62ba89ac5e594a0e3022914ff44867ffbcfc03daa24eebd7" @@ -678,21 +750,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum pest_meta 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f249ea6de7c7b7aba92b4ff4376a994c6dbd98fd2166c89d5c4947397ecb574d" "checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "175a40b9cf564ce9bf050654633dbf339978706b8ead1a907bb970b63185dd95" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rcalc_lib 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2497fc2d7578e75282922918d96de8d4790714312900d95b6d5f577168f2656e" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" "checksum rusttype 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "654103d61a05074b268a107cf6581ce120f0fc0115f2610ed9dfea363bb81139" +"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" +"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425" +"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" "checksum smithay-client-toolkit 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "93960e8975909fcb14cc755de93af2149d8b8f4eb368315537d40cfd0f324054" "checksum stb_truetype 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "69b7df505db8e81d54ff8be4693421e5b543e08214bd8d99eb761fcb4d5668ba" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" "checksum ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" "checksum wayland-client 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)" = "80d909ba6616dd772686f421e111f039402a13acecf395bcb4fc665277a504e0" diff --git a/Cargo.toml b/Cargo.toml index 0af7f61..1e2ff5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ default = [ "rcalc" ] ivy = [ "libivy" ] bc = [] rcalc = [ "rcalc_lib" ] +damage_debug = [] [dependencies] smithay-client-toolkit = "0.6.4" @@ -30,4 +31,6 @@ lazy_static = "1.4" libivy = { git = "https://github.com/kennylevinsen/libivy.git", optional = true } rcalc_lib = { version = "0.9", optional = true } rust-ini = "0.13" -shlex = "0.1" \ No newline at end of file +shlex = "0.1" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } diff --git a/config.json b/config.json new file mode 100644 index 0000000..058bbab --- /dev/null +++ b/config.json @@ -0,0 +1,43 @@ +{ + "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": {"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}} + ]} + } + }, + "outputMode": "active", + "scale": 1 +} \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index f86eeb9..48f92ee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,12 +1,12 @@ -use std::cmp::max; use std::collections::VecDeque; -use std::sync::mpsc::{channel, Sender}; +use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use chrono::Local; -use smithay_client_toolkit::keyboard::{keysyms, map_keyboard_auto, Event as KbEvent, KeyState}; -use smithay_client_toolkit::utils::DoubleMemPool; +use smithay_client_toolkit::keyboard::{ + keysyms, map_keyboard_auto, Event as KbEvent, KeyState, ModifiersState, +}; use wayland_client::protocol::{wl_compositor, wl_output, wl_pointer, wl_shm, wl_surface}; use wayland_client::{Display, EventQueue, GlobalEvent, GlobalManager, NewProxy}; @@ -16,16 +16,10 @@ use wayland_protocols::wlr::unstable::layer_shell::v1::client::{ use crate::buffer::Buffer; use crate::color::Color; +use crate::widget::{DrawContext, Widget}; use crate::cmd::Cmd; - -use crate::modules::backlight::Backlight; -use crate::modules::battery::UpowerBattery; -use crate::modules::calendar::Calendar; -use crate::modules::clock::Clock; -use crate::modules::launcher::Launcher; -use crate::modules::module::{Input, Module}; -use crate::modules::sound::PulseAudio; +use crate::doublemempool::DoubleMemPool; pub enum OutputMode { Active, @@ -39,7 +33,6 @@ struct AppInner { configured_surfaces: Arc>, outputs: Vec<(u32, wl_output::WlOutput)>, shell: Option, - dimensions: (u32, u32), draw_tx: Sender, output_mode: OutputMode, visible: bool, @@ -55,7 +48,6 @@ impl AppInner { configured_surfaces: Arc::new(Mutex::new(0)), outputs: Vec::new(), shell: None, - dimensions: (0, 0), draw_tx: tx, output_mode: output_mode, visible: true, @@ -66,7 +58,6 @@ impl AppInner { fn add_shell_surface( compositor: &wl_compositor::WlCompositor, shell: &zwlr_layer_shell_v1::ZwlrLayerShellV1, - dimensions: (u32, u32), scale: u32, configured_surfaces: Arc>, tx: Sender, @@ -79,6 +70,8 @@ impl AppInner { .create_surface(NewProxy::implement_dummy) .unwrap(); + let this_is_stupid = Arc::new(Mutex::new(false)); + let shell_surface = shell .get_layer_surface( &surface, @@ -89,9 +82,13 @@ impl AppInner { layer.implement_closure( move |evt, layer| match evt { zwlr_layer_surface_v1::Event::Configure { serial, .. } => { - *(configured_surfaces.lock().unwrap()) += 1; - layer.ack_configure(serial); - tx.send(Cmd::ForceDraw).unwrap(); + let mut x = this_is_stupid.lock().unwrap(); + if !*x { + *x = true; + *(configured_surfaces.lock().unwrap()) += 1; + layer.ack_configure(serial); + tx.send(Cmd::ForceDraw).unwrap(); + } } _ => unreachable!(), }, @@ -102,7 +99,6 @@ impl AppInner { .unwrap(); shell_surface.set_keyboard_interactivity(1); - shell_surface.set_size(dimensions.0/scale, dimensions.1/scale); surface.set_buffer_scale(scale as i32); surface.commit(); (surface, shell_surface) @@ -136,7 +132,6 @@ impl AppInner { let (surface, shell_surface) = AppInner::add_shell_surface( &compositor, &shell, - self.dimensions, self.scale, self.configured_surfaces.clone(), self.draw_tx.clone(), @@ -152,7 +147,6 @@ impl AppInner { let (surface, shell_surface) = AppInner::add_shell_surface( &compositor, &shell, - self.dimensions, self.scale, self.configured_surfaces.clone(), self.draw_tx.clone(), @@ -201,10 +195,6 @@ impl AppInner { fn set_shell(&mut self, shell: Option) { self.shell = shell } - - fn set_dimensions(&mut self, dimensions: (u32, u32)) { - self.dimensions = dimensions - } } pub struct App { @@ -212,12 +202,19 @@ pub struct App { display: Display, event_queue: EventQueue, cmd_queue: Arc>>, - modules: Vec, + widget: Option>, inner: Arc>, + last_damage: Option>, + last_dim: (u32, u32), } impl App { - pub fn redraw(&mut self, force: bool) -> Result<(), ::std::io::Error> { + pub fn redraw(&mut self, mut 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(); @@ -226,41 +223,56 @@ impl App { return Ok(()); } - let pool = match self.pools.pool() { - Some(pool) => pool, + let (last, pool) = match self.pools.pool() { + Some((last, pool)) => (last, pool), None => return Ok(()), }; - let (buf_x, buf_y) = inner.dimensions; - - let bg = Color::new(0.0, 0.0, 0.0, 0.9); + let size = widget.size(); + let size_changed = self.last_dim != size; // resize the pool if relevant - - pool.resize((4 * buf_x * buf_y) as usize) + 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, inner.dimensions); + let mut buf = Buffer::new(mmap, size); + + // Copy old damage + if let Some(d) = &self.last_damage { + if !size_changed { + 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()); + } + } else { + force = true; + } + } else { + force = true; + } + + let bg = Color::new(0.0, 0.0, 0.0, 0.9); if force { buf.memset(&bg); } - let mut damage = vec![]; - - { - let mut margin_buf = buf.subdimensions((20, 20, buf_x - 40, buf_y - 40))?; - for module in self.modules.iter() { - if module.update(&time, force)? { - let mut b = &mut margin_buf.subdimensions(module.get_bounds())?; - let mut d = module.draw(&mut b, &bg, &time)?; - damage.append(&mut d); - } - } - } + let report = widget.draw( + &mut DrawContext { + buf: &mut buf, + bg: &bg, + time: &time, + force, + }, + (0, 0), + )?; mmap.flush().unwrap(); - if damage.len() == 0 { + if !size_changed && !report.full_damage && report.damage.len() == 0 { // Nothing to do return Ok(()); } @@ -268,22 +280,33 @@ impl App { // get a buffer and attach it let new_buffer = pool.buffer( 0, - buf_x as i32, - buf_y as i32, - 4 * buf_x as i32, + 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 force { - surface.damage_buffer(0, 0, buf_x as i32, buf_y as i32); + 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 damage.iter() { + 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(()) } @@ -305,36 +328,13 @@ impl App { &mut self.event_queue } - pub fn wipe(&mut self) { - let inner = self.inner.lock().unwrap(); - let pool = match self.pools.pool() { - Some(pool) => pool, - None => return, - }; - pool.resize((4 * inner.dimensions.0 * inner.dimensions.1) as usize) - .expect("Failed to resize the memory pool."); - let mmap = pool.mmap(); - let mut buf = Buffer::new(mmap, inner.dimensions); - let bg = Color::new(0.0, 0.0, 0.0, 0.9); - buf.memset(&bg); + pub fn get_widget(&mut self) -> &mut Box { + self.widget.as_mut().unwrap() } - pub fn with_modules(&self, f: F) - where - F: Fn(&Module), - { - for m in self.modules.iter() { - f(m); - } - } - - pub fn get_module(&self, pos: (u32, u32)) -> Option<&Module> { - for m in self.modules.iter() { - if m.intersect(pos) { - return Some(&m); - } - } - None + pub fn set_widget(&mut self, w: Box) -> Result<(), ::std::io::Error> { + self.widget = Some(w); + self.redraw(true) } pub fn new(tx: Sender, output_mode: OutputMode, scale: u32) -> App { @@ -343,32 +343,6 @@ impl App { // // Set up modules // - let (mod_tx, mod_rx) = channel(); - std::thread::spawn(move || { - let mut modules = vec![ - Module::new(Box::new(Clock::new(tx.clone())), (0, 0, 536, 320)), - Module::new(Box::new(Calendar::new()), (0, 368, 1232, 344)), - ]; - - if let Ok(m) = Launcher::new(tx.clone()) { - modules.push(Module::new(Box::new(m), (0, 728, 1232, 32))); - } - - let mut vert_off = 0; - if let Ok(m) = UpowerBattery::new(tx.clone()) { - modules.push(Module::new(Box::new(m), (640, vert_off, 592, 32))); - vert_off += 32; - } - if let Ok(m) = Backlight::new() { - modules.push(Module::new(Box::new(m), (640, vert_off, 592, 32))); - vert_off += 32; - } - if let Ok(m) = PulseAudio::new(tx.clone()) { - modules.push(Module::new(Box::new(m), (640, vert_off, 592, 32))); - } - - mod_tx.send(modules).unwrap(); - }); let cmd_queue = Arc::new(Mutex::new(VecDeque::new())); @@ -435,7 +409,7 @@ impl App { }) .expect("server didn't advertise `wl_shm`"); - let pools = DoubleMemPool::new(&shm, || {}).expect("Failed to create a memory pool !"); + let pools = DoubleMemPool::new(&shm).expect("Failed to create a memory pool !"); // // Get our seat @@ -457,11 +431,18 @@ impl App { } => match state { KeyState::Pressed => match keysym { keysyms::XKB_KEY_Escape => kbd_clone.lock().unwrap().push_back(Cmd::Exit), - v => kbd_clone.lock().unwrap().push_back(Cmd::KeyboardInput { - input: Input::Keypress { - key: v, - interpreted: utf8, + v => kbd_clone.lock().unwrap().push_back(Cmd::Keyboard { + key: v, + key_state: state, + modifiers_state: ModifiersState { + ctrl: false, + alt: false, + shift: false, + caps_lock: false, + logo: false, + num_lock: false, }, + interpreted: utf8, }), }, _ => (), @@ -486,22 +467,6 @@ impl App { }, )); - // - // Calculate window dimensions - // - let modules = mod_rx.recv().unwrap(); - - let mut dimensions = (0, 0); - for m in modules.iter() { - let b = m.get_bounds(); - dimensions = (max(dimensions.0, b.0 + b.2), max(dimensions.1, b.1 + b.3)); - } - - // Add padding - inner - .lock() - .unwrap() - .set_dimensions((dimensions.0 + 40, dimensions.1 + 40)); inner.lock().unwrap().outputs_changed(); event_queue.sync_roundtrip().unwrap(); @@ -547,31 +512,19 @@ impl App { _ => {} }, wl_pointer::Event::Frame => { - if pos.0 < 20 || pos.1 < 20 { - // Ignore stuff outside our margins - return; - } - let pos = (pos.0 - 20, pos.1 - 20); if vert_scroll != 0.0 || horiz_scroll != 0.0 { - pointer_clone.lock().unwrap().push_back(Cmd::MouseInput { + pointer_clone.lock().unwrap().push_back(Cmd::MouseScroll { + scroll: (horiz_scroll, vert_scroll), pos: pos, - input: Input::Scroll { - pos: pos, - x: horiz_scroll, - y: vert_scroll, - }, }); vert_scroll = 0.0; horiz_scroll = 0.0; } if btn_clicked { - pointer_clone.lock().unwrap().push_back(Cmd::MouseInput { - pos: pos, - input: Input::Click { - pos: pos, - button: btn, - }, - }); + pointer_clone + .lock() + .unwrap() + .push_back(Cmd::MouseClick { btn: btn, pos: pos }); btn_clicked = false; } } @@ -589,8 +542,10 @@ impl App { event_queue: event_queue, cmd_queue: cmd_queue, pools: pools, - modules: modules, + widget: None, inner: inner, + last_damage: None, + last_dim: (0, 0), } } } diff --git a/src/buffer.rs b/src/buffer.rs index e784ed4..0188690 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -38,13 +38,43 @@ impl<'a> Buffer<'a> { } } + 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 { let bounds = self.get_bounds(); - if subdimensions.0 + subdimensions.2 > bounds.2 - || subdimensions.1 + subdimensions.3 > bounds.3 + if subdimensions.0 + subdimensions.2 >= bounds.2 + || subdimensions.1 + subdimensions.3 >= bounds.3 { return Err(::std::io::Error::new( ::std::io::ErrorKind::Other, @@ -67,14 +97,9 @@ impl<'a> Buffer<'a> { }) } - pub fn offset( - &mut self, - offset: (u32, u32), - ) -> Result { + pub fn offset(&mut self, offset: (u32, u32)) -> Result { let bounds = self.get_bounds(); - if offset.0 > bounds.2 - || offset.1 > bounds.3 - { + if offset.0 > bounds.2 || offset.1 > bounds.3 { return Err(::std::io::Error::new( ::std::io::ErrorKind::Other, format!( @@ -119,7 +144,7 @@ impl<'a> Buffer<'a> { pub fn put(&mut self, pos: (u32, u32), c: &Color) -> Result<(), ::std::io::Error> { let true_pos = if let Some(subdim) = self.subdimensions { - if pos.0 > subdim.2 || pos.1 > subdim.3 { + if pos.0 >= subdim.2 || pos.1 >= subdim.3 { return Err(::std::io::Error::new( ::std::io::ErrorKind::Other, format!( diff --git a/src/cmd.rs b/src/cmd.rs index 8cfdd53..25c529a 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -1,10 +1,22 @@ -use crate::modules::module::Input; +use smithay_client_toolkit::keyboard::{KeyState, ModifiersState}; pub enum Cmd { Exit, Draw, ForceDraw, ToggleVisible, - MouseInput { pos: (u32, u32), input: Input }, - KeyboardInput { input: Input }, + MouseClick { + btn: u32, + pos: (u32, u32), + }, + MouseScroll { + scroll: (f64, f64), + pos: (u32, u32), + }, + Keyboard { + key: u32, + key_state: KeyState, + modifiers_state: ModifiersState, + interpreted: Option, + }, } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..fce4b08 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,202 @@ +use crate::cmd::Cmd; +use crate::widget; +use crate::widgets; +use std::sync::mpsc::Sender; +use serde::{Deserialize, Serialize}; +use std::default::Default; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum Widget { + Margin { + margins: (u32, u32, u32, u32), + widget: Box, + }, + Fixed { + width: u32, + height: u32, + widget: Box, + }, + HorizontalLayout(Vec>), + VerticalLayout(Vec>), + Clock { + font_size: f32, + }, + Date { + font_size: f32, + }, + Calendar { + font_size: f32, + sections: u32, + }, + Launcher { + font_size: f32, + length: u32, + #[serde(default)] + app_opener: String, + #[serde(default)] + term_opener: String, + #[serde(default)] + url_opener: String, + }, + Battery { + font_size: f32, + length: u32, + }, + Backlight { + font_size: f32, + length: u32, + }, + PulseAudio { + font_size: f32, + length: u32, + }, +} + +impl Widget { + pub fn construct(self, tx: Sender) -> Option> { + match self { + Widget::Margin { + margins, + widget, + } => match widget.construct(tx.clone()) { + Some(w) => Some(widget::Margin::new(margins, w)), + None => None, + }, + Widget::Fixed { + width, + height, + widget, + } => match widget.construct(tx.clone()) { + 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(tx.clone())) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .collect(), + )), + Widget::VerticalLayout(widgets) => Some(widget::VerticalLayout::new( + widgets + .into_iter() + .map(|x| x.construct(tx.clone())) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .collect(), + )), + Widget::Clock { font_size } => Some(widgets::clock::Clock::new(font_size, tx.clone())), + Widget::Date { font_size } => Some(widgets::date::Date::new(font_size)), + Widget::Calendar { + font_size, + sections, + } => Some(widgets::calendar::Calendar::new(font_size, sections)), + Widget::Launcher { font_size, length, app_opener, term_opener, url_opener } => Some(widgets::launcher::Launcher::new( + font_size, + length, + tx.clone(), + app_opener, + term_opener, + if url_opener.len() == 0 { "xdg_open ".to_string() } else { url_opener }, + )), + Widget::Battery { font_size, length } => { + match widgets::battery::UpowerBattery::new(font_size, length, tx.clone()) { + Ok(w) => Some(w), + Err(_) => None, + } + } + Widget::Backlight { font_size, length } => { + match widgets::backlight::Backlight::new(font_size, length) { + Ok(w) => Some(w), + Err(_) => None, + } + } + Widget::PulseAudio { font_size, length } => { + match widgets::sound::PulseAudio::new(font_size, length, tx.clone()) { + 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 + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Config { + pub widget: Widget, + pub output_mode: OutputMode, + pub scale: u32, +} + +impl Default for Config { + fn default() -> Self { + Config{ + widget: Widget::Margin { + margins: (20, 20, 20, 20), + widget: Box::new(Widget::VerticalLayout(vec![ + Box::new(Widget::HorizontalLayout(vec![ + Box::new(Widget::Margin { + margins: (0, 88, 0, 32), + widget: Box::new(Widget::VerticalLayout(vec![ + Box::new(Widget::Date { font_size: 64.0 }), + Box::new(Widget::Clock { font_size: 256.0 }), + ])), + }), + Box::new(Widget::VerticalLayout(vec![ + Box::new(Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Battery { + font_size: 24.0, + length: 600, + }), + }), + Box::new(Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::Backlight { + font_size: 24.0, + length: 600, + }), + }), + Box::new(Widget::Margin { + margins: (0, 0, 0, 8), + widget: Box::new(Widget::PulseAudio { + font_size: 24.0, + length: 600, + }), + }), + ])), + ])), + Box::new(Widget::Calendar { + font_size: 16.0, + sections: 3, + }), + Box::new(Widget::Launcher { + font_size: 32.0, + length: 1200, + app_opener: "".to_string(), + term_opener: "".to_string(), + url_opener: "".to_string(), + }), + ])), + }, + output_mode: Default::default(), + scale: 1, + } + } +} \ No newline at end of file diff --git a/src/desktop.rs b/src/desktop.rs index 10b0bc4..fc068e3 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -20,7 +20,13 @@ pub struct Desktop { impl Desktop { fn parse(f: &str) -> Result> { - let file = Ini::load_from_file_opt(f, ParseOption{ enabled_quote: false, enabled_escape: false })?; + let file = Ini::load_from_file_opt( + f, + ParseOption { + enabled_quote: false, + enabled_escape: false, + }, + )?; match file.section(Some("Desktop Entry").to_owned()) { Some(desktop) => Ok(Desktop { entry_type: desktop.get("Type").unwrap_or(&"".to_string()).to_string(), diff --git a/src/doublemempool.rs b/src/doublemempool.rs new file mode 100644 index 0000000..990f863 --- /dev/null +++ b/src/doublemempool.rs @@ -0,0 +1,34 @@ +use smithay_client_toolkit::utils::MemPool; +use wayland_client::protocol::wl_shm; + +pub struct DoubleMemPool { + pool1: MemPool, + pool2: MemPool, + switch: bool, +} + +impl DoubleMemPool { + pub fn new(shm: &wl_shm::WlShm) -> ::std::io::Result { + Ok(DoubleMemPool { + pool1: MemPool::new(shm, move || {})?, + pool2: MemPool::new(shm, 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)) + } + } +} diff --git a/src/main.rs b/src/main.rs index 005cbb8..af6bbfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use std::io::{BufRead, BufReader, Read, Write}; use std::os::unix::io::AsRawFd; use std::os::unix::net::{UnixListener, UnixStream}; use std::sync::mpsc::channel; +use std::fs::File; +use std::default::Default; use nix::poll::{poll, PollFd, PollFlags}; use os_pipe::pipe; @@ -11,9 +13,12 @@ mod app; mod buffer; mod cmd; mod color; -mod draw; -mod modules; +mod config; mod desktop; +mod doublemempool; +mod draw; +mod widget; +mod widgets; use app::{App, OutputMode}; use cmd::Cmd; @@ -22,6 +27,7 @@ enum Mode { Start, StartOrKill, ToggleVisible, + PrintConfig, } fn main() { @@ -29,11 +35,24 @@ fn main() { Ok(dir) => dir + "/wldash", Err(_) => "/tmp/wldash".to_string(), }; - let scale = match env::var("WLDASH_SCALE") { - Ok(s) => s.parse().unwrap(), - Err(_) => 1, + let config_home = match env::var("XDG_CONFIG_HOME") { + Ok(dir) => dir + "/wldash", + Err(_) => match env::var("HOME") { + Ok(home) => home + "/.config/wldash", + Err(_) => panic!("unable to find user folder"), + } }; + let config: config::Config = match File::open(config_home + "/config.json") { + Ok(f) => { + let reader = BufReader::new(f); + serde_json::from_reader(reader).unwrap() + }, + Err(_) => Default::default(), + }; + + let scale = config.scale; + let args: Vec = env::args().collect(); let mode = match args.len() { 1 => Mode::Start, @@ -41,6 +60,7 @@ fn main() { "start" => Mode::Start, "start-or-kill" => Mode::StartOrKill, "toggle-visible" => Mode::ToggleVisible, + "print-config" => Mode::PrintConfig, s => { eprintln!("unsupported sub-command {}", s); std::process::exit(1); @@ -73,23 +93,37 @@ fn main() { std::process::exit(1); }; } + Mode::PrintConfig => { + println!("{}", serde_json::to_string_pretty(&config).unwrap()); + std::process::exit(0); + } } let _ = std::fs::remove_file(socket_path.clone()); - let listener = UnixListener::bind(socket_path.clone()).unwrap(); + let output_mode = match config.output_mode { + config::OutputMode::All => OutputMode::All, + config::OutputMode::Active => OutputMode::Active, + }; + + let (tx_draw, rx_draw) = channel(); + let tx_draw_mod = tx_draw.clone(); + let (mod_tx, mod_rx) = channel(); + std::thread::spawn(move || { + // Print, write to a file, or send to an HTTP server. + match config.widget.construct(tx_draw_mod) { + Some(w) => mod_tx.send(w).unwrap(), + None => panic!("no widget configured"), + } + }); + + let mut app = App::new(tx_draw, output_mode, scale); + let widget = mod_rx.recv().unwrap(); + app.set_widget(widget).unwrap(); + let (mut rx_pipe, mut tx_pipe) = pipe().unwrap(); let ipc_pipe = tx_pipe.try_clone().unwrap(); - let (tx_draw, rx_draw) = channel(); - - let output_mode = match env::var("WLDASH_ALL_OUTPUTS") { - Ok(_) => OutputMode::All, - Err(_) => OutputMode::Active, - }; - - let mut app = App::new(tx_draw, output_mode, scale); - app.wipe(); let worker_queue = app.cmd_queue(); let _ = std::thread::Builder::new() @@ -156,18 +190,22 @@ fn main() { app.redraw(true).expect("Failed to draw"); app.flush_display(); } - Cmd::MouseInput { pos, input } => { - if let Some(m) = app.get_module(pos) { - let bounds = m.get_bounds(); - let input = input.offset((bounds.0, bounds.1)); - m.input(input); - q.lock().unwrap().push_back(Cmd::Draw); - } + Cmd::MouseClick { btn, pos } => { + app.get_widget().mouse_click(btn, pos); + q.lock().unwrap().push_back(Cmd::Draw); } - Cmd::KeyboardInput { input } => { - app.with_modules(|m| { - m.input(input.clone()); - }); + 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 => { @@ -201,7 +239,7 @@ fn main() { } if fds[1].revents().unwrap().contains(PollFlags::POLLIN) { - let mut v: Vec = vec![0x00]; + let mut v = [0x00]; rx_pipe.read_exact(&mut v).unwrap(); } } diff --git a/src/modules/clock.rs b/src/modules/clock.rs deleted file mode 100644 index d895542..0000000 --- a/src/modules/clock.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::buffer::Buffer; -use crate::cmd::Cmd; -use crate::color::Color; -use crate::draw::{Font, ROBOTO_REGULAR}; -use crate::modules::module::{Input, ModuleImpl}; - -use std::sync::mpsc::Sender; - -use chrono::{DateTime, Datelike, Duration, Local, Timelike}; - -pub struct Clock { - cur_time: DateTime, - first_draw: bool, - clock_cache: Font, - date_cache: Font, -} - -impl Clock { - pub fn new(ch: Sender) -> Clock { - let _ = std::thread::Builder::new() - .name("clock_ticker".to_string()) - .spawn(move || loop { - let n = Local::now(); - let target = (n + Duration::seconds(60)) - .with_second(0) - .unwrap() - .with_nanosecond(0) - .unwrap(); - - let d = target - n; - - std::thread::sleep(d.to_std().unwrap()); - ch.send(Cmd::Draw).unwrap(); - }); - - let mut date_cache = Font::new(&ROBOTO_REGULAR, 64.0); - date_cache - .add_str_to_cache("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,/ "); - let mut clock_cache = Font::new(&ROBOTO_REGULAR, 256.0); - clock_cache.add_str_to_cache("0123456789:"); - - Clock { - cur_time: Local::now(), - first_draw: true, - date_cache: date_cache, - clock_cache: clock_cache, - } - } -} - -impl ModuleImpl for Clock { - fn draw( - &self, - buf: &mut Buffer, - bg: &Color, - time: &DateTime, - ) -> Result, ::std::io::Error> { - buf.memset(bg); - - self.date_cache.draw_text( - buf, - bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &format!( - "{:?}, {:02}/{:02}/{:4}", - time.weekday(), - time.day(), - time.month(), - time.year() - ), - )?; - - self.clock_cache.draw_text_fixed_width( - &mut buf.offset((0, 64))?, - bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &[120, 120, 56, 120, 120], - &format!("{:02}:{:02}", time.hour(), time.minute()), - )?; - - Ok(vec![buf.get_signed_bounds()]) - } - - fn update(&mut self, time: &DateTime, force: bool) -> Result { - if time.date() != self.cur_time.date() - || time.hour() != self.cur_time.hour() - || time.minute() != self.cur_time.minute() - || force - || self.first_draw - { - self.cur_time = time.clone(); - self.first_draw = false; - Ok(true) - } else { - Ok(false) - } - } - - fn input(&mut self, _input: Input) {} -} diff --git a/src/modules/launcher.rs b/src/modules/launcher.rs deleted file mode 100644 index e4fd8e0..0000000 --- a/src/modules/launcher.rs +++ /dev/null @@ -1,427 +0,0 @@ -use crate::buffer::Buffer; -use crate::cmd::Cmd; -use crate::color::Color; -use crate::draw::{Font, ROBOTO_REGULAR}; -use crate::modules::module::{Input, ModuleImpl}; -use crate::desktop::{Desktop, load_desktop_files}; - -use std::env; -use std::cell::RefCell; -use std::cmp::Ordering; -use std::sync::mpsc::Sender; -use std::process::Command; - -use chrono::{DateTime, Local}; -use fuzzy_matcher::skim::{fuzzy_indices, fuzzy_match}; -use smithay_client_toolkit::keyboard::keysyms; - -pub struct Launcher { - options: Vec, - term_opener: String, - app_opener: String, - url_opener: String, - matches: Vec, - input: String, - result: Option, - offset: usize, - font: RefCell, - dirty: bool, - tx: Sender, -} - -impl Launcher { - pub fn new(listener: Sender) -> Result { - let term = match env::var_os("WLDASH_TERM_OPENER") { - Some(s) => format!("{:} ", s.into_string().unwrap()).to_string(), - None => "".to_string(), - }; - let app = match env::var_os("WLDASH_APP_OPENER") { - Some(s) => format!("{:} ", s.into_string().unwrap()).to_string(), - None => "".to_string(), - }; - let url = match env::var_os("WLDASH_URL_OPENER") { - Some(s) => format!("{:} ", s.into_string().unwrap()).to_string(), - None => "xdg-open ".to_string(), - }; - - Ok(Launcher { - options: load_desktop_files(), - term_opener: term, - app_opener: app, - url_opener: url, - matches: vec![], - input: "".to_string(), - result: None, - offset: 0, - font: RefCell::new(Font::new(&ROBOTO_REGULAR, 32.0)), - dirty: true, - tx: listener, - }) - } - - fn draw_launcher( - &self, - buf: &mut Buffer, - bg: &Color, - ) -> Result, ::std::io::Error> { - buf.memset(bg); - - let mut x_off = if self.input.len() > 0 { - let c = if self.matches.len() == 0 { - Color::new(1.0, 0.5, 0.5, 1.0) - } else { - Color::new(1.0, 1.0, 1.0, 1.0) - }; - let dim = self.font.borrow_mut().auto_draw_text( - buf, - bg, - &c, - &self.input, - )?; - - dim.0 + 8 - } else { - 0 - }; - - let mut width_remaining: i32 = 1232 - x_off as i32; - for (idx, m) in self.matches.iter().enumerate() { - let mut b = match buf.offset((x_off, 0)) { - Ok(b) => b, - Err(_) => break, - }; - let size = if idx == self.offset && self.input.len() > 0 { - let (_, indices) = - fuzzy_indices(&m.name.to_lowercase(), &self.input.to_lowercase()).unwrap(); - let mut colors = Vec::with_capacity(m.name.len()); - for pos in 0..m.name.len() { - if indices.contains(&pos) { - colors.push(Color::new(1.0, 1.0, 1.0, 1.0)); - } else { - colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); - } - } - self.font - .borrow_mut() - .auto_draw_text_individual_colors(&mut b, bg, &colors, &m.name)? - } else { - self.font.borrow_mut().auto_draw_text( - &mut b, - bg, - &Color::new(0.5, 0.5, 0.5, 1.0), - &m.name, - )? - }; - - x_off += size.0 + 8; - width_remaining -= (size.0 + 8) as i32; - - if width_remaining < 0 { - break; - } - } - - Ok(vec![buf.get_signed_bounds()]) - } - - fn draw_calc( - &self, - buf: &mut Buffer, - bg: &Color, - ) -> Result, ::std::io::Error> { - buf.memset(bg); - - let x_off = self - .font - .borrow_mut() - .auto_draw_text( - buf, - bg, - &Color::new(1.0, 1.0, 0.0, 1.0), - "=", - )? - .0 - + 8; - - let x_off = if self.input.len() > 0 { - let dim = self.font.borrow_mut().auto_draw_text( - &mut buf.offset((x_off, 0))?, - bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &self.input[1..], - )?; - - x_off + dim.0 + 8 - } else { - 0 - }; - - if let Some(result) = &self.result { - self.font.borrow_mut().auto_draw_text( - &mut buf.offset((x_off, 0))?, - bg, - &Color::new(0.75, 0.75, 0.75, 1.0), - &format!(" = {:}", result), - )?; - } - - Ok(vec![buf.get_signed_bounds()]) - } - - fn draw_shell( - &self, - buf: &mut Buffer, - bg: &Color, - ) -> Result, ::std::io::Error> { - buf.memset(bg); - - let x_off = self - .font - .borrow_mut() - .auto_draw_text( - &mut buf.offset((0, 0))?, - bg, - &Color::new(1.0, 1.0, 0.0, 1.0), - "!", - )? - .0 - + 8; - - if self.input.len() > 0 { - self.font.borrow_mut().auto_draw_text( - &mut buf.offset((x_off, 0))?, - bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - &self.input[1..], - )?; - }; - - Ok(vec![buf.get_signed_bounds()]) - } -} - -#[cfg(feature = "ivy")] -fn calc(s: &str) -> Result { - libivy::eval(s) -} - -#[cfg(feature = "rcalc")] -fn calc(s: &str) -> Result { - rcalc_lib::parse::eval(s, &mut rcalc_lib::parse::CalcState::new()) - .map(|x| format!("{}", x).to_string()) - .map_err(|x| format!("{}", x).to_string()) -} - -#[cfg(feature = "bc")] -fn calc(s: &str) -> Result { - use std::io::Write; - let mut child = std::process::Command::new("bc") - .stdin(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .arg("--mathlib") - .spawn() - .map_err(|_| "bc not available".to_string())?; - { - let stdin = child.stdin.as_mut().unwrap(); - stdin - .write_all(s.as_bytes()) - .map_err(|_| "unable to write to stdin".to_string())?; - stdin - .write_all("\n".as_bytes()) - .map_err(|_| "unable to write to stdin".to_string())?; - } - let output = child - .wait_with_output() - .map_err(|_| "unable to run bc".to_string())?; - Ok(std::str::from_utf8(&output.stdout) - .map_err(|_| "unable to read from bc")? - .trim() - .to_string()) -} - -#[cfg(not(any(feature = "ivy", feature = "bc", feature = "rcalc")))] -fn calc(_s: &str) -> Result { - Err("no calculator implementation available".to_string()) -} - -fn wlcopy(s: &str) -> Result<(), String> { - let mut child = std::process::Command::new("wl-copy") - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .arg(s) - .spawn() - .map_err(|_| "wl-copy not available".to_string())?; - child - .wait() - .map_err(|_| "unable to run wl-copy".to_string())?; - Ok(()) -} - -impl ModuleImpl for Launcher { - fn draw( - &self, - buf: &mut Buffer, - bg: &Color, - _time: &DateTime, - ) -> Result, ::std::io::Error> { - buf.memset(bg); - - match self.input.chars().next() { - Some('=') => self.draw_calc(buf, bg), - Some('!') => self.draw_shell(buf, bg), - _ => self.draw_launcher(buf, bg), - }?; - - Ok(vec![buf.get_signed_bounds()]) - } - - fn update(&mut self, _time: &DateTime, force: bool) -> Result { - if self.dirty || force { - match self.input.chars().next() { - Some('=') => { - if self.input.len() > 1 { - match calc(&self.input.chars().skip(1).collect::()) { - Ok(v) => self.result = Some(v), - Err(_) => self.result = None, - } - } - } - Some('!') => (), - _ => { - let mut m = self - .options - .iter() - .map(|x| { - ( - fuzzy_match(&x.name.to_lowercase(), &self.input.to_lowercase()), - x, - ) - }) - .filter(|(x, _)| x.is_some()) - .map(|(x, y)| (x.unwrap(), y.clone())) - .collect::>(); - - m.sort_by(|(x1, y1), (x2, y2)| { - if x1 > x2 { - Ordering::Less - } else if x1 < x2 { - Ordering::Greater - } else if y1.name.len() < y2.name.len() { - Ordering::Less - } else if y1.name.len() > y2.name.len() { - Ordering::Greater - } else { - y1.cmp(y2) - } - }); - - self.matches = m.into_iter().map(|(_, x)| x).collect(); - } - }; - - self.dirty = false; - Ok(true) - } else { - Ok(false) - } - } - - fn input(&mut self, input: Input) { - match input { - Input::Keypress { key, interpreted } => match key { - keysyms::XKB_KEY_BackSpace => { - if self.input.len() > 0 { - self.input = self.input[..self.input.len() - 1].to_string(); - self.offset = 0; - self.result = None; - self.dirty = true - } - } - keysyms::XKB_KEY_Return => { - match self.input.chars().next() { - Some('=') => match self.result { - Some(ref v) => { - let _ = wlcopy(&v); - } - None => (), - }, - Some('!') => { - let _ = Command::new("sh") - .arg("-c") - .arg(self.input.chars().skip(1).collect::()) - .spawn(); - self.tx.send(Cmd::Exit).unwrap(); - } - _ => { - if self.matches.len() > self.offset { - let d = &self.matches[self.offset]; - if let Some(exec) = &d.exec { - let exec = exec - .replace("%f", "") - .replace("%F", "") - .replace("%u", "") - .replace("%U", ""); - let prefix = if d.term { - &self.term_opener - } else { - &self.app_opener - }; - - let lexed = if prefix.len() > 0 { - let mut prefix = shlex::split(prefix).unwrap(); - prefix.push(exec); - prefix - } else { - shlex::split(&exec).unwrap() - }; - if lexed.len() > 0 { - let _ = Command::new(lexed[0].clone()) - .args(&lexed[1..]) - .spawn(); - self.tx.send(Cmd::Exit).unwrap(); - } - } - if let Some(url) = &d.url { - if self.url_opener.len() > 0 { - let mut lexed = shlex::split(&self.url_opener).unwrap(); - lexed.push(url.to_string()); - if lexed.len() > 0 { - let _ = Command::new(lexed[0].clone()) - .args(&lexed[1..]) - .spawn(); - self.tx.send(Cmd::Exit).unwrap(); - } - } - } - } - } - }; - } - keysyms::XKB_KEY_Right => { - if self.matches.len() > 0 && self.offset < self.matches.len() - 1 { - self.offset += 1; - self.dirty = true; - } - } - keysyms::XKB_KEY_Left => { - if self.matches.len() > 0 && self.offset > 0 { - self.offset -= 1; - self.dirty = true; - } - } - _ => match interpreted { - Some(v) => { - self.input += &v; - self.offset = 0; - self.result = None; - self.dirty = true; - } - None => {} - }, - }, - _ => {} - } - } -} diff --git a/src/modules/module.rs b/src/modules/module.rs deleted file mode 100644 index 65c4436..0000000 --- a/src/modules/module.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::buffer::Buffer; -use crate::color::Color; - -use std::sync::{Arc, Mutex}; - -use chrono::{DateTime, Local}; - -#[derive(Clone)] -pub enum Input { - Click { - pos: (u32, u32), - button: u32, - }, - Scroll { - pos: (u32, u32), - x: f64, - y: f64, - }, - Keypress { - key: u32, - interpreted: Option, - }, -} - -impl Input { - pub fn offset(&self, offset: (u32, u32)) -> Input { - match self { - Input::Click { pos, button } => Input::Click { - pos: (pos.0 - offset.0, pos.1 - offset.1), - button: *button, - }, - Input::Scroll { pos, x, y } => Input::Scroll { - pos: (pos.0 - offset.0, pos.1 - offset.1), - x: *x, - y: *y, - }, - Input::Keypress { key, interpreted } => Input::Keypress { - key: *key, - interpreted: interpreted.clone(), - }, - } - } -} - -pub trait ModuleImpl { - fn draw( - &self, - buf: &mut Buffer, - bg: &Color, - time: &DateTime, - ) -> Result, ::std::io::Error>; - fn update(&mut self, time: &DateTime, force: bool) -> Result; - fn input(&mut self, input: Input); -} - -pub struct Module { - m: Arc>>, - pos: (u32, u32, u32, u32), -} - -impl Module { - pub fn new(m: Box, pos: (u32, u32, u32, u32)) -> Module { - Module { - m: Arc::new(Mutex::new(m)), - pos: pos, - } - } - - pub fn get_bounds(&self) -> (u32, u32, u32, u32) { - self.pos - } - - pub fn update(&self, time: &DateTime, force: bool) -> Result { - self.m.lock().unwrap().update(time, force) - } - - pub fn draw( - &self, - buf: &mut Buffer, - bg: &Color, - time: &DateTime, - ) -> Result, ::std::io::Error> { - self.m.lock().unwrap().draw(buf, bg, time) - } - - pub fn intersect(&self, pos: (u32, u32)) -> bool { - pos.0 >= self.pos.0 - && pos.0 < (self.pos.0 + self.pos.2) - && pos.1 >= self.pos.1 - && pos.1 < (self.pos.1 + self.pos.3) - } - - pub fn input(&self, input: Input) { - self.m.lock().unwrap().input(input) - } -} diff --git a/src/widget.rs b/src/widget.rs new file mode 100644 index 0000000..f452825 --- /dev/null +++ b/src/widget.rs @@ -0,0 +1,365 @@ +use crate::buffer::Buffer; +use crate::color::Color; +use chrono::{DateTime, Local}; +pub use smithay_client_toolkit::keyboard::{KeyState, ModifiersState}; + +pub struct DrawContext<'a> { + pub buf: &'a mut Buffer<'a>, + pub bg: &'a Color, + pub time: &'a DateTime, + 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 trait Widget { + fn size(&self) -> (u32, u32); + fn draw( + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result; + + fn keyboard_input( + &mut self, + keysym: u32, + modifier_state: ModifiersState, + key_state: KeyState, + interpreted: Option, + ); + fn mouse_click(&mut self, button: u32, pos: (u32, u32)); + fn mouse_scroll(&mut self, scroll: (f64, f64), pos: (u32, u32)); +} + +pub struct VerticalLayout { + pub children: Vec>, +} + +#[allow(dead_code)] +impl VerticalLayout { + pub fn new(children: Vec>) -> Box { + Box::new(VerticalLayout { children: children }) + } +} + +impl Widget for VerticalLayout { + 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), + ) -> Result { + let mut offset = pos.1; + 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))?; + 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 - pos.1, + damage, + full_damage, + }) + } + + fn keyboard_input( + &mut self, + keysym: u32, + modifier_state: ModifiersState, + key_state: KeyState, + interpreted: Option, + ) { + 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 { + pub children: Vec>, +} + +#[allow(dead_code)] +impl HorizontalLayout { + pub fn new(children: Vec>) -> Box { + Box::new(HorizontalLayout { children: children }) + } +} + +impl Widget for HorizontalLayout { + 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), + ) -> Result { + let mut offset = pos.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.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 - pos.0, + height, + damage, + full_damage, + }) + } + + fn keyboard_input( + &mut self, + keysym: u32, + modifier_state: ModifiersState, + key_state: KeyState, + interpreted: Option, + ) { + 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 { + pub child: Box, + pub margins: (u32, u32, u32, u32), // left, right, top, bottom +} + +#[allow(dead_code)] +impl Margin { + pub fn new(margins: (u32, u32, u32, u32), child: Box) -> Box { + Box::new(Margin { + child: child, + margins: margins, + }) + } +} + +impl Widget for Margin { + 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), + ) -> Result { + let report = self + .child + .draw(ctx, (pos.0 + self.margins.0, pos.1 + self.margins.2))?; + 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, + ) { + 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 { + pub child: Box, + pub size: (u32, u32), +} + +#[allow(dead_code)] +impl Fixed { + pub fn new(size: (u32, u32), child: Box) -> Box { + Box::new(Fixed { + child: child, + size: size, + }) + } +} + +impl Widget for Fixed { + fn size(&self) -> (u32, u32) { + self.size + } + + fn draw( + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result { + let report = self.child.draw(ctx, pos)?; + 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, + ) { + 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); + } +} diff --git a/src/modules/backlight.rs b/src/widgets/backlight.rs similarity index 50% rename from src/modules/backlight.rs rename to src/widgets/backlight.rs index 966b386..763b689 100644 --- a/src/modules/backlight.rs +++ b/src/widgets/backlight.rs @@ -1,21 +1,15 @@ -use crate::buffer::Buffer; use crate::color::Color; -use crate::draw::{draw_bar, draw_box, Font, ROBOTO_REGULAR}; -use crate::modules::module::{Input, ModuleImpl}; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use std::fs::OpenOptions; use std::io::{Error, ErrorKind}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; -use chrono::{DateTime, Local}; - pub struct Backlight { device_path: PathBuf, cur_brightness: u64, max_brightness: u64, - font: Font, - dirty: bool, } fn read_file_as_u64(path: &Path) -> Result { @@ -36,7 +30,6 @@ impl Backlight { pub fn update(&mut self) -> Result<(), Error> { self.cur_brightness = read_file_as_u64(self.device_path.join("brightness").as_path())?; self.max_brightness = read_file_as_u64(self.device_path.join("max_brightness").as_path())?; - self.dirty = true; Ok(()) } @@ -58,6 +51,11 @@ impl Backlight { (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; @@ -72,7 +70,7 @@ impl Backlight { Ok(()) } - pub fn new() -> Result { + pub fn new(font_size: f32, length: u32) -> Result, Error> { let devices = Path::new("/sys/class/backlight").read_dir()?; let first_device = match devices.take(1).next() { @@ -86,90 +84,56 @@ impl Backlight { device_path: first_device.path(), cur_brightness: 0, max_brightness: 0, - font: Font::new(&ROBOTO_REGULAR, 24.0), - dirty: true, }; - dev.font.add_str_to_cache("backlight"); - dev.update()?; - Ok(dev) + Ok(BarWidget::new_simple(font_size, length, Box::new(dev))) } } -impl ModuleImpl for Backlight { - fn draw( - &self, - buf: &mut Buffer, - bg: &Color, - _time: &DateTime, - ) -> Result, Error> { - buf.memset(bg); - let c = Color::new(1.0, 1.0, 1.0, 1.0); - self.font.draw_text( - buf, - bg, - &c, - "backlight", - )?; - draw_bar( - &mut buf.offset((128, 0))?, - &c, - 464, - 24, - self.brightness(), - )?; - draw_box(&mut buf.offset((128, 0))?, &c, (464, 24))?; - Ok(vec![buf.get_signed_bounds()]) +impl BarWidgetImpl for Backlight { + fn name(&self) -> &str { + "backlight" } - - fn update(&mut self, _time: &DateTime, force: bool) -> Result { - if self.dirty || force { - self.dirty = false; - Ok(true) + fn value(&self) -> f32 { + self.brightness() + } + fn color(&self) -> Color { + Color::new(1.0, 1.0, 1.0, 1.0) + } + fn inc(&mut self, inc: f32) { + self.add(inc).unwrap(); + match self.sync() { + Ok(val) => val, + 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 { - Ok(false) - } - } - - fn input(&mut self, input: Input) { - match input { - Input::Scroll { - pos: _pos, - x: _x, - y, - } => { - self.add(y as f32 / -800.0).unwrap(); - match self.sync() { - Ok(val) => val, - Err(err) => { - eprintln!("Error while trying to change brightness: {}", err); - () - } - } + 1 + }; + match self.sync() { + Ok(val) => val, + Err(err) => { + eprintln!("Error while trying to change brightness: {}", err); + () } - Input::Click { pos: _pos, button } => { - match button { - 273 => { - // Right click - 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); - () - } - } - } - _ => {} - } - } - _ => {} } } } diff --git a/src/widgets/bar_widget.rs b/src/widgets/bar_widget.rs new file mode 100644 index 0000000..998f130 --- /dev/null +++ b/src/widgets/bar_widget.rs @@ -0,0 +1,145 @@ +use crate::color::Color; +use crate::draw::{draw_bar, draw_box, Font, ROBOTO_REGULAR}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget}; + +use std::sync::{Arc, Mutex}; + +pub trait BarWidgetImpl { + fn name(&self) -> &str; + fn value(&self) -> f32; + fn color(&self) -> Color; + fn inc(&mut self, inc: f32); + fn set(&mut self, val: f32); + fn toggle(&mut self); +} + +pub struct BarWidget { + bar_impl: Box, + font: Font, + font_size: u32, + length: u32, + dirty: Arc>, +} + +impl BarWidget { + pub fn new_simple( + font_size: f32, + length: u32, + w: Box, + ) -> Box { + let mut font = Font::new(&ROBOTO_REGULAR, font_size); + font.add_str_to_cache(w.name()); + + Box::new(BarWidget { + bar_impl: w, + dirty: Arc::new(Mutex::new(true)), + font: font, + font_size: font_size as u32, + length: length, + }) + } + + pub fn new(font_size: f32, length: u32, f: F) -> Result, ::std::io::Error> + where + F: FnOnce(Arc>) -> Result, ::std::io::Error>, + F: Send + 'static + Clone, + { + let dirty = Arc::new(Mutex::new(true)); + let im = f(dirty.clone())?; + + let mut font = Font::new(&ROBOTO_REGULAR, font_size); + font.add_str_to_cache(im.name()); + + Ok(Box::new(BarWidget { + bar_impl: im, + dirty: dirty, + font: font, + font_size: font_size as u32, + length: length, + })) + } +} + +impl Widget for BarWidget { + fn size(&self) -> (u32, u32) { + (self.length, self.font_size) + } + + fn draw( + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result { + let (width, height) = self.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))?; + buf.memset(ctx.bg); + + let c = Color::new(1.0, 1.0, 1.0, 1.0); + self.font.draw_text(buf, ctx.bg, &c, self.bar_impl.name())?; + + let c = self.bar_impl.color(); + let bar_off = 5 * self.font_size; + let mut val = self.bar_impl.value(); + draw_bar( + &mut buf.offset((bar_off, 0))?, + &c, + self.length - bar_off, + self.font_size, + val, + )?; + let mut iter = 1.0; + 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, + self.length - bar_off, + self.font_size, + val, + )?; + } + draw_box( + &mut buf.offset((bar_off, 0))?, + &c, + (self.length - bar_off, self.font_size), + )?; + Ok(DrawReport { + width: width, + height: height, + damage: vec![buf.get_signed_bounds()], + full_damage: false, + }) + } + + fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option) {} + fn mouse_click(&mut self, button: u32, (x, _): (u32, u32)) { + *self.dirty.lock().unwrap() = true; + match button { + 272 => { + let offset = 5 * self.font_size; + if x > offset { + self.bar_impl + .set(((x - offset) + 1) as f32 / (self.length - offset) as f32); + } + } + 273 => self.bar_impl.toggle(), + x => { + dbg!(x); + } + } + } + fn mouse_scroll(&mut self, (_, y): (f64, f64), _: (u32, u32)) { + *self.dirty.lock().unwrap() = true; + self.bar_impl.inc(y as f32 / -800.0); + } +} diff --git a/src/modules/battery.rs b/src/widgets/battery.rs similarity index 52% rename from src/modules/battery.rs rename to src/widgets/battery.rs index 6916ccd..467aa93 100644 --- a/src/modules/battery.rs +++ b/src/widgets/battery.rs @@ -1,14 +1,11 @@ -use crate::buffer::Buffer; use crate::cmd::Cmd; use crate::color::Color; -use crate::draw::{draw_bar, draw_box, Font, ROBOTO_REGULAR}; -use crate::modules::module::{Input, ModuleImpl}; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use std::thread; -use chrono::{DateTime, Local}; use dbus; fn get_upower_property( @@ -40,14 +37,12 @@ fn get_upower_property( pub struct UpowerBattery { device_path: String, - font: Font, inner: Arc>, } pub struct UpowerBatteryInner { state: UpowerBatteryState, capacity: f64, - dirty: bool, } enum UpowerBatteryState { @@ -116,88 +111,89 @@ impl UpowerBattery { } }; - let mut font = Font::new(&ROBOTO_REGULAR, 24.0); - font.add_str_to_cache("battery"); - Ok(UpowerBattery { device_path, - font: font, inner: Arc::new(Mutex::new(UpowerBatteryInner { capacity: capacity, state: state, - dirty: true, })), }) } - pub fn new(listener: Sender) -> Result { - let d = UpowerBattery::from_device("BAT0")?; - let path = d.device_path.clone(); - let inner = d.inner.clone(); - let _ = thread::Builder::new() - .name("battery_monitor".to_string()) - .spawn(move || { - let con = dbus::Connection::get_private(dbus::BusType::System) - .expect("Failed to establish D-Bus connection."); - let rule = format!( - "type='signal',\ - path='{}',\ - interface='org.freedesktop.DBus.Properties',\ - member='PropertiesChanged'", - path - ); + pub fn new( + font_size: f32, + length: u32, + sender: Sender, + ) -> Result, ::std::io::Error> { + BarWidget::new(font_size, length, move |dirty| { + let d = UpowerBattery::from_device("BAT0")?; + let path = d.device_path.clone(); + let inner = d.inner.clone(); + let _ = thread::Builder::new() + .name("battery_monitor".to_string()) + .spawn(move || { + let con = dbus::Connection::get_private(dbus::BusType::System) + .expect("Failed to establish D-Bus connection."); + let rule = format!( + "type='signal',\ + path='{}',\ + interface='org.freedesktop.DBus.Properties',\ + member='PropertiesChanged'", + path + ); - // First we're going to get an (irrelevant) NameAcquired event. - con.incoming(10_000).next(); + // First we're going to get an (irrelevant) NameAcquired event. + con.incoming(10_000).next(); - con.add_match(&rule) - .expect("Failed to add D-Bus match rule."); + con.add_match(&rule) + .expect("Failed to add D-Bus match rule."); - loop { - if con.incoming(10_000).next().is_some() { - let capacity = get_upower_property(&con, &path, "Percentage") - .unwrap() - .get1::>() - .unwrap() - .0; - let state = match get_upower_property(&con, &path, "State") - .unwrap() - .get1::>() - .unwrap() - .0 - { - 1 => UpowerBatteryState::Charging, - 2 => UpowerBatteryState::Discharging, - 3 => UpowerBatteryState::Empty, - 4 => UpowerBatteryState::Full, - 5 => UpowerBatteryState::NotCharging, - 6 => UpowerBatteryState::Discharging, - _ => UpowerBatteryState::Unknown, - }; - let mut inner = inner.lock().unwrap(); - inner.state = state; - inner.capacity = capacity; - inner.dirty = true; - listener.send(Cmd::Draw).unwrap(); + loop { + if con.incoming(10_000).next().is_some() { + let capacity = get_upower_property(&con, &path, "Percentage") + .unwrap() + .get1::>() + .unwrap() + .0; + let state = match get_upower_property(&con, &path, "State") + .unwrap() + .get1::>() + .unwrap() + .0 + { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + }; + let mut inner = inner.lock().unwrap(); + inner.state = state; + inner.capacity = capacity; + *dirty.lock().unwrap() = true; + sender.send(Cmd::Draw).unwrap(); + } } - } - }); + }); - Ok(d) + Ok(Box::new(d)) + }) } } -impl ModuleImpl for UpowerBattery { - fn draw( - &self, - buf: &mut Buffer, - bg: &Color, - _time: &DateTime, - ) -> Result, ::std::io::Error> { - buf.memset(bg); +impl BarWidgetImpl for UpowerBattery { + fn name(&self) -> &str { + "battery" + } + fn value(&self) -> f32 { let inner = self.inner.lock().unwrap(); - let text_color = Color::new(1.0, 1.0, 1.0, 1.0); - let bar_color = match inner.state { + (inner.capacity as f32) / 100.0 + } + fn color(&self) -> Color { + let inner = self.inner.lock().unwrap(); + match inner.state { UpowerBatteryState::Discharging | UpowerBatteryState::Unknown => { if inner.capacity > 10.0 { Color::new(1.0, 1.0, 1.0, 1.0) @@ -211,39 +207,9 @@ impl ModuleImpl for UpowerBattery { UpowerBatteryState::NotCharging | UpowerBatteryState::Empty => { Color::new(1.0, 0.5, 0.5, 1.0) } - }; - - self.font.draw_text( - &mut buf.offset((0, 0))?, - bg, - &text_color, - "battery", - )?; - draw_bar( - &mut buf.offset((128, 0))?, - &bar_color, - 464, - 24, - (inner.capacity as f32) / 100.0, - )?; - - draw_box( - &mut buf.offset((128, 0))?, - &bar_color, - (464, 24), - )?; - Ok(vec![buf.get_signed_bounds()]) - } - - fn update(&mut self, _time: &DateTime, force: bool) -> Result { - let mut inner = self.inner.lock().unwrap(); - if inner.dirty || force { - inner.dirty = false; - Ok(true) - } else { - Ok(false) } } - - fn input(&mut self, _input: Input) {} + fn inc(&mut self, _: f32) {} + fn set(&mut self, _: f32) {} + fn toggle(&mut self) {} } diff --git a/src/modules/calendar.rs b/src/widgets/calendar.rs similarity index 58% rename from src/modules/calendar.rs rename to src/widgets/calendar.rs index 1f96b1d..87ca50d 100644 --- a/src/modules/calendar.rs +++ b/src/widgets/calendar.rs @@ -1,14 +1,16 @@ use crate::buffer::Buffer; use crate::color::Color; use crate::draw::{Font, DEJAVUSANS_MONO, ROBOTO_REGULAR}; -use crate::modules::module::{Input, ModuleImpl}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget}; -use chrono::{Date, DateTime, Datelike, Local}; +use chrono::{Date, Datelike, Local}; pub struct Calendar { cur_date: Date, dirty: bool, offset: f64, + sections: u32, + font_size: u32, calendar_cache: Font, month_cache: Font, year_cache: Font, @@ -55,7 +57,7 @@ impl Calendar { )?; if time.year() != orig.year() { self.year_cache.draw_text( - &mut buf.offset((320, 0))?, + &mut buf.offset((self.font_size * 20, 0))?, background_color, &Color::new(0.8, 0.8, 0.8, 1.0), &format!("{:}", time.year()), @@ -79,7 +81,10 @@ impl Calendar { }; self.day_cache.draw_text( - &mut buf.offset((idx * 48 + 4, (y_off * 32) + 64))?, + &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, @@ -105,7 +110,7 @@ impl Calendar { // let wk = time.iso_week(); self.calendar_cache.draw_text( - &mut buf.offset((0 * 48, (y_off * 32) + 64))?, + &mut buf.offset((0, y_off * self.font_size * 2 + self.font_size * 4))?, background_color, &Color::new(0.75, 0.75, 0.75, 1.0), &format!("{:02}", wk.week()), @@ -123,7 +128,10 @@ impl Calendar { }; self.calendar_cache.draw_text( - &mut buf.offset((x_pos * 48, (y_off * 32) + 64))?, + &mut buf.offset(( + x_pos * self.font_size * 3, + y_off * self.font_size * 2 + self.font_size * 4, + ))?, background_color, &c, &format!("{:02}", time.day()), @@ -145,38 +153,63 @@ impl Calendar { } impl Calendar { - pub fn new() -> Calendar { - let mut calendar_cache = Font::new(&DEJAVUSANS_MONO, 32.0); + pub fn new(font_size: f32, sections: u32) -> Box { + let mut calendar_cache = Font::new(&DEJAVUSANS_MONO, font_size * 2.0); calendar_cache.add_str_to_cache("0123456789"); - let mut month_cache = Font::new(&ROBOTO_REGULAR, 64.0); + let mut month_cache = Font::new(&ROBOTO_REGULAR, font_size * 4.0); month_cache.add_str_to_cache("JanuryFebMchApilJgstSmOoNvD"); - let mut year_cache = Font::new(&DEJAVUSANS_MONO, 24.0); + let mut year_cache = Font::new(&DEJAVUSANS_MONO, font_size * 1.5); year_cache.add_str_to_cache("-0123456789"); - let mut day_cache = Font::new(&DEJAVUSANS_MONO, 16.0); + let mut day_cache = Font::new(&DEJAVUSANS_MONO, font_size); day_cache.add_str_to_cache("MONTUEWDHFRISA"); - Calendar { + Box::new(Calendar { cur_date: Local::now().date(), dirty: true, offset: 0.0, + sections: sections, + font_size: font_size as u32, calendar_cache: calendar_cache, month_cache: month_cache, year_cache: year_cache, day_cache: day_cache, - } + }) } } -impl ModuleImpl for Calendar { +impl Widget for Calendar { + fn size(&self) -> (u32, u32) { + let cal_width = 7 * self.font_size * 3 + self.font_size * 2; + 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( - &self, - buf: &mut Buffer, - background_color: &Color, - time: &DateTime, - ) -> Result, ::std::io::Error> { - buf.memset(background_color); - let time = time.date(); + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result { + 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 o = (self.offset / 100.0) as i32; + + let cals = self.sections - 1; + let pre_cals = cals / 2; + for _ in 0..pre_cals { + t = t.pred().with_day(1).unwrap(); + } + if o != 0 { let mut month = (t.month() - 1) as i32 + o; let mut year = t.year(); @@ -194,64 +227,40 @@ impl ModuleImpl for Calendar { .with_month((month + 1) as u32) .unwrap(); } - self.draw_month( - &mut buf.offset((0, 0))?, - background_color, - &time, - &t.pred().with_day(1).unwrap(), - )?; - self.draw_month( - &mut buf.offset((432, 0))?, - background_color, - &time, - &t, - )?; - let n = if t.month() == 12 { - t.with_year(t.year() + 1).unwrap().with_month(1).unwrap() - } else { - t.with_month(t.month() + 1).unwrap() - }; - self.draw_month( - &mut buf.offset((864, 0))?, - background_color, - &time, - &n, - )?; - Ok(vec![buf.get_signed_bounds()]) + 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.with_year(t.year() + 1).unwrap().with_month(1).unwrap() + } else { + t.with_month(t.month() + 1).unwrap() + }; + } + Ok(DrawReport { + width: width, + height: height, + damage: vec![buf.get_signed_bounds()], + full_damage: false, + }) } - fn update(&mut self, time: &DateTime, force: bool) -> Result { - if self.dirty { - self.dirty = false; - Ok(true) - } else if time.date() != self.cur_date || force { - self.cur_date = time.date(); - Ok(true) + fn keyboard_input(&mut self, _: u32, _: ModifiersState, _: KeyState, _: Option) {} + fn mouse_click(&mut self, _: u32, (x, _): (u32, u32)) { + let cal_pad = 7 * self.font_size * 3 + self.font_size * 5; + let cals = self.sections - 1; + let pre_cals = cals / 2; + if x < pre_cals * cal_pad { + self.offset -= 100.0; + } else if x >= (pre_cals + 1) * cal_pad { + self.offset += 100.0; } else { - Ok(false) + self.offset = 0.0; } + self.dirty = true; } - - fn input(&mut self, input: Input) { - match input { - Input::Scroll { pos: _, x: _, y } => { - self.offset += y; - self.dirty = true; - } - Input::Click { - pos: (x, _), - button: _, - } => { - if x < 448 { - self.offset -= 100.0; - } else if x >= 896 { - self.offset += 100.0; - } else { - self.offset = 0.0; - } - self.dirty = true; - } - _ => {} - } + fn mouse_scroll(&mut self, (_, y): (f64, f64), _: (u32, u32)) { + self.offset += y; + self.dirty = true; } } diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs new file mode 100644 index 0000000..5da66d2 --- /dev/null +++ b/src/widgets/clock.rs @@ -0,0 +1,93 @@ +use crate::cmd::Cmd; +use crate::color::Color; +use crate::draw::{Font, ROBOTO_REGULAR}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget}; + +use std::sync::mpsc::Sender; + +use chrono::{DateTime, Datelike, Duration, Local, Timelike}; + +pub struct Clock { + cur_time: DateTime, + clock_cache: Font, + size: f32, +} + +impl Clock { + pub fn new(size: f32, ch: Sender) -> Box { + let _ = std::thread::Builder::new() + .name("clock_ticker".to_string()) + .spawn(move || loop { + let n = Local::now(); + let target = (n + Duration::seconds(60)) + .with_second(0) + .unwrap() + .with_nanosecond(0) + .unwrap(); + + let d = target - n; + + std::thread::sleep(d.to_std().unwrap()); + ch.send(Cmd::Draw).unwrap(); + }); + + let mut clock_cache = Font::new(&ROBOTO_REGULAR, size); + clock_cache.add_str_to_cache("0123456789:"); + + let time = Local::now(); + + Box::new(Clock { + cur_time: time.with_year(time.year().saturating_sub(1)).unwrap(), + clock_cache: clock_cache, + size: size, + }) + } +} + +impl Widget for Clock { + fn size(&self) -> (u32, u32) { + ((2.0 * self.size).ceil() as u32, self.size.ceil() as u32) + } + + fn draw( + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result { + 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.clone(); + + let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; + buf.memset(ctx.bg); + + let digit = (self.size * 0.45).ceil() as u32; + let colon = (self.size * 0.20).ceil() as u32; + self.clock_cache.draw_text_fixed_width( + buf, + ctx.bg, + &Color::new(1.0, 1.0, 1.0, 1.0), + &[digit, digit, colon, digit, digit], + &format!("{:02}:{:02}", ctx.time.hour(), ctx.time.minute()), + )?; + + Ok(DrawReport { + width: width, + height: 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) {} + fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} + fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} +} diff --git a/src/widgets/date.rs b/src/widgets/date.rs new file mode 100644 index 0000000..47a6f07 --- /dev/null +++ b/src/widgets/date.rs @@ -0,0 +1,73 @@ +use crate::color::Color; +use crate::draw::{Font, ROBOTO_REGULAR}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget}; + +use chrono::{DateTime, Datelike, Local}; + +pub struct Date { + cur_time: DateTime, + date_cache: Font, + size: f32, +} + +impl Date { + pub fn new(size: f32) -> Box { + let mut date_cache = Font::new(&ROBOTO_REGULAR, size); + date_cache + .add_str_to_cache("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,/ "); + + let time = Local::now(); + + Box::new(Date { + cur_time: time.with_year(time.year().saturating_sub(1)).unwrap(), + date_cache: date_cache, + size: size, + }) + } +} + +impl Widget for Date { + fn size(&self) -> (u32, u32) { + ((6.5 * self.size).ceil() as u32, self.size.ceil() as u32) + } + + fn draw( + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result { + 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.clone(); + + let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; + buf.memset(ctx.bg); + self.date_cache.draw_text( + buf, + ctx.bg, + &Color::new(1.0, 1.0, 1.0, 1.0), + &format!( + "{:?}, {:02}/{:02}/{:4}", + ctx.time.weekday(), + ctx.time.day(), + ctx.time.month(), + ctx.time.year() + ), + )?; + + Ok(DrawReport { + width: width, + height: 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) {} + fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} + fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} +} diff --git a/src/widgets/launcher.rs b/src/widgets/launcher.rs new file mode 100644 index 0000000..09960d2 --- /dev/null +++ b/src/widgets/launcher.rs @@ -0,0 +1,401 @@ +use crate::buffer::Buffer; +use crate::cmd::Cmd; +use crate::color::Color; +use crate::desktop::{load_desktop_files, Desktop}; +use crate::draw::{Font, ROBOTO_REGULAR}; +use crate::widget::{DrawContext, DrawReport, KeyState, ModifiersState, Widget}; + +use std::cell::RefCell; +use std::cmp::Ordering; +use std::process::Command; +use std::sync::mpsc::Sender; + +use fuzzy_matcher::skim::{fuzzy_indices, fuzzy_match}; +use smithay_client_toolkit::keyboard::keysyms; + +pub struct Launcher { + options: Vec, + term_opener: String, + app_opener: String, + url_opener: String, + matches: Vec, + input: String, + result: Option, + offset: usize, + font: RefCell, + font_size: u32, + length: u32, + dirty: bool, + tx: Sender, +} + +impl Launcher { + pub fn new(font_size: f32, length: u32, listener: Sender, app: String, term: String, url: String) -> Box { + Box::new(Launcher { + options: load_desktop_files(), + term_opener: term, + app_opener: app, + url_opener: url, + matches: vec![], + input: "".to_string(), + result: None, + offset: 0, + font: RefCell::new(Font::new(&ROBOTO_REGULAR, font_size)), + font_size: font_size as u32, + length: length, + dirty: true, + tx: listener, + }) + } + + fn draw_launcher(&self, buf: &mut Buffer, bg: &Color) -> Result<(), ::std::io::Error> { + let mut x_off = if self.input.len() > 0 { + let c = if self.matches.len() == 0 { + Color::new(1.0, 0.5, 0.5, 1.0) + } else { + Color::new(1.0, 1.0, 1.0, 1.0) + }; + let dim = self + .font + .borrow_mut() + .auto_draw_text(buf, bg, &c, &self.input)?; + + dim.0 + self.font_size / 4 + } else { + 0 + }; + + let mut width_remaining: i32 = (self.length - x_off) as i32; + for (idx, m) in self.matches.iter().enumerate() { + let mut b = match buf.offset((x_off, 0)) { + Ok(b) => b, + Err(_) => break, + }; + let size = if idx == self.offset && self.input.len() > 0 { + let (_, indices) = + fuzzy_indices(&m.name.to_lowercase(), &self.input.to_lowercase()).unwrap(); + let mut colors = Vec::with_capacity(m.name.len()); + for pos in 0..m.name.len() { + if indices.contains(&pos) { + colors.push(Color::new(1.0, 1.0, 1.0, 1.0)); + } else { + colors.push(Color::new(0.75, 0.75, 0.75, 1.0)); + } + } + self.font + .borrow_mut() + .auto_draw_text_individual_colors(&mut b, bg, &colors, &m.name)? + } else { + self.font.borrow_mut().auto_draw_text( + &mut b, + bg, + &Color::new(0.5, 0.5, 0.5, 1.0), + &m.name, + )? + }; + + x_off += size.0 + self.font_size / 4; + width_remaining -= (size.0 + self.font_size / 4) as i32; + + if width_remaining < 0 { + break; + } + } + + Ok(()) + } + + fn draw_calc(&self, buf: &mut Buffer, bg: &Color) -> Result<(), ::std::io::Error> { + let x_off = self + .font + .borrow_mut() + .auto_draw_text(buf, bg, &Color::new(1.0, 1.0, 0.0, 1.0), "=")? + .0 + + self.font_size / 4; + + let x_off = if self.input.len() > 0 { + let dim = self.font.borrow_mut().auto_draw_text( + &mut buf.offset((x_off, 0))?, + bg, + &Color::new(1.0, 1.0, 1.0, 1.0), + &self.input[1..], + )?; + + x_off + dim.0 + self.font_size / 4 + } else { + 0 + }; + + if let Some(result) = &self.result { + self.font.borrow_mut().auto_draw_text( + &mut buf.offset((x_off, 0))?, + bg, + &Color::new(0.75, 0.75, 0.75, 1.0), + &format!(" = {:}", result), + )?; + } + + Ok(()) + } + + fn draw_shell(&self, buf: &mut Buffer, bg: &Color) -> Result<(), ::std::io::Error> { + let x_off = self + .font + .borrow_mut() + .auto_draw_text( + &mut buf.offset((0, 0))?, + bg, + &Color::new(1.0, 1.0, 0.0, 1.0), + "!", + )? + .0 + + self.font_size / 4; + + if self.input.len() > 0 { + self.font.borrow_mut().auto_draw_text( + &mut buf.offset((x_off, 0))?, + bg, + &Color::new(1.0, 1.0, 1.0, 1.0), + &self.input[1..], + )?; + }; + + Ok(()) + } +} + +#[cfg(feature = "ivy")] +fn calc(s: &str) -> Result { + libivy::eval(s) +} + +#[cfg(feature = "rcalc")] +fn calc(s: &str) -> Result { + rcalc_lib::parse::eval(s, &mut rcalc_lib::parse::CalcState::new()) + .map(|x| format!("{}", x).to_string()) + .map_err(|x| format!("{}", x).to_string()) +} + +#[cfg(feature = "bc")] +fn calc(s: &str) -> Result { + use std::io::Write; + let mut child = std::process::Command::new("bc") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .arg("--mathlib") + .spawn() + .map_err(|_| "bc not available".to_string())?; + { + let stdin = child.stdin.as_mut().unwrap(); + stdin + .write_all(s.as_bytes()) + .map_err(|_| "unable to write to stdin".to_string())?; + stdin + .write_all("\n".as_bytes()) + .map_err(|_| "unable to write to stdin".to_string())?; + } + let output = child + .wait_with_output() + .map_err(|_| "unable to run bc".to_string())?; + Ok(std::str::from_utf8(&output.stdout) + .map_err(|_| "unable to read from bc")? + .trim() + .to_string()) +} + +#[cfg(not(any(feature = "ivy", feature = "bc", feature = "rcalc")))] +fn calc(_s: &str) -> Result { + Err("no calculator implementation available".to_string()) +} + +fn wlcopy(s: &str) -> Result<(), String> { + let mut child = std::process::Command::new("wl-copy") + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .arg(s) + .spawn() + .map_err(|_| "wl-copy not available".to_string())?; + child + .wait() + .map_err(|_| "unable to run wl-copy".to_string())?; + Ok(()) +} + +impl Widget for Launcher { + fn size(&self) -> (u32, u32) { + (self.length, self.font_size) + } + + fn draw( + &mut self, + ctx: &mut DrawContext, + pos: (u32, u32), + ) -> Result { + let (width, height) = self.size(); + if !self.dirty && !ctx.force { + return Ok(DrawReport::empty(width, height)); + } + self.dirty = false; + + match self.input.chars().next() { + Some('=') => { + if self.input.len() > 1 { + match calc(&self.input.chars().skip(1).collect::()) { + Ok(v) => self.result = Some(v), + Err(_) => self.result = None, + } + } + } + Some('!') => (), + _ => { + let mut m = self + .options + .iter() + .map(|x| { + ( + fuzzy_match(&x.name.to_lowercase(), &self.input.to_lowercase()), + x, + ) + }) + .filter(|(x, _)| x.is_some()) + .map(|(x, y)| (x.unwrap(), y.clone())) + .collect::>(); + + m.sort_by(|(x1, y1), (x2, y2)| { + if x1 > x2 { + Ordering::Less + } else if x1 < x2 { + Ordering::Greater + } else if y1.name.len() < y2.name.len() { + Ordering::Less + } else if y1.name.len() > y2.name.len() { + Ordering::Greater + } else { + y1.cmp(y2) + } + }); + + self.matches = m.into_iter().map(|(_, x)| x).collect(); + } + }; + + let buf = &mut ctx.buf.subdimensions((pos.0, pos.1, width, height))?; + buf.memset(ctx.bg); + + match self.input.chars().next() { + Some('=') => self.draw_calc(buf, ctx.bg), + Some('!') => self.draw_shell(buf, ctx.bg), + _ => self.draw_launcher(buf, ctx.bg), + }?; + + Ok(DrawReport { + width: width, + height: height, + damage: vec![buf.get_signed_bounds()], + full_damage: false, + }) + } + + fn keyboard_input( + &mut self, + key: u32, + _: ModifiersState, + _: KeyState, + interpreted: Option, + ) { + match key { + keysyms::XKB_KEY_BackSpace => { + if self.input.len() > 0 { + self.input = self.input[..self.input.len() - 1].to_string(); + self.offset = 0; + self.result = None; + self.dirty = true + } + } + keysyms::XKB_KEY_Return => { + match self.input.chars().next() { + Some('=') => match self.result { + Some(ref v) => { + let _ = wlcopy(&v); + } + None => (), + }, + Some('!') => { + let _ = Command::new("sh") + .arg("-c") + .arg(self.input.chars().skip(1).collect::()) + .spawn(); + self.tx.send(Cmd::Exit).unwrap(); + } + _ => { + if self.matches.len() > self.offset { + let d = &self.matches[self.offset]; + if let Some(exec) = &d.exec { + let exec = exec + .replace("%f", "") + .replace("%F", "") + .replace("%u", "") + .replace("%U", ""); + let prefix = if d.term { + &self.term_opener + } else { + &self.app_opener + }; + + let lexed = if prefix.len() > 0 { + let mut prefix = shlex::split(prefix).unwrap(); + prefix.push(exec); + prefix + } else { + shlex::split(&exec).unwrap() + }; + if lexed.len() > 0 { + let _ = + Command::new(lexed[0].clone()).args(&lexed[1..]).spawn(); + self.tx.send(Cmd::Exit).unwrap(); + } + } + if let Some(url) = &d.url { + if self.url_opener.len() > 0 { + let mut lexed = shlex::split(&self.url_opener).unwrap(); + lexed.push(url.to_string()); + if lexed.len() > 0 { + let _ = Command::new(lexed[0].clone()) + .args(&lexed[1..]) + .spawn(); + self.tx.send(Cmd::Exit).unwrap(); + } + } + } + } + } + }; + } + keysyms::XKB_KEY_Right => { + if self.matches.len() > 0 && self.offset < self.matches.len() - 1 { + self.offset += 1; + self.dirty = true; + } + } + keysyms::XKB_KEY_Left => { + if self.matches.len() > 0 && self.offset > 0 { + self.offset -= 1; + self.dirty = true; + } + } + _ => match interpreted { + Some(v) => { + self.input += &v; + self.offset = 0; + self.result = None; + self.dirty = true; + } + None => {} + }, + } + } + fn mouse_click(&mut self, _: u32, _: (u32, u32)) {} + fn mouse_scroll(&mut self, _: (f64, f64), _: (u32, u32)) {} +} diff --git a/src/modules/mod.rs b/src/widgets/mod.rs similarity index 75% rename from src/modules/mod.rs rename to src/widgets/mod.rs index 1213f0a..ea0a17f 100644 --- a/src/modules/mod.rs +++ b/src/widgets/mod.rs @@ -1,7 +1,8 @@ pub mod backlight; +pub mod bar_widget; pub mod battery; pub mod calendar; pub mod clock; +pub mod date; pub mod launcher; -pub mod module; pub mod sound; diff --git a/src/modules/sound.rs b/src/widgets/sound.rs similarity index 85% rename from src/modules/sound.rs rename to src/widgets/sound.rs index 9ddc1e5..a75d895 100644 --- a/src/modules/sound.rs +++ b/src/widgets/sound.rs @@ -1,8 +1,6 @@ -use crate::buffer::Buffer; use crate::cmd::Cmd; use crate::color::Color; -use crate::draw::{draw_bar, draw_box, Font, ROBOTO_REGULAR}; -use crate::modules::module::{Input, ModuleImpl}; +use crate::widgets::bar_widget::{BarWidget, BarWidgetImpl}; use std::cell::RefCell; use std::collections::HashMap; @@ -12,8 +10,6 @@ use std::sync::mpsc::{channel, Sender}; use std::sync::{Arc, Mutex}; use std::thread; -use chrono::{DateTime, Local}; - use libpulse_binding::callbacks::ListResult; use libpulse_binding::context::{ flags, introspect::ServerInfo, introspect::SinkInfo, subscribe::subscription_masks, @@ -385,12 +381,17 @@ impl PulseAudioSoundDevice { 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 set_volume(&mut self, step: f32) -> Result<(), ::std::io::Error> { + 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, @@ -427,6 +428,38 @@ impl PulseAudioSoundDevice { 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_NORM.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_NORM.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; @@ -447,91 +480,46 @@ impl PulseAudioSoundDevice { pub struct PulseAudio { device: PulseAudioSoundDevice, - font: Font, - dirty: Arc>, } impl PulseAudio { - pub fn new(listener: Sender) -> Result { - let mut font = Font::new(&ROBOTO_REGULAR, 24.0); - font.add_str_to_cache("volume"); - let dirty = Arc::new(Mutex::new(true)); - let dev_dirty = dirty.clone(); - let device = PulseAudioSoundDevice::new(move || { - *dev_dirty.lock().unwrap() = true; - listener.send(Cmd::Draw).unwrap(); - })?; - - let pa = PulseAudio { - device: device, - font: font, - dirty: dirty, - }; - Ok(pa) + pub fn new( + font_size: f32, + length: u32, + sender: Sender, + ) -> Result, ::std::io::Error> { + BarWidget::new(font_size, length, move |dirty| { + let device = PulseAudioSoundDevice::new(move || { + *dirty.lock().unwrap() = true; + sender.send(Cmd::Draw).unwrap(); + })?; + Ok(Box::new(PulseAudio { device: device })) + }) } } -impl ModuleImpl for PulseAudio { - fn draw( - &self, - buf: &mut Buffer, - bg: &Color, - _time: &DateTime, - ) -> Result, ::std::io::Error> { - let muted = self.device.inner.lock().unwrap().muted; - let mut vol = self.device.volume(); - buf.memset(bg); - let c = if muted { +impl BarWidgetImpl for PulseAudio { + 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) - }; - self.font.draw_text( - buf, - bg, - &Color::new(1.0, 1.0, 1.0, 1.0), - "volume", - )?; - draw_bar(&mut buf.offset((128, 0))?, &c, 464, 24, vol)?; - let mut iter = 1.0; - while vol > 1.0 { - let c = &Color::new(0.75 / iter, 0.25 / iter, 0.25 / iter, 1.0); - vol -= 1.0; - iter += 1.0; - draw_bar(&mut buf.offset((128, 0))?, &c, 464, 24, vol)?; - } - draw_box(&mut buf.offset((128, 0))?, &c, (464, 24))?; - Ok(vec![buf.get_signed_bounds()]) - } - - fn update(&mut self, _time: &DateTime, force: bool) -> Result { - let mut d = self.dirty.lock().unwrap(); - if *d || force { - *d = false; - Ok(true) - } else { - Ok(false) } } - - fn input(&mut self, input: Input) { - match input { - Input::Scroll { - pos: _pos, - x: _x, - y, - } => { - self.device.set_volume(y as f32 / -800.0).unwrap(); - *self.dirty.lock().unwrap() = true; - } - Input::Click { pos: _pos, button } => match button { - 273 => { - self.device.toggle().unwrap(); - *self.dirty.lock().unwrap() = true; - } - _ => {} - }, - _ => {} - } + 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(); } }