Compare commits

...

63 commits

Author SHA1 Message Date
3e1bc8c6ac Bump version to 0.4.17, update README.md 2024-09-13 16:57:23 +09:00
47db86ba59 Add option to change info text font size 2024-09-13 16:57:19 +09:00
ab61924e5f Bump version 0.4.16 2024-09-13 15:39:45 +09:00
3e95544fb3 Minor fix, cargo fmt 2024-09-13 15:32:14 +09:00
5d0116e9de cargo update 2024-09-13 15:32:01 +09:00
578865abe7 Tweak text height constant 2024-08-05 20:58:24 +09:00
ca21d5c640 Update dependencies and fixes related to update 2024-08-04 22:15:51 +09:00
075754fcd0 Tweak font sizes for display 2024-08-04 22:06:12 +09:00
c145bdbd9c Version 0.4.14 2024-07-22 11:16:00 +09:00
502795c6cf More sane variants of "Ordering" used
Stores use "Ordering::Release", loads use "Ordering::Acquire", and swaps
use "Ordering::AcqRel".
2024-07-21 21:22:28 +09:00
e8b170e0e2 cargo update 2024-07-21 21:15:10 +09:00
d276482c8b Minor refactorings 2024-06-24 15:45:21 +09:00
08b78467b5 cargo update 2024-06-24 14:28:58 +09:00
0ddd33a898 Version 0.4.13 2024-06-24 14:28:27 +09:00
8ebeac0499 Refactor time_to_percentage(...) 2024-06-24 14:27:44 +09:00
8e6305d934 Round percentage value 2024-06-24 14:11:10 +09:00
1b92da1ab2 Update README.md 2024-06-24 14:08:15 +09:00
00240e4205 Version 0.4.12 2024-06-24 14:07:05 +09:00
e4cdccce63 Impl. show percentage, minor fixes
Implement showing percentage of song played next to time remaining.

Add flag to disable showing percentage.

Fix time remaining background persisting when song is no longer playing.
2024-06-24 14:05:28 +09:00
015da43d1b Bump version to 0.4.11 2024-06-23 20:59:43 +09:00
773587b664 Fix not quitting when requested, cargo fmt 2024-06-23 20:56:54 +09:00
2f23e63349 Update LICENSE year 2024-06-23 20:45:26 +09:00
8cd599aadd Bump version to 0.4.10 2024-06-23 20:43:43 +09:00
2865352f7a Allow window resize behavior 2024-06-23 20:41:10 +09:00
180898103a cargo update 2024-06-23 20:41:01 +09:00
d90c86c894 cargo update, bump version 2024-02-28 11:53:21 +09:00
f42fadd403 Update README.md
Previously, the README referenced dependency structopt, which was
replaced with dependency clap.
2023-11-28 21:42:12 +09:00
f2f58047a5 Version 0.4.8 2023-11-28 15:02:27 +09:00
d457098f2a Fix use of deprecated object from bindgen 2023-11-28 15:02:09 +09:00
d0beff5a98 Update build dependency bindgen 2023-11-28 15:00:26 +09:00
7a860e323a Update image and wgpu dependencies 2023-11-28 12:29:35 +09:00
ef234f0ec0 Replace dependency structopt with clap 2023-11-28 12:20:49 +09:00
391949cde6 Version 0.4.7 2023-11-26 15:49:48 +09:00
f66880f13d Update ggez dependency 2023-11-26 15:42:04 +09:00
83bb20c246 Remove no longer needed comment in Cargo.toml 2023-06-27 18:59:12 +09:00
dd868969cc Version 0.4.6 with new version of ggez 2023-06-27 10:41:04 +09:00
1dc28d7b07 Update README.md 2023-02-27 15:55:31 +09:00
f7ffa62e02 Update README.md 2023-02-27 15:49:05 +09:00
bcea959381 Merge branch 'devel' 2023-02-27 15:39:48 +09:00
aa6fb750e7 Refactor "no-password-fix"
Refactors the fix for the use case of when MPD requires no password.
2023-02-04 21:32:31 +09:00
a1706913e6 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-02-04 20:41:47 +09:00
e28d20a5da
Update README.md 2023-02-04 19:59:04 +09:00
4653399fe9 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.

