feat: initialize K-Shrink workspace with multiple crates for image shrinking utility
- Add Cargo.toml for workspace configuration with dependencies. - Create README.md with project description, usage, and architecture details. - Implement `bin` crate for the main executable, including clipboard processing logic. - Add `config` crate for handling configuration in TOML format. - Develop `lib` crate containing core image processing logic and error handling. - Introduce `platform` crate for platform-specific clipboard interactions, starting with Wayland. - Implement tests for image shrinking functionality and clipboard interactions.
This commit is contained in:
47
crates/bin/src/lib.rs
Normal file
47
crates/bin/src/lib.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use lib::{image_hash, shrink, ClipboardError, ClipboardProvider, ShrinkOptions};
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
pub fn process_once(
|
||||
provider: &dyn ClipboardProvider,
|
||||
opts: &ShrinkOptions,
|
||||
last_hash: &mut Option<[u8; 32]>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (data, mime_type) = match provider.capture() {
|
||||
Ok(x) => x,
|
||||
Err(ClipboardError::Empty) => {
|
||||
trace!("clipboard empty");
|
||||
return Ok(());
|
||||
}
|
||||
Err(ClipboardError::InvalidType(t)) => {
|
||||
trace!("no image in clipboard: {t}");
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
if !mime_type.starts_with("image/") {
|
||||
trace!("non-image mime type: {mime_type}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let incoming_hash = image_hash(&data);
|
||||
if Some(incoming_hash) == *last_hash {
|
||||
debug!("clipboard unchanged, skipping");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("compressing {} bytes ({mime_type})", data.len());
|
||||
let result = shrink(&data, opts)?;
|
||||
info!(
|
||||
"compressed to {} bytes ({})",
|
||||
result.data.len(),
|
||||
result.mime_type
|
||||
);
|
||||
|
||||
provider.distribute(&[(&result.data, result.mime_type.as_str())])?;
|
||||
|
||||
// Track hash of OUTPUT. After distributing webp-only, next capture will
|
||||
// find image/webp (from our subprocess) → same hash → skip. No loop.
|
||||
*last_hash = Some(image_hash(&result.data));
|
||||
Ok(())
|
||||
}
|
||||
35
crates/bin/src/main.rs
Normal file
35
crates/bin/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use config::load_config;
|
||||
use lib::ShrinkOptions;
|
||||
use platform::WaylandBackend;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let cfg = match load_config() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
warn!("config error ({e}), using defaults");
|
||||
config::Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
let poll = Duration::from_millis(cfg.general.poll_ms);
|
||||
let opts = ShrinkOptions {
|
||||
quality: cfg.general.quality,
|
||||
target_format: cfg.general.format.into(),
|
||||
};
|
||||
|
||||
let backend = WaylandBackend;
|
||||
let mut last_hash: Option<[u8; 32]> = None;
|
||||
|
||||
info!("k-shrink daemon started (poll={}ms)", cfg.general.poll_ms);
|
||||
loop {
|
||||
if let Err(e) = k_shrink::process_once(&backend, &opts, &mut last_hash) {
|
||||
error!("error: {e}");
|
||||
}
|
||||
tokio::time::sleep(poll).await;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user