Compare commits
63 commits
Author | SHA1 | Date | |
---|---|---|---|
3e1bc8c6ac | |||
47db86ba59 | |||
ab61924e5f | |||
3e95544fb3 | |||
5d0116e9de | |||
578865abe7 | |||
ca21d5c640 | |||
075754fcd0 | |||
c145bdbd9c | |||
502795c6cf | |||
e8b170e0e2 | |||
d276482c8b | |||
08b78467b5 | |||
0ddd33a898 | |||
8ebeac0499 | |||
8e6305d934 | |||
1b92da1ab2 | |||
00240e4205 | |||
e4cdccce63 | |||
015da43d1b | |||
773587b664 | |||
2f23e63349 | |||
8cd599aadd | |||
2865352f7a | |||
180898103a | |||
d90c86c894 | |||
f42fadd403 | |||
f2f58047a5 | |||
d457098f2a | |||
d0beff5a98 | |||
7a860e323a | |||
ef234f0ec0 | |||
391949cde6 | |||
f66880f13d | |||
83bb20c246 | |||
dd868969cc | |||
1dc28d7b07 | |||
f7ffa62e02 | |||
bcea959381 | |||
aa6fb750e7 | |||
a1706913e6 | |||
e28d20a5da | |||
4653399fe9 | |||
e0be69df81 | |||
8643542b7a | |||
e9e57c9dff | |||
d1590bee0a | |||
56f6784892 | |||
a223d8b530 | |||
fad82f6448 | |||
934aa1a610 | |||
6d400cd7c7 | |||
c902f0fcf3 | |||
e025a48735 | |||
74a84d9f7a | |||
f2f93f5393 | |||
1a623f451d | |||
ef07d7936a | |||
7f6a24545c | |||
34d3e47863 | |||
a9441536c4 | |||
a1fe8c3120 | |||
ce45a40df5 |
10 changed files with 2702 additions and 1782 deletions
3783
Cargo.lock
generated
3783
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
14
Cargo.toml
14
Cargo.toml
|
@ -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"]
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||||
|
|
65
README.md
65
README.md
|
@ -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
|
||||||
|
|
||||||
|
|
2
build.rs
2
build.rs
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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,14 +9,12 @@ 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)
|
||||||
|
@ -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}");
|
||||||
}
|
}
|
||||||
|
|
394
src/display.rs
394
src/display.rs
|
@ -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}"));
|
||||||
reader.decode().map_err(|e| {
|
if let Ok(reader) = guessed_reader {
|
||||||
format!(
|
reader.decode().map_err(|e| {
|
||||||
"Error: Failed to decode album art image (guessed format): {}",
|
format!("Error: Failed to decode album art image (guessed format): {e}")
|
||||||
e
|
})
|
||||||
)
|
} else {
|
||||||
})
|
// Convert Ok(_) to Ok(DynamicImage) which will never be used
|
||||||
|
// since the if statement covers it.
|
||||||
|
guessed_reader.map(|_| -> DynamicImage { unreachable!() })
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ImageReader::with_format(Cursor::new(&image_ref), image_format)
|
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(),
|
x: self.timer_x,
|
||||||
PxScale {
|
y: self.timer_y,
|
||||||
x: self.timer_x,
|
});
|
||||||
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
119
src/main.rs
119
src/main.rs
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in a new issue