Backported from `devel` branch.
2023-02-04 19:56:09 +09:00
e0be69df81 Impl passing password via file 2023-01-31 16:19:55 +09:00
8643542b7a
Update README.md 2023-01-31 16:05:48 +09:00
e9e57c9dff Update README.md 2023-01-31 16:02:02 +09:00
d1590bee0a Update README.md 2023-01-31 15:59:19 +09:00
56f6784892 Fix where display doesn't work when no password
Previous implementation only worked if MPD was accessed with a password.
This commit fixes no-password access of MPD.
2023-01-31 15:57:56 +09:00
a223d8b530
Update README.md 2023-01-29 16:57:54 +09:00
fad82f6448
Update README.md 2023-01-29 16:55:20 +09:00
934aa1a610 Version 0.4.1 2023-01-29 16:49:04 +09:00
6d400cd7c7 Workaround for album art not drawing bug
ggez (at the time of this commit) latest release has a bug that results
in the album art not drawing. Workaround is to build against a ggez
commit in its "devel" branch.
2023-01-29 16:45:40 +09:00
c902f0fcf3 Fix clippy warnings 2023-01-29 14:50:26 +09:00
e025a48735 Version 0.4.0
Testing shows that even with some of the expect()/unwrap() additions,
the program is still stable enough to use. Will merge into the main
branch.
2023-01-08 17:43:19 +09:00
74a84d9f7a Change rust project edition to "2021"
Also update Cargo.lock.
2022-12-11 14:36:06 +09:00
f2f93f5393 Fix clippy warning 2022-12-07 12:22:06 +09:00
1a623f451d Fix not-staying-in-fullscreen bug 2022-12-06 22:26:02 +09:00
ef07d7936a Fix "brighter" displayed album art bug 2022-12-06 22:19:48 +09:00
7f6a24545c Update to latest ggez (0.8.1)
A lot of ".expect(...)" or ".unwrap(...)" lines were added, so these
need to be refactored out. For now, just got it to work with the latest
ggez.

Currently noticed bugs:

 - Album art is brighter than original.

 - Fullscreen doesn't work.
2022-12-06 22:03:46 +09:00
34d3e47863 Update README.md 2022-11-01 20:44:23 +09:00
a9441536c4 Refactor handling of Result conversion 2022-11-01 17:36:16 +09:00
a1fe8c3120 Impl better handling of unknown image type 2022-10-30 19:41:50 +09:00
ce45a40df5 Fix clippy warnings 2022-10-30 14:05:04 +09:00
10 changed files with 2702 additions and 1782 deletions

