Skip to content

Commit 41ae1e2

Browse files
feat: display profile avatar image with terminal graphics protocols (#23)
* feat: display profile avatar with terminal image protocols * docs: add image display documentation to README
1 parent f076a6b commit 41ae1e2

8 files changed

Lines changed: 1480 additions & 30 deletions

File tree

Cargo.lock

Lines changed: 992 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ anyhow = "1"
2424
steamworks = { version = "0.12", features = ["raw-bindings"] }
2525
libloading = "0.8"
2626
indicatif = "0.17"
27+
image = "0.25"
28+
icy_sixel = "0.5"
29+
base64 = "0.22.1"
30+
libc = "0.2.182"

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,18 @@ export STEAM_ID="your_steam_id_here"
8282
# Display your Steam stats
8383
steamfetch
8484

85+
# Show profile avatar image instead of ASCII logo
86+
steamfetch --image
87+
88+
# Specify image protocol (auto, kitty, iterm, sixel)
89+
steamfetch --image --image-protocol sixel
90+
8591
# Demo mode (no API key required)
8692
steamfetch --demo
8793

94+
# Demo mode with image
95+
steamfetch --demo --image
96+
8897
# Show config file path
8998
steamfetch --config-path
9099

@@ -110,8 +119,23 @@ steamfetch --help
110119
- Recently played games (last 2 weeks)
111120
- Rarest achievement display
112121
- Beautiful SteamOS ASCII art with gradient colors
122+
- **Image display**: Show your Steam avatar with `--image` flag
113123
- Demo mode for testing without API setup
114124

125+
### Image Display
126+
127+
Use `--image` to show your Steam profile avatar instead of the ASCII logo.
128+
129+
Supported protocols:
130+
- **Sixel** - Windows Terminal, WezTerm, foot, mlterm, xterm
131+
- **Kitty** - Kitty terminal
132+
- **iTerm2** - iTerm2
133+
- **Block characters** - Fallback for unsupported terminals
134+
135+
Protocol is auto-detected by default. Use `--image-protocol` to override.
136+
137+
Images are cached locally at `~/.cache/steamfetch/images/`.
138+
115139
## How It Works
116140

117141
### With Steam Client Running

src/display.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,76 @@
11
use colored::Colorize;
2+
use std::io::{self, Write};
23

4+
use crate::image_display;
35
use crate::steam::SteamStats;
6+
use crate::ImageProtocol;
47

5-
pub fn render(stats: &SteamStats) {
8+
const IMAGE_COLS: u32 = 34;
9+
const IMAGE_ROWS: u32 = 18;
10+
11+
pub struct ImageConfig {
12+
pub enabled: bool,
13+
pub protocol: ImageProtocol,
14+
}
15+
16+
pub async fn render(stats: &SteamStats, image_config: &ImageConfig) {
617
let info_lines = build_info_lines(stats);
18+
19+
if image_config.enabled {
20+
render_with_image(stats, &info_lines, image_config).await;
21+
} else {
22+
render_with_ascii(&info_lines);
23+
}
24+
}
25+
26+
async fn render_with_image(stats: &SteamStats, info_lines: &[String], config: &ImageConfig) {
27+
let avatar = match &stats.avatar_url {
28+
Some(url) => {
29+
let cache_key = format!("avatar_{}.png", stats.username);
30+
image_display::load_cached_or_download(url, &cache_key).await
31+
}
32+
None => None,
33+
};
34+
35+
let Some(img) = avatar else {
36+
return render_with_ascii(info_lines);
37+
};
38+
39+
println!();
40+
41+
// Print image and rewind cursor to top-left of image area
42+
let image_rows =
43+
image_display::print_image_and_rewind(&img, &config.protocol, IMAGE_COLS, IMAGE_ROWS);
44+
45+
let Some(image_rows) = image_rows else {
46+
return render_with_ascii(info_lines);
47+
};
48+
49+
let col_offset = IMAGE_COLS + 3; // image width + gap
50+
let mut stdout = io::stdout().lock();
51+
52+
// Print info lines to the right of the image
53+
for (i, line) in info_lines.iter().enumerate() {
54+
if i > 0 {
55+
writeln!(stdout).unwrap();
56+
}
57+
image_display::cursor_right(col_offset);
58+
write!(stdout, "{}", line).unwrap();
59+
}
60+
61+
// Ensure we end up below the image area
62+
let extra = (image_rows as usize).saturating_sub(info_lines.len());
63+
for _ in 0..=extra {
64+
writeln!(stdout).unwrap();
65+
}
66+
stdout.flush().unwrap();
67+
}
68+
69+
fn render_with_ascii(info_lines: &[String]) {
770
let logo_lines = build_logo();
871

972
println!();
1073
for (i, logo_line) in logo_lines.iter().enumerate() {
11-
// Offset info by 1 line to align vertically
1274
let info = if i == 0 {
1375
""
1476
} else {

0 commit comments

Comments
 (0)