Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Overview

Standalone CLI application to interact with the Display FS V1 family (0.96 inch + 3.5 inch), detect if it's connected, and display content.
Spotify now-playing output is width-aware on both display sizes.

## Hardware

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ The `--orientation` flag switches between landscape (160x80, default) and portra

### Spotify Now Playing (macOS)

Display the currently playing Spotify track:
Display the currently playing Spotify track. The title/artist lines automatically truncate
to the current display width (including large-screen auto-fit):

```bash
# Show once
Expand Down
4 changes: 2 additions & 2 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ pub fn measure_multiline_text(text: &str, font_size: f32) -> (u32, u32) {
(max_width, total_height)
}

const MIN_FONT_SIZE: f32 = 8.0;
const MAX_FONT_SIZE: f32 = 72.0;
pub const MIN_FONT_SIZE: f32 = 8.0;
pub const MAX_FONT_SIZE: f32 = 72.0;
const HORIZONTAL_PADDING: u32 = 8;
const VERTICAL_PADDING: u32 = 4;

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use image::{
calculate_max_lines_oriented, create_blank_image_for_display, create_text_image,
create_text_image_for_display, create_text_image_oriented, image_to_rgb565_bytes,
image_to_rgb565_bytes_for_display, image_to_rgb565_bytes_oriented, measure_text_with_font_size,
Orientation, DISPLAY_HEIGHT, DISPLAY_WIDTH,
Orientation, DISPLAY_HEIGHT, DISPLAY_WIDTH, MAX_FONT_SIZE, MIN_FONT_SIZE,
};
pub use port::{
find_display_port, is_display_connected, open_connection, DisplayConfig, DisplayModel,
Expand Down
90 changes: 63 additions & 27 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use clap::{Parser, Subcommand, ValueEnum};
use display_fs::{
calculate_auto_fit_size_for_display, create_text_image_for_display, find_display_port,
get_now_playing, image_to_rgb565_bytes_for_display, is_display_connected, open_connection,
calculate_auto_fit_size_for_display, calculate_max_chars_per_line_for_display,
create_text_image_for_display, find_display_port, get_now_playing,
image_to_rgb565_bytes_for_display, is_display_connected, open_connection,
send_image_to_display_for_model, split_into_pages_for_display, DisplayConfig, DisplayModel,
Orientation,
Orientation, MIN_FONT_SIZE,
};
use std::process::{Command, ExitCode};
use std::thread;
Expand Down Expand Up @@ -453,28 +454,18 @@ fn run_spotify(args: SpotifyArgs) -> ExitCode {
let interval = Duration::from_secs_f32(args.display.effective_delay());

loop {
let text = match get_now_playing() {
Some(np) if np.is_playing => {
format!(
"♪ {}\nby {}",
truncate(&np.track, 18),
truncate(&np.artist, 18)
)
}
Some(np) => {
format!(
"|| {}\nby {}",
truncate(&np.track, 18),
truncate(&np.artist, 18)
)
}
None => "Spotify not running".to_string(),
};

let current = get_now_playing().map(|np| (np.track, np.artist));
let now_playing = get_now_playing();
let current = now_playing
.as_ref()
.map(|np| (np.track.clone(), np.artist.clone()));
let should_update = current != last_track;

if should_update {
let max_line_len = spotify_max_line_len(&args.display, orientation, display_config);
let text = match now_playing {
Some(np) => format_spotify_text(&np.track, &np.artist, np.is_playing, max_line_len),
None => "Spotify not running".to_string(),
};
let font_size = if args.display.auto {
let size = calculate_auto_fit_size_for_display(
&text,
Expand Down Expand Up @@ -525,12 +516,57 @@ fn run_spotify(args: SpotifyArgs) -> ExitCode {
ExitCode::SUCCESS
}

fn truncate(s: &str, max_len: usize) -> String {
if s.chars().count() <= max_len {
s.to_string()
} else {
format!("{}...", s.chars().take(max_len - 3).collect::<String>())
fn spotify_max_line_len(
display: &DisplayOptions,
orientation: Orientation,
config: DisplayConfig,
) -> usize {
if display.auto {
return calculate_max_chars_per_line_for_display(
MIN_FONT_SIZE,
orientation,
config.width as u32,
config.height as u32,
);
}

calculate_max_chars_per_line_for_display(
display.font_size,
orientation,
config.width as u32,
config.height as u32,
)
}

fn format_spotify_text(track: &str, artist: &str, is_playing: bool, max_len: usize) -> String {
let prefix = if is_playing { "♪" } else { "||" };
let prefix_len = prefix.chars().count();
let track_line = format!(
"{} {}",
prefix,
trim_to_width(track, max_len.saturating_sub(prefix_len + 1))
);
let artist_line = format!("by {}", trim_to_width(artist, max_len.saturating_sub(3)));

format!("{}\n{}", track_line, artist_line)
}

fn trim_to_width(text: &str, max_len: usize) -> String {
if max_len == 0 {
return String::new();
}

let text_len = text.chars().count();
if text_len <= max_len {
return text.to_string();
}

if max_len <= 3 {
return text.chars().take(max_len).collect();
}

let truncated: String = text.chars().take(max_len - 3).collect();
format!("{}...", truncated)
}

fn detect_display() -> ExitCode {
Expand Down
Loading