wl-seasonal-hours-clock/src/main.rs

238 lines
7.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extern crate smithay_client_toolkit as sctk;
use calloop::{timer::Timer, EventLoop};
use clap::Parser;
use sctk::reexports::client::protocol::{wl_shm, wl_surface};
use sctk::shm::AutoMemPool;
use sctk::window::{Event as WEvent, FallbackFrame};
use svg::node::element::path::Data as PathData;
mod clock;
mod config;
mod svg_clock;
use clock::get_current_hour_name;
use config::{get_config, Config};
use svg_clock::{cache_hour_name_paths, gen_svg, svg_to_usvg};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// Print the current hours name and exit
#[clap(short, long)]
now: bool,
/// Use FILE as the configuration file
#[clap(short, long, value_name = "FILE")]
config: Option<String>,
}
sctk::default_environment!(SeasonalClock, desktop);
fn main() {
let args = Args::parse();
if args.now {
println!("{}", get_current_hour_name());
std::process::exit(0);
}
let config = get_config(args.config);
run_windowed(&config);
}
fn run_windowed(config: &Option<Config>) {
let (env, display, queue) = sctk::new_default_environment!(SeasonalClock, desktop)
.expect("Unable to connect to a Wayland compositor");
let surface = env
.create_surface_with_scale_callback(|dpi, _surface, _dispatch_data| {
println!("dpi changed to {}", dpi);
})
.detach();
let mut next_action = None::<WEvent>;
let mut window = env
.create_window::<FallbackFrame, _>(
surface,
None,
(100, 100),
move |evt, mut dispatch_data| {
let next_action = dispatch_data.get::<Option<WEvent>>().unwrap();
let replace = matches!(
(&evt, &*next_action),
(_, &None)
| (_, &Some(WEvent::Refresh))
| (&WEvent::Configure { .. }, &Some(WEvent::Configure { .. }))
| (&WEvent::Close, _)
);
if replace {
*next_action = Some(evt);
}
},
)
.expect("Failed to create a window !");
window.set_title("Seasonal Hours Clock".to_string());
let mut pool = env
.create_auto_pool()
.expect("Failed to create the memory pool.");
let mut need_redraw = false;
let mut dimensions = (700, 700);
let hour_name_path_cache = cache_hour_name_paths();
if !env.get_shell().unwrap().needs_configure() {
redraw(
&mut pool,
window.surface(),
dimensions,
config,
&hour_name_path_cache,
)
.expect("Failed to draw");
window.refresh()
}
let mut event_loop = EventLoop::<Option<WEvent>>::try_new().unwrap();
let handle = event_loop.handle();
let source = Timer::new().expect("Failed to create timer event source!");
let timer_handle = source.handle();
timer_handle.add_timeout(std::time::Duration::from_secs(1), "");
handle
.insert_source(source, |_, timer_handle, event| {
timer_handle.add_timeout(std::time::Duration::from_secs(1), "");
if event.is_none() {
*event = Some(sctk::window::Event::Refresh);
}
})
.unwrap();
sctk::WaylandSource::new(queue)
.quick_insert(handle)
.unwrap();
loop {
// Update every second
match next_action.take() {
Some(WEvent::Close) => break,
Some(WEvent::Refresh) => {
redraw(
&mut pool,
window.surface(),
dimensions,
config,
&hour_name_path_cache,
)
.expect("Failed to draw");
window.refresh();
window.surface().commit();
}
Some(WEvent::Configure {
new_size,
states: _,
}) => {
if let Some((w, h)) = new_size {
if dimensions != (w, h) {
dimensions = (w, h);
}
}
window.resize(dimensions.0, dimensions.1);
window.refresh();
need_redraw = true;
}
None => {}
}
if need_redraw {
need_redraw = false;
redraw(
&mut pool,
window.surface(),
dimensions,
config,
&hour_name_path_cache,
)
.expect("Failed to draw")
}
if let Err(e) = display.flush() {
if e.kind() != ::std::io::ErrorKind::WouldBlock {
eprintln!("Error while trying to flush the wayland socket: {:?}", e);
}
}
event_loop.dispatch(None, &mut next_action).unwrap();
}
}
fn redraw(
pool: &mut AutoMemPool,
surface: &wl_surface::WlSurface,
(buf_x, buf_y): (u32, u32),
config: &Option<Config>,
hour_name_path_cache: &[(PathData, PathData); 24],
) -> Result<(), ::std::io::Error> {
let document = gen_svg(config, hour_name_path_cache);
let svg_tree = svg_to_usvg(document);
let (canvas, new_buffer) = pool.buffer(
buf_x as i32,
buf_y as i32,
4 * buf_x as i32,
wl_shm::Format::Argb8888,
)?;
let image_size = ::std::cmp::min(buf_x, buf_y);
let move_x = (buf_x - image_size) as f32 / 2.0;
let move_y = (buf_y - image_size) as f32 / 2.0;
let mut pixmap = tiny_skia::Pixmap::new(buf_x, buf_y).unwrap();
// pre-fill the pixmap with our background color so we dont have a white strip at the edges
pixmap.fill(
tiny_skia::Color::from_rgba(19f32 / 255f32, 17f32 / 255f32, 30f32 / 255f32, 1f32).unwrap(),
);
resvg::render(
&svg_tree,
usvg::FitTo::Size(image_size, image_size),
tiny_skia::Transform::from_translate(move_x, move_y),
pixmap.as_mut(),
)
.unwrap();
// We do not have anything to draw yet, so draw an empty surface
for (dst_pixel, src_pixel) in canvas.chunks_exact_mut(4).zip(pixmap.pixels()) {
let r = src_pixel.red() as u32;
let g = src_pixel.green() as u32;
let b = src_pixel.blue() as u32;
let a = src_pixel.alpha() as u32;
let r = ::std::cmp::min(0xFF, (0xFF * (0xFF - a) + a * r) / 0xFF);
let g = ::std::cmp::min(0xFF, (0xFF * (0xFF - a) + a * g) / 0xFF);
let b = ::std::cmp::min(0xFF, (0xFF * (0xFF - a) + a * b) / 0xFF);
let pixel: [u8; 4] = ((0xFF << 24) + (r << 16) + (g << 8) + b).to_ne_bytes();
dst_pixel[0] = pixel[0];
dst_pixel[1] = pixel[1];
dst_pixel[2] = pixel[2];
dst_pixel[3] = pixel[3];
}
surface.attach(Some(&new_buffer), 0, 0);
if surface.as_ref().version() >= 4 {
surface.damage_buffer(0, 0, buf_x as i32, buf_y as i32);
} else {
surface.damage(0, 0, buf_x as i32, buf_y as i32);
}
surface.commit();
Ok(())
}