diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..572517f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2026-03-17 + +### Added +- Initial public release. +- Wayland clipboard daemon: detects image copies and rewrites with compressed output. +- Supported output formats: `webp`, `jpeg`, `avif`, `png`, `qoi`, `farbfeld`, `tiff`, + `gif`, `hdr`, `openexr`, `bmp`, `tga`, `pnm`, `ico`. +- `quality` option (0–100) for lossy formats (`jpeg`, `avif`). +- `poll_ms` option: configurable clipboard polling interval (min 100 ms). +- `extra_mimes` option: advertise compressed bytes under additional MIME aliases + (useful for Discord, Slack, and browsers that request specific MIME types). +- Filesystem image detection via `text/uri-list` (file manager copies). +- SHA-256 deduplication to prevent infinite recompression loops. +- Workspace-based multi-crate architecture: `lib`, `config`, `platform`, `bin`. +- systemd user service (`contrib/k-shrink.service`). +- AUR PKGBUILD (`contrib/PKGBUILD`). +- Man page (`man/k-shrink.1`). diff --git a/Cargo.lock b/Cargo.lock index fbf6e04..eb12362 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,7 +85,7 @@ dependencies = [ "num-traits", "pastey", "rayon", - "thiserror 2.0.18", + "thiserror", "v_frame", "y4m", ] @@ -199,12 +199,12 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "config" -version = "0.1.0" +version = "1.0.0" dependencies = [ "dirs", "lib", "serde", - "thiserror 2.0.18", + "thiserror", "toml", ] @@ -288,23 +288,23 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -352,7 +352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -578,13 +578,13 @@ dependencies = [ [[package]] name = "k-shrink" -version = "0.1.0" +version = "1.0.0" dependencies = [ "config", "image", "lib", "platform", - "thiserror 2.0.18", + "thiserror", "tokio", "tracing", "tracing-subscriber", @@ -604,11 +604,11 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "lib" -version = "0.1.0" +version = "1.0.0" dependencies = [ "image", "sha2", - "thiserror 2.0.18", + "thiserror", "tracing", ] @@ -701,7 +701,7 @@ checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -741,7 +741,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -813,7 +813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -876,10 +876,10 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform" -version = "0.1.0" +version = "1.0.0" dependencies = [ "lib", - "thiserror 2.0.18", + "thiserror", "tracing", "wl-clipboard-rs", ] @@ -1038,7 +1038,7 @@ dependencies = [ "rand", "rand_chacha", "simd_helpers", - "thiserror 2.0.18", + "thiserror", "v_frame", "wasm-bindgen", ] @@ -1089,13 +1089,13 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -1114,7 +1114,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1161,11 +1161,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1232,7 +1232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1252,33 +1252,13 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1329,7 +1309,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1345,44 +1325,42 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", - "toml_write", + "toml_parser", + "toml_writer", "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_datetime" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.7+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" [[package]] name = "tracing" @@ -1629,15 +1607,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1647,71 +1616,11 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "winnow" -version = "0.7.15" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" [[package]] name = "wit-bindgen" @@ -1729,7 +1638,7 @@ dependencies = [ "log", "os_pipe", "rustix", - "thiserror 2.0.18", + "thiserror", "tree_magic_mini", "wayland-backend", "wayland-client", diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..01df107 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,81 @@ +# Installation + +## AUR (Arch Linux) + +```bash +yay -S k-shrink +``` + +AUR page: https://aur.archlinux.org/packages/k-shrink *(placeholder — submit PKGBUILD first)* + +Then enable the service: + +```bash +systemctl --user enable --now k-shrink.service +``` + +--- + +## Manual (from source) + +**Prerequisites:** Rust toolchain, `wayland` libraries. + +```bash +git clone https://github.com/PLACEHOLDER/k-shrink +cd k-shrink +cargo build --release +``` + +Install the binary: + +```bash +install -Dm755 target/release/k-shrink ~/.local/bin/k-shrink +``` + +Install the systemd service: + +```bash +mkdir -p ~/.config/systemd/user +cp contrib/k-shrink.service ~/.config/systemd/user/ +systemctl --user enable --now k-shrink.service +``` + +Install the man page (optional): + +```bash +mkdir -p ~/.local/share/man/man1 +cp man/k-shrink.1 ~/.local/share/man/man1/ +gzip ~/.local/share/man/man1/k-shrink.1 +mandb ~/.local/share/man # update man index +``` + +--- + +## cargo install + +```bash +cargo install --git https://github.com/PLACEHOLDER/k-shrink k-shrink +``` + +Then enable the service (uses `%h/.cargo/bin/k-shrink`): + +```bash +mkdir -p ~/.config/systemd/user +# Download the service file from the repo contrib/ directory, then: +systemctl --user enable --now k-shrink.service +``` + +--- + +## Verifying + +```bash +systemctl --user status k-shrink.service +# Copy an image, then paste — it should arrive compressed. +``` + +Logs: + +```bash +journalctl --user -u k-shrink.service -f +``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14fac91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index b202dc1..abdbf77 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,126 @@ -# K-Shrink +# k-shrink -Utility tool for shrinking/compressing images from the clipboard seamlessly. Built in Rust. +![Build](https://img.shields.io/github/actions/workflow/status/PLACEHOLDER/k-shrink/ci.yml?branch=main) +![AUR version](https://img.shields.io/aur/version/k-shrink) +![License](https://img.shields.io/badge/license-MIT-blue) -# Why? +Wayland clipboard daemon that automatically compresses images the moment you copy them. +Copy a 3 MB screenshot, paste a 300 KB WebP — no extra steps. + +## Quick Install (Arch Linux) + +```bash +yay -S k-shrink +systemctl --user enable --now k-shrink.service +``` + +For other installation methods see [INSTALL.md](INSTALL.md). + +## How It Works + +k-shrink runs silently in the background. When it detects an image on the clipboard +(from a browser, image viewer, or file manager), it compresses it with your configured +format and quality and writes the result back. Your next paste delivers the smaller image. + +A SHA-256 hash of the output bytes prevents k-shrink from reprocessing its own output, +so there are no infinite loops. + +## Why? Recently, I found myself sharing tons of memes and screenshots with friends, and it has come to my attention that many of those images are HUGE and it makes my blood boil. So, I decided to build a tool that will automatically take the image I have copied, whether from the web/facebook/discord/whatever or local files, or ftp, and shrink using user provided configuration (which format, quality, etc) and then put the shrunk image back to the clipboard, so that when I paste it, it's already shrunk and ready to be shared. -# How does it work? +## Configuration -It is basically a daemon that runs in the background and listens for clipboard changes. When it detects a change, it checks if the clipboard contains an image. If it does, it processes the image according to the user configuration and then puts the shrunk image back to the clipboard. We need some sort of lock or signature to prevent infinite loops. - -# Configuration - -The configuration is stored in a file called `config.toml` in ~/.config/k-shrink/ directory. The configuration file is in TOML format and contains the following fields: +Config file: `~/.config/k-shrink/config.toml` (created with defaults if absent). ```toml [general] -# The format to shrink the image to. Supported formats are: png, jpeg, webp -format = "webp" -# The quality of the shrunk image. Supported values are: 0-100 -quality = 80 +format = "webp" # output format +quality = 80 # 0-100, only for jpeg/avif +poll_ms = 500 # clipboard polling interval (ms, min 100) +extra_mimes = [] # additional MIME aliases (see below) ``` -# Portability +### Config Reference -I personally use Arch Linux with Wayland, which is why for now it supports only wayland. But because I value portability and future-proofing, I have designed architecture in a way that it should be easy to add support for x11, windows, macos, what-have-you in the future. +| Field | Type | Default | Description | +| ------------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `format` | string | `"webp"` | Output image format. See formats table below. | +| `quality` | integer 0–100 | `80` | Compression quality. Applied only for `jpeg` and `avif`. Ignored for all lossless formats. | +| `poll_ms` | integer ≥ 100 | `500` | How often to poll the clipboard (ms). Lower = more responsive, more CPU. | +| `extra_mimes` | string array | `[]` | Extra MIME types to serve alongside the real one. Same compressed bytes, different label. Useful when the paste target only requests a specific type. | -# Architecture +### Supported Formats -That being said, let's talk about crates and general architecture. +| Format | Lossy | Notes | +| ---------- | ----- | ------------------------------------------------------- | +| `webp` | No | Best for screenshots/UI. Smaller than PNG. **Default.** | +| `jpeg` | Yes | Best for photos. `quality` applies. | +| `avif` | Yes | Modern, better than JPEG. `quality` applies. | +| `png` | No | Universal lossless. No size reduction vs source PNG. | +| `qoi` | No | Fast lossless. Usually larger than PNG. | +| `farbfeld` | No | Simple 16-bit lossless. Rarely needed. | +| `tiff` | No | Lossless TIFF. Professional workflows. | +| `gif` | No | 256 colors only. Avoid for photos. | +| `hdr` | No | Radiance HDR floating-point. | +| `openexr` | No | OpenEXR high dynamic range. | +| `bmp` | No | Uncompressed. Will be larger than source. | +| `tga` | No | Uncompressed. | +| `pnm` | No | Uncompressed. | +| `ico` | No | Uncompressed. | -All crates live in the `crates` directory. There are three main crates: `lib`, `platform`, and `bin`. +## Troubleshooting -- `lib` crate is pure business logic, it doesn't care or know about the platform, it just provides the functionality to shrink images. It has no dependencies on platform-specific libraries, and it is tested with unit tests and integration tests. It is also the most stable crate, and it should not be changed frequently. -- `platform` crate is the crate that provides the platform-specific implementation of the clipboard provider. It depends on the `lib` crate, and it is tested with integration tests. It is also the most volatile crate, and it may change frequently as we add support for more platforms. -- `bin` crate is the crate that provides the binary executable. It depends on both `lib` and `platform` crates, and it is tested with integration tests. It is just an orchestrator that ties everything together, and it should not contain any business logic. +### Paste fails in Discord / Slack / browser -Configuration should be handled by new crate called `config`, which will take care of toml, reading and writing config. Also in `platform` crate we should provide a way for `config` crate to properly work across platforms. (this crate could be debated whether it should be separate or not, I am not convinced that separating it is the best idea, but we will see) +Some apps only request `image/png` even if other formats are available. +Use `extra_mimes` to serve the compressed bytes under that label: -# Critical things I care about +```toml +[general] +format = "webp" +extra_mimes = ["image/png"] +``` -- Performance: Entire point of this tool is to be invisible to the user, which means it's gotta be fast. it can't get in the way because then it defeats the purpose. -- Reliability: It should work consistently and not crash or cause any issues with the clipboard. It should also handle edge cases gracefully, such as unsupported formats, large images, etc. -- Portability: It should work on multiple platforms, and it should be easy to add support for new platforms in the future. -- User experience: It should be easy to use and configure, and it should provide feedback to the user when something goes wrong (e.g. unsupported format, etc). It should also have a good default configuration that works well for most users. -- Feature flags: We should use feature flags to enable or disable certain features, such as support for specific platforms, or support for specific image formats. This will allow us to keep the binary size small and avoid unnecessary dependencies for users who don't need those features. +The image is still encoded as WebP; the label is just what k-shrink advertises. +Most apps decode by content rather than MIME type, so this works. -# Contributing +### Images keep getting reprocessed (loop) -Contributions are welcome! If you want to contribute, please fork the repository and create a pull request. Please make sure to follow the coding style and conventions used in the project. Also, please make sure to write tests for your changes. +k-shrink uses a SHA-256 hash of its output to skip reprocessing. If you see a loop, +check that `IMAGE_MIMES` priority is intact (webp is listed first so k-shrink's own +output is matched before external types). This should not occur in normal usage. -# License +### File-manager copies not detected + +Filesystem copies (Ctrl+C in Nautilus, Dolphin, etc.) are detected via `text/uri-list`. +Only single-image files are supported. Multi-file selections and non-image files are +ignored silently. + +## Portability + +Wayland only. The architecture isolates platform code in the `platform` crate, so +X11/Windows/macOS backends can be added without touching business logic. + +## Architecture + +``` +bin (k-shrink) → lib + platform + config +platform → lib + wl-clipboard-rs +config → lib + serde/toml/dirs +lib → image + sha2 (zero platform deps) +``` + +- **lib** — pure image compression logic, no platform deps, fully unit-tested. +- **config** — TOML parsing, validation, and path resolution. +- **platform** — Wayland clipboard backend via `wl-clipboard-rs`. +- **bin** — tokio event loop; orchestrates the other three crates. + +## Contributing + +Contributions are welcome. Fork the repo and open a pull request. Please include +tests for new behavior and follow the existing code style. + +## License MIT. I seriously don't care what you do with this code or binaries, do whatever you want with it. If somehow it breaks something, it's your problem, not mine. #works-on-my-machine diff --git a/contrib/PKGBUILD b/contrib/PKGBUILD new file mode 100644 index 0000000..2f62b2e --- /dev/null +++ b/contrib/PKGBUILD @@ -0,0 +1,34 @@ +# Maintainer: Your Name +pkgname=k-shrink +pkgver=1.0.0 +pkgrel=1 +pkgdesc="Wayland clipboard daemon that automatically compresses copied images" +arch=('x86_64' 'aarch64') +url="https://github.com/GKaszewski/k-shrink" +license=('MIT') +depends=('wayland') +makedepends=('rust' 'cargo') +source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz") +sha256sums=('SKIP') + +build() { + cd "$pkgname-$pkgver" + cargo build --release --locked +} + +package() { + cd "$pkgname-$pkgver" + + install -Dm755 "target/release/k-shrink" "$pkgdir/usr/bin/k-shrink" + + # systemd user service (AUR path uses /usr/bin/k-shrink) + install -Dm644 "contrib/k-shrink.service" "$pkgdir/usr/lib/systemd/user/k-shrink.service" + sed -i 's|%h/.cargo/bin/k-shrink|/usr/bin/k-shrink|' \ + "$pkgdir/usr/lib/systemd/user/k-shrink.service" + + # man page + install -Dm644 "man/k-shrink.1" "$pkgdir/usr/share/man/man1/k-shrink.1" + gzip -9 "$pkgdir/usr/share/man/man1/k-shrink.1" + + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} diff --git a/contrib/k-shrink.service b/contrib/k-shrink.service new file mode 100644 index 0000000..827d6c5 --- /dev/null +++ b/contrib/k-shrink.service @@ -0,0 +1,13 @@ +[Unit] +Description=k-shrink clipboard image compressor +Documentation=man:k-shrink(1) +PartOf=graphical-session.target +After=graphical-session.target + +[Service] +ExecStart=%h/.cargo/bin/k-shrink +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=graphical-session.target diff --git a/crates/bin/Cargo.toml b/crates/bin/Cargo.toml index 416ab8d..4f265b4 100644 --- a/crates/bin/Cargo.toml +++ b/crates/bin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "k-shrink" -version = "0.1.0" +version = "1.0.0" edition = "2024" default-run = "k-shrink" diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index b5d45b3..b2f8fa5 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "config" -version = "0.1.0" +version = "1.0.0" edition = "2024" [dependencies] serde = { version = "1", features = ["derive"] } -toml = "0.8" -dirs = "5" +toml = "1" +dirs = "6" thiserror = { workspace = true } lib = { path = "../lib" } diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 8b31d62..4d9dd5e 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lib" -version = "0.1.0" +version = "1.0.0" edition = "2024" [dependencies] diff --git a/crates/platform/Cargo.toml b/crates/platform/Cargo.toml index 4aab6eb..7d41d03 100644 --- a/crates/platform/Cargo.toml +++ b/crates/platform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "platform" -version = "0.1.0" +version = "1.0.0" edition = "2024" [features] diff --git a/man/k-shrink.1 b/man/k-shrink.1 new file mode 100644 index 0000000..a9ee2ac --- /dev/null +++ b/man/k-shrink.1 @@ -0,0 +1,92 @@ +.TH K\-SHRINK 1 "2026-03-17" "k-shrink 1.0.0" "User Commands" +.SH NAME +k\-shrink \- Wayland clipboard image compression daemon +.SH SYNOPSIS +.B k\-shrink +.SH DESCRIPTION +.B k\-shrink +is a background daemon that monitors the Wayland clipboard for image content. +When an image is detected, it is compressed according to user configuration and +written back to the clipboard. The next paste action delivers the compressed +image transparently. + +It handles images copied from web browsers, image viewers, and file managers +(via +.I text/uri-list +for filesystem paths). A SHA-256 hash of the output bytes is used to detect +its own output and avoid infinite recompression loops. +.SH CONFIGURATION +Configuration is stored in +.I ~/.config/k\-shrink/config.toml +(created automatically with defaults on first run if absent). + +.SS [general] +.TP +.BR format \ (string,\ default:\ \fIwebp\fR) +Output image format. Supported values: +.BR webp , +.BR jpeg , +.BR avif , +.BR png , +.BR qoi , +.BR farbfeld , +.BR tiff , +.BR gif , +.BR hdr , +.BR openexr , +.BR bmp , +.BR tga , +.BR pnm , +.BR ico . + +Lossy (quality applies): +.B jpeg +and +.BR avif . +All others are lossless or uncompressed. +.TP +.BR quality \ (integer\ 0\-100,\ default:\ \fI80\fR) +Compression quality. Only applied when +.I format +is +.B jpeg +or +.BR avif . +Ignored for all other formats. +.TP +.BR poll_ms \ (integer,\ minimum\ 100,\ default:\ \fI500\fR) +Clipboard polling interval in milliseconds. Lower values are more responsive +but consume more CPU. +.TP +.BR extra_mimes \ (array\ of\ strings,\ default:\ \fI[]\fR) +Additional MIME types under which to serve the compressed bytes. The actual +format does not change; the same compressed data is advertised under each +listed type. Useful for applications that only request a specific MIME type +(e.g.\& +.IR image/png ) +but can still decode the real format. + +Example: +.I extra_mimes = ["image/png"] +.SH FILES +.TP +.I ~/.config/k\-shrink/config.toml +User configuration file. +.SH SIGNALS +.TP +.B SIGINT\fR,\fB SIGTERM +Graceful shutdown. +.SH EXIT STATUS +.TP +.B 0 +Normal exit (signal received). +.TP +.B 1 +Fatal error (clipboard backend unavailable, config parse failure). +.SH SEE ALSO +.BR systemctl (1), +.BR wl-paste (1), +.BR wl-copy (1) +.SH AUTHORS +Written by the k\-shrink contributors. +See the project repository for the full contributor list.