iwaku config changes, load changes, hypr support only IF passed the same as zshell

This commit is contained in:
2026-05-13 13:59:42 +02:00
parent 075cd42064
commit cf634a76f2
5 changed files with 70 additions and 74 deletions
-7
View File
@@ -9,10 +9,3 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information # MSVC Windows builds of rustc generate these, which store debugging information
*.pdb *.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
Generated
+11 -11
View File
@@ -405,6 +405,17 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "iwaku"
version = "0.1.0"
dependencies = [
"anyhow",
"image",
"serde",
"serde_json",
"tiny-skia",
]
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.34" version = "0.1.34"
@@ -790,17 +801,6 @@ version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
[[package]]
name = "rs-pictures"
version = "0.1.0"
dependencies = [
"anyhow",
"image",
"serde",
"serde_json",
"tiny-skia",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
+7 -19
View File
@@ -1,39 +1,27 @@
[package] [package]
name = "rs-pictures" name = "iwaku"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[[bin]] [[bin]]
name = "rs-pictures" name = "iwaku"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
# Image loading and encoding image = { version = "0.25", features = ["png"] }
image = { version = "0.25", features = ["png", "jpeg"] }
# 2D rendering for effects (rounded corners, drop shadow)
tiny-skia = "0.11" tiny-skia = "0.11"
# Config serialization
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
# Error handling
anyhow = "1" anyhow = "1"
serde_json = "1.0.149" serde_json = "1.0.149"
# ── Build profiles ────────────────────────────────────────────────────────────
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
lto = "thin" # link-time optimisation across crates lto = "thin"
codegen-units = 1 # better inlining at the cost of compile time codegen-units = 1
strip = true # strip debug symbols → smaller binary strip = true
# Dev builds are slow for pixel-processing code. This gives opt-level 2
# to our own crate only while keeping dependencies at their default (opt=3
# they already compiled with), so incremental rebuilds stay fast.
[profile.dev] [profile.dev]
opt-level = 0 opt-level = 0
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 # all deps at full optimisation even in dev mode opt-level = 3
+6 -25
View File
@@ -5,11 +5,12 @@ use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config { pub struct Config {
#[serde(rename = "screenshot")] #[serde(rename = "screenshot")]
pub effects: EffectsConfig, pub screenshot: EffectsConfig,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EffectsConfig { pub struct EffectsConfig {
pub mode: String,
pub rounded_corners: bool, pub rounded_corners: bool,
pub corner_radius: f32, pub corner_radius: f32,
pub drop_shadow: bool, pub drop_shadow: bool,
@@ -19,28 +20,6 @@ pub struct EffectsConfig {
pub shadow_color: [u8; 4], pub shadow_color: [u8; 4],
} }
impl Default for EffectsConfig {
fn default() -> Self {
Self {
rounded_corners: false,
corner_radius: 12.0,
drop_shadow: false,
shadow_blur_radius: 20.0,
shadow_offset_x: 5.0,
shadow_offset_y: 8.0,
shadow_color: [0, 0, 0, 160],
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
effects: EffectsConfig::default(),
}
}
}
impl Config { impl Config {
pub fn config_path() -> Option<PathBuf> { pub fn config_path() -> Option<PathBuf> {
let home = std::env::var("HOME").ok()?; let home = std::env::var("HOME").ok()?;
@@ -54,10 +33,12 @@ impl Config {
pub fn load() -> Result<Self> { pub fn load() -> Result<Self> {
let path = Self::config_path().context("Could not determine HOME directory")?; let path = Self::config_path().context("Could not determine HOME directory")?;
Self::load_from(&path)
}
let raw = std::fs::read_to_string(&path) pub fn load_from(path: &PathBuf) -> Result<Self> {
let raw = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read config at {}", path.display()))?; .with_context(|| format!("Failed to read config at {}", path.display()))?;
serde_json::from_str(&raw) serde_json::from_str(&raw)
.with_context(|| format!("Failed to parse JSON config at {}", path.display())) .with_context(|| format!("Failed to parse JSON config at {}", path.display()))
} }
+46 -12
View File
@@ -1,36 +1,70 @@
mod config; mod config;
mod effects; mod effects;
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use std::io::Write as _; use std::io::Write as _;
use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
fn main() -> Result<()> { fn main() -> Result<()> {
let paths: Vec<String> = std::env::args().skip(1).collect(); let args: Vec<String> = std::env::args().skip(1).collect();
if paths.is_empty() { let mut image_path: Option<String> = None;
eprintln!("Usage: rs-pictures <image> [image2 ...]"); let mut hypr_path: Option<String> = None;
eprintln!("No image paths provided.");
std::process::exit(1); let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"-image" => {
i += 1;
image_path = Some(
args.get(i)
.cloned()
.context("Expected a path after -image")?,
);
} }
"-hypr" => {
i += 1;
hypr_path = Some(
args.get(i)
.cloned()
.context("Expected a path after -hypr")?,
);
}
unknown => bail!("Unknown argument: {unknown}"),
}
i += 1;
}
let image_path =
image_path.context("Usage: iwaku -image <path> [-hypr <config_path>]\nMissing -image")?;
let config = config::Config::load().context("Failed to load config")?; let config = config::Config::load().context("Failed to load config")?;
for path in &paths { let effects = if config.screenshot.mode == "auto" {
if let Err(e) = process_image(path, &config) { let hypr_config_path = hypr_path
eprintln!("Error processing '{}': {e:#}", path); .map(PathBuf::from)
} .context("Mode is 'auto' but -hypr <config_path> was not provided")?;
let hypr_config =
config::Config::load_from(&hypr_config_path).context("Failed to load hypr config")?;
hypr_config.screenshot
} else {
config.screenshot
};
if let Err(e) = process_image(&image_path, &effects) {
eprintln!("Error processing '{}': {e:#}", image_path);
} }
Ok(()) Ok(())
} }
fn process_image(path: &str, config: &config::Config) -> Result<()> { fn process_image(path: &str, effects: &config::EffectsConfig) -> Result<()> {
let img = image::open(path) let img = image::open(path)
.with_context(|| format!("Failed to open image '{path}'"))? .with_context(|| format!("Failed to open image '{path}'"))?
.into_rgba8(); .into_rgba8();
let processed = effects::apply_effects(img, &config.effects); let processed = effects::apply_effects(img, effects);
let mut png_bytes: Vec<u8> = Vec::new(); let mut png_bytes: Vec<u8> = Vec::new();
image::DynamicImage::ImageRgba8(processed) image::DynamicImage::ImageRgba8(processed)