Compare commits

..

6 commits

Author SHA1 Message Date
d576a0ff8c Version 0.4.4, update README.md 2023-03-01 22:00:24 +09:00
6bf05623d8 Backport: Fix timer bg not updating
This commit fixes the bg mesh (slightly-transparent-black-bg for text)
not updating when the timer-text changes size.
2023-03-01 21:54:13 +09:00
675df211cd Update README.md 2023-02-04 19:44:59 +09:00
c782a3048d Update version, README.md 2023-02-04 19:42:02 +09:00
27c3d8c5cd Backport Fix: display not working when no password
Previous implementation only worked if MPD was accessed with a password.
This commit fixes no-password access of MPD.
2023-02-04 19:40:27 +09:00
cd4d4f60e4 workaround version 0.4.2 2023-02-04 19:33:57 +09:00
10 changed files with 1769 additions and 2651 deletions

3757
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,21 @@
[package] [package]
name = "mpd_info_screen" name = "mpd_info_screen"
version = "0.4.17" version = "0.4.4"
edition = "2021" edition = "2018"
description = "Displays info on currently playing music from an MPD daemon" description = "Displays info on currently playing music from an MPD daemon"
license = "MIT" license = "MIT"
repository = "https://github.com/Stephen-Seo/mpd_info_screen" repository = "https://github.com/Stephen-Seo/mpd_info_screen"
resolver = "2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = { version = "4.5", features = ["derive"] } structopt = "0.3"
image = "0.25" image = "0.24"
ggez = "0.9.3" ggez = "0.7"
freetype = { version = "0.7", optional = true } freetype = { version = "0.7", optional = true }
wgpu-types = "0.16"
[build-dependencies] [build-dependencies]
bindgen = { version = "0.69", optional = true } bindgen = { version = "0.53", optional = true }
[features] [features]
unicode_support = ["dep:freetype", "dep:bindgen"] unicode_support = ["dep:freetype", "dep:bindgen"]

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021-2024 Stephen Seo Copyright (c) 2021-2022 Stephen Seo
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -14,8 +14,20 @@ counter, and the filename currently being played
## Known Bugs ❗❗ ## Known Bugs ❗❗
Currently there are no known bugs. Please report any bugs you find to the Version `0.4.4` is a "workaround" release that is branched off of version
[issue tracker](https://github.com/Stephen-Seo/mpd_info_screen/issues). `0.3.7`. Once a new release of `ggez` is released that fixes the known bugs,
a new version will be released with the fixes. Because this is based on
`0.3.7` of `mpd_info_screen`, Wayland support may not work. Try using `xwayland`
with the environment variable `WINIT_UNIX_BACKEND=x11` set. A future release
using the latest version of `ggez` should work with Wayland.
Currently, the dependency "ggez 0.8.1"
[fails to render album art](https://github.com/Stephen-Seo/mpd_info_screen/issues/1)
on my machines using the latest version of this program (`main` branch). A
version with this fix cannot be published to https://crates.io due to this
version referring to a git commit as a dependency. Once ggez has released a new
version with the commit that fixes this bug, this repository will be updated to
use that version.
## Unicode Support ## Unicode Support
@ -35,43 +47,29 @@ or through crates.io:
# Usage # Usage
Displays info on currently playing music from an MPD daemon mpd_info_screen 0.4.4
Usage: mpd_info_screen [OPTIONS] <HOST> [PORT] USAGE:
mpd_info_screen [FLAGS] [OPTIONS] <host> [port]
Arguments: FLAGS:
<HOST> --disable-show-album disable album display
[PORT] [default: 6600] --disable-show-artist disable artist display
--disable-show-filename disable filename display
--disable-show-title disable title display
--no-scale-fill don't scale-fill the album art to the window
--pprompt input password via prompt
-h, --help Prints help information
-V, --version Prints version information
Options: OPTIONS:
-p <PASSWORD> -l, --log-level <log-level> [default: Error] [possible values: Error, Warning, Debug, Verbose]
-p <password>
-t, --text-bg-opacity <text-bg-opacity> sets the opacity of the text background (0-255) [default: 190]
--disable-show-title ARGS:
disable title display <host>
--disable-show-artist <port> [default: 6600]
disable artist display
--disable-show-album
disable album display
--disable-show-filename
disable filename display
--disable-show-percentage
disable percentage display
--force-text-height-scale <FORCE_TEXT_HEIGHT_SCALE>
force-set text height relative to window height as a ratio (default 0.12)
--pprompt
input password via prompt
--pfile <PASSWORD_FILE>
read password from file
--no-scale-fill
don't scale-fill the album art to the window
-l, --log-level <LOG_LEVEL>
[default: error] [possible values: error, warning, debug, verbose]
-t, --text-bg-opacity <TEXT_BG_OPACITY>
sets the opacity of the text background (0-255) [default: 190]
-h, --help
Print help
-V, --version
Print version
Note that presing the Escape key when the window is focused closes the program. Note that presing the Escape key when the window is focused closes the program.
@ -100,8 +98,8 @@ MIT license.
Uses dependency [image](https://crates.io/crates/image) which is licensed under Uses dependency [image](https://crates.io/crates/image) which is licensed under
MIT license. MIT license.
Uses dependency [clap](https://crates.io/crates/clap) which is licensed under Uses dependency [structopt](https://crates.io/crates/structopt) which is
Apache-2.0 or MIT licenses. licensed under Apache-2.0 or MIT licenses.
## Unicode Support Dependencies ## Unicode Support Dependencies

View file

@ -14,7 +14,7 @@ fn main() {
let bindings = bindgen::Builder::default() let bindings = bindgen::Builder::default()
.header("src/bindgen_wrapper.h") .header("src/bindgen_wrapper.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate() .generate()
.expect("Unable to generate bindings"); .expect("Unable to generate bindings");

View file

@ -1,7 +1,7 @@
use clap::ValueEnum;
use std::fmt::Display; use std::fmt::Display;
use structopt::clap::arg_enum;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub enum LogState { pub enum LogState {
Error, Error,
Warning, Warning,
@ -9,13 +9,15 @@ pub enum LogState {
Verbose, Verbose,
} }
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)] arg_enum! {
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum LogLevel { pub enum LogLevel {
Error, Error,
Warning, Warning,
Debug, Debug,
Verbose, Verbose,
} }
}
pub fn log<T>(msg: T, state: LogState, level: LogLevel) pub fn log<T>(msg: T, state: LogState, level: LogLevel)
where where
@ -44,26 +46,26 @@ pub fn log_error<T>(msg: T)
where where
T: Display, T: Display,
{ {
println!("Error: {msg}"); println!("Error: {}", msg);
} }
pub fn log_warning<T>(msg: T) pub fn log_warning<T>(msg: T)
where where
T: Display, T: Display,
{ {
println!("Warning: {msg}"); println!("Warning: {}", msg);
} }
pub fn log_debug<T>(msg: T) pub fn log_debug<T>(msg: T)
where where
T: Display, T: Display,
{ {
println!("Debug: {msg}"); println!("Debug: {}", msg);
} }
pub fn log_verbose<T>(msg: T) pub fn log_verbose<T>(msg: T)
where where
T: Display, T: Display,
{ {
println!("Verbose: {msg}"); println!("Verbose: {}", msg);
} }

View file

@ -1,16 +1,13 @@
use crate::debug_log::{self, log}; use crate::debug_log::{self, log};
use crate::mpd_handler::{InfoFromShared, MPDHandler, MPDHandlerState, MPDPlayState}; use crate::mpd_handler::{InfoFromShared, MPDHandler, MPDHandlerState, MPDPlayState};
use crate::Opt; use crate::Opt;
use ggez::event::EventHandler; use ggez::event::{self, EventHandler};
use ggez::graphics::{ use ggez::graphics::{
self, Color, DrawMode, DrawParam, Drawable, Image, Mesh, MeshBuilder, PxScale, Rect, Text, self, Color, DrawMode, DrawParam, Drawable, Font, Image, Mesh, MeshBuilder, PxScale, Rect,
TextFragment, Transform, Text, TextFragment, Transform,
}; };
use ggez::input::keyboard::{self, KeyInput}; use ggez::{timer, Context, GameError, GameResult};
use ggez::mint::Vector2; use image::io::Reader as ImageReader;
use ggez::{Context, GameError, GameResult};
use image::DynamicImage;
use image::ImageReader;
use std::io::Cursor; use std::io::Cursor;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@ -19,16 +16,14 @@ use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
const POLL_TIME: Duration = Duration::from_millis(333); const POLL_TIME: Duration = Duration::from_millis(333);
const INIT_FONT_SIZE_RATIO: f32 = 1.4167; const INIT_FONT_SIZE_X: f32 = 24.0;
const INIT_FONT_SIZE_X: f32 = 36.0; const INIT_FONT_SIZE_Y: f32 = 34.0;
const INIT_FONT_SIZE_Y: f32 = INIT_FONT_SIZE_X * INIT_FONT_SIZE_RATIO;
const TEXT_X_OFFSET: f32 = 0.3; const TEXT_X_OFFSET: f32 = 0.3;
const TEXT_OFFSET_Y_SPACING: f32 = 0.4; const TEXT_OFFSET_Y_SPACING: f32 = 0.4;
const TEXT_HEIGHT_SCALE: f32 = 0.12; const TEXT_HEIGHT_SCALE: f32 = 0.1;
const ARTIST_HEIGHT_SCALE: f32 = 0.12; const ARTIST_HEIGHT_SCALE: f32 = 0.08;
const ALBUM_HEIGHT_SCALE: f32 = 0.12; const ALBUM_HEIGHT_SCALE: f32 = 0.08;
const TIMER_HEIGHT_SCALE_RATIO: f32 = 0.875; const TIMER_HEIGHT_SCALE: f32 = 0.07;
const TIMER_HEIGHT_SCALE: f32 = TEXT_HEIGHT_SCALE * TIMER_HEIGHT_SCALE_RATIO;
const MIN_WIDTH_RATIO: f32 = 4.0 / 5.0; const MIN_WIDTH_RATIO: f32 = 4.0 / 5.0;
const INCREASE_AMT: f32 = 6.0 / 5.0; const INCREASE_AMT: f32 = 6.0 / 5.0;
const DECREASE_AMT: f32 = 5.0 / 6.0; const DECREASE_AMT: f32 = 5.0 / 6.0;
@ -56,15 +51,11 @@ fn seconds_to_time(seconds: f64) -> String {
result result
} }
fn time_to_percentage(total: f64, current: f64) -> String {
((100.0f64 * current / total).round() as i32).to_string() + "%"
}
#[cfg(not(feature = "unicode_support"))] #[cfg(not(feature = "unicode_support"))]
#[allow(clippy::ptr_arg)] #[allow(clippy::ptr_arg)]
fn string_to_text( fn string_to_text(
string: String, string: String,
_loaded_fonts: &mut Vec<(PathBuf, String)>, _loaded_fonts: &mut Vec<(PathBuf, Font)>,
_ctx: &mut Context, _ctx: &mut Context,
) -> Text { ) -> Text {
Text::new(TextFragment::from(string)) Text::new(TextFragment::from(string))
@ -73,7 +64,7 @@ fn string_to_text(
#[cfg(feature = "unicode_support")] #[cfg(feature = "unicode_support")]
fn string_to_text( fn string_to_text(
string: String, string: String,
loaded_fonts: &mut Vec<(PathBuf, String)>, loaded_fonts: &mut Vec<(PathBuf, Font)>,
ctx: &mut Context, ctx: &mut Context,
) -> Text { ) -> Text {
use super::unicode_support; use super::unicode_support;
@ -88,7 +79,7 @@ fn string_to_text(
} }
let find_font = let find_font =
|c: char, loaded_fonts: &mut Vec<(PathBuf, String)>, ctx: &mut Context| -> Option<usize> { |c: char, loaded_fonts: &mut Vec<(PathBuf, Font)>, ctx: &mut Context| -> Option<usize> {
for (idx, (path, _)) in loaded_fonts.iter().enumerate() { for (idx, (path, _)) in loaded_fonts.iter().enumerate() {
let result = unicode_support::font_has_char(c, path); let result = unicode_support::font_has_char(c, path);
if result.is_ok() && result.unwrap() { if result.is_ok() && result.unwrap() {
@ -98,16 +89,9 @@ fn string_to_text(
let find_result = unicode_support::get_matching_font_from_char(c); let find_result = unicode_support::get_matching_font_from_char(c);
if let Ok(path) = find_result { if let Ok(path) = find_result {
let new_font = ggez::graphics::FontData::from_path(ctx, &path); let new_font = Font::new(ctx, &path);
if let Ok(font) = new_font { if let Ok(font) = new_font {
let font_name: String = path loaded_fonts.push((path, font));
.file_name()
.expect("Should be valid filename at end of Font path.")
.to_str()
.expect("Font filename should be valid unicode.")
.to_owned();
ctx.gfx.add_font(&font_name, font);
loaded_fonts.push((path, font_name));
return Some(loaded_fonts.len() - 1); return Some(loaded_fonts.len() - 1);
} else { } else {
log( log(
@ -118,7 +102,7 @@ fn string_to_text(
} }
} else { } else {
log( log(
format!("Failed to find font for {c}"), format!("Failed to find font for {}", c),
debug_log::LogState::Error, debug_log::LogState::Error,
debug_log::LogLevel::Error, debug_log::LogLevel::Error,
); );
@ -148,13 +132,13 @@ fn string_to_text(
text.add(current_fragment); text.add(current_fragment);
current_fragment = Default::default(); current_fragment = Default::default();
} }
let (_, font) = &loaded_fonts[idx]; let (_, font) = loaded_fonts[idx];
current_fragment.font = Some(font.clone()); current_fragment.font = Some(font);
} }
current_fragment.text.push(c); current_fragment.text.push(c);
} else if let Some(idx) = idx_opt { } else if let Some(idx) = idx_opt {
let font = &loaded_fonts[idx].1; let font = loaded_fonts[idx].1;
if let Some(current_font) = current_fragment.font.as_ref() { if let Some(current_font) = current_fragment.font {
if current_font == font { if current_font == font {
current_fragment.text.push(c); current_fragment.text.push(c);
} else { } else {
@ -163,17 +147,17 @@ fn string_to_text(
current_fragment = Default::default(); current_fragment = Default::default();
} }
current_fragment.text.push(c); current_fragment.text.push(c);
current_fragment.font = Some(font.clone()); current_fragment.font = Some(font);
} }
} else if current_fragment.text.is_empty() { } else if current_fragment.text.is_empty() {
current_fragment.text.push(c); current_fragment.text.push(c);
current_fragment.font = Some(font.clone()); current_fragment.font = Some(font);
} else { } else {
text.add(current_fragment); text.add(current_fragment);
current_fragment = Default::default(); current_fragment = Default::default();
current_fragment.text.push(c); current_fragment.text.push(c);
current_fragment.font = Some(font.clone()); current_fragment.font = Some(font);
} }
} else { } else {
if !current_fragment.text.is_empty() && current_fragment.font.is_some() { if !current_fragment.text.is_empty() && current_fragment.font.is_some() {
@ -233,9 +217,8 @@ pub struct MPDDisplay {
text_bg_mesh: Option<Mesh>, text_bg_mesh: Option<Mesh>,
hide_text: bool, hide_text: bool,
tried_album_art_in_dir: bool, tried_album_art_in_dir: bool,
prev_mpd_play_state: MPDPlayState,
mpd_play_state: MPDPlayState, mpd_play_state: MPDPlayState,
loaded_fonts: Vec<(PathBuf, String)>, loaded_fonts: Vec<(PathBuf, Font)>,
} }
impl MPDDisplay { impl MPDDisplay {
@ -247,7 +230,7 @@ impl MPDDisplay {
is_initialized: false, is_initialized: false,
is_authenticated: false, is_authenticated: false,
notice_text: Text::default(), notice_text: Text::default(),
poll_instant: Instant::now().checked_sub(POLL_TIME).unwrap(), poll_instant: Instant::now() - POLL_TIME,
shared: None, shared: None,
password_entered: false, password_entered: false,
dirty_flag: None, dirty_flag: None,
@ -274,7 +257,6 @@ impl MPDDisplay {
text_bg_mesh: None, text_bg_mesh: None,
hide_text: false, hide_text: false,
tried_album_art_in_dir: false, tried_album_art_in_dir: false,
prev_mpd_play_state: MPDPlayState::Playing,
mpd_play_state: MPDPlayState::Playing, mpd_play_state: MPDPlayState::Playing,
loaded_fonts: Vec::new(), loaded_fonts: Vec::new(),
filename_string_cache: String::new(), filename_string_cache: String::new(),
@ -321,24 +303,24 @@ impl MPDDisplay {
fn get_album_art_transform(&mut self, ctx: &mut Context, fill_scaled: bool) { fn get_album_art_transform(&mut self, ctx: &mut Context, fill_scaled: bool) {
if fill_scaled { if fill_scaled {
if let Some(image) = &self.album_art { if let Some(image) = &self.album_art {
let drawable_size = ctx.gfx.drawable_size(); let screen_coords: Rect = graphics::screen_coordinates(ctx);
let art_rect: Rect = image.dimensions(ctx).expect("Image should have dimensions"); let art_rect: Rect = image.dimensions();
// try to fit to width first // try to fit to width first
let mut x_scale = drawable_size.0 / art_rect.w; let mut x_scale = screen_coords.w / art_rect.w;
let mut y_scale = x_scale; let mut y_scale = x_scale;
let mut new_width = art_rect.w * x_scale; let mut new_width = art_rect.w * x_scale;
let mut new_height = art_rect.h * y_scale; let mut new_height = art_rect.h * y_scale;
if new_height > drawable_size.1.abs() { if new_height > screen_coords.h.abs() {
// fit to height instead // fit to height instead
y_scale = drawable_size.1.abs() / art_rect.h; y_scale = screen_coords.h.abs() / art_rect.h;
x_scale = y_scale; x_scale = y_scale;
new_width = art_rect.w * x_scale; new_width = art_rect.w * x_scale;
new_height = art_rect.h * y_scale; new_height = art_rect.h * y_scale;
} }
let offset_x: f32 = (drawable_size.0.abs() - new_width) / 2.0f32; let offset_x: f32 = (screen_coords.w.abs() - new_width) / 2.0f32;
let offset_y: f32 = (drawable_size.1.abs() - new_height) / 2.0f32; let offset_y: f32 = (screen_coords.h.abs() - new_height) / 2.0f32;
self.album_art_draw_transform = Some(Transform::Values { self.album_art_draw_transform = Some(Transform::Values {
dest: [offset_x, offset_y].into(), dest: [offset_x, offset_y].into(),
@ -350,10 +332,10 @@ impl MPDDisplay {
self.album_art_draw_transform = None; self.album_art_draw_transform = None;
} }
} else if let Some(image) = &self.album_art { } else if let Some(image) = &self.album_art {
let drawable_size = ctx.gfx.drawable_size(); let screen_coords: Rect = graphics::screen_coordinates(ctx);
let art_rect: Rect = image.dimensions(ctx).expect("Image should have dimensions"); let art_rect: Rect = image.dimensions();
let offset_x: f32 = (drawable_size.0.abs() - art_rect.w.abs()) / 2.0f32; let offset_x: f32 = (screen_coords.w.abs() - art_rect.w.abs()) / 2.0f32;
let offset_y: f32 = (drawable_size.1.abs() - art_rect.h.abs()) / 2.0f32; let offset_y: f32 = (screen_coords.h.abs() - art_rect.h.abs()) / 2.0f32;
self.album_art_draw_transform = Some(Transform::Values { self.album_art_draw_transform = Some(Transform::Values {
dest: [offset_x, offset_y].into(), dest: [offset_x, offset_y].into(),
rotation: 0.0f32, rotation: 0.0f32,
@ -428,23 +410,20 @@ impl MPDDisplay {
} }
let img_result = if is_unknown_format { let img_result = if is_unknown_format {
let reader = ImageReader::new(Cursor::new(image_ref)); let mut reader = ImageReader::new(Cursor::new(&image_ref));
let guessed_reader = reader reader = reader
.with_guessed_format() .with_guessed_format()
.map_err(|e| format!("Error: Failed to guess format of album art image: {e}")); .map_err(|e| format!("Error: Failed to guess format of album art image: {}", e))?;
if let Ok(reader) = guessed_reader {
reader.decode().map_err(|e| { reader.decode().map_err(|e| {
format!("Error: Failed to decode album art image (guessed format): {e}") format!(
"Error: Failed to decode album art image (guessed format): {}",
e
)
}) })
} else { } else {
// Convert Ok(_) to Ok(DynamicImage) which will never be used ImageReader::with_format(Cursor::new(&image_ref), image_format)
// since the if statement covers it.
guessed_reader.map(|_| -> DynamicImage { unreachable!() })
}
} else {
ImageReader::with_format(Cursor::new(image_ref), image_format)
.decode() .decode()
.map_err(|e| format!("Error: Failed to decode album art image: {e}")) .map_err(|e| format!("Error: Failed to decode album art image: {}", e))
}; };
if img_result.is_err() && !self.tried_album_art_in_dir { if img_result.is_err() && !self.tried_album_art_in_dir {
return try_second_art_fetch_method( return try_second_art_fetch_method(
@ -456,13 +435,13 @@ impl MPDDisplay {
} }
let img = img_result?; let img = img_result?;
let rgba8 = img.to_rgba8(); let rgba8 = img.to_rgba8();
let ggez_img = Image::from_pixels( let ggez_img = Image::from_rgba8(
ctx, ctx,
rgba8.width() as u16,
rgba8.height() as u16,
rgba8.as_raw(), rgba8.as_raw(),
wgpu_types::TextureFormat::Rgba8UnormSrgb, )
rgba8.width(), .map_err(|e| format!("Error: Failed to load album art image in ggez Image: {}", e))?;
rgba8.height(),
);
self.album_art = Some(ggez_img); self.album_art = Some(ggez_img);
@ -470,31 +449,14 @@ impl MPDDisplay {
} }
fn refresh_text_transforms(&mut self, ctx: &mut Context) -> GameResult<()> { fn refresh_text_transforms(&mut self, ctx: &mut Context) -> GameResult<()> {
let drawable_size = ctx.gfx.drawable_size(); let screen_coords: Rect = graphics::screen_coordinates(ctx);
let text_height_scale: f32; let text_height_limit = TEXT_HEIGHT_SCALE * screen_coords.h.abs();
let album_height_scale: f32; let album_height_limit = ALBUM_HEIGHT_SCALE * screen_coords.h.abs();
let artist_height_scale: f32; let artist_height_limit = ARTIST_HEIGHT_SCALE * screen_coords.h.abs();
let timer_height_scale: f32; let timer_height = TIMER_HEIGHT_SCALE * screen_coords.h.abs();
if let Some(forced_scale) = &self.opts.force_text_height_scale { let mut offset_y: f32 = screen_coords.h;
text_height_scale = *forced_scale;
album_height_scale = *forced_scale;
artist_height_scale = *forced_scale;
timer_height_scale = *forced_scale * TIMER_HEIGHT_SCALE_RATIO;
} else {
text_height_scale = TEXT_HEIGHT_SCALE;
album_height_scale = ALBUM_HEIGHT_SCALE;
artist_height_scale = ARTIST_HEIGHT_SCALE;
timer_height_scale = TIMER_HEIGHT_SCALE;
}
let text_height_limit = text_height_scale * drawable_size.1.abs();
let album_height_limit = album_height_scale * drawable_size.1.abs();
let artist_height_limit = artist_height_scale * drawable_size.1.abs();
let timer_height = timer_height_scale * drawable_size.1.abs();
let mut offset_y: f32 = drawable_size.1;
let set_transform = |text: &mut Text, let set_transform = |text: &mut Text,
transform: &mut Transform, transform: &mut Transform,
@ -507,7 +469,8 @@ impl MPDDisplay {
timer_y: &mut f32| { timer_y: &mut f32| {
let mut current_x = INIT_FONT_SIZE_X; let mut current_x = INIT_FONT_SIZE_X;
let mut current_y = INIT_FONT_SIZE_Y; let mut current_y = INIT_FONT_SIZE_Y;
let mut width_height: Vector2<f32> = Vector2 { x: 0.0, y: 0.0 }; let mut width: f32;
let mut height: f32 = 0.0;
let mut iteration_count: u8 = 0; let mut iteration_count: u8 = 0;
loop { loop {
iteration_count += 1; iteration_count += 1;
@ -521,13 +484,12 @@ impl MPDDisplay {
y: current_y, y: current_y,
}); });
} }
width_height = text width = text.width(ctx);
.measure(ctx) height = text.height(ctx);
.expect("Should be able to get width/height of text.");
if is_string { if is_string {
if drawable_size.0 < width_height.x if screen_coords.w < width
|| width_height.y || height
>= (if is_artist { >= (if is_artist {
artist_height_limit artist_height_limit
} else if is_album { } else if is_album {
@ -539,7 +501,7 @@ impl MPDDisplay {
current_x *= DECREASE_AMT; current_x *= DECREASE_AMT;
current_y *= DECREASE_AMT; current_y *= DECREASE_AMT;
continue; continue;
} else if drawable_size.0 * MIN_WIDTH_RATIO > width_height.x { } else if screen_coords.w * MIN_WIDTH_RATIO > width {
current_x *= INCREASE_AMT; current_x *= INCREASE_AMT;
current_y *= INCREASE_AMT; current_y *= INCREASE_AMT;
continue; continue;
@ -547,7 +509,7 @@ impl MPDDisplay {
break; break;
} }
} else { } else {
let diff_scale_y = current_y / width_height.y * timer_height; let diff_scale_y = current_y / height * timer_height;
let current_x = current_x * diff_scale_y / current_y; let current_x = current_x * diff_scale_y / current_y;
for fragment in text.fragments_mut() { for fragment in text.fragments_mut() {
fragment.scale = Some(PxScale { fragment.scale = Some(PxScale {
@ -558,23 +520,20 @@ impl MPDDisplay {
*timer_x = current_x; *timer_x = current_x;
*timer_y = diff_scale_y; *timer_y = diff_scale_y;
// width = text.width(ctx); // not really used after this // width = text.width(ctx); // not really used after this
width_height.y = text height = text.height(ctx);
.measure(ctx)
.expect("Should be able to get width/height of text.")
.y;
break; break;
} }
} }
*y = *offset_y - width_height.y; *y = *offset_y - height;
*transform = Transform::Values { *transform = Transform::Values {
dest: [TEXT_X_OFFSET, *offset_y - width_height.y].into(), dest: [TEXT_X_OFFSET, *offset_y - height].into(),
rotation: 0.0, rotation: 0.0,
scale: [1.0, 1.0].into(), scale: [1.0, 1.0].into(),
offset: [0.0, 0.0].into(), offset: [0.0, 0.0].into(),
}; };
*offset_y -= width_height.y + TEXT_OFFSET_Y_SPACING; *offset_y -= height + TEXT_OFFSET_Y_SPACING;
}; };
if !self.filename_text.contents().is_empty() && !self.opts.disable_show_filename { if !self.filename_text.contents().is_empty() && !self.opts.disable_show_filename {
@ -669,26 +628,11 @@ impl MPDDisplay {
} }
fn update_bg_mesh(&mut self, ctx: &mut Context) -> GameResult<()> { fn update_bg_mesh(&mut self, ctx: &mut Context) -> GameResult<()> {
let filename_dimensions = self let filename_dimensions = self.filename_text.dimensions(ctx);
.filename_text let album_dimensions = self.album_text.dimensions(ctx);
.dimensions(ctx) let artist_dimensions = self.artist_text.dimensions(ctx);
.expect("Should be able to get dimensions of Text."); let title_dimensions = self.title_text.dimensions(ctx);
let album_dimensions = self let timer_dimensions = self.timer_text.dimensions(ctx);
.album_text
.dimensions(ctx)
.expect("Should be able to get dimensions of Text.");
let artist_dimensions = self
.artist_text
.dimensions(ctx)
.expect("Should be able to get dimensions of Text.");
let title_dimensions = self
.title_text
.dimensions(ctx)
.expect("Should be able to get dimensions of Text.");
let timer_dimensions = self
.timer_text
.dimensions(ctx)
.expect("Should be able to get dimensions of Text.");
let mut mesh_builder: MeshBuilder = MeshBuilder::new(); let mut mesh_builder: MeshBuilder = MeshBuilder::new();
if !self.opts.disable_show_filename { if !self.opts.disable_show_filename {
@ -739,8 +683,8 @@ impl MPDDisplay {
Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity), Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity),
)?; )?;
} }
if self.mpd_play_state == MPDPlayState::Playing { let mesh: Mesh = mesh_builder
mesh_builder.rectangle( .rectangle(
DrawMode::fill(), DrawMode::fill(),
Rect { Rect {
x: TEXT_X_OFFSET, x: TEXT_X_OFFSET,
@ -749,9 +693,8 @@ impl MPDDisplay {
h: timer_dimensions.h, h: timer_dimensions.h,
}, },
Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity), Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity),
)?; )?
} .build(ctx)?;
let mesh: Mesh = Mesh::from_data(ctx, mesh_builder.build());
self.text_bg_mesh = Some(mesh); self.text_bg_mesh = Some(mesh);
@ -764,7 +707,8 @@ impl EventHandler for MPDDisplay {
if !self.is_valid { if !self.is_valid {
if let Err(mpd_handler_error) = &self.mpd_handler { if let Err(mpd_handler_error) = &self.mpd_handler {
return Err(GameError::EventLoopError(format!( return Err(GameError::EventLoopError(format!(
"Failed to initialize MPDHandler: {mpd_handler_error}" "Failed to initialize MPDHandler: {}",
mpd_handler_error
))); )));
} else { } else {
return Err(GameError::EventLoopError( return Err(GameError::EventLoopError(
@ -820,8 +764,6 @@ impl EventHandler for MPDDisplay {
} }
} }
self.prev_mpd_play_state = self.mpd_play_state;
if self.is_valid && self.is_initialized && self.poll_instant.elapsed() > POLL_TIME { if self.is_valid && self.is_initialized && self.poll_instant.elapsed() > POLL_TIME {
self.poll_instant = Instant::now(); self.poll_instant = Instant::now();
if self.dirty_flag.is_some() if self.dirty_flag.is_some()
@ -829,7 +771,7 @@ impl EventHandler for MPDDisplay {
.dirty_flag .dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.swap(false, Ordering::AcqRel) .swap(false, Ordering::Relaxed)
{ {
log( log(
"dirty_flag cleared, acquiring shared data...", "dirty_flag cleared, acquiring shared data...",
@ -877,7 +819,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Release); .store(true, Ordering::Relaxed);
} }
if !shared.artist.is_empty() { if !shared.artist.is_empty() {
if shared.artist != self.artist_string_cache { if shared.artist != self.artist_string_cache {
@ -892,7 +834,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Release); .store(true, Ordering::Relaxed);
} }
if !shared.album.is_empty() { if !shared.album.is_empty() {
if shared.album != self.album_string_cache { if shared.album != self.album_string_cache {
@ -907,7 +849,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Release); .store(true, Ordering::Relaxed);
} }
if !shared.filename.is_empty() { if !shared.filename.is_empty() {
if shared.filename != self.filename_string_cache { if shared.filename != self.filename_string_cache {
@ -926,7 +868,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Release); .store(true, Ordering::Relaxed);
} }
self.timer = shared.pos; self.timer = shared.pos;
self.length = shared.length; self.length = shared.length;
@ -952,111 +894,107 @@ impl EventHandler for MPDDisplay {
} }
} }
let delta = ctx.time.delta(); let delta = timer::delta(ctx);
self.timer += delta.as_secs_f64(); self.timer += delta.as_secs_f64();
let mut timer_diff = seconds_to_time(self.length - self.timer); let timer_diff = seconds_to_time(self.length - self.timer);
if !self.opts.disable_show_percentage {
timer_diff = timer_diff + " " + &time_to_percentage(self.length, self.timer);
}
let timer_diff_len = timer_diff.len(); let timer_diff_len = timer_diff.len();
self.timer_text = Text::new(timer_diff); self.timer_text = Text::new(timer_diff);
self.timer_text.set_scale(PxScale { self.timer_text.set_font(
Font::default(),
PxScale {
x: self.timer_x, x: self.timer_x,
y: self.timer_y, y: self.timer_y,
}); },
);
if timer_diff_len != self.timer_text_len { if timer_diff_len != self.timer_text_len {
self.timer_text_len = timer_diff_len; self.timer_text_len = timer_diff_len;
self.update_bg_mesh(ctx)?; self.update_bg_mesh(ctx)?;
} else if self.mpd_play_state != MPDPlayState::Playing
&& self.prev_mpd_play_state == MPDPlayState::Playing
{
self.update_bg_mesh(ctx)?;
} }
Ok(()) Ok(())
} }
fn draw(&mut self, ctx: &mut ggez::Context) -> Result<(), GameError> { fn draw(&mut self, ctx: &mut ggez::Context) -> Result<(), GameError> {
let mut canvas = graphics::Canvas::from_frame(ctx, Color::BLACK); graphics::clear(ctx, Color::BLACK);
if self.mpd_play_state != MPDPlayState::Stopped if self.mpd_play_state != MPDPlayState::Stopped
&& self.album_art.is_some() && self.album_art.is_some()
&& self.album_art_draw_transform.is_some() && self.album_art_draw_transform.is_some()
{ {
canvas.draw( self.album_art.as_ref().unwrap().draw(
self.album_art.as_ref().unwrap(), ctx,
DrawParam { DrawParam {
transform: self.album_art_draw_transform.unwrap(), trans: self.album_art_draw_transform.unwrap(),
..Default::default() ..Default::default()
}, },
); )?;
} }
if !self.hide_text { if !self.hide_text {
canvas.draw(&self.notice_text, DrawParam::default()); self.notice_text.draw(ctx, DrawParam::default())?;
if self.mpd_play_state != MPDPlayState::Stopped && self.is_valid && self.is_initialized if self.mpd_play_state != MPDPlayState::Stopped && self.is_valid && self.is_initialized
{ {
if let Some(mesh) = &self.text_bg_mesh { if let Some(mesh) = &self.text_bg_mesh {
canvas.draw(mesh, DrawParam::default()); mesh.draw(ctx, DrawParam::default())?;
} }
if !self.opts.disable_show_filename { if !self.opts.disable_show_filename {
canvas.draw( self.filename_text.draw(
&self.filename_text, ctx,
DrawParam { DrawParam {
transform: self.filename_transform, trans: self.filename_transform,
..Default::default() ..Default::default()
}, },
); )?;
} }
if !self.opts.disable_show_album { if !self.opts.disable_show_album {
canvas.draw( self.album_text.draw(
&self.album_text, ctx,
DrawParam { DrawParam {
transform: self.album_transform, trans: self.album_transform,
..Default::default() ..Default::default()
}, },
); )?;
} }
if !self.opts.disable_show_artist { if !self.opts.disable_show_artist {
canvas.draw( self.artist_text.draw(
&self.artist_text, ctx,
DrawParam { DrawParam {
transform: self.artist_transform, trans: self.artist_transform,
..Default::default() ..Default::default()
}, },
); )?;
} }
if !self.opts.disable_show_title { if !self.opts.disable_show_title {
canvas.draw( self.title_text.draw(
&self.title_text, ctx,
DrawParam { DrawParam {
transform: self.title_transform, trans: self.title_transform,
..Default::default() ..Default::default()
}, },
); )?;
} }
if self.mpd_play_state == MPDPlayState::Playing { if self.mpd_play_state == MPDPlayState::Playing {
canvas.draw( self.timer_text.draw(
&self.timer_text, ctx,
DrawParam { DrawParam {
transform: self.timer_transform, trans: self.timer_transform,
..Default::default() ..Default::default()
}, },
); )?;
} }
} }
} }
canvas.finish(ctx) graphics::present(ctx)
} }
fn text_input_event(&mut self, _ctx: &mut Context, character: char) -> Result<(), GameError> { fn text_input_event(&mut self, _ctx: &mut Context, character: char) {
if !self.is_initialized && self.opts.enable_prompt_password && !character.is_control() { if !self.is_initialized && self.opts.enable_prompt_password && !character.is_control() {
if self.opts.password.is_none() { if self.opts.password.is_none() {
let s = String::from(character); let s = String::from(character);
@ -1067,18 +1005,17 @@ impl EventHandler for MPDDisplay {
self.notice_text.add('*'); self.notice_text.add('*');
} }
} }
Ok(())
} }
fn key_down_event( fn key_down_event(
&mut self, &mut self,
_ctx: &mut Context, _ctx: &mut Context,
input: KeyInput, keycode: event::KeyCode,
_keymods: event::KeyMods,
_repeat: bool, _repeat: bool,
) -> Result<(), GameError> { ) {
if !self.is_initialized && self.opts.enable_prompt_password { if !self.is_initialized && self.opts.enable_prompt_password {
if input.keycode == Some(keyboard::KeyCode::Back) { if keycode == event::KeyCode::Back {
let s: String = self.notice_text.contents(); let s: String = self.notice_text.contents();
if s.ends_with('*') { if s.ends_with('*') {
@ -1088,34 +1025,28 @@ impl EventHandler for MPDDisplay {
if let Some(input_p) = &mut self.opts.password { if let Some(input_p) = &mut self.opts.password {
input_p.pop(); input_p.pop();
} }
} else if input.keycode == Some(keyboard::KeyCode::Return) { } else if keycode == event::KeyCode::Return {
self.password_entered = true; self.password_entered = true;
} }
} else if input.keycode == Some(keyboard::KeyCode::H) { } else if keycode == event::KeyCode::H {
self.hide_text = true; self.hide_text = true;
} }
Ok(())
} }
fn key_up_event(&mut self, _ctx: &mut Context, input: KeyInput) -> Result<(), GameError> { fn key_up_event(
if input.keycode == Some(keyboard::KeyCode::H) { &mut self,
_ctx: &mut Context,
keycode: event::KeyCode,
_keymods: event::KeyMods,
) {
if keycode == event::KeyCode::H {
self.hide_text = false; self.hide_text = false;
} }
Ok(())
} }
fn resize_event( fn resize_event(&mut self, ctx: &mut Context, _width: f32, _height: f32) {
&mut self,
ctx: &mut Context,
_width: f32,
_height: f32,
) -> Result<(), GameError> {
self.get_album_art_transform(ctx, !self.opts.do_not_fill_scale_album_art); self.get_album_art_transform(ctx, !self.opts.do_not_fill_scale_album_art);
self.refresh_text_transforms(ctx) self.refresh_text_transforms(ctx)
.expect("Failed to set text transforms"); .expect("Failed to set text transforms");
Ok(())
} }
} }

View file

@ -4,56 +4,52 @@ mod mpd_handler;
#[cfg(feature = "unicode_support")] #[cfg(feature = "unicode_support")]
mod unicode_support; mod unicode_support;
use clap::Parser;
use ggez::conf::{WindowMode, WindowSetup}; use ggez::conf::{WindowMode, WindowSetup};
use ggez::event::winit_event::{ElementState, KeyboardInput, ModifiersState}; use ggez::event::winit_event::{ElementState, KeyboardInput, ModifiersState};
use ggez::event::{self, ControlFlow, EventHandler}; use ggez::event::{self, ControlFlow, EventHandler};
use ggez::input::keyboard::{self, KeyInput}; use ggez::filesystem::mount;
use ggez::graphics::{self, Rect};
use ggez::{ContextBuilder, GameError}; use ggez::{ContextBuilder, GameError};
use std::fs::File;
use std::io::Read;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::path::PathBuf; use std::path::PathBuf;
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use structopt::StructOpt;
use debug_log::log; use debug_log::log;
#[derive(Parser, Debug, Clone)] #[derive(StructOpt, Debug, Clone)]
#[command(author, version, about, long_about = None)] #[structopt(name = "mpd_info_screen")]
pub struct Opt { pub struct Opt {
host: Ipv4Addr, host: Ipv4Addr,
#[arg(default_value = "6600")] #[structopt(default_value = "6600")]
port: u16, port: u16,
#[arg(short = 'p')] #[structopt(short = "p")]
password: Option<String>, password: Option<String>,
#[arg(long = "disable-show-title", help = "disable title display")] #[structopt(long = "disable-show-title", help = "disable title display")]
disable_show_title: bool, disable_show_title: bool,
#[arg(long = "disable-show-artist", help = "disable artist display")] #[structopt(long = "disable-show-artist", help = "disable artist display")]
disable_show_artist: bool, disable_show_artist: bool,
#[arg(long = "disable-show-album", help = "disable album display")] #[structopt(long = "disable-show-album", help = "disable album display")]
disable_show_album: bool, disable_show_album: bool,
#[arg(long = "disable-show-filename", help = "disable filename display")] #[structopt(long = "disable-show-filename", help = "disable filename display")]
disable_show_filename: bool, disable_show_filename: bool,
#[arg(long = "disable-show-percentage", help = "disable percentage display")] #[structopt(long = "pprompt", help = "input password via prompt")]
disable_show_percentage: bool,
#[arg(
long = "force-text-height-scale",
help = "force-set text height relative to window height as a ratio (default 0.12)"
)]
force_text_height_scale: Option<f32>,
#[arg(long = "pprompt", help = "input password via prompt")]
enable_prompt_password: bool, enable_prompt_password: bool,
#[arg(long = "pfile", help = "read password from file")] #[structopt(
password_file: Option<PathBuf>,
#[arg(
long = "no-scale-fill", long = "no-scale-fill",
help = "don't scale-fill the album art to the window" help = "don't scale-fill the album art to the window"
)] )]
do_not_fill_scale_album_art: bool, do_not_fill_scale_album_art: bool,
#[arg(short = 'l', long = "log-level", default_value = "error")] #[structopt(
short = "l",
long = "log-level",
possible_values = &debug_log::LogLevel::variants(),
default_value = "Error",
case_insensitive = true,
)]
log_level: debug_log::LogLevel, log_level: debug_log::LogLevel,
#[arg( #[structopt(
short, short,
long, long,
help = "sets the opacity of the text background (0-255)", help = "sets the opacity of the text background (0-255)",
@ -63,35 +59,9 @@ pub struct Opt {
} }
fn main() -> Result<(), String> { fn main() -> Result<(), String> {
let mut opt = Opt::parse(); let opt = Opt::from_args();
if let Some(forced_scale) = &mut opt.force_text_height_scale {
if *forced_scale < 0.01 {
*forced_scale = 0.01;
println!("WARNING: Clamped \"force-text-height-scale\" to minimum of 0.01!");
} else if *forced_scale > 0.5 {
*forced_scale = 0.5;
println!("WARNING: Clamped \"force-text-height-scale\" to maximum of 0.5!");
}
}
println!("Got host addr == {}, port == {}", opt.host, opt.port); println!("Got host addr == {}, port == {}", opt.host, opt.port);
// Read password from file if exists, error otherwise.
if let Some(psswd_file_path) = opt.password_file.as_ref() {
let mut file = File::open(psswd_file_path).expect("pfile/password_file should exist");
let mut content: String = String::new();
file.read_to_string(&mut content)
.expect("Should be able to read from pfile/password_file");
if content.ends_with("\r\n") {
content.truncate(content.len() - 2);
} else if content.ends_with('\n') {
content.truncate(content.len() - 1);
}
opt.password = Some(content);
}
let (mut ctx, event_loop) = ContextBuilder::new("mpd_info_screen", "Stephen Seo") let (mut ctx, event_loop) = ContextBuilder::new("mpd_info_screen", "Stephen Seo")
.window_setup(WindowSetup { .window_setup(WindowSetup {
title: "mpd info screen".into(), title: "mpd info screen".into(),
@ -99,21 +69,20 @@ fn main() -> Result<(), String> {
}) })
.window_mode(WindowMode { .window_mode(WindowMode {
resizable: true, resizable: true,
resize_on_scale_factor_change: true,
..Default::default() ..Default::default()
}) })
.build() .build()
.expect("Failed to create ggez context"); .expect("Failed to create ggez context");
// mount "/" read-only so that fonts can be loaded via absolute paths // mount "/" read-only so that fonts can be loaded via absolute paths
ctx.fs.mount(&PathBuf::from("/"), true); mount(&mut ctx, &PathBuf::from("/"), true);
let mut display = display::MPDDisplay::new(&mut ctx, opt.clone()); let mut display = display::MPDDisplay::new(&mut ctx, opt.clone());
let mut modifiers_state: ModifiersState = ModifiersState::default(); let mut modifiers_state: ModifiersState = ModifiersState::default();
event_loop.run(move |mut event, _window_target, control_flow| { event_loop.run(move |mut event, _window_target, control_flow| {
if !ctx.continuing || ctx.quit_requested { if !ctx.continuing {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} }
@ -125,7 +94,7 @@ fn main() -> Result<(), String> {
event::process_event(ctx, &mut event); event::process_event(ctx, &mut event);
match event { match event {
event::winit_event::Event::WindowEvent { event, .. } => match event { event::winit_event::Event::WindowEvent { event, .. } => match event {
event::winit_event::WindowEvent::CloseRequested => ctx.request_quit(), event::winit_event::WindowEvent::CloseRequested => event::quit(ctx),
event::winit_event::WindowEvent::ModifiersChanged(state) => { event::winit_event::WindowEvent::ModifiersChanged(state) => {
modifiers_state = state; modifiers_state = state;
} }
@ -139,37 +108,40 @@ fn main() -> Result<(), String> {
}, },
is_synthetic: _, is_synthetic: _,
} => { } => {
if keycode == keyboard::KeyCode::Escape { if keycode == event::KeyCode::Escape {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} }
let ki = KeyInput {
scancode: 0,
keycode: Some(keycode),
mods: From::from(modifiers_state),
};
if state == ElementState::Pressed { if state == ElementState::Pressed {
display.key_down_event(ctx, ki, false).ok(); display.key_down_event(ctx, keycode, modifiers_state.into(), false);
} else { } else {
display.key_up_event(ctx, ki).ok(); display.key_up_event(ctx, keycode, modifiers_state.into());
} }
} }
event::winit_event::WindowEvent::Resized(phys_size) => { event::winit_event::WindowEvent::Resized(phys_size) => {
display graphics::set_screen_coordinates(
.resize_event(ctx, phys_size.width as f32, phys_size.height as f32) ctx,
.ok(); Rect {
x: 0.0,
y: 0.0,
w: phys_size.width as f32,
h: phys_size.height as f32,
},
)
.expect("Failed to handle resizing window");
display.resize_event(ctx, phys_size.width as f32, phys_size.height as f32);
} }
event::winit_event::WindowEvent::ReceivedCharacter(ch) => { event::winit_event::WindowEvent::ReceivedCharacter(ch) => {
display.text_input_event(ctx, ch).ok(); display.text_input_event(ctx, ch);
} }
x => log( x => log(
format!("Other window event fired: {x:?}"), format!("Other window event fired: {:?}", x),
debug_log::LogState::Verbose, debug_log::LogState::Verbose,
opt.log_level, opt.log_level,
), ),
}, },
event::winit_event::Event::MainEventsCleared => { event::winit_event::Event::MainEventsCleared => {
ctx.time.tick(); ctx.timer_context.tick();
let mut game_result: Result<(), GameError> = display.update(ctx); let mut game_result: Result<(), GameError> = display.update(ctx);
if game_result.is_err() { if game_result.is_err() {
@ -177,24 +149,21 @@ fn main() -> Result<(), String> {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} }
ctx.gfx.begin_frame().unwrap();
game_result = display.draw(ctx); game_result = display.draw(ctx);
if game_result.is_err() { if game_result.is_err() {
println!("Error draw: {}", game_result.unwrap_err()); println!("Error draw: {}", game_result.unwrap_err());
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} }
ctx.gfx.end_frame().unwrap();
ctx.mouse.reset_delta(); ctx.mouse_context.reset_delta();
// sleep to force ~5 fps // sleep to force ~5 fps
thread::sleep(Duration::from_millis(200)); thread::sleep(Duration::from_millis(200));
ggez::timer::yield_now(); ggez::timer::yield_now();
} }
x => log( x => log(
format!("Device event fired: {x:?}"), format!("Device event fired: {:?}", x),
debug_log::LogState::Verbose, debug_log::LogState::Verbose,
opt.log_level, opt.log_level,
), ),

View file

@ -250,8 +250,6 @@ impl MPDHandler {
) )
.map_err(|_| String::from("Failed to get TCP connection (is MPD running?)"))?; .map_err(|_| String::from("Failed to get TCP connection (is MPD running?)"))?;
let password_is_empty = password.is_empty();
let s = MPDHandler { let s = MPDHandler {
state: Arc::new(RwLock::new(MPDHandlerState { state: Arc::new(RwLock::new(MPDHandlerState {
art_data: Vec::new(), art_data: Vec::new(),
@ -265,10 +263,10 @@ impl MPDHandler {
current_binary_size: 0, current_binary_size: 0,
poll_state: PollState::None, poll_state: PollState::None,
stream, stream,
password, password: password.clone(),
error_text: String::new(), error_text: String::new(),
can_authenticate: true, can_authenticate: true,
is_authenticated: password_is_empty, is_authenticated: password.is_empty(),
can_get_album_art: true, can_get_album_art: true,
can_get_album_art_in_dir: true, can_get_album_art_in_dir: true,
can_get_status: true, can_get_status: true,
@ -276,9 +274,9 @@ impl MPDHandler {
did_check_overtime: false, did_check_overtime: false,
force_get_status: false, force_get_status: false,
force_get_current_song: false, force_get_current_song: false,
song_title_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(), song_title_get_time: Instant::now() - Duration::from_secs(10),
song_pos_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(), song_pos_get_time: Instant::now() - Duration::from_secs(10),
song_length_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(), song_length_get_time: Instant::now() - Duration::from_secs(10),
self_thread: None, self_thread: None,
dirty_flag: Arc::new(AtomicBool::new(true)), dirty_flag: Arc::new(AtomicBool::new(true)),
stop_flag: Arc::new(AtomicBool::new(false)), stop_flag: Arc::new(AtomicBool::new(false)),
@ -332,7 +330,7 @@ impl MPDHandler {
#[allow(dead_code)] #[allow(dead_code)]
pub fn is_dirty(&self) -> Result<bool, ()> { pub fn is_dirty(&self) -> Result<bool, ()> {
if let Ok(write_lock) = self.state.try_write() { if let Ok(write_lock) = self.state.try_write() {
return Ok(write_lock.dirty_flag.swap(false, Ordering::AcqRel)); return Ok(write_lock.dirty_flag.swap(false, Ordering::Relaxed));
} }
Err(()) Err(())
@ -372,7 +370,7 @@ impl MPDHandler {
pub fn stop_thread(&self) -> Result<(), ()> { pub fn stop_thread(&self) -> Result<(), ()> {
let read_handle = self.state.try_read().map_err(|_| ())?; let read_handle = self.state.try_read().map_err(|_| ())?;
read_handle.stop_flag.store(true, Ordering::Release); read_handle.stop_flag.store(true, Ordering::Relaxed);
Ok(()) Ok(())
} }
@ -428,20 +426,20 @@ impl MPDHandler {
if let Err(err_string) = self.handler_read_block(&mut buf, &mut saved, &mut saved_str) { if let Err(err_string) = self.handler_read_block(&mut buf, &mut saved, &mut saved_str) {
log( log(
format!("read_block error: {err_string}"), format!("read_block error: {}", err_string),
LogState::Warning, LogState::Warning,
log_level, log_level,
); );
} else if let Err(err_string) = self.handler_write_block() { } else if let Err(err_string) = self.handler_write_block() {
log( log(
format!("write_block error: {err_string}"), format!("write_block error: {}", err_string),
LogState::Warning, LogState::Warning,
log_level, log_level,
); );
} }
if let Ok(read_handle) = self.state.try_read() { if let Ok(read_handle) = self.state.try_read() {
if read_handle.stop_flag.load(Ordering::Acquire) || !read_handle.can_authenticate { if read_handle.stop_flag.load(Ordering::Relaxed) || !read_handle.can_authenticate {
break 'main; break 'main;
} }
} }
@ -479,7 +477,7 @@ impl MPDHandler {
let read_result = write_handle.stream.read(buf); let read_result = write_handle.stream.read(buf);
if let Err(io_err) = read_result { if let Err(io_err) = read_result {
if io_err.kind() != io::ErrorKind::WouldBlock { if io_err.kind() != io::ErrorKind::WouldBlock {
return Err(format!("TCP stream error: {io_err}")); return Err(format!("TCP stream error: {}", io_err));
} else { } else {
return Ok(()); return Ok(());
} }
@ -511,7 +509,7 @@ impl MPDHandler {
write_handle.log_level, write_handle.log_level,
); );
if write_handle.art_data.len() == write_handle.art_data_size { if write_handle.art_data.len() == write_handle.art_data_size {
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
} }
} else { } else {
write_handle.art_data.extend_from_slice(&buf_vec); write_handle.art_data.extend_from_slice(&buf_vec);
@ -526,7 +524,7 @@ impl MPDHandler {
write_handle.log_level, write_handle.log_level,
); );
if write_handle.art_data.len() == write_handle.art_data_size { if write_handle.art_data.len() == write_handle.art_data_size {
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
} }
break 'handle_buf; break 'handle_buf;
} }
@ -561,7 +559,7 @@ impl MPDHandler {
PollState::ReadPicture => { PollState::ReadPicture => {
if write_handle.art_data.is_empty() { if write_handle.art_data.is_empty() {
write_handle.can_get_album_art = false; write_handle.can_get_album_art = false;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
log( log(
"No embedded album art", "No embedded album art",
LogState::Warning, LogState::Warning,
@ -572,7 +570,7 @@ impl MPDHandler {
PollState::ReadPictureInDir => { PollState::ReadPictureInDir => {
if write_handle.art_data.is_empty() { if write_handle.art_data.is_empty() {
write_handle.can_get_album_art_in_dir = false; write_handle.can_get_album_art_in_dir = false;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
log( log(
"No album art in dir", "No album art in dir",
LogState::Warning, LogState::Warning,
@ -589,13 +587,13 @@ impl MPDHandler {
match write_handle.poll_state { match write_handle.poll_state {
PollState::Password => { PollState::Password => {
write_handle.can_authenticate = false; write_handle.can_authenticate = false;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
write_handle.error_text = "Failed to authenticate to MPD".into(); write_handle.error_text = "Failed to authenticate to MPD".into();
write_handle.stop_flag.store(true, Ordering::Release); write_handle.stop_flag.store(true, Ordering::Relaxed);
} }
PollState::CurrentSong | PollState::Status => { PollState::CurrentSong | PollState::Status => {
write_handle.can_get_status = false; write_handle.can_get_status = false;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
write_handle.error_text = "Failed to get MPD status".into(); write_handle.error_text = "Failed to get MPD status".into();
if line.contains("don't have permission") { if line.contains("don't have permission") {
write_handle.can_authenticate = false; write_handle.can_authenticate = false;
@ -604,7 +602,7 @@ impl MPDHandler {
} }
PollState::ReadPicture => { PollState::ReadPicture => {
write_handle.can_get_album_art = false; write_handle.can_get_album_art = false;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
log( log(
"Failed to get readpicture", "Failed to get readpicture",
LogState::Warning, LogState::Warning,
@ -615,7 +613,7 @@ impl MPDHandler {
} }
PollState::ReadPictureInDir => { PollState::ReadPictureInDir => {
write_handle.can_get_album_art_in_dir = false; write_handle.can_get_album_art_in_dir = false;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
log( log(
"Failed to get albumart", "Failed to get albumart",
LogState::Warning, LogState::Warning,
@ -651,9 +649,9 @@ impl MPDHandler {
MPDPlayState::Paused MPDPlayState::Paused
}; };
write_handle.error_text.clear(); write_handle.error_text.clear();
write!(&mut write_handle.error_text, "MPD has {got_mpd_state:?}").ok(); write!(&mut write_handle.error_text, "MPD has {:?}", got_mpd_state).ok();
log( log(
format!("MPD is {got_mpd_state:?}"), format!("MPD is {:?}", got_mpd_state),
LogState::Warning, LogState::Warning,
write_handle.log_level, write_handle.log_level,
); );
@ -677,13 +675,13 @@ impl MPDHandler {
write_handle.force_get_status = true; write_handle.force_get_status = true;
write_handle.error_text.clear(); write_handle.error_text.clear();
} }
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
write_handle.song_title_get_time = Instant::now(); write_handle.song_title_get_time = Instant::now();
} else if line.starts_with("elapsed: ") { } else if line.starts_with("elapsed: ") {
let parse_pos_result = f64::from_str(&line.split_off(9)); let parse_pos_result = f64::from_str(&line.split_off(9));
if let Ok(value) = parse_pos_result { if let Ok(value) = parse_pos_result {
write_handle.current_song_position = value; write_handle.current_song_position = value;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
write_handle.song_pos_get_time = Instant::now(); write_handle.song_pos_get_time = Instant::now();
} else { } else {
log( log(
@ -696,7 +694,7 @@ impl MPDHandler {
let parse_pos_result = f64::from_str(&line.split_off(10)); let parse_pos_result = f64::from_str(&line.split_off(10));
if let Ok(value) = parse_pos_result { if let Ok(value) = parse_pos_result {
write_handle.current_song_length = value; write_handle.current_song_length = value;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
write_handle.song_length_get_time = Instant::now(); write_handle.song_length_get_time = Instant::now();
} else { } else {
log( log(
@ -709,7 +707,7 @@ impl MPDHandler {
let parse_artsize_result = usize::from_str(&line.split_off(6)); let parse_artsize_result = usize::from_str(&line.split_off(6));
if let Ok(value) = parse_artsize_result { if let Ok(value) = parse_artsize_result {
write_handle.art_data_size = value; write_handle.art_data_size = value;
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
} else { } else {
log( log(
"Failed to parse album art byte size", "Failed to parse album art byte size",
@ -738,7 +736,7 @@ impl MPDHandler {
write_handle.art_data_type = line.split_off(6); write_handle.art_data_type = line.split_off(6);
} else { } else {
log( log(
format!("Got unrecognized/ignored line: {line}"), format!("Got unrecognized/ignored line: {}", line),
LogState::Warning, LogState::Warning,
write_handle.log_level, write_handle.log_level,
); );
@ -762,7 +760,7 @@ impl MPDHandler {
} // 'handle_buf: loop } // 'handle_buf: loop
if got_mpd_state != write_handle.mpd_play_state { if got_mpd_state != write_handle.mpd_play_state {
write_handle.dirty_flag.store(true, Ordering::Release); write_handle.dirty_flag.store(true, Ordering::Relaxed);
if got_mpd_state == MPDPlayState::Playing { if got_mpd_state == MPDPlayState::Playing {
write_handle.error_text.clear(); write_handle.error_text.clear();
} }
@ -801,12 +799,12 @@ impl MPDHandler {
let p = write_handle.password.clone(); let p = write_handle.password.clone();
let write_result = write_handle let write_result = write_handle
.stream .stream
.write(format!("password {p}\n").as_bytes()); .write(format!("password {}\n", p).as_bytes());
if write_result.is_ok() { if write_result.is_ok() {
write_handle.poll_state = PollState::Password; write_handle.poll_state = PollState::Password;
} else if let Err(e) = write_result { } else if let Err(e) = write_result {
log( log(
format!("Failed to send password for authentication: {e}"), format!("Failed to send password for authentication: {}", e),
LogState::Error, LogState::Error,
write_handle.log_level, write_handle.log_level,
); );
@ -822,7 +820,7 @@ impl MPDHandler {
write_handle.poll_state = PollState::CurrentSong; write_handle.poll_state = PollState::CurrentSong;
} else if let Err(e) = write_result { } else if let Err(e) = write_result {
log( log(
format!("Failed to request song info over stream: {e}"), format!("Failed to request song info over stream: {}", e),
LogState::Error, LogState::Error,
write_handle.log_level, write_handle.log_level,
); );
@ -838,7 +836,7 @@ impl MPDHandler {
write_handle.poll_state = PollState::Status; write_handle.poll_state = PollState::Status;
} else if let Err(e) = write_result { } else if let Err(e) = write_result {
log( log(
format!("Failed to request status over stream: {e}"), format!("Failed to request status over stream: {}", e),
LogState::Error, LogState::Error,
write_handle.log_level, write_handle.log_level,
); );
@ -850,14 +848,14 @@ impl MPDHandler {
let title = write_handle.current_song_filename.clone(); let title = write_handle.current_song_filename.clone();
let art_data_length = write_handle.art_data.len(); let art_data_length = write_handle.art_data.len();
if write_handle.can_get_album_art { if write_handle.can_get_album_art {
let write_result = write_handle let write_result = write_handle.stream.write(
.stream format!("readpicture \"{}\" {}\n", title, art_data_length).as_bytes(),
.write(format!("readpicture \"{title}\" {art_data_length}\n").as_bytes()); );
if write_result.is_ok() { if write_result.is_ok() {
write_handle.poll_state = PollState::ReadPicture; write_handle.poll_state = PollState::ReadPicture;
} else if let Err(e) = write_result { } else if let Err(e) = write_result {
log( log(
format!("Failed to request album art: {e}"), format!("Failed to request album art: {}", e),
LogState::Error, LogState::Error,
write_handle.log_level, write_handle.log_level,
); );
@ -865,12 +863,12 @@ impl MPDHandler {
} else if write_handle.can_get_album_art_in_dir { } else if write_handle.can_get_album_art_in_dir {
let write_result = write_handle let write_result = write_handle
.stream .stream
.write(format!("albumart \"{title}\" {art_data_length}\n").as_bytes()); .write(format!("albumart \"{}\" {}\n", title, art_data_length).as_bytes());
if write_result.is_ok() { if write_result.is_ok() {
write_handle.poll_state = PollState::ReadPictureInDir; write_handle.poll_state = PollState::ReadPictureInDir;
} else if let Err(e) = write_result { } else if let Err(e) = write_result {
log( log(
format!("Failed to request album art in dir: {e}"), format!("Failed to request album art in dir: {}", e),
LogState::Error, LogState::Error,
write_handle.log_level, write_handle.log_level,
); );

View file

@ -67,7 +67,8 @@ mod ffi {
return Err(String::from("Failed to FcFontMatch (FcResult is not FcResultMatch; result_pattern is not null)")); return Err(String::from("Failed to FcFontMatch (FcResult is not FcResultMatch; result_pattern is not null)"));
} else { } else {
return Err(format!( return Err(format!(
"Failed to FcFontMatch (FcResult is not FcResultMatch; {result:?})" "Failed to FcFontMatch (FcResult is not FcResultMatch; {:?})",
result
)); ));
} }
} else if result_pattern.is_null() { } else if result_pattern.is_null() {