3783
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,23 @@
[package] [package]
name = "mpd_info_screen" name = "mpd_info_screen"
version = "0.3.7" version = "0.4.17"
edition = "2018" edition = "2021"
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]
structopt = "0.3" clap = { version = "4.5", features = ["derive"] }
image = "0.24" image = "0.25"
ggez = "0.7" ggez = "0.9.3"
freetype = { version = "0.7", optional = true } freetype = { version = "0.7", optional = true }
wgpu-types = "0.16"
[build-dependencies] [build-dependencies]
bindgen = { version = "0.53", optional = true } bindgen = { version = "0.69", 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-2022 Stephen Seo Copyright (c) 2021-2024 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

@ -12,6 +12,11 @@ A Rust program that displays info about the currently running MPD server.
The window shows albumart (may be embedded in the audio file, or is a "cover.jpg" in the same directory as the song file), a "time-remaining" The window shows albumart (may be embedded in the audio file, or is a "cover.jpg" in the same directory as the song file), a "time-remaining"
counter, and the filename currently being played counter, and the filename currently being played
## Known Bugs ❗❗
Currently there are no known bugs. Please report any bugs you find to the
[issue tracker](https://github.com/Stephen-Seo/mpd_info_screen/issues).
## Unicode Support ## Unicode Support
By default, unicode characters will not display properly. Build the project with By default, unicode characters will not display properly. Build the project with
@ -23,32 +28,50 @@ installed already).
cargo build --release --features unicode_support cargo build --release --features unicode_support
or through crates.io:
cargo install --features unicode_support mpd_info_screen
# Usage # Usage
mpd_info_screen 0.3.7 Displays info on currently playing music from an MPD daemon
USAGE: Usage: mpd_info_screen [OPTIONS] <HOST> [PORT]
mpd_info_screen [FLAGS] [OPTIONS] <host> [port]
FLAGS: Arguments:
--disable-show-album disable album display <HOST>
--disable-show-artist disable artist display [PORT] [default: 6600]
--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:
-l, --log-level <log-level> [default: Error] [possible values: Error, Warning, Debug, Verbose] -p <PASSWORD>
-p <password>
-t, --text-bg-opacity <text-bg-opacity> sets the opacity of the text background (0-255) [default: 190]
ARGS: --disable-show-title
<host> disable title display
<port> [default: 6600] --disable-show-artist
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.
@ -77,8 +100,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 [structopt](https://crates.io/crates/structopt) which is Uses dependency [clap](https://crates.io/crates/clap) which is licensed under
licensed under Apache-2.0 or MIT licenses. 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)) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.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)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LogState { pub enum LogState {
Error, Error,
Warning, Warning,
@ -9,15 +9,13 @@ pub enum LogState {
Verbose, Verbose,
} }
arg_enum! { #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
#[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
@ -46,26 +44,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,13 +1,16 @@
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::{self, EventHandler}; use ggez::event::EventHandler;
use ggez::graphics::{ use ggez::graphics::{
self, Color, DrawMode, DrawParam, Drawable, Font, Image, Mesh, MeshBuilder, PxScale, Rect, self, Color, DrawMode, DrawParam, Drawable, Image, Mesh, MeshBuilder, PxScale, Rect, Text,
Text, TextFragment, Transform, TextFragment, Transform,
}; };
use ggez::{timer, Context, GameError, GameResult}; use ggez::input::keyboard::{self, KeyInput};
use image::io::Reader as ImageReader; use ggez::mint::Vector2;
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;
@ -16,14 +19,16 @@ 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_X: f32 = 24.0; const INIT_FONT_SIZE_RATIO: f32 = 1.4167;
const INIT_FONT_SIZE_Y: f32 = 34.0; const INIT_FONT_SIZE_X: f32 = 36.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.1; const TEXT_HEIGHT_SCALE: f32 = 0.12;
const ARTIST_HEIGHT_SCALE: f32 = 0.08; const ARTIST_HEIGHT_SCALE: f32 = 0.12;
const ALBUM_HEIGHT_SCALE: f32 = 0.08; const ALBUM_HEIGHT_SCALE: f32 = 0.12;
const TIMER_HEIGHT_SCALE: f32 = 0.07; const TIMER_HEIGHT_SCALE_RATIO: f32 = 0.875;
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;
@ -51,11 +56,15 @@ 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, Font)>, _loaded_fonts: &mut Vec<(PathBuf, String)>,
_ctx: &mut Context, _ctx: &mut Context,
) -> Text { ) -> Text {
Text::new(TextFragment::from(string)) Text::new(TextFragment::from(string))
@ -64,7 +73,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, Font)>, loaded_fonts: &mut Vec<(PathBuf, String)>,
ctx: &mut Context, ctx: &mut Context,
) -> Text { ) -> Text {
use super::unicode_support; use super::unicode_support;
@ -79,7 +88,7 @@ fn string_to_text(
} }
let find_font = let find_font =
|c: char, loaded_fonts: &mut Vec<(PathBuf, Font)>, ctx: &mut Context| -> Option<usize> { |c: char, loaded_fonts: &mut Vec<(PathBuf, String)>, 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() {
@ -89,9 +98,16 @@ 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 = Font::new(ctx, &path); let new_font = ggez::graphics::FontData::from_path(ctx, &path);
if let Ok(font) = new_font { if let Ok(font) = new_font {
loaded_fonts.push((path, font)); let font_name: String = path
.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(
@ -102,7 +118,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,
); );
@ -132,13 +148,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); current_fragment.font = Some(font.clone());
} }
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 { if let Some(current_font) = current_fragment.font.as_ref() {
if current_font == font { if current_font == font {
current_fragment.text.push(c); current_fragment.text.push(c);
} else { } else {
@ -147,17 +163,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); current_fragment.font = Some(font.clone());
} }
} 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); current_fragment.font = Some(font.clone());
} 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); current_fragment.font = Some(font.clone());
} }
} else { } else {
if !current_fragment.text.is_empty() && current_fragment.font.is_some() { if !current_fragment.text.is_empty() && current_fragment.font.is_some() {
@ -203,16 +219,23 @@ pub struct MPDDisplay {
album_string_cache: String, album_string_cache: String,
album_transform: Transform, album_transform: Transform,
timer_text: Text, timer_text: Text,
timer_text_len: usize,
timer_transform: Transform, timer_transform: Transform,
timer_x: f32, timer_x: f32,
timer_y: f32, timer_y: f32,
timer: f64, timer: f64,
length: f64, length: f64,
cached_filename_y: f32,
cached_album_y: f32,
cached_artist_y: f32,
cached_title_y: f32,
cached_timer_y: f32,
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, Font)>, loaded_fonts: Vec<(PathBuf, String)>,
} }
impl MPDDisplay { impl MPDDisplay {
@ -224,7 +247,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() - POLL_TIME, poll_instant: Instant::now().checked_sub(POLL_TIME).unwrap(),
shared: None, shared: None,
password_entered: false, password_entered: false,
dirty_flag: None, dirty_flag: None,
@ -237,14 +260,21 @@ impl MPDDisplay {
title_text: Text::default(), title_text: Text::default(),
title_transform: Transform::default(), title_transform: Transform::default(),
timer_text: Text::new("0"), timer_text: Text::new("0"),
timer_text_len: 0,
timer_transform: Transform::default(), timer_transform: Transform::default(),
timer_x: INIT_FONT_SIZE_X, timer_x: INIT_FONT_SIZE_X,
timer_y: INIT_FONT_SIZE_Y, timer_y: INIT_FONT_SIZE_Y,
timer: 0.0, timer: 0.0,
length: 0.0, length: 0.0,
cached_filename_y: 0.0f32,
cached_album_y: 0.0f32,
cached_artist_y: 0.0f32,
cached_title_y: 0.0f32,
cached_timer_y: 0.0f32,
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(),
@ -291,24 +321,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 screen_coords: Rect = graphics::screen_coordinates(ctx); let drawable_size = ctx.gfx.drawable_size();
let art_rect: Rect = image.dimensions(); let art_rect: Rect = image.dimensions(ctx).expect("Image should have dimensions");
// try to fit to width first // try to fit to width first
let mut x_scale = screen_coords.w / art_rect.w; let mut x_scale = drawable_size.0 / 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 > screen_coords.h.abs() { if new_height > drawable_size.1.abs() {
// fit to height instead // fit to height instead
y_scale = screen_coords.h.abs() / art_rect.h; y_scale = drawable_size.1.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 = (screen_coords.w.abs() - new_width) / 2.0f32; let offset_x: f32 = (drawable_size.0.abs() - new_width) / 2.0f32;
let offset_y: f32 = (screen_coords.h.abs() - new_height) / 2.0f32; let offset_y: f32 = (drawable_size.1.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(),
@ -320,10 +350,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 screen_coords: Rect = graphics::screen_coordinates(ctx); let drawable_size = ctx.gfx.drawable_size();
let art_rect: Rect = image.dimensions(); let art_rect: Rect = image.dimensions(ctx).expect("Image should have dimensions");
let offset_x: f32 = (screen_coords.w.abs() - art_rect.w.abs()) / 2.0f32; let offset_x: f32 = (drawable_size.0.abs() - art_rect.w.abs()) / 2.0f32;
let offset_y: f32 = (screen_coords.h.abs() - art_rect.h.abs()) / 2.0f32; let offset_y: f32 = (drawable_size.1.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,
@ -398,20 +428,23 @@ impl MPDDisplay {
} }
let img_result = if is_unknown_format { let img_result = if is_unknown_format {
let mut reader = ImageReader::new(Cursor::new(&image_ref)); let reader = ImageReader::new(Cursor::new(image_ref));
reader = reader let guessed_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!( format!("Error: Failed to decode album art image (guessed format): {e}")
"Error: Failed to decode album art image (guessed format): {}",
e
)
}) })
} else { } else {
ImageReader::with_format(Cursor::new(&image_ref), image_format) // Convert Ok(_) to Ok(DynamicImage) which will never be used
// 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(
@ -423,13 +456,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_rgba8( let ggez_img = Image::from_pixels(
ctx, ctx,
rgba8.width() as u16,
rgba8.height() as u16,
rgba8.as_raw(), rgba8.as_raw(),
) wgpu_types::TextureFormat::Rgba8UnormSrgb,
.map_err(|e| format!("Error: Failed to load album art image in ggez Image: {}", e))?; rgba8.width(),
rgba8.height(),
);
self.album_art = Some(ggez_img); self.album_art = Some(ggez_img);
@ -437,20 +470,31 @@ impl MPDDisplay {
} }
fn refresh_text_transforms(&mut self, ctx: &mut Context) -> GameResult<()> { fn refresh_text_transforms(&mut self, ctx: &mut Context) -> GameResult<()> {
let screen_coords: Rect = graphics::screen_coordinates(ctx); let drawable_size = ctx.gfx.drawable_size();
let text_height_limit = TEXT_HEIGHT_SCALE * screen_coords.h.abs(); let text_height_scale: f32;
let album_height_limit = ALBUM_HEIGHT_SCALE * screen_coords.h.abs(); let album_height_scale: f32;
let artist_height_limit = ARTIST_HEIGHT_SCALE * screen_coords.h.abs(); let artist_height_scale: f32;
let timer_height = TIMER_HEIGHT_SCALE * screen_coords.h.abs(); let timer_height_scale: f32;
let mut offset_y: f32 = screen_coords.h; if let Some(forced_scale) = &self.opts.force_text_height_scale {
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 mut filename_y: f32 = 0.0; let text_height_limit = text_height_scale * drawable_size.1.abs();
let mut album_y: f32 = 0.0; let album_height_limit = album_height_scale * drawable_size.1.abs();
let mut artist_y: f32 = 0.0; let artist_height_limit = artist_height_scale * drawable_size.1.abs();
let mut title_y: f32 = 0.0; let timer_height = timer_height_scale * drawable_size.1.abs();
let mut timer_y: f32 = 0.0;
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,
@ -463,8 +507,7 @@ 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: f32; let mut width_height: Vector2<f32> = Vector2 { x: 0.0, y: 0.0 };
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;
@ -478,12 +521,13 @@ impl MPDDisplay {
y: current_y, y: current_y,
}); });
} }
width = text.width(ctx); width_height = text
height = text.height(ctx); .measure(ctx)
.expect("Should be able to get width/height of text.");
if is_string { if is_string {
if screen_coords.w < width if drawable_size.0 < width_height.x
|| height || width_height.y
>= (if is_artist { >= (if is_artist {
artist_height_limit artist_height_limit
} else if is_album { } else if is_album {
@ -495,7 +539,7 @@ impl MPDDisplay {
current_x *= DECREASE_AMT; current_x *= DECREASE_AMT;
current_y *= DECREASE_AMT; current_y *= DECREASE_AMT;
continue; continue;
} else if screen_coords.w * MIN_WIDTH_RATIO > width { } else if drawable_size.0 * MIN_WIDTH_RATIO > width_height.x {
current_x *= INCREASE_AMT; current_x *= INCREASE_AMT;
current_y *= INCREASE_AMT; current_y *= INCREASE_AMT;
continue; continue;
@ -503,7 +547,7 @@ impl MPDDisplay {
break; break;
} }
} else { } else {
let diff_scale_y = current_y / height * timer_height; let diff_scale_y = current_y / width_height.y * 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 {
@ -514,20 +558,23 @@ 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
height = text.height(ctx); width_height.y = text
.measure(ctx)
.expect("Should be able to get width/height of text.")
.y;
break; break;
} }
} }
*y = *offset_y - height; *y = *offset_y - width_height.y;
*transform = Transform::Values { *transform = Transform::Values {
dest: [TEXT_X_OFFSET, *offset_y - height].into(), dest: [TEXT_X_OFFSET, *offset_y - width_height.y].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 -= height + TEXT_OFFSET_Y_SPACING; *offset_y -= width_height.y + 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 {
@ -535,7 +582,7 @@ impl MPDDisplay {
&mut self.filename_text, &mut self.filename_text,
&mut self.filename_transform, &mut self.filename_transform,
&mut offset_y, &mut offset_y,
&mut filename_y, &mut self.cached_filename_y,
true, true,
false, false,
false, false,
@ -555,7 +602,7 @@ impl MPDDisplay {
&mut self.album_text, &mut self.album_text,
&mut self.album_transform, &mut self.album_transform,
&mut offset_y, &mut offset_y,
&mut album_y, &mut self.cached_album_y,
true, true,
false, false,
true, true,
@ -569,7 +616,7 @@ impl MPDDisplay {
&mut self.artist_text, &mut self.artist_text,
&mut self.artist_transform, &mut self.artist_transform,
&mut offset_y, &mut offset_y,
&mut artist_y, &mut self.cached_artist_y,
true, true,
true, true,
false, false,
@ -589,7 +636,7 @@ impl MPDDisplay {
&mut self.title_text, &mut self.title_text,
&mut self.title_transform, &mut self.title_transform,
&mut offset_y, &mut offset_y,
&mut title_y, &mut self.cached_title_y,
true, true,
false, false,
false, false,
@ -608,7 +655,7 @@ impl MPDDisplay {
&mut self.timer_text, &mut self.timer_text,
&mut self.timer_transform, &mut self.timer_transform,
&mut offset_y, &mut offset_y,
&mut timer_y, &mut self.cached_timer_y,
false, false,
false, false,
false, false,
@ -616,11 +663,32 @@ impl MPDDisplay {
&mut self.timer_y, &mut self.timer_y,
); );
let filename_dimensions = self.filename_text.dimensions(ctx); self.update_bg_mesh(ctx)?;
let album_dimensions = self.album_text.dimensions(ctx);
let artist_dimensions = self.artist_text.dimensions(ctx); Ok(())
let title_dimensions = self.title_text.dimensions(ctx); }
let timer_dimensions = self.timer_text.dimensions(ctx);
fn update_bg_mesh(&mut self, ctx: &mut Context) -> GameResult<()> {
let filename_dimensions = self
.filename_text
.dimensions(ctx)
.expect("Should be able to get dimensions of Text.");
let album_dimensions = self
.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 {
@ -628,7 +696,7 @@ impl MPDDisplay {
DrawMode::fill(), DrawMode::fill(),
Rect { Rect {
x: TEXT_X_OFFSET, x: TEXT_X_OFFSET,
y: filename_y, y: self.cached_filename_y,
w: filename_dimensions.w, w: filename_dimensions.w,
h: filename_dimensions.h, h: filename_dimensions.h,
}, },
@ -640,7 +708,7 @@ impl MPDDisplay {
DrawMode::fill(), DrawMode::fill(),
Rect { Rect {
x: TEXT_X_OFFSET, x: TEXT_X_OFFSET,
y: album_y, y: self.cached_album_y,
w: album_dimensions.w, w: album_dimensions.w,
h: album_dimensions.h, h: album_dimensions.h,
}, },
@ -652,7 +720,7 @@ impl MPDDisplay {
DrawMode::fill(), DrawMode::fill(),
Rect { Rect {
x: TEXT_X_OFFSET, x: TEXT_X_OFFSET,
y: artist_y, y: self.cached_artist_y,
w: artist_dimensions.w, w: artist_dimensions.w,
h: artist_dimensions.h, h: artist_dimensions.h,
}, },
@ -664,25 +732,26 @@ impl MPDDisplay {
DrawMode::fill(), DrawMode::fill(),
Rect { Rect {
x: TEXT_X_OFFSET, x: TEXT_X_OFFSET,
y: title_y, y: self.cached_title_y,
w: title_dimensions.w, w: title_dimensions.w,
h: title_dimensions.h, h: title_dimensions.h,
}, },
Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity), Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity),
)?; )?;
} }
let mesh: Mesh = mesh_builder if self.mpd_play_state == MPDPlayState::Playing {
.rectangle( mesh_builder.rectangle(
DrawMode::fill(), DrawMode::fill(),
Rect { Rect {
x: TEXT_X_OFFSET, x: TEXT_X_OFFSET,
y: timer_y, y: self.cached_timer_y,
w: timer_dimensions.w, w: timer_dimensions.w,
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);
@ -695,8 +764,7 @@ 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: {}", "Failed to initialize MPDHandler: {mpd_handler_error}"
mpd_handler_error
))); )));
} else { } else {
return Err(GameError::EventLoopError( return Err(GameError::EventLoopError(
@ -752,6 +820,8 @@ 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()
@ -759,7 +829,7 @@ impl EventHandler for MPDDisplay {
.dirty_flag .dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.swap(false, Ordering::Relaxed) .swap(false, Ordering::AcqRel)
{ {
log( log(
"dirty_flag cleared, acquiring shared data...", "dirty_flag cleared, acquiring shared data...",
@ -807,7 +877,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Relaxed); .store(true, Ordering::Release);
} }
if !shared.artist.is_empty() { if !shared.artist.is_empty() {
if shared.artist != self.artist_string_cache { if shared.artist != self.artist_string_cache {
@ -822,7 +892,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Relaxed); .store(true, Ordering::Release);
} }
if !shared.album.is_empty() { if !shared.album.is_empty() {
if shared.album != self.album_string_cache { if shared.album != self.album_string_cache {
@ -837,7 +907,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Relaxed); .store(true, Ordering::Release);
} }
if !shared.filename.is_empty() { if !shared.filename.is_empty() {
if shared.filename != self.filename_string_cache { if shared.filename != self.filename_string_cache {
@ -856,7 +926,7 @@ impl EventHandler for MPDDisplay {
self.dirty_flag self.dirty_flag
.as_ref() .as_ref()
.unwrap() .unwrap()
.store(true, Ordering::Relaxed); .store(true, Ordering::Release);
} }
self.timer = shared.pos; self.timer = shared.pos;
self.length = shared.length; self.length = shared.length;
@ -882,102 +952,111 @@ impl EventHandler for MPDDisplay {
} }
} }
let delta = timer::delta(ctx); let delta = ctx.time.delta();
self.timer += delta.as_secs_f64(); self.timer += delta.as_secs_f64();
let timer_diff = seconds_to_time(self.length - self.timer); let mut 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();
self.timer_text = Text::new(timer_diff); self.timer_text = Text::new(timer_diff);
self.timer_text.set_font( self.timer_text.set_scale(PxScale {
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 {
self.timer_text_len = timer_diff_len;
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> {
graphics::clear(ctx, Color::BLACK); let mut canvas = graphics::Canvas::from_frame(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()
{ {
self.album_art.as_ref().unwrap().draw( canvas.draw(
ctx, self.album_art.as_ref().unwrap(),
DrawParam { DrawParam {
trans: self.album_art_draw_transform.unwrap(), transform: self.album_art_draw_transform.unwrap(),
..Default::default() ..Default::default()
}, },
)?; );
} }
if !self.hide_text { if !self.hide_text {
self.notice_text.draw(ctx, DrawParam::default())?; canvas.draw(&self.notice_text, 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 {
mesh.draw(ctx, DrawParam::default())?; canvas.draw(mesh, DrawParam::default());
} }
if !self.opts.disable_show_filename { if !self.opts.disable_show_filename {
self.filename_text.draw( canvas.draw(
ctx, &self.filename_text,
DrawParam { DrawParam {
trans: self.filename_transform, transform: self.filename_transform,
..Default::default() ..Default::default()
}, },
)?; );
} }
if !self.opts.disable_show_album { if !self.opts.disable_show_album {
self.album_text.draw( canvas.draw(
ctx, &self.album_text,
DrawParam { DrawParam {
trans: self.album_transform, transform: self.album_transform,
..Default::default() ..Default::default()
}, },
)?; );
} }
if !self.opts.disable_show_artist { if !self.opts.disable_show_artist {
self.artist_text.draw( canvas.draw(
ctx, &self.artist_text,
DrawParam { DrawParam {
trans: self.artist_transform, transform: self.artist_transform,
..Default::default() ..Default::default()
}, },
)?; );
} }
if !self.opts.disable_show_title { if !self.opts.disable_show_title {
self.title_text.draw( canvas.draw(
ctx, &self.title_text,
DrawParam { DrawParam {
trans: self.title_transform, transform: self.title_transform,
..Default::default() ..Default::default()
}, },
)?; );
} }
if self.mpd_play_state == MPDPlayState::Playing { if self.mpd_play_state == MPDPlayState::Playing {
self.timer_text.draw( canvas.draw(
ctx, &self.timer_text,
DrawParam { DrawParam {
trans: self.timer_transform, transform: self.timer_transform,
..Default::default() ..Default::default()
}, },
)?; );
} }
} }
} }
graphics::present(ctx) canvas.finish(ctx)
} }
fn text_input_event(&mut self, _ctx: &mut Context, character: char) { fn text_input_event(&mut self, _ctx: &mut Context, character: char) -> Result<(), GameError> {
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);
@ -988,17 +1067,18 @@ 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,
keycode: event::KeyCode, input: KeyInput,
_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 keycode == event::KeyCode::Back { if input.keycode == Some(keyboard::KeyCode::Back) {
let s: String = self.notice_text.contents(); let s: String = self.notice_text.contents();
if s.ends_with('*') { if s.ends_with('*') {
@ -1008,28 +1088,34 @@ 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 keycode == event::KeyCode::Return { } else if input.keycode == Some(keyboard::KeyCode::Return) {
self.password_entered = true; self.password_entered = true;
} }
} else if keycode == event::KeyCode::H { } else if input.keycode == Some(keyboard::KeyCode::H) {
self.hide_text = true; self.hide_text = true;
} }
Ok(())
} }
fn key_up_event( fn key_up_event(&mut self, _ctx: &mut Context, input: KeyInput) -> Result<(), GameError> {
&mut self, if input.keycode == Some(keyboard::KeyCode::H) {
_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(&mut self, ctx: &mut Context, _width: f32, _height: f32) { fn resize_event(
&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,52 +4,56 @@ 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::filesystem::mount; use ggez::input::keyboard::{self, KeyInput};
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(StructOpt, Debug, Clone)] #[derive(Parser, Debug, Clone)]
#[structopt(name = "mpd_info_screen")] #[command(author, version, about, long_about = None)]
pub struct Opt { pub struct Opt {
host: Ipv4Addr, host: Ipv4Addr,
#[structopt(default_value = "6600")] #[arg(default_value = "6600")]
port: u16, port: u16,
#[structopt(short = "p")] #[arg(short = 'p')]
password: Option<String>, password: Option<String>,
#[structopt(long = "disable-show-title", help = "disable title display")] #[arg(long = "disable-show-title", help = "disable title display")]
disable_show_title: bool, disable_show_title: bool,
#[structopt(long = "disable-show-artist", help = "disable artist display")] #[arg(long = "disable-show-artist", help = "disable artist display")]
disable_show_artist: bool, disable_show_artist: bool,
#[structopt(long = "disable-show-album", help = "disable album display")] #[arg(long = "disable-show-album", help = "disable album display")]
disable_show_album: bool, disable_show_album: bool,
#[structopt(long = "disable-show-filename", help = "disable filename display")] #[arg(long = "disable-show-filename", help = "disable filename display")]
disable_show_filename: bool, disable_show_filename: bool,
#[structopt(long = "pprompt", help = "input password via prompt")] #[arg(long = "disable-show-percentage", help = "disable percentage display")]
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,
#[structopt( #[arg(long = "pfile", help = "read password from file")]
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,
#[structopt( #[arg(short = 'l', long = "log-level", default_value = "error")]
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,
#[structopt( #[arg(
short, short,
long, long,
help = "sets the opacity of the text background (0-255)", help = "sets the opacity of the text background (0-255)",
@ -59,9 +63,35 @@ pub struct Opt {
} }
fn main() -> Result<(), String> { fn main() -> Result<(), String> {
let opt = Opt::from_args(); let mut opt = Opt::parse();
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(),
@ -69,20 +99,21 @@ 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
mount(&mut ctx, &PathBuf::from("/"), true); ctx.fs.mount(&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 { if !ctx.continuing || ctx.quit_requested {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} }
@ -94,7 +125,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 => event::quit(ctx), event::winit_event::WindowEvent::CloseRequested => ctx.request_quit(),
event::winit_event::WindowEvent::ModifiersChanged(state) => { event::winit_event::WindowEvent::ModifiersChanged(state) => {
modifiers_state = state; modifiers_state = state;
} }
@ -108,40 +139,37 @@ fn main() -> Result<(), String> {
}, },
is_synthetic: _, is_synthetic: _,
} => { } => {
if keycode == event::KeyCode::Escape { if keycode == keyboard::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, keycode, modifiers_state.into(), false); display.key_down_event(ctx, ki, false).ok();
} else { } else {
display.key_up_event(ctx, keycode, modifiers_state.into()); display.key_up_event(ctx, ki).ok();
} }
} }
event::winit_event::WindowEvent::Resized(phys_size) => { event::winit_event::WindowEvent::Resized(phys_size) => {
graphics::set_screen_coordinates( display
ctx, .resize_event(ctx, phys_size.width as f32, phys_size.height as f32)
Rect { .ok();
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); display.text_input_event(ctx, ch).ok();
} }
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.timer_context.tick(); ctx.time.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() {
@ -149,21 +177,24 @@ 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_context.reset_delta(); ctx.mouse.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,6 +250,8 @@ 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(),
@ -266,7 +268,7 @@ impl MPDHandler {
password, password,
error_text: String::new(), error_text: String::new(),
can_authenticate: true, can_authenticate: true,
is_authenticated: false, 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,
@ -274,9 +276,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() - Duration::from_secs(10), song_title_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(),
song_pos_get_time: Instant::now() - Duration::from_secs(10), song_pos_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(),
song_length_get_time: Instant::now() - Duration::from_secs(10), song_length_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(),
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)),
@ -330,7 +332,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::Relaxed)); return Ok(write_lock.dirty_flag.swap(false, Ordering::AcqRel));
} }
Err(()) Err(())
@ -370,7 +372,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::Relaxed); read_handle.stop_flag.store(true, Ordering::Release);
Ok(()) Ok(())
} }
@ -426,20 +428,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::Relaxed) || !read_handle.can_authenticate { if read_handle.stop_flag.load(Ordering::Acquire) || !read_handle.can_authenticate {
break 'main; break 'main;
} }
} }
@ -477,7 +479,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(());
} }
@ -509,7 +511,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
} }
} else { } else {
write_handle.art_data.extend_from_slice(&buf_vec); write_handle.art_data.extend_from_slice(&buf_vec);
@ -524,7 +526,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
} }
break 'handle_buf; break 'handle_buf;
} }
@ -559,7 +561,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
log( log(
"No embedded album art", "No embedded album art",
LogState::Warning, LogState::Warning,
@ -570,7 +572,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
log( log(
"No album art in dir", "No album art in dir",
LogState::Warning, LogState::Warning,
@ -587,13 +589,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
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::Relaxed); write_handle.stop_flag.store(true, Ordering::Release);
} }
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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
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;
@ -602,7 +604,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
log( log(
"Failed to get readpicture", "Failed to get readpicture",
LogState::Warning, LogState::Warning,
@ -613,7 +615,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
log( log(
"Failed to get albumart", "Failed to get albumart",
LogState::Warning, LogState::Warning,
@ -649,9 +651,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,
); );
@ -675,13 +677,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
write_handle.song_pos_get_time = Instant::now(); write_handle.song_pos_get_time = Instant::now();
} else { } else {
log( log(
@ -694,7 +696,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
write_handle.song_length_get_time = Instant::now(); write_handle.song_length_get_time = Instant::now();
} else { } else {
log( log(
@ -707,7 +709,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
} else { } else {
log( log(
"Failed to parse album art byte size", "Failed to parse album art byte size",
@ -736,7 +738,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,
); );
@ -760,7 +762,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::Relaxed); write_handle.dirty_flag.store(true, Ordering::Release);
if got_mpd_state == MPDPlayState::Playing { if got_mpd_state == MPDPlayState::Playing {
write_handle.error_text.clear(); write_handle.error_text.clear();
} }
@ -799,12 +801,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 {}\n", p).as_bytes()); .write(format!("password {p}\n").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,
); );
@ -820,7 +822,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,
); );
@ -836,7 +838,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,
); );
@ -848,14 +850,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.stream.write( let write_result = write_handle
format!("readpicture \"{}\" {}\n", title, art_data_length).as_bytes(), .stream
); .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,
); );
@ -863,12 +865,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 \"{}\" {}\n", title, art_data_length).as_bytes()); .write(format!("albumart \"{title}\" {art_data_length}\n").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,8 +67,7 @@ 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; {:?})", "Failed to FcFontMatch (FcResult is not FcResultMatch; {result:?})"
result
)); ));
} }
} else if result_pattern.is_null() { } else if result_pattern.is_null() {