feat: add initial release with installation instructions, service configuration, and man page

This commit is contained in:
2026-03-17 22:07:38 +01:00
parent 0c463703a3
commit ffd849dec2
12 changed files with 431 additions and 186 deletions

24
CHANGELOG.md Normal file
View File

@@ -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 (0100) 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`).

207
Cargo.lock generated
View File

@@ -85,7 +85,7 @@ dependencies = [
"num-traits", "num-traits",
"pastey", "pastey",
"rayon", "rayon",
"thiserror 2.0.18", "thiserror",
"v_frame", "v_frame",
"y4m", "y4m",
] ]
@@ -199,12 +199,12 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "config" name = "config"
version = "0.1.0" version = "1.0.0"
dependencies = [ dependencies = [
"dirs", "dirs",
"lib", "lib",
"serde", "serde",
"thiserror 2.0.18", "thiserror",
"toml", "toml",
] ]
@@ -288,23 +288,23 @@ dependencies = [
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "5.0.1" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [ dependencies = [
"dirs-sys", "dirs-sys",
] ]
[[package]] [[package]]
name = "dirs-sys" name = "dirs-sys"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users",
"windows-sys 0.48.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -352,7 +352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@@ -578,13 +578,13 @@ dependencies = [
[[package]] [[package]]
name = "k-shrink" name = "k-shrink"
version = "0.1.0" version = "1.0.0"
dependencies = [ dependencies = [
"config", "config",
"image", "image",
"lib", "lib",
"platform", "platform",
"thiserror 2.0.18", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@@ -604,11 +604,11 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]] [[package]]
name = "lib" name = "lib"
version = "0.1.0" version = "1.0.0"
dependencies = [ dependencies = [
"image", "image",
"sha2", "sha2",
"thiserror 2.0.18", "thiserror",
"tracing", "tracing",
] ]
@@ -701,7 +701,7 @@ checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@@ -741,7 +741,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@@ -813,7 +813,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@@ -876,10 +876,10 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "platform" name = "platform"
version = "0.1.0" version = "1.0.0"
dependencies = [ dependencies = [
"lib", "lib",
"thiserror 2.0.18", "thiserror",
"tracing", "tracing",
"wl-clipboard-rs", "wl-clipboard-rs",
] ]
@@ -1038,7 +1038,7 @@ dependencies = [
"rand", "rand",
"rand_chacha", "rand_chacha",
"simd_helpers", "simd_helpers",
"thiserror 2.0.18", "thiserror",
"v_frame", "v_frame",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -1089,13 +1089,13 @@ dependencies = [
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.6" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [ dependencies = [
"getrandom 0.2.17", "getrandom 0.2.17",
"libredox", "libredox",
"thiserror 1.0.69", "thiserror",
] ]
[[package]] [[package]]
@@ -1114,7 +1114,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@@ -1161,11 +1161,11 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.9" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [ dependencies = [
"serde", "serde_core",
] ]
[[package]] [[package]]
@@ -1232,7 +1232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@@ -1252,33 +1252,13 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.18" version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [ dependencies = [
"thiserror-impl 2.0.18", "thiserror-impl",
]
[[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",
] ]
[[package]] [[package]]
@@ -1329,7 +1309,7 @@ dependencies = [
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@@ -1345,44 +1325,42 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.23" version = "1.0.7+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96"
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"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde_core",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_write", "toml_parser",
"toml_writer",
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "toml_write" name = "toml_datetime"
version = "0.1.2" version = "1.0.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "tracing" name = "tracing"
@@ -1629,15 +1607,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@@ -1647,71 +1616,11 @@ dependencies = [
"windows-link", "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]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.15" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
@@ -1729,7 +1638,7 @@ dependencies = [
"log", "log",
"os_pipe", "os_pipe",
"rustix", "rustix",
"thiserror 2.0.18", "thiserror",
"tree_magic_mini", "tree_magic_mini",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",

81
INSTALL.md Normal file
View File

@@ -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
```

21
LICENSE Normal file
View File

@@ -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.

133
README.md
View File

@@ -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. 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. Config file: `~/.config/k-shrink/config.toml` (created with defaults if absent).
# 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:
```toml ```toml
[general] [general]
# The format to shrink the image to. Supported formats are: png, jpeg, webp format = "webp" # output format
format = "webp" quality = 80 # 0-100, only for jpeg/avif
# The quality of the shrunk image. Supported values are: 0-100 poll_ms = 500 # clipboard polling interval (ms, min 100)
quality = 80 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 0100 | `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. ### Paste fails in Discord / Slack / browser
- `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.
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. The image is still encoded as WebP; the label is just what k-shrink advertises.
- 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. Most apps decode by content rather than MIME type, so this works.
- 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.
# 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 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

34
contrib/PKGBUILD Normal file
View File

@@ -0,0 +1,34 @@
# Maintainer: Your Name <your@email.com>
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"
}

13
contrib/k-shrink.service Normal file
View File

@@ -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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-shrink" name = "k-shrink"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
default-run = "k-shrink" default-run = "k-shrink"

View File

@@ -1,11 +1,11 @@
[package] [package]
name = "config" name = "config"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
toml = "0.8" toml = "1"
dirs = "5" dirs = "6"
thiserror = { workspace = true } thiserror = { workspace = true }
lib = { path = "../lib" } lib = { path = "../lib" }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "lib" name = "lib"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "platform" name = "platform"
version = "0.1.0" version = "1.0.0"
edition = "2024" edition = "2024"
[features] [features]

92
man/k-shrink.1 Normal file
View File

@@ -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.