Fix favicon icons by converting to RGBA PNG refine README

This commit is contained in:
Sebastian Palencsar
2026-02-23 08:43:40 +01:00
parent 8d0e46a846
commit ec77654450
2 changed files with 24 additions and 8 deletions

View File

@@ -1,8 +1,8 @@
<h1 align="center">deskify</h1> <h1 align="center">deskify</h1>
<p align="center"> <p align="center">
<b>A blazing fast, lightweight Nativefier alternative written in Rust & Tauri.</b><br> <b>Turn websites into first-class Linux desktop applications with Rust, Tauri, and the system WebView.</b><br>
Instantly turn any website or web app into a native, standalone Linux desktop application. Build native-feeling, standalone web app wrappers with CLI-first Linux integration.
</p> </p>
<p align="center"> <p align="center">
@@ -35,7 +35,7 @@ The comparison below is intentionally rough and practical (not benchmark marketi
| Electron / Nativefier | Bundled Chromium per app | Good | Low to medium | Per-app process, app-bundled runtime | Easy to medium | Depends on the site | | Electron / Nativefier | Bundled Chromium per app | Good | Low to medium | Per-app process, app-bundled runtime | Easy to medium | Depends on the site |
| PWA | Browser runtime | Limited / browser-dependent | Low | Shared browser context | Very easy | Depends on browser + site | | PWA | Browser runtime | Limited / browser-dependent | Low | Shared browser context | Very easy | Depends on browser + site |
| Flatpak web app models | Sandbox-oriented runtime model | Good | Limited | Strong sandbox model | Medium | Depends on runtime + site | | Flatpak web app models | Sandbox-oriented runtime model | Good | Limited | Strong sandbox model | Medium | Depends on runtime + site |
| **Deskify** | System WebView | Native (`.desktop`, icons, WMClass) | **High (CLI-first)** | Medium (system webview model) | Medium (Tauri prerequisites) | Depends on the site | | **Deskify** | System WebView | Native (`.desktop`, icons, WMClass) | **High (CLI-first)** | Medium (shared system WebView security model) | Medium (Tauri prerequisites) | Depends on the site |
### Deskify Focus (Today) ### Deskify Focus (Today)
@@ -119,7 +119,7 @@ The table above covers the technical tradeoffs in more detail. In practice, `des
- a system-WebView-based runtime model instead of shipping a bundled browser per app - a system-WebView-based runtime model instead of shipping a bundled browser per app
### Key Features ### Key Features
- **Extremely Lightweight:** Generated binaries are tiny (~4-6 MB) and RAM consumption is minimal. - **System-WebView Runtime Model:** Generated wrappers stay small because Deskify relies on the system WebView instead of bundling a browser runtime per app.
- **Automatic Icon Fetching:** Scrapes high-quality 128x128 favicons automatically using the Google Favicon API. - **Automatic Icon Fetching:** Scrapes high-quality 128x128 favicons automatically using the Google Favicon API.
- **XDG-Compliant System Integration:** Safely creates `.desktop` entries in `~/.local/share/applications` and manages application icons in `~/.local/share/icons/hicolor/`. - **XDG-Compliant System Integration:** Safely creates `.desktop` entries in `~/.local/share/applications` and manages application icons in `~/.local/share/icons/hicolor/`.
- **Wayland/X11 Ready:** Perfectly binds `StartupWMClass` to ensure your DE groups the app exactly to its custom icon (no generic gear icons in GNOME/KDE taskbars). - **Wayland/X11 Ready:** Perfectly binds `StartupWMClass` to ensure your DE groups the app exactly to its custom icon (no generic gear icons in GNOME/KDE taskbars).

View File

@@ -1,6 +1,7 @@
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Result, anyhow};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use directories::BaseDirs; use directories::BaseDirs;
use image::{DynamicImage, ImageFormat};
use regex::Regex; use regex::Regex;
use serde_json::{Map, Value, json}; use serde_json::{Map, Value, json};
use std::fs; use std::fs;
@@ -92,6 +93,16 @@ fn is_deskify_desktop_entry(content: &str) -> bool {
&& (content.contains(".local/bin/") || content.contains("deskify"))) && (content.contains(".local/bin/") || content.contains("deskify")))
} }
fn write_rgba_png(img: DynamicImage, output_path: &Path) -> Result<()> {
// Tauri expects PNG icons in RGBA format; many favicons are palette/LA/ICO etc.
let rgba = img.to_rgba8();
let rgba_img = DynamicImage::ImageRgba8(rgba);
rgba_img
.save_with_format(output_path, ImageFormat::Png)
.with_context(|| format!("Failed to write RGBA PNG icon to {}", output_path.display()))?;
Ok(())
}
fn fetch_or_create_icon( fn fetch_or_create_icon(
website_url: &str, website_url: &str,
custom_icon: Option<&String>, custom_icon: Option<&String>,
@@ -99,8 +110,9 @@ fn fetch_or_create_icon(
) -> Result<()> { ) -> Result<()> {
if let Some(icon_path) = custom_icon { if let Some(icon_path) = custom_icon {
if Path::new(icon_path).exists() { if Path::new(icon_path).exists() {
fs::copy(icon_path, output_path) let img = image::open(icon_path)
.with_context(|| format!("Failed to copy custom icon from {}", icon_path))?; .with_context(|| format!("Failed to read custom icon image from {}", icon_path))?;
write_rgba_png(img, output_path)?;
return Ok(()); return Ok(());
} else { } else {
eprintln!( eprintln!(
@@ -118,8 +130,12 @@ fn fetch_or_create_icon(
if let Ok(response) = ureq::get(&api_url).call() { if let Ok(response) = ureq::get(&api_url).call() {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
if response.into_reader().read_to_end(&mut bytes).is_ok() && !bytes.is_empty() { if response.into_reader().read_to_end(&mut bytes).is_ok() && !bytes.is_empty() {
fs::write(output_path, bytes).context("Failed to write downloaded icon to disk")?; if let Ok(img) = image::load_from_memory(&bytes) {
write_rgba_png(img, output_path)?;
return Ok(()); return Ok(());
} else {
eprintln!("Warning: Downloaded favicon could not be decoded as an image, falling back to dummy icon.");
}
} }
} }
} }