Compare commits

..

6 commits

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

3240
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,21 @@
[package]
name = "mpd_info_screen"
version = "0.4.9"
edition = "2021"
version = "0.4.4"
edition = "2018"
description = "Displays info on currently playing music from an MPD daemon"
license = "MIT"
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
[dependencies]
clap = { version = "4.4", features = ["derive"] }
structopt = "0.3"
image = "0.24"
ggez = "0.9.3"
ggez = "0.7"
freetype = { version = "0.7", optional = true }
wgpu = "0.16"
[build-dependencies]
bindgen = { version = "0.69", optional = true }
bindgen = { version = "0.53", optional = true }
[features]
unicode_support = ["dep:freetype", "dep:bindgen"]

View file

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

View file

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

View file

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

View file

@ -1,16 +1,13 @@
use crate::debug_log::{self, log};
use crate::mpd_handler::{InfoFromShared, MPDHandler, MPDHandlerState, MPDPlayState};
use crate::Opt;
use ggez::event::EventHandler;
use ggez::event::{self, EventHandler};
use ggez::graphics::{
self, Color, DrawMode, DrawParam, Drawable, Image, Mesh, MeshBuilder, PxScale, Rect, Text,
TextFragment, Transform,
self, Color, DrawMode, DrawParam, Drawable, Font, Image, Mesh, MeshBuilder, PxScale, Rect,
Text, TextFragment, Transform,
};
use ggez::input::keyboard::{self, KeyInput};
use ggez::mint::Vector2;
use ggez::{Context, GameError, GameResult};
use ggez::{timer, Context, GameError, GameResult};
use image::io::Reader as ImageReader;
use image::DynamicImage;
use std::io::Cursor;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
@ -58,7 +55,7 @@ fn seconds_to_time(seconds: f64) -> String {
#[allow(clippy::ptr_arg)]
fn string_to_text(
string: String,
_loaded_fonts: &mut Vec<(PathBuf, String)>,
_loaded_fonts: &mut Vec<(PathBuf, Font)>,
_ctx: &mut Context,
) -> Text {
Text::new(TextFragment::from(string))
@ -67,7 +64,7 @@ fn string_to_text(
#[cfg(feature = "unicode_support")]
fn string_to_text(
string: String,
loaded_fonts: &mut Vec<(PathBuf, String)>,
loaded_fonts: &mut Vec<(PathBuf, Font)>,
ctx: &mut Context,
) -> Text {
use super::unicode_support;
@ -82,7 +79,7 @@ fn string_to_text(
}
let find_font =
|c: char, loaded_fonts: &mut Vec<(PathBuf, String)>, ctx: &mut Context| -> Option<usize> {
|c: char, loaded_fonts: &mut Vec<(PathBuf, Font)>, ctx: &mut Context| -> Option<usize> {
for (idx, (path, _)) in loaded_fonts.iter().enumerate() {
let result = unicode_support::font_has_char(c, path);
if result.is_ok() && result.unwrap() {
@ -92,16 +89,9 @@ fn string_to_text(
let find_result = unicode_support::get_matching_font_from_char(c);
if let Ok(path) = find_result {
let new_font = ggez::graphics::FontData::from_path(ctx, &path);
let new_font = Font::new(ctx, &path);
if let Ok(font) = new_font {
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));
loaded_fonts.push((path, font));
return Some(loaded_fonts.len() - 1);
} else {
log(
@ -112,7 +102,7 @@ fn string_to_text(
}
} else {
log(
format!("Failed to find font for {c}"),
format!("Failed to find font for {}", c),
debug_log::LogState::Error,
debug_log::LogLevel::Error,
);
@ -142,13 +132,13 @@ fn string_to_text(
text.add(current_fragment);
current_fragment = Default::default();
}
let (_, font) = &loaded_fonts[idx];
current_fragment.font = Some(font.clone());
let (_, font) = loaded_fonts[idx];
current_fragment.font = Some(font);
}
current_fragment.text.push(c);
} else if let Some(idx) = idx_opt {
let font = &loaded_fonts[idx].1;
if let Some(current_font) = current_fragment.font.as_ref() {
let font = loaded_fonts[idx].1;
if let Some(current_font) = current_fragment.font {
if current_font == font {
current_fragment.text.push(c);
} else {
@ -157,17 +147,17 @@ fn string_to_text(
current_fragment = Default::default();
}
current_fragment.text.push(c);
current_fragment.font = Some(font.clone());
current_fragment.font = Some(font);
}
} else if current_fragment.text.is_empty() {
current_fragment.text.push(c);
current_fragment.font = Some(font.clone());
current_fragment.font = Some(font);
} else {
text.add(current_fragment);
current_fragment = Default::default();
current_fragment.text.push(c);
current_fragment.font = Some(font.clone());
current_fragment.font = Some(font);
}
} else {
if !current_fragment.text.is_empty() && current_fragment.font.is_some() {
@ -228,7 +218,7 @@ pub struct MPDDisplay {
hide_text: bool,
tried_album_art_in_dir: bool,
mpd_play_state: MPDPlayState,
loaded_fonts: Vec<(PathBuf, String)>,
loaded_fonts: Vec<(PathBuf, Font)>,
}
impl MPDDisplay {
@ -240,7 +230,7 @@ impl MPDDisplay {
is_initialized: false,
is_authenticated: false,
notice_text: Text::default(),
poll_instant: Instant::now().checked_sub(POLL_TIME).unwrap(),
poll_instant: Instant::now() - POLL_TIME,
shared: None,
password_entered: false,
dirty_flag: None,
@ -313,24 +303,24 @@ impl MPDDisplay {
fn get_album_art_transform(&mut self, ctx: &mut Context, fill_scaled: bool) {
if fill_scaled {
if let Some(image) = &self.album_art {
let drawable_size = ctx.gfx.drawable_size();
let art_rect: Rect = image.dimensions(ctx).expect("Image should have dimensions");
let screen_coords: Rect = graphics::screen_coordinates(ctx);
let art_rect: Rect = image.dimensions();
// try to fit to width first
let mut x_scale = drawable_size.0 / art_rect.w;
let mut x_scale = screen_coords.w / art_rect.w;
let mut y_scale = x_scale;
let mut new_width = art_rect.w * x_scale;
let mut new_height = art_rect.h * y_scale;
if new_height > drawable_size.1.abs() {
if new_height > screen_coords.h.abs() {
// fit to height instead
y_scale = drawable_size.1.abs() / art_rect.h;
y_scale = screen_coords.h.abs() / art_rect.h;
x_scale = y_scale;
new_width = art_rect.w * x_scale;
new_height = art_rect.h * y_scale;
}
let offset_x: f32 = (drawable_size.0.abs() - new_width) / 2.0f32;
let offset_y: f32 = (drawable_size.1.abs() - new_height) / 2.0f32;
let offset_x: f32 = (screen_coords.w.abs() - new_width) / 2.0f32;
let offset_y: f32 = (screen_coords.h.abs() - new_height) / 2.0f32;
self.album_art_draw_transform = Some(Transform::Values {
dest: [offset_x, offset_y].into(),
@ -342,10 +332,10 @@ impl MPDDisplay {
self.album_art_draw_transform = None;
}
} else if let Some(image) = &self.album_art {
let drawable_size = ctx.gfx.drawable_size();
let art_rect: Rect = image.dimensions(ctx).expect("Image should have dimensions");
let offset_x: f32 = (drawable_size.0.abs() - art_rect.w.abs()) / 2.0f32;
let offset_y: f32 = (drawable_size.1.abs() - art_rect.h.abs()) / 2.0f32;
let screen_coords: Rect = graphics::screen_coordinates(ctx);
let art_rect: Rect = image.dimensions();
let offset_x: f32 = (screen_coords.w.abs() - art_rect.w.abs()) / 2.0f32;
let offset_y: f32 = (screen_coords.h.abs() - art_rect.h.abs()) / 2.0f32;
self.album_art_draw_transform = Some(Transform::Values {
dest: [offset_x, offset_y].into(),
rotation: 0.0f32,
@ -420,23 +410,20 @@ impl MPDDisplay {
}
let img_result = if is_unknown_format {
let reader = ImageReader::new(Cursor::new(image_ref));
let guessed_reader = reader
let mut reader = ImageReader::new(Cursor::new(&image_ref));
reader = reader
.with_guessed_format()
.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| {
format!("Error: Failed to decode album art image (guessed format): {e}")
})
} else {
// Convert Ok(_) to Ok(DynamicImage) which will never be used
// since the if statement covers it.
guessed_reader.map(|_| -> DynamicImage { unreachable!() })
}
.map_err(|e| format!("Error: Failed to guess format of album art image: {}", e))?;
reader.decode().map_err(|e| {
format!(
"Error: Failed to decode album art image (guessed format): {}",
e
)
})
} else {
ImageReader::with_format(Cursor::new(image_ref), image_format)
ImageReader::with_format(Cursor::new(&image_ref), image_format)
.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 {
return try_second_art_fetch_method(
@ -448,13 +435,13 @@ impl MPDDisplay {
}
let img = img_result?;
let rgba8 = img.to_rgba8();
let ggez_img = Image::from_pixels(
let ggez_img = Image::from_rgba8(
ctx,
rgba8.width() as u16,
rgba8.height() as u16,
rgba8.as_raw(),
wgpu::TextureFormat::Rgba8UnormSrgb,
rgba8.width(),
rgba8.height(),
);
)
.map_err(|e| format!("Error: Failed to load album art image in ggez Image: {}", e))?;
self.album_art = Some(ggez_img);
@ -462,14 +449,14 @@ impl MPDDisplay {
}
fn refresh_text_transforms(&mut self, ctx: &mut Context) -> GameResult<()> {
let drawable_size = ctx.gfx.drawable_size();
let screen_coords: Rect = graphics::screen_coordinates(ctx);
let text_height_limit = TEXT_HEIGHT_SCALE * drawable_size.1.abs();
let album_height_limit = ALBUM_HEIGHT_SCALE * drawable_size.1.abs();
let artist_height_limit = ARTIST_HEIGHT_SCALE * drawable_size.1.abs();
let timer_height = TIMER_HEIGHT_SCALE * drawable_size.1.abs();
let text_height_limit = TEXT_HEIGHT_SCALE * screen_coords.h.abs();
let album_height_limit = ALBUM_HEIGHT_SCALE * screen_coords.h.abs();
let artist_height_limit = ARTIST_HEIGHT_SCALE * screen_coords.h.abs();
let timer_height = TIMER_HEIGHT_SCALE * screen_coords.h.abs();
let mut offset_y: f32 = drawable_size.1;
let mut offset_y: f32 = screen_coords.h;
let set_transform = |text: &mut Text,
transform: &mut Transform,
@ -482,7 +469,8 @@ impl MPDDisplay {
timer_y: &mut f32| {
let mut current_x = INIT_FONT_SIZE_X;
let mut current_y = INIT_FONT_SIZE_Y;
let mut width_height: Vector2<f32> = Vector2 { x: 0.0, y: 0.0 };
let mut width: f32;
let mut height: f32 = 0.0;
let mut iteration_count: u8 = 0;
loop {
iteration_count += 1;
@ -496,13 +484,12 @@ impl MPDDisplay {
y: current_y,
});
}
width_height = text
.measure(ctx)
.expect("Should be able to get width/height of text.");
width = text.width(ctx);
height = text.height(ctx);
if is_string {
if drawable_size.0 < width_height.x
|| width_height.y
if screen_coords.w < width
|| height
>= (if is_artist {
artist_height_limit
} else if is_album {
@ -514,7 +501,7 @@ impl MPDDisplay {
current_x *= DECREASE_AMT;
current_y *= DECREASE_AMT;
continue;
} else if drawable_size.0 * MIN_WIDTH_RATIO > width_height.x {
} else if screen_coords.w * MIN_WIDTH_RATIO > width {
current_x *= INCREASE_AMT;
current_y *= INCREASE_AMT;
continue;
@ -522,7 +509,7 @@ impl MPDDisplay {
break;
}
} else {
let diff_scale_y = current_y / width_height.y * timer_height;
let diff_scale_y = current_y / height * timer_height;
let current_x = current_x * diff_scale_y / current_y;
for fragment in text.fragments_mut() {
fragment.scale = Some(PxScale {
@ -533,23 +520,20 @@ impl MPDDisplay {
*timer_x = current_x;
*timer_y = diff_scale_y;
// width = text.width(ctx); // not really used after this
width_height.y = text
.measure(ctx)
.expect("Should be able to get width/height of text.")
.y;
height = text.height(ctx);
break;
}
}
*y = *offset_y - width_height.y;
*y = *offset_y - height;
*transform = Transform::Values {
dest: [TEXT_X_OFFSET, *offset_y - width_height.y].into(),
dest: [TEXT_X_OFFSET, *offset_y - height].into(),
rotation: 0.0,
scale: [1.0, 1.0].into(),
offset: [0.0, 0.0].into(),
};
*offset_y -= width_height.y + TEXT_OFFSET_Y_SPACING;
*offset_y -= height + TEXT_OFFSET_Y_SPACING;
};
if !self.filename_text.contents().is_empty() && !self.opts.disable_show_filename {
@ -644,26 +628,11 @@ impl MPDDisplay {
}
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 filename_dimensions = self.filename_text.dimensions(ctx);
let album_dimensions = self.album_text.dimensions(ctx);
let artist_dimensions = self.artist_text.dimensions(ctx);
let title_dimensions = self.title_text.dimensions(ctx);
let timer_dimensions = self.timer_text.dimensions(ctx);
let mut mesh_builder: MeshBuilder = MeshBuilder::new();
if !self.opts.disable_show_filename {
@ -714,21 +683,18 @@ impl MPDDisplay {
Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity),
)?;
}
let mesh: Mesh = Mesh::from_data(
ctx,
mesh_builder
.rectangle(
DrawMode::fill(),
Rect {
x: TEXT_X_OFFSET,
y: self.cached_timer_y,
w: timer_dimensions.w,
h: timer_dimensions.h,
},
Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity),
)?
.build(),
);
let mesh: Mesh = mesh_builder
.rectangle(
DrawMode::fill(),
Rect {
x: TEXT_X_OFFSET,
y: self.cached_timer_y,
w: timer_dimensions.w,
h: timer_dimensions.h,
},
Color::from_rgba(0, 0, 0, self.opts.text_bg_opacity),
)?
.build(ctx)?;
self.text_bg_mesh = Some(mesh);
@ -741,7 +707,8 @@ impl EventHandler for MPDDisplay {
if !self.is_valid {
if let Err(mpd_handler_error) = &self.mpd_handler {
return Err(GameError::EventLoopError(format!(
"Failed to initialize MPDHandler: {mpd_handler_error}"
"Failed to initialize MPDHandler: {}",
mpd_handler_error
)));
} else {
return Err(GameError::EventLoopError(
@ -927,15 +894,18 @@ impl EventHandler for MPDDisplay {
}
}
let delta = ctx.time.delta();
let delta = timer::delta(ctx);
self.timer += delta.as_secs_f64();
let timer_diff = seconds_to_time(self.length - self.timer);
let timer_diff_len = timer_diff.len();
self.timer_text = Text::new(timer_diff);
self.timer_text.set_scale(PxScale {
x: self.timer_x,
y: self.timer_y,
});
self.timer_text.set_font(
Font::default(),
PxScale {
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)?;
@ -945,86 +915,86 @@ impl EventHandler for MPDDisplay {
}
fn draw(&mut self, ctx: &mut ggez::Context) -> Result<(), GameError> {
let mut canvas = graphics::Canvas::from_frame(ctx, Color::BLACK);
graphics::clear(ctx, Color::BLACK);
if self.mpd_play_state != MPDPlayState::Stopped
&& self.album_art.is_some()
&& self.album_art_draw_transform.is_some()
{
canvas.draw(
self.album_art.as_ref().unwrap(),
self.album_art.as_ref().unwrap().draw(
ctx,
DrawParam {
transform: self.album_art_draw_transform.unwrap(),
trans: self.album_art_draw_transform.unwrap(),
..Default::default()
},
);
)?;
}
if !self.hide_text {
canvas.draw(&self.notice_text, DrawParam::default());
self.notice_text.draw(ctx, DrawParam::default())?;
if self.mpd_play_state != MPDPlayState::Stopped && self.is_valid && self.is_initialized
{
if let Some(mesh) = &self.text_bg_mesh {
canvas.draw(mesh, DrawParam::default());
mesh.draw(ctx, DrawParam::default())?;
}
if !self.opts.disable_show_filename {
canvas.draw(
&self.filename_text,
self.filename_text.draw(
ctx,
DrawParam {
transform: self.filename_transform,
trans: self.filename_transform,
..Default::default()
},
);
)?;
}
if !self.opts.disable_show_album {
canvas.draw(
&self.album_text,
self.album_text.draw(
ctx,
DrawParam {
transform: self.album_transform,
trans: self.album_transform,
..Default::default()
},
);
)?;
}
if !self.opts.disable_show_artist {
canvas.draw(
&self.artist_text,
self.artist_text.draw(
ctx,
DrawParam {
transform: self.artist_transform,
trans: self.artist_transform,
..Default::default()
},
);
)?;
}
if !self.opts.disable_show_title {
canvas.draw(
&self.title_text,
self.title_text.draw(
ctx,
DrawParam {
transform: self.title_transform,
trans: self.title_transform,
..Default::default()
},
);
)?;
}
if self.mpd_play_state == MPDPlayState::Playing {
canvas.draw(
&self.timer_text,
self.timer_text.draw(
ctx,
DrawParam {
transform: self.timer_transform,
trans: self.timer_transform,
..Default::default()
},
);
)?;
}
}
}
canvas.finish(ctx)
graphics::present(ctx)
}
fn text_input_event(&mut self, _ctx: &mut Context, character: char) -> Result<(), GameError> {
fn text_input_event(&mut self, _ctx: &mut Context, character: char) {
if !self.is_initialized && self.opts.enable_prompt_password && !character.is_control() {
if self.opts.password.is_none() {
let s = String::from(character);
@ -1035,18 +1005,17 @@ impl EventHandler for MPDDisplay {
self.notice_text.add('*');
}
}
Ok(())
}
fn key_down_event(
&mut self,
_ctx: &mut Context,
input: KeyInput,
keycode: event::KeyCode,
_keymods: event::KeyMods,
_repeat: bool,
) -> Result<(), GameError> {
) {
if !self.is_initialized && self.opts.enable_prompt_password {
if input.keycode == Some(keyboard::KeyCode::Back) {
if keycode == event::KeyCode::Back {
let s: String = self.notice_text.contents();
if s.ends_with('*') {
@ -1056,34 +1025,28 @@ impl EventHandler for MPDDisplay {
if let Some(input_p) = &mut self.opts.password {
input_p.pop();
}
} else if input.keycode == Some(keyboard::KeyCode::Return) {
} else if keycode == event::KeyCode::Return {
self.password_entered = true;
}
} else if input.keycode == Some(keyboard::KeyCode::H) {
} else if keycode == event::KeyCode::H {
self.hide_text = true;
}
Ok(())
}
fn key_up_event(&mut self, _ctx: &mut Context, input: KeyInput) -> Result<(), GameError> {
if input.keycode == Some(keyboard::KeyCode::H) {
fn key_up_event(
&mut self,
_ctx: &mut Context,
keycode: event::KeyCode,
_keymods: event::KeyMods,
) {
if keycode == event::KeyCode::H {
self.hide_text = false;
}
Ok(())
}
fn resize_event(
&mut self,
ctx: &mut Context,
_width: f32,
_height: f32,
) -> Result<(), GameError> {
fn resize_event(&mut self, ctx: &mut Context, _width: f32, _height: f32) {
self.get_album_art_transform(ctx, !self.opts.do_not_fill_scale_album_art);
self.refresh_text_transforms(ctx)
.expect("Failed to set text transforms");
Ok(())
}
}

View file

@ -7,50 +7,49 @@ mod unicode_support;
use ggez::conf::{WindowMode, WindowSetup};
use ggez::event::winit_event::{ElementState, KeyboardInput, ModifiersState};
use ggez::event::{self, ControlFlow, EventHandler};
use ggez::input::keyboard::{self, KeyInput};
use ggez::filesystem::mount;
use ggez::graphics::{self, Rect};
use ggez::{ContextBuilder, GameError};
use std::fs::File;
use std::io::Read;
use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};
use clap::Parser;
use structopt::StructOpt;
use debug_log::log;
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
#[derive(StructOpt, Debug, Clone)]
#[structopt(name = "mpd_info_screen")]
pub struct Opt {
host: Ipv4Addr,
#[arg(default_value = "6600")]
#[structopt(default_value = "6600")]
port: u16,
#[arg(short = 'p')]
#[structopt(short = "p")]
password: Option<String>,
#[arg(long = "disable-show-title", help = "disable title display")]
#[structopt(long = "disable-show-title", help = "disable title display")]
disable_show_title: bool,
#[arg(long = "disable-show-artist", help = "disable artist display")]
#[structopt(long = "disable-show-artist", help = "disable artist display")]
disable_show_artist: bool,
#[arg(long = "disable-show-album", help = "disable album display")]
#[structopt(long = "disable-show-album", help = "disable album display")]
disable_show_album: bool,
#[arg(long = "disable-show-filename", help = "disable filename display")]
#[structopt(long = "disable-show-filename", help = "disable filename display")]
disable_show_filename: bool,
#[arg(long = "pprompt", help = "input password via prompt")]
#[structopt(long = "pprompt", help = "input password via prompt")]
enable_prompt_password: bool,
#[arg(long = "pfile", help = "read password from file")]
password_file: Option<PathBuf>,
#[arg(
#[structopt(
long = "no-scale-fill",
help = "don't scale-fill the album art to the window"
)]
do_not_fill_scale_album_art: bool,
#[arg(
short = 'l',
#[structopt(
short = "l",
long = "log-level",
default_value = "error",
possible_values = &debug_log::LogLevel::variants(),
default_value = "Error",
case_insensitive = true,
)]
log_level: debug_log::LogLevel,
#[arg(
#[structopt(
short,
long,
help = "sets the opacity of the text background (0-255)",
@ -60,26 +59,9 @@ pub struct Opt {
}
fn main() -> Result<(), String> {
let mut opt = Opt::parse();
let opt = Opt::from_args();
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")
.window_setup(WindowSetup {
title: "mpd info screen".into(),
@ -93,7 +75,7 @@ fn main() -> Result<(), String> {
.expect("Failed to create ggez context");
// mount "/" read-only so that fonts can be loaded via absolute paths
ctx.fs.mount(&PathBuf::from("/"), true);
mount(&mut ctx, &PathBuf::from("/"), true);
let mut display = display::MPDDisplay::new(&mut ctx, opt.clone());
@ -112,7 +94,7 @@ fn main() -> Result<(), String> {
event::process_event(ctx, &mut event);
match event {
event::winit_event::Event::WindowEvent { event, .. } => match event {
event::winit_event::WindowEvent::CloseRequested => ctx.request_quit(),
event::winit_event::WindowEvent::CloseRequested => event::quit(ctx),
event::winit_event::WindowEvent::ModifiersChanged(state) => {
modifiers_state = state;
}
@ -126,37 +108,40 @@ fn main() -> Result<(), String> {
},
is_synthetic: _,
} => {
if keycode == keyboard::KeyCode::Escape {
if keycode == event::KeyCode::Escape {
*control_flow = ControlFlow::Exit;
return;
}
let ki = KeyInput {
scancode: 0,
keycode: Some(keycode),
mods: From::from(modifiers_state),
};
if state == ElementState::Pressed {
display.key_down_event(ctx, ki, false).ok();
display.key_down_event(ctx, keycode, modifiers_state.into(), false);
} else {
display.key_up_event(ctx, ki).ok();
display.key_up_event(ctx, keycode, modifiers_state.into());
}
}
event::winit_event::WindowEvent::Resized(phys_size) => {
display
.resize_event(ctx, phys_size.width as f32, phys_size.height as f32)
.ok();
graphics::set_screen_coordinates(
ctx,
Rect {
x: 0.0,
y: 0.0,
w: phys_size.width as f32,
h: phys_size.height as f32,
},
)
.expect("Failed to handle resizing window");
display.resize_event(ctx, phys_size.width as f32, phys_size.height as f32);
}
event::winit_event::WindowEvent::ReceivedCharacter(ch) => {
display.text_input_event(ctx, ch).ok();
display.text_input_event(ctx, ch);
}
x => log(
format!("Other window event fired: {x:?}"),
format!("Other window event fired: {:?}", x),
debug_log::LogState::Verbose,
opt.log_level,
),
},
event::winit_event::Event::MainEventsCleared => {
ctx.time.tick();
ctx.timer_context.tick();
let mut game_result: Result<(), GameError> = display.update(ctx);
if game_result.is_err() {
@ -164,24 +149,21 @@ fn main() -> Result<(), String> {
*control_flow = ControlFlow::Exit;
return;
}
ctx.gfx.begin_frame().unwrap();
game_result = display.draw(ctx);
if game_result.is_err() {
println!("Error draw: {}", game_result.unwrap_err());
*control_flow = ControlFlow::Exit;
return;
}
ctx.gfx.end_frame().unwrap();
ctx.mouse.reset_delta();
ctx.mouse_context.reset_delta();
// sleep to force ~5 fps
thread::sleep(Duration::from_millis(200));
ggez::timer::yield_now();
}
x => log(
format!("Device event fired: {x:?}"),
format!("Device event fired: {:?}", x),
debug_log::LogState::Verbose,
opt.log_level,
),

View file

@ -250,8 +250,6 @@ impl MPDHandler {
)
.map_err(|_| String::from("Failed to get TCP connection (is MPD running?)"))?;
let password_is_empty = password.is_empty();
let s = MPDHandler {
state: Arc::new(RwLock::new(MPDHandlerState {
art_data: Vec::new(),
@ -265,10 +263,10 @@ impl MPDHandler {
current_binary_size: 0,
poll_state: PollState::None,
stream,
password,
password: password.clone(),
error_text: String::new(),
can_authenticate: true,
is_authenticated: password_is_empty,
is_authenticated: password.is_empty(),
can_get_album_art: true,
can_get_album_art_in_dir: true,
can_get_status: true,
@ -276,9 +274,9 @@ impl MPDHandler {
did_check_overtime: false,
force_get_status: false,
force_get_current_song: false,
song_title_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(),
song_pos_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(),
song_length_get_time: Instant::now().checked_sub(Duration::from_secs(10)).unwrap(),
song_title_get_time: Instant::now() - Duration::from_secs(10),
song_pos_get_time: Instant::now() - Duration::from_secs(10),
song_length_get_time: Instant::now() - Duration::from_secs(10),
self_thread: None,
dirty_flag: Arc::new(AtomicBool::new(true)),
stop_flag: Arc::new(AtomicBool::new(false)),
@ -428,13 +426,13 @@ impl MPDHandler {
if let Err(err_string) = self.handler_read_block(&mut buf, &mut saved, &mut saved_str) {
log(
format!("read_block error: {err_string}"),
format!("read_block error: {}", err_string),
LogState::Warning,
log_level,
);
} else if let Err(err_string) = self.handler_write_block() {
log(
format!("write_block error: {err_string}"),
format!("write_block error: {}", err_string),
LogState::Warning,
log_level,
);
@ -479,7 +477,7 @@ impl MPDHandler {
let read_result = write_handle.stream.read(buf);
if let Err(io_err) = read_result {
if io_err.kind() != io::ErrorKind::WouldBlock {
return Err(format!("TCP stream error: {io_err}"));
return Err(format!("TCP stream error: {}", io_err));
} else {
return Ok(());
}
@ -651,9 +649,9 @@ impl MPDHandler {
MPDPlayState::Paused
};
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(
format!("MPD is {got_mpd_state:?}"),
format!("MPD is {:?}", got_mpd_state),
LogState::Warning,
write_handle.log_level,
);
@ -738,7 +736,7 @@ impl MPDHandler {
write_handle.art_data_type = line.split_off(6);
} else {
log(
format!("Got unrecognized/ignored line: {line}"),
format!("Got unrecognized/ignored line: {}", line),
LogState::Warning,
write_handle.log_level,
);
@ -801,12 +799,12 @@ impl MPDHandler {
let p = write_handle.password.clone();
let write_result = write_handle
.stream
.write(format!("password {p}\n").as_bytes());
.write(format!("password {}\n", p).as_bytes());
if write_result.is_ok() {
write_handle.poll_state = PollState::Password;
} else if let Err(e) = write_result {
log(
format!("Failed to send password for authentication: {e}"),
format!("Failed to send password for authentication: {}", e),
LogState::Error,
write_handle.log_level,
);
@ -822,7 +820,7 @@ impl MPDHandler {
write_handle.poll_state = PollState::CurrentSong;
} else if let Err(e) = write_result {
log(
format!("Failed to request song info over stream: {e}"),
format!("Failed to request song info over stream: {}", e),
LogState::Error,
write_handle.log_level,
);
@ -838,7 +836,7 @@ impl MPDHandler {
write_handle.poll_state = PollState::Status;
} else if let Err(e) = write_result {
log(
format!("Failed to request status over stream: {e}"),
format!("Failed to request status over stream: {}", e),
LogState::Error,
write_handle.log_level,
);
@ -850,14 +848,14 @@ impl MPDHandler {
let title = write_handle.current_song_filename.clone();
let art_data_length = write_handle.art_data.len();
if write_handle.can_get_album_art {
let write_result = write_handle
.stream
.write(format!("readpicture \"{title}\" {art_data_length}\n").as_bytes());
let write_result = write_handle.stream.write(
format!("readpicture \"{}\" {}\n", title, art_data_length).as_bytes(),
);
if write_result.is_ok() {
write_handle.poll_state = PollState::ReadPicture;
} else if let Err(e) = write_result {
log(
format!("Failed to request album art: {e}"),
format!("Failed to request album art: {}", e),
LogState::Error,
write_handle.log_level,
);
@ -865,12 +863,12 @@ impl MPDHandler {
} else if write_handle.can_get_album_art_in_dir {
let write_result = write_handle
.stream
.write(format!("albumart \"{title}\" {art_data_length}\n").as_bytes());
.write(format!("albumart \"{}\" {}\n", title, art_data_length).as_bytes());
if write_result.is_ok() {
write_handle.poll_state = PollState::ReadPictureInDir;
} else if let Err(e) = write_result {
log(
format!("Failed to request album art in dir: {e}"),
format!("Failed to request album art in dir: {}", e),
LogState::Error,
write_handle.log_level,
);

View file

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