Compare commits
22 Commits
27ede7fc64
...
refonte
| Author | SHA1 | Date | |
|---|---|---|---|
| 46dfa0fd49 | |||
| 72ad88e888 | |||
| 4f18a72785 | |||
| 041e90a8f2 | |||
| fc085a8a83 | |||
| 8ea259531e | |||
| 595d025160 | |||
| d173db3342 | |||
| 8b07e305f0 | |||
| 20f33f5694 | |||
| 989e0aef91 | |||
| 54ddc9851c | |||
| dcc863c451 | |||
| 9e56322705 | |||
| 5f691c2e2f | |||
| 62fb8cd330 | |||
| 86cff34cd5 | |||
| fb8c852a4d | |||
| e038981d7f | |||
| 05ced3b7ea | |||
| d46c35c49f | |||
| eb34efb652 |
2200
Cargo.lock
generated
2200
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
@ -3,7 +3,23 @@ name = "rklip"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
[dependencies]
|
||||
arboard = "3.6.1"
|
||||
aes-gcm = "0.10.3"
|
||||
argon2 = "0.5.3"
|
||||
base64 = "0.22.1"
|
||||
chrono = "0.4.44"
|
||||
crossterm = "0.29.0"
|
||||
directories = "6.0.0"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
image = "0.25.9"
|
||||
ratatui = "0.30.0"
|
||||
ratatui-image = { version = "10.0.6", features = ["crossterm"] }
|
||||
regex = "1.12.3"
|
||||
rklipd = {path = "rklipd"}
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
syntect = "5.3.0"
|
||||
uuid = "1.22.0"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
140
README.md
Normal file
140
README.md
Normal file
@ -0,0 +1,140 @@
|
||||
|
||||
# rklipd
|
||||
|
||||
A lightweight clipboard history manager for Linux — daemon + TUI client.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- Captures text and images automatically (X11 polling & Wayland events)
|
||||
- SQLite storage — images saved as JPEG (quality 70)
|
||||
- Fuzzy search (`~`), regex search (`/pattern`), date filters (`after:2025-01` `before:2025-06-01`)
|
||||
- Type filter: All / Text / Image (`t`)
|
||||
- Per-entry AES-256-GCM encryption with Argon2 password (`e`)
|
||||
- Syntax highlighting in preview (300+ languages via syntect)
|
||||
- Image preview in terminal (sixel / kitty / halfblocks via ratatui-image)
|
||||
- Undo last delete (`u`)
|
||||
- IPC Unix socket — fully scriptable
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
rklipd (daemon) rklip (TUI client)
|
||||
┌─────────────────────┐ ┌──────────────────────┐
|
||||
│ monitor (X11/Wayland│──────────▶│ app.rs (state) │
|
||||
│ database.rs (SQLite)│◀──IPC────▶│ ui.rs (ratatui) │
|
||||
│ ipc.rs (Unix sock) │ │ ipc.rs (client) │
|
||||
│ crypto.rs (AES-GCM) │ │ crypto.rs (Argon2) │
|
||||
└─────────────────────┘ └──────────────────────┘
|
||||
|
||||
~/.local/share/com.zefad.rklipd/
|
||||
├── clipboard.db # SQLite history
|
||||
├── images/ # JPEG images
|
||||
├── master.key # Machine key (enc:)
|
||||
├── crypto2.salt # Argon2 salt (enc2:)
|
||||
└── rklip.sock # IPC socket
|
||||
```
|
||||
|
||||
## Build & Install
|
||||
|
||||
**Dependencies:** `libxcb` (X11) or Wayland libs, `libsqlite3`
|
||||
|
||||
```bash
|
||||
# X11
|
||||
cargo build --release --features x11 -p rklipd
|
||||
cargo build --release -p rklip
|
||||
|
||||
# Wayland
|
||||
cargo build --release --features wayland -p rklipd
|
||||
cargo build --release -p rklip
|
||||
|
||||
# Install
|
||||
sudo cp target/release/rklipd /usr/local/bin/
|
||||
sudo cp target/release/rklip /usr/local/bin/
|
||||
```
|
||||
|
||||
**Autostart (systemd user):**
|
||||
|
||||
```ini
|
||||
# ~/.config/systemd/user/rklipd.service
|
||||
[Unit]
|
||||
Description=rklipd clipboard daemon
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/rklipd
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```bash
|
||||
systemctl --user enable --now rklipd
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
rklipd [OPTIONS] # start daemon
|
||||
rklip # open TUI
|
||||
|
||||
Options:
|
||||
--max-entries <N> Max history entries (default: 500)
|
||||
--max-entry-size-kb <N> Max text entry size in KB (default: 512)
|
||||
--expiry-days <N> Auto-delete entries > N days
|
||||
```
|
||||
|
||||
## Keybindings
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `j` / `↓` | Next entry |
|
||||
| `k` / `↑` | Previous entry |
|
||||
| `Enter` | Paste selected & quit |
|
||||
| `/` | Fuzzy search mode |
|
||||
| `t` | Cycle type filter (All → Text → Image) |
|
||||
| `e` | Encrypt / Decrypt selected entry |
|
||||
| `dd` | Delete selected (confirm) |
|
||||
| `u` | Undo last delete |
|
||||
| `gg` / `G` | Jump to top / bottom |
|
||||
| `Ctrl+j/k` | Scroll preview |
|
||||
| `:clear` | Clear entire history |
|
||||
| `:p` | Set session password |
|
||||
| `q` / `:q` | Quit |
|
||||
|
||||
**Search syntax:**
|
||||
|
||||
```
|
||||
rust # fuzzy match
|
||||
/fn\s+\w+\( # regex (prefix with /)
|
||||
after:2025-01 before:2025-06 config # date filters + text
|
||||
```
|
||||
|
||||
## Encryption
|
||||
|
||||
Two independent layers:
|
||||
|
||||
| Prefix | Method | Key source | Use case |
|
||||
|--------|--------|-----------|----------|
|
||||
| `enc:` | AES-256-GCM | Machine key (`master.key`) | Legacy / auto |
|
||||
| `enc2:` | Argon2 + AES-256-GCM | User password | Sensitive entries |
|
||||
|
||||
Press `e` on any entry to encrypt/decrypt with a password. Encrypted entries show as `🔒 [Chiffré]` and require your password to paste.
|
||||
|
||||
## IPC (scripting)
|
||||
|
||||
The daemon exposes a JSON Unix socket. Example with `socat`:
|
||||
|
||||
```bash
|
||||
# Fetch last 5 entries
|
||||
echo '{"GetHistory":{"limit":5}}' | socat - UNIX-CONNECT:~/.local/share/com.zefad.rklipd/rklip.sock
|
||||
|
||||
# Set clipboard content
|
||||
echo '{"SetClipboard":{"content":"hello"}}' | socat - UNIX-CONNECT:...
|
||||
|
||||
# Clear history
|
||||
echo '"ClearHistory"' | socat - UNIX-CONNECT:...
|
||||
```
|
||||
335
rklipd/Cargo.lock
generated
335
rklipd/Cargo.lock
generated
@ -8,6 +8,41 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.4.3"
|
||||
@ -133,6 +168,12 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.3"
|
||||
@ -205,6 +246,16 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-master"
|
||||
version = "4.0.0"
|
||||
@ -242,6 +293,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@ -282,6 +342,47 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
@ -292,6 +393,12 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
@ -424,6 +531,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
@ -434,6 +551,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
@ -459,6 +587,16 @@ dependencies = [
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.14.1"
|
||||
@ -571,6 +709,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
@ -645,6 +792,15 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.36.0"
|
||||
@ -948,6 +1104,28 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@ -1008,6 +1186,18 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@ -1076,6 +1266,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.39.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
@ -1104,7 +1303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1114,7 +1313,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1205,6 +1413,17 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.53"
|
||||
@ -1215,11 +1434,18 @@ checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||
name = "rklipd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"arboard",
|
||||
"base64",
|
||||
"clipboard-master",
|
||||
"directories",
|
||||
"image",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
"wayland-clipboard-listener",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1285,6 +1511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1365,6 +1592,12 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@ -1410,6 +1643,12 @@ dependencies = [
|
||||
"zune-jpeg 0.4.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
@ -1422,6 +1661,16 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.22.0"
|
||||
@ -1451,6 +1700,18 @@ version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
@ -1548,6 +1809,76 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-clipboard-listener"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e27b4464dcbc041268b018226dceda1ce94ca9d1a020fe47c251fa3de88e7706"
|
||||
dependencies = [
|
||||
"log",
|
||||
"os_pipe",
|
||||
"thiserror",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
|
||||
@ -9,7 +9,17 @@ image = "0.25.9"
|
||||
clipboard-master = "4.0.0"
|
||||
uuid = {version = "1.22.0", features = ["v4", "serde"]}
|
||||
rusqlite = "0.38.0"
|
||||
wayland-clipboard-listener = "0.6.0"
|
||||
directories = "6.0.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
base64 = "0.22.1"
|
||||
aes-gcm = "0.10.3"
|
||||
x11rb = "0.13.2"
|
||||
|
||||
[features]
|
||||
x11 = []
|
||||
wayland = []
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
use arboard::{Clipboard, ImageData};
|
||||
use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
|
||||
use std::error::Error;
|
||||
use std::time::SystemTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||
|
||||
use clipboard_master::{CallbackResult, ClipboardHandler};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
pub struct Handler {
|
||||
pub clipboard_tx: Sender<()>,
|
||||
}
|
||||
|
||||
impl ClipboardHandler for Handler {
|
||||
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||
if let Err(e) = self.clipboard_tx.send(()) {
|
||||
eprintln!("{}", e);
|
||||
}
|
||||
CallbackResult::Next
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ImageDataExt {
|
||||
fn to_png(&self) -> Result<Vec<u8>, Box<dyn Error>>;
|
||||
}
|
||||
|
||||
impl ImageDataExt for ImageData<'_> {
|
||||
fn to_png(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let mut buffer = Vec::new();
|
||||
let encoder = PngEncoder::new(&mut buffer);
|
||||
encoder.write_image(
|
||||
&self.bytes,
|
||||
self.width as u32,
|
||||
self.height as u32,
|
||||
ExtendedColorType::Rgba8,
|
||||
)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClipboardEntry {
|
||||
pub fn new(clipboard: &mut Clipboard) -> Result<ClipboardEntry, Box<dyn Error>> {
|
||||
let clipboard_data_opt: Option<ClipboardData> = match clipboard.get_text() {
|
||||
Ok(text) => Some(ClipboardData::Text(text)),
|
||||
Err(_) => match clipboard.get_image() {
|
||||
Ok(image) => {
|
||||
let id = Uuid::new_v4();
|
||||
Some(ClipboardData::Image(Image {
|
||||
bytes: Some(image.to_png()?),
|
||||
id,
|
||||
}))
|
||||
}
|
||||
Err(_) => None,
|
||||
},
|
||||
};
|
||||
|
||||
let Some(clipboard_data) = clipboard_data_opt else {
|
||||
return Err("Clipboard empty".into());
|
||||
};
|
||||
|
||||
Ok(ClipboardEntry {
|
||||
content: clipboard_data,
|
||||
timestamp: SystemTime::now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
74
rklipd/src/config.rs
Normal file
74
rklipd/src/config.rs
Normal file
@ -0,0 +1,74 @@
|
||||
/// rklipd --max-entries 500 --max-entry-size-kb 512 --expiry-days 30
|
||||
pub struct Config {
|
||||
pub max_entries: usize,
|
||||
pub max_entry_size_kb: usize,
|
||||
pub expiry_days: Option<u64>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_entries: 500,
|
||||
max_entry_size_kb: 512,
|
||||
expiry_days: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_args() -> Self {
|
||||
let mut cfg = Self::default();
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let mut i = 0;
|
||||
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--max-entries" => {
|
||||
i += 1;
|
||||
match args.get(i).and_then(|s| s.parse::<usize>().ok()) {
|
||||
Some(0) => eprintln!("--max-entries doit être > 0"),
|
||||
Some(v) => cfg.max_entries = v,
|
||||
None => eprintln!("--max-entries requiert une valeur entière positive"),
|
||||
}
|
||||
}
|
||||
"--max-entry-size-kb" => {
|
||||
i += 1;
|
||||
match args.get(i).and_then(|s| s.parse::<usize>().ok()) {
|
||||
Some(0) => eprintln!("--max-entry-size-kb doit être > 0"),
|
||||
Some(v) => cfg.max_entry_size_kb = v,
|
||||
None => {
|
||||
eprintln!("--max-entry-size-kb requiert une valeur entière positive")
|
||||
}
|
||||
}
|
||||
}
|
||||
"--expiry-days" => {
|
||||
i += 1;
|
||||
match args.get(i).and_then(|s| s.parse::<u64>().ok()) {
|
||||
Some(0) => eprintln!(
|
||||
"--expiry-days doit être > 0 (0 supprimerait tout immédiatement)"
|
||||
),
|
||||
Some(v) => cfg.expiry_days = Some(v),
|
||||
None => eprintln!("--expiry-days requiert une valeur entière positive"),
|
||||
}
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
println!(
|
||||
r#"Usage: rklipd [OPTIONS]
|
||||
|
||||
Options:
|
||||
--max-entries <N> Nombre max d'entrées (défaut: 500)
|
||||
--max-entry-size-kb <N> Taille max d'une entrée en Ko (défaut: 512)
|
||||
--expiry-days <N> Supprime les entrées > N jours (défaut: désactivé)
|
||||
--help Affiche cette aide"#
|
||||
);
|
||||
std::process::exit(0);
|
||||
}
|
||||
unknown => {
|
||||
eprintln!("Argument inconnu : {unknown}. Utilisez --help.");
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
cfg
|
||||
}
|
||||
}
|
||||
83
rklipd/src/crypto.rs
Normal file
83
rklipd/src/crypto.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use aes_gcm::{
|
||||
Aes256Gcm, Key, Nonce,
|
||||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||||
};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
const ENC_PREFIX: &str = "enc:";
|
||||
const ENC2_PREFIX: &str = "enc2:";
|
||||
|
||||
pub struct Crypto {
|
||||
key: [u8; 32],
|
||||
}
|
||||
|
||||
impl Crypto {
|
||||
pub fn load_or_create(data_dir: &Path) -> Result<Self, Box<dyn Error>> {
|
||||
let key_path = data_dir.join("master.key");
|
||||
|
||||
if key_path.exists() {
|
||||
let key_bytes = fs::read(&key_path)?;
|
||||
if key_bytes.len() != 32 {
|
||||
return Err("Fichier de clé invalide (attendu 32 octets)".into());
|
||||
}
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(&key_bytes);
|
||||
Ok(Self { key })
|
||||
} else {
|
||||
let key = Aes256Gcm::generate_key(OsRng);
|
||||
let key_bytes: [u8; 32] = key.into();
|
||||
fs::write(&key_path, key_bytes)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::set_permissions(&key_path, fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
Ok(Self { key: key_bytes })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, plaintext: &str) -> Result<String, Box<dyn Error>> {
|
||||
let key = Key::<Aes256Gcm>::from_slice(&self.key);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
let ciphertext = cipher
|
||||
.encrypt(&nonce, plaintext.as_bytes())
|
||||
.map_err(|e| format!("Erreur de chiffrement : {e}"))?;
|
||||
let mut combined = nonce.to_vec();
|
||||
combined.extend_from_slice(&ciphertext);
|
||||
Ok(format!("{}{}", ENC_PREFIX, BASE64.encode(combined)))
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, encrypted: &str) -> Result<String, Box<dyn Error>> {
|
||||
let encoded = encrypted
|
||||
.strip_prefix(ENC_PREFIX)
|
||||
.ok_or("Pas une entrée chiffrée (enc:)")?;
|
||||
let combined = BASE64.decode(encoded)?;
|
||||
if combined.len() < 12 {
|
||||
return Err("Données chiffrées trop courtes".into());
|
||||
}
|
||||
let (nonce_bytes, ciphertext) = combined.split_at(12);
|
||||
let key = Key::<Aes256Gcm>::from_slice(&self.key);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
let nonce = Nonce::from_slice(nonce_bytes);
|
||||
let plaintext = cipher
|
||||
.decrypt(nonce, ciphertext)
|
||||
.map_err(|e| format!("Erreur de déchiffrement : {e}"))?;
|
||||
Ok(String::from_utf8(plaintext)?)
|
||||
}
|
||||
|
||||
pub fn is_legacy_encrypted(content: &str) -> bool {
|
||||
content.starts_with(ENC_PREFIX) && !content.starts_with(ENC2_PREFIX)
|
||||
}
|
||||
|
||||
pub fn is_password_encrypted(content: &str) -> bool {
|
||||
content.starts_with(ENC2_PREFIX)
|
||||
}
|
||||
|
||||
pub fn is_any_encrypted(content: &str) -> bool {
|
||||
content.starts_with(ENC_PREFIX) || content.starts_with(ENC2_PREFIX)
|
||||
}
|
||||
}
|
||||
@ -1,114 +1,317 @@
|
||||
use crate::config::Config;
|
||||
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||
use image::codecs::jpeg::JpegEncoder;
|
||||
use image::{ExtendedColorType, ImageEncoder};
|
||||
use rusqlite::Connection;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct Database {
|
||||
conn: Connection,
|
||||
dir_path: String,
|
||||
max_entries: usize,
|
||||
max_entry_size_bytes: usize,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn init(dir_path: &str) -> Result<Self, Box<dyn Error>> {
|
||||
if !std::fs::exists(dir_path)? {
|
||||
fs::create_dir(dir_path)?;
|
||||
} else {
|
||||
println!("{:?} dir already exists.", { dir_path });
|
||||
}
|
||||
pub fn init(dir_path: &str, config: &Config) -> Result<Self, Box<dyn Error>> {
|
||||
let base_path = Path::new(dir_path);
|
||||
fs::create_dir_all(base_path.join("images"))?;
|
||||
|
||||
let image_path = format!("{}/images", dir_path);
|
||||
if !std::fs::exists(&image_path)? {
|
||||
fs::create_dir(&image_path)?;
|
||||
} else {
|
||||
println!("{:?} dir already exists.", { image_path });
|
||||
}
|
||||
let conn = Connection::open(base_path.join("clipboard.db"))?;
|
||||
|
||||
let db_path = format!("{}/clipboard.db", dir_path);
|
||||
let conn = Connection::open(&db_path)?;
|
||||
conn.execute_batch(
|
||||
"PRAGMA journal_mode=WAL;
|
||||
PRAGMA synchronous=NORMAL;
|
||||
PRAGMA foreign_keys=ON;",
|
||||
)?;
|
||||
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL DEFAULT 1);",
|
||||
)?;
|
||||
conn.execute(
|
||||
"INSERT INTO schema_version (version) SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM schema_version)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL DEFAULT 0
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
let version: i64 = conn
|
||||
.query_row("SELECT version FROM schema_version", [], |r| r.get(0))
|
||||
.unwrap_or(1);
|
||||
|
||||
if version < 2 {
|
||||
let col_exists: bool = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM pragma_table_info('history') WHERE name='pinned'",
|
||||
[],
|
||||
|r| r.get::<_, i64>(0),
|
||||
)
|
||||
.unwrap_or(0)
|
||||
> 0;
|
||||
|
||||
if !col_exists {
|
||||
conn.execute_batch(
|
||||
"ALTER TABLE history ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;",
|
||||
)?;
|
||||
}
|
||||
conn.execute("UPDATE schema_version SET version = 2", [])?;
|
||||
println!("DB migrée → schema v2 (colonne `pinned`)");
|
||||
}
|
||||
|
||||
conn.execute_batch(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_history_content ON history(content);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_timestamp ON history(timestamp DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_pinned ON history(pinned);",
|
||||
)?;
|
||||
|
||||
conn.execute_batch(
|
||||
"DELETE FROM history WHERE id NOT IN (
|
||||
SELECT MAX(id) FROM history GROUP BY content
|
||||
);",
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
conn,
|
||||
dir_path: dir_path.to_string(),
|
||||
max_entries: config.max_entries,
|
||||
max_entry_size_bytes: config.max_entry_size_kb * 1024,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn append(&self, entry: ClipboardEntry) -> Result<(), Box<dyn Error>> {
|
||||
let timestamp_millis = entry.timestamp.duration_since(UNIX_EPOCH)?.as_millis() as i64;
|
||||
let ts = entry.timestamp.duration_since(UNIX_EPOCH)?.as_millis() as i64;
|
||||
|
||||
let (entry_type, content) = match &entry.content {
|
||||
ClipboardData::Text(text) => ("text", text.clone()),
|
||||
ClipboardData::Image(img) => {
|
||||
if let Some(bytes) = &img.bytes {
|
||||
let img_path = format!("{}/images/{}.png", self.dir_path, img.id);
|
||||
fs::write(&img_path, bytes)?;
|
||||
let (kind, content) = match &entry.content {
|
||||
ClipboardData::Text(t) => {
|
||||
if t.trim().is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
("image", img.id.to_string())
|
||||
if t.len() > self.max_entry_size_bytes {
|
||||
return Ok(());
|
||||
}
|
||||
("text", t.clone())
|
||||
}
|
||||
ClipboardData::Image(img) => {
|
||||
match &img.raw_pixels {
|
||||
Some(px) => {
|
||||
let rgb: Vec<u8> = px
|
||||
.chunks_exact(4)
|
||||
.flat_map(|rgba| [rgba[0], rgba[1], rgba[2]])
|
||||
.collect();
|
||||
|
||||
let mut buf = Vec::new();
|
||||
JpegEncoder::new_with_quality(Cursor::new(&mut buf), 70).write_image(
|
||||
&rgb,
|
||||
img.width,
|
||||
img.height,
|
||||
ExtendedColorType::Rgb8,
|
||||
)?;
|
||||
|
||||
if buf.len() > self.max_entry_size_bytes {
|
||||
eprintln!(
|
||||
"Image rejetée : JPEG {} Ko > limite {} Ko",
|
||||
buf.len() / 1024,
|
||||
self.max_entry_size_bytes / 1024
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = img.file_path(&self.dir_path);
|
||||
fs::write(&path, &buf)?;
|
||||
}
|
||||
None => return Ok(()),
|
||||
}
|
||||
("image", format!("{}.jpg", img.id))
|
||||
}
|
||||
};
|
||||
|
||||
self.conn.execute(
|
||||
"INSERT INTO history (type, content, timestamp) VALUES (?1, ?2, ?3)",
|
||||
(entry_type, content, timestamp_millis),
|
||||
"INSERT OR REPLACE INTO history (type, content, timestamp, pinned)
|
||||
VALUES (?1, ?2, ?3,
|
||||
COALESCE(
|
||||
(SELECT pinned FROM history WHERE content = ?2),
|
||||
0
|
||||
)
|
||||
)",
|
||||
(kind, &content, ts),
|
||||
)?;
|
||||
|
||||
self.trim_to_max()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn trim_to_max(&self) -> Result<(), Box<dyn Error>> {
|
||||
if self.max_entries == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tx = self.conn.unchecked_transaction()?;
|
||||
|
||||
let image_files: Vec<String> = {
|
||||
let mut stmt = tx.prepare(
|
||||
"SELECT content FROM history
|
||||
WHERE type = 'image' AND pinned = 0
|
||||
AND id NOT IN (
|
||||
SELECT id FROM history WHERE pinned = 0
|
||||
ORDER BY timestamp DESC LIMIT ?1
|
||||
)",
|
||||
)?;
|
||||
stmt.query_map([self.max_entries as i64], |row| row.get(0))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect()
|
||||
};
|
||||
|
||||
tx.execute(
|
||||
"DELETE FROM history
|
||||
WHERE pinned = 0
|
||||
AND id NOT IN (
|
||||
SELECT id FROM history WHERE pinned = 0
|
||||
ORDER BY timestamp DESC LIMIT ?1
|
||||
)",
|
||||
[self.max_entries as i64],
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
for filename in image_files {
|
||||
let path = Path::new(&self.dir_path).join("images").join(&filename);
|
||||
if path.exists() {
|
||||
if let Err(e) = fs::remove_file(&path) {
|
||||
eprintln!("Impossible de supprimer l'image {filename} : {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_history(&self) -> Result<Vec<ClipboardEntry>, Box<dyn Error>> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT type, content, timestamp FROM history ORDER BY timestamp ASC")?;
|
||||
pub fn read_history(&self, limit: usize) -> Result<Vec<ClipboardEntry>, Box<dyn Error>> {
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT type, content, timestamp, pinned
|
||||
FROM history
|
||||
ORDER BY pinned DESC, timestamp DESC
|
||||
LIMIT ?1",
|
||||
)?;
|
||||
|
||||
let rows = stmt.query_map([], |row| {
|
||||
let ty: String = row.get(0)?;
|
||||
let content: String = row.get(1)?;
|
||||
let timestamp: i64 = row.get(2)?;
|
||||
Ok((ty, content, timestamp))
|
||||
let rows = stmt.query_map([limit as i64], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, i64>(2)?,
|
||||
row.get::<_, bool>(3)?,
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for row in rows {
|
||||
let (ty, content, timestamp) = row?;
|
||||
|
||||
let timestamp = UNIX_EPOCH + Duration::from_millis(timestamp as u64);
|
||||
let (ty, content, ts_ms, pinned) = row?;
|
||||
let timestamp = UNIX_EPOCH + Duration::from_millis(ts_ms as u64);
|
||||
let data = if ty == "text" {
|
||||
ClipboardData::Text(content)
|
||||
} else {
|
||||
let id = Uuid::parse_str(&content)?;
|
||||
ClipboardData::Image(Image { id, bytes: None })
|
||||
let id = Uuid::parse_str(content.trim_end_matches(".jpg"))?;
|
||||
ClipboardData::Image(Image {
|
||||
id,
|
||||
raw_pixels: None,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
};
|
||||
|
||||
// let data = if ty == "text" {
|
||||
// ClipboardData::Text(content)
|
||||
// } else {
|
||||
// let id = Uuid::parse_str(&content)?;
|
||||
// let img_path = format!("{}/images/{}.png", self.dir_path, id);
|
||||
// let bytes = fs::read(&img_path).unwrap_or_default();
|
||||
// ClipboardData::Image(Image {
|
||||
// bytes: Some(bytes),
|
||||
// id,
|
||||
// })
|
||||
// };
|
||||
|
||||
entries.push(ClipboardEntry {
|
||||
content: data,
|
||||
timestamp,
|
||||
pinned,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
pub fn set_pin(&self, content: &str, pinned: bool) -> Result<(), Box<dyn Error>> {
|
||||
let rows = self.conn.execute(
|
||||
"UPDATE history SET pinned = ?1 WHERE content = ?2",
|
||||
(pinned as i32, content),
|
||||
)?;
|
||||
if rows == 0 {
|
||||
return Err(format!("Entrée introuvable pour pin : {content}").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_entry_by_content(&self, content: &str) -> Result<(), Box<dyn Error>> {
|
||||
self.conn
|
||||
.execute("DELETE FROM history WHERE content = ?1", [content])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_entry_content(&self, old: &str, new: &str) -> Result<(), Box<dyn Error>> {
|
||||
let rows_affected = self.conn.execute(
|
||||
"UPDATE history SET content = ?1 WHERE content = ?2",
|
||||
[new, old],
|
||||
)?;
|
||||
if rows_affected == 0 {
|
||||
return Err(format!("Entrée introuvable : {old}").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_entries_older_than(&self, days: u64) -> Result<usize, Box<dyn Error>> {
|
||||
let cutoff_ms = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as i64
|
||||
- (days as i64 * 86_400_000);
|
||||
|
||||
let tx = self.conn.unchecked_transaction()?;
|
||||
|
||||
let image_files: Vec<String> = {
|
||||
let mut stmt = tx.prepare(
|
||||
"SELECT content FROM history
|
||||
WHERE type = 'image' AND pinned = 0 AND timestamp < ?1",
|
||||
)?;
|
||||
stmt.query_map([cutoff_ms], |row| row.get(0))?
|
||||
.filter_map(|r| r.ok())
|
||||
.collect()
|
||||
};
|
||||
|
||||
let count = tx.execute(
|
||||
"DELETE FROM history WHERE timestamp < ?1 AND pinned = 0",
|
||||
[cutoff_ms],
|
||||
)?;
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
for filename in image_files {
|
||||
let path = Path::new(&self.dir_path).join("images").join(&filename);
|
||||
if path.exists() {
|
||||
if let Err(e) = fs::remove_file(&path) {
|
||||
eprintln!("Impossible de supprimer l'image expirée {filename} : {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn clear_history(&self) -> Result<(), Box<dyn Error>> {
|
||||
self.conn.execute("DELETE FROM history", [])?;
|
||||
|
||||
let images_dir = Path::new(&self.dir_path).join("images");
|
||||
if images_dir.exists() {
|
||||
fs::remove_dir_all(&images_dir)?;
|
||||
}
|
||||
fs::create_dir_all(&images_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
288
rklipd/src/ipc.rs
Normal file
288
rklipd/src/ipc.rs
Normal file
@ -0,0 +1,288 @@
|
||||
use crate::crypto::Crypto;
|
||||
use crate::database::Database;
|
||||
use crate::models::{ClipboardData, ClipboardEntry};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
const IPC_READ_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const IPC_MAX_REQUEST_BYTES: usize = 4 * 1024 * 1024;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct HistoryItem {
|
||||
pub content: String,
|
||||
pub timestamp: i64,
|
||||
#[serde(default)]
|
||||
pub pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum IpcRequest {
|
||||
GetHistory {
|
||||
limit: usize,
|
||||
},
|
||||
SetClipboard {
|
||||
content: String,
|
||||
},
|
||||
DeleteEntry {
|
||||
content: String,
|
||||
},
|
||||
UpdateEntry {
|
||||
old_content: String,
|
||||
new_content: String,
|
||||
},
|
||||
AddEntry {
|
||||
content: String,
|
||||
},
|
||||
PinEntry {
|
||||
content: String,
|
||||
pinned: bool,
|
||||
},
|
||||
ClearHistory,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum IpcResponse {
|
||||
History(Vec<HistoryItem>),
|
||||
Ok,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
fn reply(stream: &mut std::os::unix::net::UnixStream, resp: IpcResponse) {
|
||||
if let Ok(json) = serde_json::to_string(&resp) {
|
||||
let _ = stream.write_all(json.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_server(
|
||||
db: Arc<Mutex<Database>>,
|
||||
crypto: Arc<Crypto>,
|
||||
socket_path: &Path,
|
||||
data_dir: Arc<PathBuf>,
|
||||
) {
|
||||
if socket_path.exists() {
|
||||
let _ = fs::remove_file(socket_path);
|
||||
}
|
||||
|
||||
let listener = match UnixListener::bind(socket_path) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
eprintln!("Erreur socket IPC : {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
if let Err(e) = fs::set_permissions(socket_path, fs::Permissions::from_mode(0o600)) {
|
||||
eprintln!("Impossible de restreindre les permissions du socket : {e}");
|
||||
}
|
||||
}
|
||||
|
||||
println!("IPC server en écoute sur {:?}", socket_path);
|
||||
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(mut stream) => {
|
||||
if let Err(e) = stream.set_read_timeout(Some(IPC_READ_TIMEOUT)) {
|
||||
eprintln!("Impossible de définir le timeout IPC : {e}");
|
||||
}
|
||||
|
||||
let db_clone = Arc::clone(&db);
|
||||
let crypto_clone = Arc::clone(&crypto);
|
||||
let data_dir_clone = Arc::clone(&data_dir);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
handle_connection(&mut stream, db_clone, crypto_clone, data_dir_clone);
|
||||
});
|
||||
}
|
||||
Err(e) => eprintln!("Erreur connexion IPC : {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_connection(
|
||||
stream: &mut std::os::unix::net::UnixStream,
|
||||
db: Arc<Mutex<Database>>,
|
||||
crypto: Arc<Crypto>,
|
||||
data_dir: Arc<PathBuf>,
|
||||
) {
|
||||
let mut buf = Vec::new();
|
||||
let mut tmp = [0u8; 4096];
|
||||
|
||||
loop {
|
||||
match stream.read(&mut tmp) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
buf.extend_from_slice(&tmp[..n]);
|
||||
if buf.len() > IPC_MAX_REQUEST_BYTES {
|
||||
eprintln!("IPC : requête trop grande, abandon");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e)
|
||||
if e.kind() == std::io::ErrorKind::WouldBlock
|
||||
|| e.kind() == std::io::ErrorKind::TimedOut =>
|
||||
{
|
||||
eprintln!("IPC : timeout de lecture");
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("IPC read error : {e}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let buf_str = match String::from_utf8(buf) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("IPC : requête non-UTF8 : {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let req = match serde_json::from_str::<IpcRequest>(&buf_str) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!("IPC parse error : {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match req {
|
||||
IpcRequest::GetHistory { limit } => {
|
||||
let limit = limit.min(1000);
|
||||
let lock = db.lock().unwrap_or_else(|p| p.into_inner());
|
||||
let history = lock.read_history(limit).unwrap_or_default();
|
||||
let items: Vec<HistoryItem> = history
|
||||
.into_iter()
|
||||
.map(|e| {
|
||||
let content = match e.content {
|
||||
ClipboardData::Text(t) => t,
|
||||
ClipboardData::Image(img) => format!("{}.jpg", img.id),
|
||||
};
|
||||
let ts = e
|
||||
.timestamp
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64;
|
||||
HistoryItem {
|
||||
content,
|
||||
timestamp: ts,
|
||||
pinned: e.pinned,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
reply(stream, IpcResponse::History(items));
|
||||
}
|
||||
|
||||
IpcRequest::SetClipboard { content } => {
|
||||
let actual = if Crypto::is_legacy_encrypted(&content) {
|
||||
crypto.decrypt(&content).unwrap_or_else(|e| {
|
||||
eprintln!("Impossible de déchiffrer l'entrée enc: : {e}");
|
||||
content.clone()
|
||||
})
|
||||
} else if Crypto::is_password_encrypted(&content) {
|
||||
reply(
|
||||
stream,
|
||||
IpcResponse::Error(
|
||||
"Entrée chiffrée par mot de passe : déchiffrez côté client avant de coller"
|
||||
.to_string(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
content
|
||||
};
|
||||
|
||||
match arboard::Clipboard::new() {
|
||||
Ok(mut cb) => {
|
||||
if actual.ends_with(".jpg") || actual.ends_with(".png") {
|
||||
let path = data_dir.join("images").join(&actual);
|
||||
if let Ok(img) = image::open(&path) {
|
||||
let rgba = img.into_rgba8();
|
||||
let (w, h) = (rgba.width() as usize, rgba.height() as usize);
|
||||
let _ = cb.set_image(arboard::ImageData {
|
||||
width: w,
|
||||
height: h,
|
||||
bytes: std::borrow::Cow::Owned(rgba.into_raw()),
|
||||
});
|
||||
reply(stream, IpcResponse::Ok);
|
||||
} else {
|
||||
reply(
|
||||
stream,
|
||||
IpcResponse::Error(format!("Image introuvable : {actual}")),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let _ = cb.set_text(actual);
|
||||
reply(stream, IpcResponse::Ok);
|
||||
}
|
||||
}
|
||||
Err(e) => reply(stream, IpcResponse::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
IpcRequest::DeleteEntry { content } => {
|
||||
{
|
||||
let lock = db.lock().unwrap_or_else(|p| p.into_inner());
|
||||
let _ = lock.delete_entry_by_content(&content);
|
||||
}
|
||||
if !Crypto::is_any_encrypted(&content)
|
||||
&& (content.ends_with(".jpg") || content.ends_with(".png"))
|
||||
{
|
||||
let p = data_dir.join("images").join(&content);
|
||||
if p.exists() {
|
||||
let _ = fs::remove_file(p);
|
||||
}
|
||||
}
|
||||
reply(stream, IpcResponse::Ok);
|
||||
}
|
||||
|
||||
IpcRequest::UpdateEntry {
|
||||
old_content,
|
||||
new_content,
|
||||
} => {
|
||||
let lock = db.lock().unwrap_or_else(|p| p.into_inner());
|
||||
match lock.update_entry_content(&old_content, &new_content) {
|
||||
Ok(_) => reply(stream, IpcResponse::Ok),
|
||||
Err(e) => reply(stream, IpcResponse::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
IpcRequest::AddEntry { content } => {
|
||||
let entry = ClipboardEntry {
|
||||
content: ClipboardData::Text(content),
|
||||
timestamp: SystemTime::now(),
|
||||
pinned: false,
|
||||
};
|
||||
let lock = db.lock().unwrap_or_else(|p| p.into_inner());
|
||||
match lock.append(entry) {
|
||||
Ok(_) => reply(stream, IpcResponse::Ok),
|
||||
Err(e) => reply(stream, IpcResponse::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
IpcRequest::PinEntry { content, pinned } => {
|
||||
let lock = db.lock().unwrap_or_else(|p| p.into_inner());
|
||||
match lock.set_pin(&content, pinned) {
|
||||
Ok(_) => reply(stream, IpcResponse::Ok),
|
||||
Err(e) => reply(stream, IpcResponse::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
IpcRequest::ClearHistory => {
|
||||
let lock = db.lock().unwrap_or_else(|p| p.into_inner());
|
||||
match lock.clear_history() {
|
||||
Ok(_) => reply(stream, IpcResponse::Ok),
|
||||
Err(e) => reply(stream, IpcResponse::Error(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,67 @@
|
||||
use arboard::Clipboard;
|
||||
|
||||
use crate::database::Database;
|
||||
|
||||
mod clipboard;
|
||||
mod config;
|
||||
mod crypto;
|
||||
mod database;
|
||||
mod ipc;
|
||||
mod models;
|
||||
mod monitor;
|
||||
mod ws;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::crypto::Crypto;
|
||||
use crate::database::Database;
|
||||
use arboard::Clipboard;
|
||||
use directories::ProjectDirs;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = Config::from_args();
|
||||
|
||||
println!(
|
||||
"rklipd démarrage — max_entries={}, max_entry_size_kb={}, expiry_days={}",
|
||||
config.max_entries,
|
||||
config.max_entry_size_kb,
|
||||
config
|
||||
.expiry_days
|
||||
.map(|d| d.to_string())
|
||||
.unwrap_or_else(|| "désactivé".to_string())
|
||||
);
|
||||
|
||||
let clipboard = Clipboard::new()?;
|
||||
|
||||
let dir_path = "clipboard";
|
||||
let proj_dirs =
|
||||
ProjectDirs::from("com", "zefad", "rklipd").expect("Impossible d'ouvrir le répertoire");
|
||||
let dir_path = proj_dirs.data_dir().to_path_buf();
|
||||
let dir_path_str = dir_path.to_str().expect("Chemin invalide").to_string();
|
||||
|
||||
let db = Database::init(dir_path)?;
|
||||
let db = Arc::new(Mutex::new(Database::init(&dir_path_str, &config)?));
|
||||
let crypto = Arc::new(Crypto::load_or_create(&dir_path)?);
|
||||
|
||||
println!("{:#?}", db.read_history());
|
||||
let socket_path = dir_path.join("rklip.sock");
|
||||
let db_for_ipc = Arc::clone(&db);
|
||||
let crypto_for_ipc = Arc::clone(&crypto);
|
||||
let data_dir = Arc::new(dir_path.clone());
|
||||
std::thread::spawn(move || {
|
||||
crate::ipc::start_server(db_for_ipc, crypto_for_ipc, &socket_path, data_dir);
|
||||
});
|
||||
|
||||
if let Some(days) = config.expiry_days {
|
||||
let db_for_expiry = Arc::clone(&db);
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
{
|
||||
let lock = db_for_expiry.lock().unwrap_or_else(|p| p.into_inner());
|
||||
match lock.delete_entries_older_than(days) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => println!("Expiration : {n} entrée(s) > {days} jours supprimée(s)"),
|
||||
Err(e) => eprintln!("Erreur expiration : {e}"),
|
||||
}
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(3600));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
monitor::start(db, clipboard)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
use std::{fs, io};
|
||||
use uuid::Uuid;
|
||||
@ -6,6 +7,7 @@ use uuid::Uuid;
|
||||
pub struct ClipboardEntry {
|
||||
pub content: ClipboardData,
|
||||
pub timestamp: SystemTime,
|
||||
pub pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -16,16 +18,20 @@ pub enum ClipboardData {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Image {
|
||||
pub bytes: Option<Vec<u8>>,
|
||||
pub raw_pixels: Option<Vec<u8>>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub id: Uuid,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn file_path(&self, base_dir: &str) -> PathBuf {
|
||||
std::path::Path::new(base_dir)
|
||||
.join("images")
|
||||
.join(format!("{}.jpg", self.id))
|
||||
}
|
||||
|
||||
pub fn load_bytes(&self, dir_path: &str) -> io::Result<Vec<u8>> {
|
||||
if let Some(b) = &self.bytes {
|
||||
return Ok(b.clone());
|
||||
}
|
||||
let img_path = format!("{}/images/{}.png", dir_path, self.id);
|
||||
fs::read(img_path)
|
||||
fs::read(self.file_path(dir_path))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,45 @@
|
||||
use std::{error::Error, sync::mpsc::channel};
|
||||
|
||||
use arboard::Clipboard;
|
||||
use clipboard_master::Master;
|
||||
|
||||
use crate::clipboard::Handler;
|
||||
use crate::database::Database;
|
||||
use crate::models::ClipboardEntry;
|
||||
use arboard::Clipboard;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex, mpsc};
|
||||
|
||||
pub fn start(db: Database, mut clipboard: Clipboard) -> Result<(), Box<dyn Error>> {
|
||||
let (tx, rx) = channel();
|
||||
pub fn start(db: Arc<Mutex<Database>>, clipboard: Clipboard) -> Result<(), Box<dyn Error>> {
|
||||
let (tx, rx) = mpsc::channel::<ClipboardEntry>();
|
||||
|
||||
let mut master = Master::new(Handler { clipboard_tx: tx })?;
|
||||
std::thread::spawn(move || {
|
||||
if let Err(e) = master.run() {
|
||||
eprintln!("Clipboard monitor error : {}", e);
|
||||
for entry in rx {
|
||||
let lock = db.lock().unwrap_or_else(|poisoned| {
|
||||
eprintln!("Mutex DB empoisonné, récupération forcée");
|
||||
poisoned.into_inner()
|
||||
});
|
||||
if let Err(e) = lock.append(entry) {
|
||||
eprintln!("SQLite write error: {e}");
|
||||
} else {
|
||||
println!("SQLite updated!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for _ in rx {
|
||||
print!("Clipboard changed!");
|
||||
if let Ok(entry) = ClipboardEntry::new(&mut clipboard) {
|
||||
if let Err(e) = db.append(entry) {
|
||||
eprintln!("SQLite writing error: {}", e);
|
||||
} else {
|
||||
println!("SQLite edited!");
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature = "wayland", not(feature = "x11")))]
|
||||
{
|
||||
crate::ws::wayland::start(tx, clipboard)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
#[cfg(all(feature = "x11", not(feature = "wayland")))]
|
||||
{
|
||||
crate::ws::x11::start(tx, clipboard)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "x11", feature = "wayland"))]
|
||||
{
|
||||
let _ = (tx, clipboard);
|
||||
Err("Les features 'x11' et 'wayland' sont mutuellement exclusives".into())
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "x11", feature = "wayland")))]
|
||||
{
|
||||
let _ = (tx, clipboard);
|
||||
Err("Aucune feature de système de fenêtrage activée (--features x11 ou wayland)".into())
|
||||
}
|
||||
}
|
||||
|
||||
4
rklipd/src/ws.rs
Normal file
4
rklipd/src/ws.rs
Normal file
@ -0,0 +1,4 @@
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod wayland;
|
||||
#[cfg(feature = "x11")]
|
||||
pub mod x11;
|
||||
104
rklipd/src/ws/wayland.rs
Normal file
104
rklipd/src/ws/wayland.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::error::Error;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::mpsc;
|
||||
use std::time::SystemTime;
|
||||
use uuid::Uuid;
|
||||
use wayland_clipboard_listener::{WlClipboardPasteStream, WlListenType};
|
||||
|
||||
const MAX_IMAGE_PIXELS: usize = 3840 * 2160;
|
||||
|
||||
fn hash_bytes(data: &[u8]) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
data.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
tx: mpsc::Sender<ClipboardEntry>,
|
||||
_clipboard: arboard::Clipboard,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut stream = WlClipboardPasteStream::init(WlListenType::ListenOnCopy)
|
||||
.map_err(|e| format!("Impossible d'initialiser Wayland : {e}"))?;
|
||||
|
||||
println!("Écoute du presse-papier Wayland...");
|
||||
|
||||
let mut last_text: Option<String> = None;
|
||||
let mut last_image_hash: Option<u64> = None;
|
||||
|
||||
for msg in stream.paste_stream().flatten() {
|
||||
let data: &[u8] = msg.context.context.as_slice();
|
||||
|
||||
if data.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = if let Ok(text) = String::from_utf8(data.to_vec()) {
|
||||
let text = text.trim_end_matches('\n').to_string();
|
||||
if text.is_empty() || Some(&text) == last_text.as_ref() {
|
||||
continue;
|
||||
}
|
||||
last_text = Some(text.clone());
|
||||
last_image_hash = None;
|
||||
println!("Clipboard update (texte)");
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Text(text),
|
||||
timestamp: SystemTime::now(),
|
||||
pinned: false,
|
||||
}
|
||||
} else {
|
||||
let hash = hash_bytes(data);
|
||||
if Some(hash) == last_image_hash {
|
||||
continue;
|
||||
}
|
||||
|
||||
match image::load_from_memory(data) {
|
||||
Ok(img) => {
|
||||
let (width, height) = (img.width(), img.height());
|
||||
|
||||
if (width as usize) * (height as usize) > MAX_IMAGE_PIXELS {
|
||||
eprintln!(
|
||||
"Image Wayland ignorée : {}×{} ({} Mpx > limite {}×{})",
|
||||
width,
|
||||
height,
|
||||
(width as usize * height as usize) / 1_000_000,
|
||||
3840,
|
||||
2160
|
||||
);
|
||||
last_image_hash = Some(hash);
|
||||
last_text = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
last_image_hash = Some(hash);
|
||||
last_text = None;
|
||||
println!("Clipboard update (image)");
|
||||
|
||||
let rgba = img.into_rgba8();
|
||||
ClipboardEntry {
|
||||
content: ClipboardData::Image(Image {
|
||||
raw_pixels: Some(rgba.into_raw()),
|
||||
width,
|
||||
height,
|
||||
id: Uuid::new_v4(),
|
||||
}),
|
||||
timestamp: SystemTime::now(),
|
||||
pinned: false,
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Clipboard ignoré (format inconnu) : {e}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if tx.send(entry).is_err() {
|
||||
eprintln!("Wayland : writer thread disparu, arrêt");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
149
rklipd/src/ws/x11.rs
Normal file
149
rklipd/src/ws/x11.rs
Normal file
@ -0,0 +1,149 @@
|
||||
use crate::models::{ClipboardData, ClipboardEntry, Image};
|
||||
use arboard::Clipboard;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::error::Error;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use uuid::Uuid;
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::Event;
|
||||
use x11rb::protocol::xfixes::{ConnectionExt as XfixesExt, SelectionEventMask};
|
||||
use x11rb::protocol::xproto::{ConnectionExt as XprotoExt, CreateWindowAux, WindowClass};
|
||||
use x11rb::rust_connection::RustConnection;
|
||||
|
||||
const MAX_IMAGE_PIXELS: usize = 3840 * 2160;
|
||||
|
||||
fn hash_bytes(data: &[u8]) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
data.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn start(
|
||||
tx: mpsc::Sender<ClipboardEntry>,
|
||||
mut clipboard: Clipboard,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let (conn, screen_num) =
|
||||
RustConnection::connect(None).map_err(|e| format!("Connexion X11 impossible : {e}"))?;
|
||||
|
||||
let root = conn.setup().roots[screen_num].root;
|
||||
|
||||
let win = conn.generate_id()?;
|
||||
conn.create_window(
|
||||
0,
|
||||
win,
|
||||
root,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
WindowClass::INPUT_ONLY,
|
||||
0,
|
||||
&CreateWindowAux::new(),
|
||||
)?
|
||||
.check()?;
|
||||
|
||||
conn.xfixes_query_version(5, 0)
|
||||
.map_err(|e| format!("Extension XFIXES indisponible : {e}"))?
|
||||
.reply()?;
|
||||
|
||||
let clipboard_atom = conn.intern_atom(false, b"CLIPBOARD")?.reply()?.atom;
|
||||
conn.xfixes_select_selection_input(
|
||||
win,
|
||||
clipboard_atom,
|
||||
SelectionEventMask::SET_SELECTION_OWNER,
|
||||
)?
|
||||
.check()?;
|
||||
conn.flush()?;
|
||||
|
||||
println!("Clipboard monitor démarré (X11 XFIXES — zéro polling)");
|
||||
|
||||
let mut last_text: Option<String> = None;
|
||||
let mut last_image_hash: Option<u64> = None;
|
||||
|
||||
loop {
|
||||
let event = conn.wait_for_event()?;
|
||||
|
||||
if let Event::XfixesSelectionNotify(_) = event {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
handle_clipboard_event(&mut clipboard, &tx, &mut last_text, &mut last_image_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_clipboard_event(
|
||||
clipboard: &mut Clipboard,
|
||||
tx: &mpsc::Sender<ClipboardEntry>,
|
||||
last_text: &mut Option<String>,
|
||||
last_image_hash: &mut Option<u64>,
|
||||
) {
|
||||
match clipboard.get_text() {
|
||||
Ok(raw) => {
|
||||
let text = raw.trim_end_matches('\n').to_string();
|
||||
if text.is_empty() || Some(&text) == last_text.as_ref() {
|
||||
return;
|
||||
}
|
||||
*last_text = Some(text.clone());
|
||||
*last_image_hash = None;
|
||||
println!("Clipboard update (texte)");
|
||||
|
||||
if tx
|
||||
.send(ClipboardEntry {
|
||||
content: ClipboardData::Text(text),
|
||||
timestamp: SystemTime::now(),
|
||||
pinned: false,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
eprintln!("X11 : writer thread disparu");
|
||||
}
|
||||
}
|
||||
|
||||
Err(_) => {
|
||||
let Ok(img_data) = clipboard.get_image() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let pixel_count = img_data.width * img_data.height;
|
||||
if pixel_count > MAX_IMAGE_PIXELS {
|
||||
eprintln!(
|
||||
"Image ignorée : {}×{} ({} Mpx > limite 4K)",
|
||||
img_data.width,
|
||||
img_data.height,
|
||||
pixel_count / 1_000_000
|
||||
);
|
||||
let sentinel_hash = hash_bytes(&img_data.bytes[..img_data.bytes.len().min(256)]);
|
||||
*last_image_hash = Some(sentinel_hash);
|
||||
*last_text = None;
|
||||
return;
|
||||
}
|
||||
|
||||
let hash = hash_bytes(&img_data.bytes);
|
||||
if Some(hash) == *last_image_hash {
|
||||
return;
|
||||
}
|
||||
*last_image_hash = Some(hash);
|
||||
*last_text = None;
|
||||
println!("Clipboard update (image)");
|
||||
|
||||
if tx
|
||||
.send(ClipboardEntry {
|
||||
content: ClipboardData::Image(Image {
|
||||
raw_pixels: Some(img_data.bytes.into_owned()),
|
||||
width: img_data.width as u32,
|
||||
height: img_data.height as u32,
|
||||
id: Uuid::new_v4(),
|
||||
}),
|
||||
timestamp: SystemTime::now(),
|
||||
pinned: false,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
eprintln!("X11 : writer thread disparu");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
898
src/app.rs
Normal file
898
src/app.rs
Normal file
@ -0,0 +1,898 @@
|
||||
use crate::crypto::Crypto;
|
||||
use crate::ipc::{self, HistoryItem};
|
||||
use chrono::{Local, NaiveDate, TimeZone};
|
||||
use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
|
||||
use image::DynamicImage;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::ListState;
|
||||
use ratatui_image::{picker::Picker, protocol};
|
||||
use regex::Regex;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::time::{Duration, Instant};
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::{FontStyle as SynFontStyle, ThemeSet};
|
||||
use syntect::parsing::SyntaxSet;
|
||||
use syntect::util::LinesWithEndings;
|
||||
|
||||
const PREVIEW_MAX_WIDTH: u32 = 1280;
|
||||
const PREVIEW_MAX_HEIGHT: u32 = 720;
|
||||
const IMAGE_CACHE_MAX: usize = 8;
|
||||
const PAGE_SIZE: usize = 50;
|
||||
const MAX_HIGHLIGHT_LINES: usize = 500;
|
||||
const SYNC_INTERVAL_MS: u64 = 1000;
|
||||
const UNDO_STACK_MAX: usize = 50;
|
||||
|
||||
#[inline]
|
||||
pub fn is_image(s: &str) -> bool {
|
||||
s.ends_with(".jpg") || s.ends_with(".png")
|
||||
}
|
||||
|
||||
pub fn is_url_only(content: &str) -> bool {
|
||||
let t = content.trim();
|
||||
!t.contains('\n')
|
||||
&& !t.contains(' ')
|
||||
&& (t.starts_with("http://") || t.starts_with("https://"))
|
||||
&& t.len() > 10
|
||||
}
|
||||
|
||||
pub fn extract_url(content: &str) -> Option<String> {
|
||||
static RE: OnceLock<Regex> = OnceLock::new();
|
||||
let re = RE
|
||||
.get_or_init(|| Regex::new(r#"https?://[^\s<>"'()\[\]{}]+"#).expect("URL regex invalide"));
|
||||
re.find(content).map(|m| {
|
||||
m.as_str()
|
||||
.trim_end_matches(|c: char| matches!(c, '.' | ',' | ';' | ':' | '!' | '?'))
|
||||
.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Mode {
|
||||
Normal,
|
||||
Command,
|
||||
Search,
|
||||
ConfirmDelete,
|
||||
PasswordInput,
|
||||
Help,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PendingAction {
|
||||
EncryptSelected,
|
||||
DecryptSelected,
|
||||
PasteEncrypted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TypeFilter {
|
||||
All,
|
||||
Text,
|
||||
Image,
|
||||
}
|
||||
|
||||
impl TypeFilter {
|
||||
pub fn next(self) -> Self {
|
||||
match self {
|
||||
Self::All => Self::Text,
|
||||
Self::Text => Self::Image,
|
||||
Self::Image => Self::All,
|
||||
}
|
||||
}
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::All => "Tous",
|
||||
Self::Text => "Texte",
|
||||
Self::Image => "Image",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub mode: Mode,
|
||||
pub all_items: Vec<HistoryItem>,
|
||||
pub filtered_items: Vec<HistoryItem>,
|
||||
pub list_state: ListState,
|
||||
pub input_buffer: String,
|
||||
pub should_quit: bool,
|
||||
pub undo_stack: Vec<HistoryItem>,
|
||||
pub current_image: Option<protocol::StatefulProtocol>,
|
||||
pub last_selected_index: Option<usize>,
|
||||
pub picker: Picker,
|
||||
pub preview_scroll: u16,
|
||||
pub crypto: Option<Crypto>,
|
||||
pub salt: Vec<u8>,
|
||||
pub pending_action: Option<PendingAction>,
|
||||
pub error_message: Option<(String, Instant)>,
|
||||
pub status_message: Option<(String, Instant)>,
|
||||
pub syntax_set: SyntaxSet,
|
||||
pub theme_set: ThemeSet,
|
||||
pub type_filter: TypeFilter,
|
||||
pub loaded_count: usize,
|
||||
pub has_more: bool,
|
||||
pub preview_highlighted: Option<Vec<Line<'static>>>,
|
||||
pub preview_lang: Option<String>,
|
||||
pub data_dir: Option<PathBuf>,
|
||||
last_sync: Instant,
|
||||
image_cache: HashMap<String, Arc<DynamicImage>>,
|
||||
image_cache_order: VecDeque<String>,
|
||||
matcher: SkimMatcherV2,
|
||||
}
|
||||
|
||||
fn syn_color(c: syntect::highlighting::Color) -> Color {
|
||||
Color::Rgb(c.r, c.g, c.b)
|
||||
}
|
||||
|
||||
fn detect_syntax<'a>(
|
||||
content: &str,
|
||||
syntax_set: &'a SyntaxSet,
|
||||
) -> &'a syntect::parsing::SyntaxReference {
|
||||
if let Some(s) = syntax_set.find_syntax_by_first_line(content) {
|
||||
if s.name != "Plain Text" {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
for line in content.lines().take(3) {
|
||||
if let Some(word) = line.split_whitespace().last() {
|
||||
if let Some(ext) = word.rsplit('.').next() {
|
||||
if (1..=6).contains(&ext.len()) && ext.chars().all(|c| c.is_ascii_alphanumeric()) {
|
||||
if let Some(s) = syntax_set.find_syntax_by_extension(ext) {
|
||||
if s.name != "Plain Text" {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
syntax_set.find_syntax_plain_text()
|
||||
}
|
||||
|
||||
pub fn detect_lang(content: &str, syntax_set: &SyntaxSet) -> Option<String> {
|
||||
let s = detect_syntax(content, syntax_set);
|
||||
if s.name == "Plain Text" {
|
||||
None
|
||||
} else {
|
||||
Some(s.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight_code(
|
||||
content: &str,
|
||||
syntax_set: &SyntaxSet,
|
||||
theme_set: &ThemeSet,
|
||||
) -> Vec<Line<'static>> {
|
||||
let theme = &theme_set.themes["base16-ocean.dark"];
|
||||
let syntax = detect_syntax(content, syntax_set);
|
||||
let mut h = HighlightLines::new(syntax, theme);
|
||||
let mut lines = Vec::new();
|
||||
let total_lines = content.lines().count();
|
||||
|
||||
for (no, line) in LinesWithEndings::from(content)
|
||||
.enumerate()
|
||||
.take(MAX_HIGHLIGHT_LINES)
|
||||
{
|
||||
let ranges = h.highlight_line(line, syntax_set).unwrap_or_default();
|
||||
let mut spans = vec![Span::styled(
|
||||
format!("{:>4} │ ", no + 1),
|
||||
Style::default().fg(Color::Rgb(80, 80, 100)),
|
||||
)];
|
||||
for (style, text) in &ranges {
|
||||
let mut s = Style::default().fg(syn_color(style.foreground));
|
||||
if style.font_style.contains(SynFontStyle::BOLD) {
|
||||
s = s.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
if style.font_style.contains(SynFontStyle::ITALIC) {
|
||||
s = s.add_modifier(Modifier::ITALIC);
|
||||
}
|
||||
spans.push(Span::styled(text.trim_end_matches('\n').to_string(), s));
|
||||
}
|
||||
lines.push(Line::from(spans));
|
||||
}
|
||||
|
||||
if total_lines > MAX_HIGHLIGHT_LINES {
|
||||
lines.push(Line::from(Span::styled(
|
||||
format!(
|
||||
" … {} lignes supplémentaires non affichées",
|
||||
total_lines - MAX_HIGHLIGHT_LINES
|
||||
),
|
||||
Style::default().fg(Color::Rgb(100, 100, 120)),
|
||||
)));
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
let data_dir = directories::ProjectDirs::from("com", "zefad", "rklipd")
|
||||
.map(|d| d.data_dir().to_path_buf());
|
||||
|
||||
let items = ipc::fetch_history(PAGE_SIZE).unwrap_or_default();
|
||||
let has_more = items.len() == PAGE_SIZE;
|
||||
let mut list_state = ListState::default();
|
||||
list_state.select(if items.is_empty() { None } else { Some(0) });
|
||||
|
||||
let picker = Picker::from_query_stdio().unwrap_or_else(|_| Picker::halfblocks());
|
||||
|
||||
let salt = match &data_dir {
|
||||
Some(dir) => match Crypto::load_or_create_salt(dir) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Erreur sel cryptographique : {e}");
|
||||
vec![0u8; 32]
|
||||
}
|
||||
},
|
||||
None => {
|
||||
eprintln!("Impossible de déterminer le répertoire de données");
|
||||
vec![0u8; 32]
|
||||
}
|
||||
};
|
||||
|
||||
let mut app = Self {
|
||||
mode: Mode::Normal,
|
||||
filtered_items: items.clone(),
|
||||
all_items: items,
|
||||
list_state,
|
||||
input_buffer: String::new(),
|
||||
should_quit: false,
|
||||
undo_stack: Vec::new(),
|
||||
current_image: None,
|
||||
last_selected_index: None,
|
||||
picker,
|
||||
preview_scroll: 0,
|
||||
crypto: None,
|
||||
salt,
|
||||
pending_action: None,
|
||||
error_message: None,
|
||||
status_message: None,
|
||||
syntax_set: SyntaxSet::load_defaults_newlines(),
|
||||
theme_set: ThemeSet::load_defaults(),
|
||||
type_filter: TypeFilter::All,
|
||||
loaded_count: PAGE_SIZE,
|
||||
has_more,
|
||||
last_sync: Instant::now() - Duration::from_secs(10),
|
||||
preview_highlighted: None,
|
||||
preview_lang: None,
|
||||
data_dir,
|
||||
image_cache: HashMap::new(),
|
||||
image_cache_order: VecDeque::new(),
|
||||
matcher: SkimMatcherV2::default(),
|
||||
};
|
||||
app.update_preview();
|
||||
app
|
||||
}
|
||||
|
||||
fn try_load_more(&mut self) -> bool {
|
||||
if !self.has_more {
|
||||
return false;
|
||||
}
|
||||
let new_limit = self.loaded_count + PAGE_SIZE;
|
||||
let Some(items) = ipc::fetch_history(new_limit) else {
|
||||
return false;
|
||||
};
|
||||
if items.len() <= self.all_items.len() {
|
||||
self.has_more = false;
|
||||
return false;
|
||||
}
|
||||
self.has_more = items.len() == new_limit;
|
||||
self.loaded_count = new_limit;
|
||||
let selected_content = self.get_selected_item().map(|i| i.content.clone());
|
||||
self.all_items = items;
|
||||
self.update_search();
|
||||
if let Some(content) = selected_content {
|
||||
if let Some(pos) = self
|
||||
.filtered_items
|
||||
.iter()
|
||||
.position(|x| x.content == content)
|
||||
{
|
||||
self.list_state.select(Some(pos));
|
||||
self.last_selected_index = None;
|
||||
self.update_preview();
|
||||
}
|
||||
}
|
||||
self.set_status(format!("{} entrées chargées", self.all_items.len()));
|
||||
true
|
||||
}
|
||||
|
||||
fn get_cached_image(
|
||||
&mut self,
|
||||
filename: &str,
|
||||
base_dir: &std::path::Path,
|
||||
) -> Option<Arc<DynamicImage>> {
|
||||
if self.image_cache.contains_key(filename) {
|
||||
self.image_cache_order.retain(|k| k != filename);
|
||||
self.image_cache_order.push_back(filename.to_string());
|
||||
return self.image_cache.get(filename).cloned();
|
||||
}
|
||||
let path = base_dir.join("images").join(filename);
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
let img = image::open(&path).ok()?;
|
||||
let img = if img.width() > PREVIEW_MAX_WIDTH || img.height() > PREVIEW_MAX_HEIGHT {
|
||||
img.thumbnail(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT)
|
||||
} else {
|
||||
img
|
||||
};
|
||||
if self.image_cache.len() >= IMAGE_CACHE_MAX {
|
||||
if let Some(oldest) = self.image_cache_order.pop_front() {
|
||||
self.image_cache.remove(&oldest);
|
||||
}
|
||||
}
|
||||
let arc = Arc::new(img);
|
||||
self.image_cache_order.push_back(filename.to_string());
|
||||
self.image_cache
|
||||
.insert(filename.to_string(), Arc::clone(&arc));
|
||||
Some(arc)
|
||||
}
|
||||
|
||||
pub fn format_timestamp(ts_ms: i64) -> String {
|
||||
let secs = ts_ms / 1000;
|
||||
let nsecs = ((ts_ms % 1000) * 1_000_000) as u32;
|
||||
match Local.timestamp_opt(secs, nsecs) {
|
||||
chrono::LocalResult::Single(dt) => {
|
||||
let today = Local::now().date_naive();
|
||||
let diff_days = (today - dt.date_naive()).num_days();
|
||||
if diff_days == 0 {
|
||||
dt.format("%H:%M:%S").to_string()
|
||||
} else if diff_days < 365 {
|
||||
dt.format("%d %b %H:%M").to_string()
|
||||
} else {
|
||||
dt.format("%d/%m/%Y").to_string()
|
||||
}
|
||||
}
|
||||
_ => "?".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle_type_filter(&mut self) {
|
||||
self.type_filter = self.type_filter.next();
|
||||
self.update_search();
|
||||
}
|
||||
|
||||
pub fn update_search(&mut self) {
|
||||
self.last_selected_index = None;
|
||||
|
||||
let query = self.input_buffer.trim().to_string();
|
||||
let (date_before, date_after, text_query) = parse_date_filters(&query);
|
||||
|
||||
let base: Vec<HistoryItem> = self
|
||||
.all_items
|
||||
.iter()
|
||||
.filter(|item| {
|
||||
let ts_s = item.timestamp / 1000;
|
||||
if let Some(before) = date_before {
|
||||
if ts_s >= before {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(after) = date_after {
|
||||
if ts_s < after {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
match self.type_filter {
|
||||
TypeFilter::All => true,
|
||||
TypeFilter::Text => !is_image(&item.content),
|
||||
TypeFilter::Image => is_image(&item.content),
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let search_str = |item: &HistoryItem| -> String {
|
||||
if Crypto::is_any_encrypted(&item.content) {
|
||||
"[chiffré]".to_string()
|
||||
} else if is_image(&item.content) {
|
||||
format!("image {}", item.content)
|
||||
} else {
|
||||
item.content.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let is_regex = text_query.starts_with('/') && text_query.len() > 1;
|
||||
|
||||
self.filtered_items = if text_query.is_empty() {
|
||||
base
|
||||
} else if is_regex {
|
||||
let pattern = &text_query[1..];
|
||||
match Regex::new(pattern) {
|
||||
Ok(re) => base
|
||||
.into_iter()
|
||||
.filter(|item| re.is_match(&search_str(item)))
|
||||
.collect(),
|
||||
Err(e) => {
|
||||
self.error_message = Some((
|
||||
format!(
|
||||
"Regex invalide : {}",
|
||||
e.to_string().lines().next().unwrap_or("")
|
||||
),
|
||||
Instant::now(),
|
||||
));
|
||||
base
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut matched: Vec<(i64, HistoryItem)> = base
|
||||
.into_iter()
|
||||
.filter_map(|item| {
|
||||
let score = self.matcher.fuzzy_match(&search_str(&item), &text_query)?;
|
||||
let adjusted = score + if item.pinned { 1000 } else { 0 };
|
||||
Some((adjusted, item))
|
||||
})
|
||||
.collect();
|
||||
matched.sort_by(|a, b| b.0.cmp(&a.0));
|
||||
matched.into_iter().map(|(_, i)| i).collect()
|
||||
};
|
||||
|
||||
self.list_state.select(if self.filtered_items.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(0)
|
||||
});
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
if self.filtered_items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let current = self.list_state.selected().unwrap_or(0);
|
||||
let last = self.filtered_items.len() - 1;
|
||||
if current >= last {
|
||||
if self.try_load_more() {
|
||||
let current = self.list_state.selected().unwrap_or(0);
|
||||
if current + 1 < self.filtered_items.len() {
|
||||
self.list_state.select(Some(current + 1));
|
||||
self.update_preview();
|
||||
}
|
||||
} else {
|
||||
self.list_state.select(Some(0));
|
||||
self.update_preview();
|
||||
}
|
||||
} else {
|
||||
self.list_state.select(Some(current + 1));
|
||||
self.update_preview();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
if self.filtered_items.is_empty() {
|
||||
return;
|
||||
}
|
||||
let i = self.list_state.selected().map_or(0, |i| {
|
||||
if i == 0 {
|
||||
self.filtered_items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
});
|
||||
self.list_state.select(Some(i));
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn delete_selected(&mut self) {
|
||||
if let Some(i) = self.list_state.selected() {
|
||||
if i < self.filtered_items.len() {
|
||||
let item = self.filtered_items.remove(i);
|
||||
|
||||
// Borner la pile d'annulation pour éviter une fuite mémoire
|
||||
if self.undo_stack.len() >= UNDO_STACK_MAX {
|
||||
self.undo_stack.remove(0);
|
||||
}
|
||||
self.undo_stack.push(item.clone());
|
||||
|
||||
self.all_items.retain(|x| x.content != item.content);
|
||||
if is_image(&item.content) {
|
||||
self.image_cache.remove(&item.content);
|
||||
self.image_cache_order.retain(|k| k != &item.content);
|
||||
}
|
||||
let new_sel = if self.filtered_items.is_empty() {
|
||||
None
|
||||
} else if i >= self.filtered_items.len() {
|
||||
Some(self.filtered_items.len() - 1)
|
||||
} else {
|
||||
Some(i)
|
||||
};
|
||||
self.list_state.select(new_sel);
|
||||
}
|
||||
}
|
||||
self.last_selected_index = None;
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn undo_delete(&mut self) {
|
||||
if let Some(item) = self.undo_stack.pop() {
|
||||
ipc::add_entry(item.content.clone());
|
||||
if let Some(new_items) = ipc::fetch_history(self.loaded_count) {
|
||||
self.has_more = new_items.len() == self.loaded_count;
|
||||
self.all_items = new_items;
|
||||
} else {
|
||||
self.all_items.insert(0, item.clone());
|
||||
}
|
||||
self.update_search();
|
||||
if let Some(pos) = self
|
||||
.filtered_items
|
||||
.iter()
|
||||
.position(|x| x.content == item.content)
|
||||
{
|
||||
self.list_state.select(Some(pos));
|
||||
self.last_selected_index = None;
|
||||
}
|
||||
}
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn toggle_pin(&mut self) {
|
||||
let item = match self.get_selected_item() {
|
||||
Some(i) => i.clone(),
|
||||
None => return,
|
||||
};
|
||||
let new_pinned = !item.pinned;
|
||||
|
||||
if !ipc::pin_entry(item.content.clone(), new_pinned) {
|
||||
self.set_error("Erreur pin : daemon injoignable".into());
|
||||
return;
|
||||
}
|
||||
|
||||
for e in self.all_items.iter_mut() {
|
||||
if e.content == item.content {
|
||||
e.pinned = new_pinned;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.all_items
|
||||
.sort_by(|a, b| b.pinned.cmp(&a.pinned).then(b.timestamp.cmp(&a.timestamp)));
|
||||
|
||||
let sel_content = item.content.clone();
|
||||
self.update_search();
|
||||
|
||||
if let Some(pos) = self
|
||||
.filtered_items
|
||||
.iter()
|
||||
.position(|x| x.content == sel_content)
|
||||
{
|
||||
self.list_state.select(Some(pos));
|
||||
self.last_selected_index = None;
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
self.set_status(if new_pinned {
|
||||
"★ Épinglé".into()
|
||||
} else {
|
||||
"Désépinglé".into()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn open_url_selected(&mut self) {
|
||||
let content = match self.get_selected_item().map(|i| i.content.clone()) {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
match extract_url(&content) {
|
||||
Some(url) => match std::process::Command::new("xdg-open").arg(&url).spawn() {
|
||||
Ok(_) => {
|
||||
let preview: String = url.chars().take(48).collect();
|
||||
let preview = if url.chars().count() > 48 {
|
||||
format!("{preview}…")
|
||||
} else {
|
||||
preview
|
||||
};
|
||||
self.set_status(format!("Ouverture : {preview}"));
|
||||
}
|
||||
Err(e) => self.set_error(format!("xdg-open : {e}")),
|
||||
},
|
||||
None => self.set_error("Aucune URL trouvée dans cette entrée".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_encrypt(&mut self) {
|
||||
let content = match self.get_selected_item() {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
if Crypto::is_legacy_encrypted(&content) {
|
||||
self.set_error(
|
||||
"Entrée chiffrée avec l'ancienne clé machine — non modifiable ici".into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.crypto = None;
|
||||
if Crypto::is_password_encrypted(&content) {
|
||||
self.pending_action = Some(PendingAction::DecryptSelected);
|
||||
} else {
|
||||
self.pending_action = Some(PendingAction::EncryptSelected);
|
||||
}
|
||||
self.enter_password_mode();
|
||||
}
|
||||
|
||||
fn enter_password_mode(&mut self) {
|
||||
self.mode = Mode::PasswordInput;
|
||||
self.input_buffer.clear();
|
||||
}
|
||||
|
||||
pub fn apply_password(&mut self, password: String) {
|
||||
if password.is_empty() {
|
||||
self.set_error("Mot de passe vide".into());
|
||||
return;
|
||||
}
|
||||
match Crypto::from_password(&password, &self.salt) {
|
||||
Ok(crypto) => {
|
||||
self.crypto = Some(crypto);
|
||||
match self.pending_action.take() {
|
||||
Some(PendingAction::EncryptSelected) => self.do_encrypt_selected(),
|
||||
Some(PendingAction::DecryptSelected) => self.do_decrypt_selected(),
|
||||
Some(PendingAction::PasteEncrypted) => self.do_paste_encrypted(),
|
||||
None => self.set_status("Mot de passe défini pour la session".into()),
|
||||
}
|
||||
}
|
||||
Err(e) => self.set_error(format!("Erreur crypto : {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_encrypt_selected(&mut self) {
|
||||
let content = match self.get_selected_item() {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
let result = match &self.crypto {
|
||||
Some(k) => k.encrypt(&content),
|
||||
None => return,
|
||||
};
|
||||
self.crypto = None;
|
||||
match result {
|
||||
Ok(enc) => {
|
||||
if ipc::update_entry(content.clone(), enc.clone()) {
|
||||
self.replace_content(&content, enc);
|
||||
self.set_status("Entrée chiffrée 🔒".into());
|
||||
} else {
|
||||
self.set_error("Erreur mise à jour BDD".into());
|
||||
}
|
||||
}
|
||||
Err(e) => self.set_error(format!("Chiffrement : {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_decrypt_selected(&mut self) {
|
||||
let content = match self.get_selected_item() {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
let result = match &self.crypto {
|
||||
Some(k) => k.decrypt(&content),
|
||||
None => return,
|
||||
};
|
||||
self.crypto = None;
|
||||
match result {
|
||||
Ok(plain) => {
|
||||
if ipc::update_entry(content.clone(), plain.clone()) {
|
||||
self.replace_content(&content, plain);
|
||||
self.set_status("Entrée déchiffrée".into());
|
||||
} else {
|
||||
self.set_error("Erreur mise à jour BDD".into());
|
||||
}
|
||||
}
|
||||
Err(_) => self.set_error(
|
||||
"Déchiffrement échoué — mauvais mot de passe. Réessayez avec [e].".into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_paste_encrypted(&mut self) {
|
||||
let content = match self.get_selected_item() {
|
||||
Some(i) => i.content.clone(),
|
||||
None => return,
|
||||
};
|
||||
let result = match &self.crypto {
|
||||
Some(k) => k.decrypt(&content),
|
||||
None => return,
|
||||
};
|
||||
self.crypto = None;
|
||||
match result {
|
||||
Ok(plain) => {
|
||||
if ipc::set_clipboard(plain) {
|
||||
self.should_quit = true;
|
||||
} else {
|
||||
self.set_error("Erreur : impossible de définir le presse-papier".into());
|
||||
}
|
||||
}
|
||||
Err(_) => self.set_error(
|
||||
"Déchiffrement échoué — mauvais mot de passe. Réessayez avec Entrée.".into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_content(&mut self, old: &str, new: String) {
|
||||
if let Some(p) = self.all_items.iter().position(|x| x.content == old) {
|
||||
self.all_items[p].content = new.clone();
|
||||
}
|
||||
if let Some(p) = self.filtered_items.iter().position(|x| x.content == old) {
|
||||
self.filtered_items[p].content = new;
|
||||
}
|
||||
self.last_selected_index = None;
|
||||
self.update_preview();
|
||||
}
|
||||
|
||||
pub fn paste_selected(&mut self) {
|
||||
let content = match self.get_selected_item().map(|i| i.content.clone()) {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
if Crypto::is_password_encrypted(&content) {
|
||||
self.crypto = None;
|
||||
self.pending_action = Some(PendingAction::PasteEncrypted);
|
||||
self.enter_password_mode();
|
||||
} else if ipc::set_clipboard(content) {
|
||||
self.should_quit = true;
|
||||
} else {
|
||||
self.set_error("Impossible de définir le presse-papier (daemon injoignable ?)".into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_history(&mut self) {
|
||||
if ipc::clear_history() {
|
||||
self.all_items.clear();
|
||||
self.filtered_items.clear();
|
||||
self.undo_stack.clear();
|
||||
self.list_state.select(None);
|
||||
self.current_image = None;
|
||||
self.image_cache.clear();
|
||||
self.image_cache_order.clear();
|
||||
self.preview_highlighted = None;
|
||||
self.preview_lang = None;
|
||||
self.loaded_count = PAGE_SIZE;
|
||||
self.has_more = false;
|
||||
self.set_status("Historique effacé".into());
|
||||
} else {
|
||||
self.set_error("Erreur lors de l'effacement".into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_preview(&mut self) {
|
||||
let idx = self.list_state.selected();
|
||||
if self.last_selected_index == idx {
|
||||
return;
|
||||
}
|
||||
self.last_selected_index = idx;
|
||||
self.current_image = None;
|
||||
self.preview_scroll = 0;
|
||||
self.preview_highlighted = None;
|
||||
self.preview_lang = None;
|
||||
|
||||
let content = match self.get_selected_item().map(|i| i.content.clone()) {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if is_image(&content) {
|
||||
if let Some(dir) = self.data_dir.clone() {
|
||||
if let Some(arc_img) = self.get_cached_image(&content, &dir) {
|
||||
let img = (*arc_img).clone();
|
||||
self.current_image = Some(self.picker.new_resize_protocol(img));
|
||||
}
|
||||
}
|
||||
} else if !Crypto::is_any_encrypted(&content) {
|
||||
self.preview_lang = detect_lang(&content, &self.syntax_set);
|
||||
self.preview_highlighted =
|
||||
Some(highlight_code(&content, &self.syntax_set, &self.theme_set));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_preview_down(&mut self) {
|
||||
self.preview_scroll = self.preview_scroll.saturating_add(3);
|
||||
}
|
||||
pub fn scroll_preview_up(&mut self) {
|
||||
self.preview_scroll = self.preview_scroll.saturating_sub(3);
|
||||
}
|
||||
|
||||
pub fn get_selected_item(&self) -> Option<&HistoryItem> {
|
||||
self.list_state
|
||||
.selected()
|
||||
.and_then(|i| self.filtered_items.get(i))
|
||||
}
|
||||
|
||||
pub fn sync_with_daemon(&mut self) {
|
||||
if self.last_sync.elapsed() < Duration::from_millis(SYNC_INTERVAL_MS) {
|
||||
return;
|
||||
}
|
||||
self.last_sync = Instant::now();
|
||||
|
||||
let Some(new) = ipc::fetch_history(self.loaded_count) else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.has_more = new.len() == self.loaded_count;
|
||||
|
||||
let changed = self.all_items.len() != new.len()
|
||||
|| self
|
||||
.all_items
|
||||
.iter()
|
||||
.zip(&new)
|
||||
.any(|(a, b)| a.content != b.content || a.pinned != b.pinned);
|
||||
|
||||
if changed {
|
||||
let selected_content = self.get_selected_item().map(|i| i.content.clone());
|
||||
self.all_items = new;
|
||||
self.update_search();
|
||||
if let Some(content) = selected_content {
|
||||
if let Some(pos) = self
|
||||
.filtered_items
|
||||
.iter()
|
||||
.position(|x| x.content == content)
|
||||
{
|
||||
self.list_state.select(Some(pos));
|
||||
self.last_selected_index = None;
|
||||
self.update_preview();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_error(&mut self, msg: String) {
|
||||
self.error_message = Some((msg, Instant::now()));
|
||||
}
|
||||
pub fn set_status(&mut self, msg: String) {
|
||||
self.status_message = Some((msg, Instant::now()));
|
||||
}
|
||||
|
||||
pub fn tick_messages(&mut self) {
|
||||
let ttl = Duration::from_secs(3);
|
||||
if self
|
||||
.error_message
|
||||
.as_ref()
|
||||
.map_or(false, |(_, t)| t.elapsed() > ttl)
|
||||
{
|
||||
self.error_message = None;
|
||||
}
|
||||
if self
|
||||
.status_message
|
||||
.as_ref()
|
||||
.map_or(false, |(_, t)| t.elapsed() > ttl)
|
||||
{
|
||||
self.status_message = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_date_filters(query: &str) -> (Option<i64>, Option<i64>, String) {
|
||||
let mut before = None;
|
||||
let mut after = None;
|
||||
let mut rest = Vec::new();
|
||||
|
||||
for token in query.split_whitespace() {
|
||||
if let Some(d) = token.strip_prefix("before:") {
|
||||
if let Some(ts) = parse_date(d) {
|
||||
before = Some(ts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(d) = token.strip_prefix("after:") {
|
||||
if let Some(ts) = parse_date(d) {
|
||||
after = Some(ts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
rest.push(token);
|
||||
}
|
||||
(before, after, rest.join(" "))
|
||||
}
|
||||
|
||||
fn parse_date(s: &str) -> Option<i64> {
|
||||
if let Ok(d) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
|
||||
let dt = d.and_hms_opt(0, 0, 0)?;
|
||||
return Some(Local.from_local_datetime(&dt).single()?.timestamp());
|
||||
}
|
||||
if let Ok(d) = NaiveDate::parse_from_str(&format!("{s}-01"), "%Y-%m-%d") {
|
||||
let dt = d.and_hms_opt(0, 0, 0)?;
|
||||
return Some(Local.from_local_datetime(&dt).single()?.timestamp());
|
||||
}
|
||||
if let Ok(d) = NaiveDate::parse_from_str(&format!("{s}-01-01"), "%Y-%m-%d") {
|
||||
let dt = d.and_hms_opt(0, 0, 0)?;
|
||||
return Some(Local.from_local_datetime(&dt).single()?.timestamp());
|
||||
}
|
||||
None
|
||||
}
|
||||
93
src/crypto.rs
Normal file
93
src/crypto.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use aes_gcm::aead::rand_core::RngCore;
|
||||
use aes_gcm::{
|
||||
Aes256Gcm, Key, Nonce,
|
||||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||||
};
|
||||
use argon2::Argon2;
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
pub const ENC2_PREFIX: &str = "enc2:";
|
||||
const LEGACY_PREFIX: &str = "enc:";
|
||||
const SALT_LEN: usize = 32;
|
||||
const KEY_LEN: usize = 32;
|
||||
|
||||
pub struct Crypto {
|
||||
key: [u8; KEY_LEN],
|
||||
}
|
||||
|
||||
impl Crypto {
|
||||
pub fn from_password(password: &str, salt: &[u8]) -> Result<Self, Box<dyn Error>> {
|
||||
let mut key = [0u8; KEY_LEN];
|
||||
Argon2::default()
|
||||
.hash_password_into(password.as_bytes(), salt, &mut key)
|
||||
.map_err(|e| format!("Argon2 : {e}"))?;
|
||||
Ok(Self { key })
|
||||
}
|
||||
|
||||
pub fn load_or_create_salt(data_dir: &Path) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let path = data_dir.join("crypto2.salt");
|
||||
if path.exists() {
|
||||
let bytes = fs::read(&path)?;
|
||||
if bytes.len() == SALT_LEN {
|
||||
return Ok(bytes);
|
||||
}
|
||||
return Err(format!(
|
||||
"Fichier sel corrompu ({} octets au lieu de {}). \
|
||||
Supprimez {:?} manuellement si vous souhaitez réinitialiser le chiffrement.",
|
||||
bytes.len(),
|
||||
SALT_LEN,
|
||||
path
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let mut salt = vec![0u8; SALT_LEN];
|
||||
OsRng.fill_bytes(&mut salt);
|
||||
fs::write(&path, &salt)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
Ok(salt)
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, plaintext: &str) -> Result<String, Box<dyn Error>> {
|
||||
let key = Key::<Aes256Gcm>::from_slice(&self.key);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
let ct = cipher
|
||||
.encrypt(&nonce, plaintext.as_bytes())
|
||||
.map_err(|e| format!("Chiffrement : {e}"))?;
|
||||
let mut combined = nonce.to_vec();
|
||||
combined.extend_from_slice(&ct);
|
||||
Ok(format!("{}{}", ENC2_PREFIX, B64.encode(combined)))
|
||||
}
|
||||
|
||||
pub fn decrypt(&self, encrypted: &str) -> Result<String, Box<dyn Error>> {
|
||||
let encoded = encrypted.strip_prefix(ENC2_PREFIX).ok_or("Pas un enc2")?;
|
||||
let combined = B64.decode(encoded)?;
|
||||
if combined.len() < 12 {
|
||||
return Err("Données trop courtes".into());
|
||||
}
|
||||
let (nonce_b, ct) = combined.split_at(12);
|
||||
let key = Key::<Aes256Gcm>::from_slice(&self.key);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
let plaintext = cipher
|
||||
.decrypt(Nonce::from_slice(nonce_b), ct)
|
||||
.map_err(|_| "Déchiffrement échoué — mot de passe incorrect ?")?;
|
||||
Ok(String::from_utf8(plaintext)?)
|
||||
}
|
||||
|
||||
pub fn is_password_encrypted(s: &str) -> bool {
|
||||
s.starts_with(ENC2_PREFIX)
|
||||
}
|
||||
pub fn is_legacy_encrypted(s: &str) -> bool {
|
||||
s.starts_with(LEGACY_PREFIX) && !s.starts_with(ENC2_PREFIX)
|
||||
}
|
||||
pub fn is_any_encrypted(s: &str) -> bool {
|
||||
s.starts_with(ENC2_PREFIX) || s.starts_with(LEGACY_PREFIX)
|
||||
}
|
||||
}
|
||||
105
src/ipc.rs
Normal file
105
src/ipc.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct HistoryItem {
|
||||
pub content: String,
|
||||
pub timestamp: i64,
|
||||
#[serde(default)]
|
||||
pub pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum IpcRequest {
|
||||
GetHistory {
|
||||
limit: usize,
|
||||
},
|
||||
SetClipboard {
|
||||
content: String,
|
||||
},
|
||||
DeleteEntry {
|
||||
content: String,
|
||||
},
|
||||
UpdateEntry {
|
||||
old_content: String,
|
||||
new_content: String,
|
||||
},
|
||||
AddEntry {
|
||||
content: String,
|
||||
},
|
||||
PinEntry {
|
||||
content: String,
|
||||
pinned: bool,
|
||||
},
|
||||
ClearHistory,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum IpcResponse {
|
||||
History(Vec<HistoryItem>),
|
||||
Ok,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
fn send_request(req: &IpcRequest) -> Option<IpcResponse> {
|
||||
let dir = directories::ProjectDirs::from("com", "zefad", "rklipd")?
|
||||
.data_dir()
|
||||
.to_path_buf();
|
||||
let mut stream = UnixStream::connect(dir.join("rklip.sock")).ok()?;
|
||||
let json = serde_json::to_string(req).ok()?;
|
||||
stream.write_all(json.as_bytes()).ok()?;
|
||||
stream.shutdown(std::net::Shutdown::Write).ok()?;
|
||||
let mut buf = String::new();
|
||||
stream.read_to_string(&mut buf).ok()?;
|
||||
serde_json::from_str(&buf).ok()
|
||||
}
|
||||
|
||||
pub fn fetch_history(limit: usize) -> Option<Vec<HistoryItem>> {
|
||||
match send_request(&IpcRequest::GetHistory { limit })? {
|
||||
IpcResponse::History(items) => Some(items),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_clipboard(content: String) -> bool {
|
||||
matches!(
|
||||
send_request(&IpcRequest::SetClipboard { content }),
|
||||
Some(IpcResponse::Ok)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn delete_entry(content: String) -> bool {
|
||||
matches!(
|
||||
send_request(&IpcRequest::DeleteEntry { content }),
|
||||
Some(IpcResponse::Ok)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_entry(old_content: String, new_content: String) -> bool {
|
||||
matches!(
|
||||
send_request(&IpcRequest::UpdateEntry {
|
||||
old_content,
|
||||
new_content
|
||||
}),
|
||||
Some(IpcResponse::Ok)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_entry(content: String) {
|
||||
let _ = send_request(&IpcRequest::AddEntry { content });
|
||||
}
|
||||
|
||||
pub fn pin_entry(content: String, pinned: bool) -> bool {
|
||||
matches!(
|
||||
send_request(&IpcRequest::PinEntry { content, pinned }),
|
||||
Some(IpcResponse::Ok)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn clear_history() -> bool {
|
||||
matches!(
|
||||
send_request(&IpcRequest::ClearHistory),
|
||||
Some(IpcResponse::Ok)
|
||||
)
|
||||
}
|
||||
291
src/main.rs
291
src/main.rs
@ -1,24 +1,277 @@
|
||||
use arboard::Clipboard;
|
||||
mod app;
|
||||
mod crypto;
|
||||
mod ipc;
|
||||
mod models;
|
||||
mod ui;
|
||||
|
||||
fn main() {
|
||||
let Ok(mut clipboard) = Clipboard::new() else {
|
||||
println!("Clipboard init error");
|
||||
return;
|
||||
};
|
||||
use app::{App, Mode};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyModifiers},
|
||||
execute,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use ratatui::{Terminal, backend::CrosstermBackend};
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
|
||||
// let Ok(test) = clipboard.get_text() else {
|
||||
// println!("Clipboard empty");
|
||||
// return;
|
||||
// };
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let test = clipboard.get_text().unwrap_or_else(|_| "".to_string());
|
||||
println!("clipboard content : {}", test);
|
||||
let mut app = App::new();
|
||||
let res = run_app(&mut terminal, &mut app);
|
||||
|
||||
// let _ = clipboard.clear().expect("");
|
||||
|
||||
// clipboard.set_text("test").unwrap();
|
||||
|
||||
// for (key, value) in std::env::vars() {
|
||||
// println!("{key}: {value}");
|
||||
// }
|
||||
disable_raw_mode()?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||
terminal.show_cursor()?;
|
||||
if let Err(err) = res {
|
||||
eprintln!("{:?}", err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App) -> io::Result<()> {
|
||||
let mut last_d = false;
|
||||
let mut last_g = false;
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| ui::render(f, app))?;
|
||||
app.tick_messages();
|
||||
|
||||
if event::poll(Duration::from_millis(250))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
// Ctrl+j / Ctrl+k : scroll prévisualisation (tous modes sauf aide)
|
||||
if key.modifiers.contains(KeyModifiers::CONTROL) && app.mode != Mode::Help {
|
||||
match key.code {
|
||||
KeyCode::Char('j') => {
|
||||
app.scroll_preview_down();
|
||||
continue;
|
||||
}
|
||||
KeyCode::Char('k') => {
|
||||
app.scroll_preview_up();
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
match app.mode {
|
||||
// ----------------------------------------------------------
|
||||
Mode::Help => {
|
||||
// N'importe quelle touche ferme l'aide
|
||||
app.mode = Mode::Normal;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
Mode::Normal => {
|
||||
last_d = handle_normal(app, key.code, last_d, &mut last_g);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
Mode::Search => match key.code {
|
||||
KeyCode::Esc => {
|
||||
app.mode = Mode::Normal;
|
||||
app.input_buffer.clear();
|
||||
app.update_search();
|
||||
}
|
||||
KeyCode::Enter => app.paste_selected(),
|
||||
KeyCode::Down => app.next(),
|
||||
KeyCode::Up => app.previous(),
|
||||
KeyCode::Char('o') if app.input_buffer.is_empty() => {
|
||||
// `o` sans texte saisi → ouvre URL
|
||||
app.open_url_selected();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.input_buffer.push(c);
|
||||
app.update_search();
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
app.input_buffer.pop();
|
||||
app.update_search();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
// ----------------------------------------------------------
|
||||
Mode::Command => match key.code {
|
||||
KeyCode::Esc => {
|
||||
app.mode = Mode::Normal;
|
||||
app.input_buffer.clear();
|
||||
}
|
||||
KeyCode::Char(c) => app.input_buffer.push(c),
|
||||
KeyCode::Backspace => {
|
||||
app.input_buffer.pop();
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let cmd = app.input_buffer.trim().to_string();
|
||||
app.input_buffer.clear();
|
||||
app.mode = Mode::Normal;
|
||||
match cmd.as_str() {
|
||||
"q" | "quit" => app.should_quit = true,
|
||||
"clear" => app.clear_history(),
|
||||
"p" | "password" => {
|
||||
app.pending_action = None;
|
||||
app.mode = Mode::PasswordInput;
|
||||
}
|
||||
_ => app.set_error(format!("Commande inconnue : {cmd}")),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
// ----------------------------------------------------------
|
||||
Mode::ConfirmDelete => match key.code {
|
||||
KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
|
||||
if let Some(item) = app.get_selected_item() {
|
||||
let content = item.content.clone();
|
||||
if ipc::delete_entry(content) {
|
||||
app.delete_selected();
|
||||
} else {
|
||||
app.set_error(
|
||||
"Erreur : daemon injoignable, entrée non supprimée".into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
app.mode = Mode::Normal;
|
||||
}
|
||||
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
|
||||
app.mode = Mode::Normal;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
|
||||
// ----------------------------------------------------------
|
||||
Mode::PasswordInput => match key.code {
|
||||
KeyCode::Esc => {
|
||||
app.mode = Mode::Normal;
|
||||
app.input_buffer.clear();
|
||||
app.pending_action = None;
|
||||
}
|
||||
KeyCode::Char(c) => app.input_buffer.push(c),
|
||||
KeyCode::Backspace => {
|
||||
app.input_buffer.pop();
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
let pw = app.input_buffer.clone();
|
||||
app.input_buffer.clear();
|
||||
app.mode = Mode::Normal;
|
||||
app.apply_password(pw);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app.sync_with_daemon();
|
||||
}
|
||||
|
||||
if app.should_quit {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gère les touches en mode Normal. Retourne le nouvel état de `last_d`.
|
||||
fn handle_normal(app: &mut App, code: KeyCode, last_d: bool, last_g: &mut bool) -> bool {
|
||||
match code {
|
||||
KeyCode::Char('?') => {
|
||||
app.mode = Mode::Help;
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
app.paste_selected();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('j') | KeyCode::Down => {
|
||||
app.next();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('k') | KeyCode::Up => {
|
||||
app.previous();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('G') => {
|
||||
if !app.filtered_items.is_empty() {
|
||||
let l = app.filtered_items.len() - 1;
|
||||
app.list_state.select(Some(l));
|
||||
app.update_preview();
|
||||
}
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('g') => {
|
||||
if *last_g {
|
||||
if !app.filtered_items.is_empty() {
|
||||
app.list_state.select(Some(0));
|
||||
app.update_preview();
|
||||
}
|
||||
*last_g = false;
|
||||
} else {
|
||||
*last_g = true;
|
||||
}
|
||||
false
|
||||
}
|
||||
KeyCode::Char('d') => {
|
||||
*last_g = false;
|
||||
if last_d {
|
||||
app.mode = Mode::ConfirmDelete;
|
||||
false
|
||||
} else {
|
||||
true // dernier appui était 'd'
|
||||
}
|
||||
}
|
||||
KeyCode::Char('u') => {
|
||||
app.undo_delete();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('p') => {
|
||||
app.toggle_pin();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('o') => {
|
||||
app.open_url_selected();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('e') => {
|
||||
app.toggle_encrypt();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('t') => {
|
||||
app.cycle_type_filter();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('/') => {
|
||||
app.mode = Mode::Search;
|
||||
app.input_buffer.clear();
|
||||
app.update_search();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char(':') => {
|
||||
app.mode = Mode::Command;
|
||||
app.input_buffer.clear();
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
KeyCode::Char('q') => {
|
||||
app.should_quit = true;
|
||||
false
|
||||
}
|
||||
_ => {
|
||||
*last_g = false;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
src/models.rs
Normal file
36
src/models.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTime;
|
||||
use std::{fs, io};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClipboardEntry {
|
||||
pub content: ClipboardData,
|
||||
pub timestamp: SystemTime,
|
||||
pub pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ClipboardData {
|
||||
Text(String),
|
||||
Image(Image),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Image {
|
||||
pub raw_pixels: Option<Vec<u8>>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub id: Uuid,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn file_path(&self, base_dir: &str) -> PathBuf {
|
||||
std::path::Path::new(base_dir)
|
||||
.join("images")
|
||||
.join(format!("{}.jpg", self.id))
|
||||
}
|
||||
pub fn load_bytes(&self, dir_path: &str) -> io::Result<Vec<u8>> {
|
||||
fs::read(self.file_path(dir_path))
|
||||
}
|
||||
}
|
||||
241
src/target/rust-analyzer/flycheck0/stderr
Normal file
241
src/target/rust-analyzer/flycheck0/stderr
Normal file
@ -0,0 +1,241 @@
|
||||
Updating crates.io index
|
||||
Locking 16 packages to latest Rust 1.92.0 compatible versions
|
||||
Adding fallible-iterator v0.3.0
|
||||
Adding fallible-streaming-iterator v0.1.9
|
||||
Adding foldhash v0.2.0
|
||||
Adding hashlink v0.11.0
|
||||
Adding js-sys v0.3.91
|
||||
Adding libsqlite3-sys v0.36.0
|
||||
Adding pkg-config v0.3.32
|
||||
Adding rsqlite-vfs v0.1.0
|
||||
Adding rusqlite v0.38.0
|
||||
Adding sqlite-wasm-rs v0.5.2
|
||||
Adding uuid v1.22.0
|
||||
Adding vcpkg v0.2.15
|
||||
Updating wasm-bindgen v0.2.108 -> v0.2.114
|
||||
Updating wasm-bindgen-macro v0.2.108 -> v0.2.114
|
||||
Updating wasm-bindgen-macro-support v0.2.108 -> v0.2.114
|
||||
Updating wasm-bindgen-shared v0.2.108 -> v0.2.114
|
||||
warning: rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip) ignoring invalid dependency `rklipd` which is missing a lib target
|
||||
1.038457734s INFO prepare_target{force=false package_id=rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip) target="rklip"}: cargo::core::compiler::fingerprint: fingerprint error for rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip)/Check { test: false }/TargetInner { name: "rklip", doc: true, ..: with_path("/home/zefad/Documents/Code/Rust/rklip/src/main.rs", Edition2024) }
|
||||
1.038475304s INFO prepare_target{force=false package_id=rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip) target="rklip"}: cargo::core::compiler::fingerprint: err: failed to read `/home/zefad/Documents/Code/Rust/rklip/target/debug/.fingerprint/rklip-07d4fd1c9485c752/bin-rklip`
|
||||
|
||||
Caused by:
|
||||
No such file or directory (os error 2)
|
||||
|
||||
Stack backtrace:
|
||||
0: cargo_util::paths::read_bytes
|
||||
1: cargo_util::paths::read
|
||||
2: cargo::core::compiler::fingerprint::_compare_old_fingerprint
|
||||
3: cargo::core::compiler::fingerprint::prepare_target
|
||||
4: cargo::core::compiler::compile
|
||||
5: <cargo::core::compiler::build_runner::BuildRunner>::compile
|
||||
6: cargo::ops::cargo_compile::compile_ws
|
||||
7: cargo::ops::cargo_compile::compile_with_exec
|
||||
8: cargo::ops::cargo_compile::compile
|
||||
9: cargo::commands::check::exec
|
||||
10: <cargo::cli::Exec>::exec
|
||||
11: cargo::main
|
||||
12: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
|
||||
13: std::rt::lang_start::<()>::{closure#0}
|
||||
14: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/core/src/ops/function.rs:287:21
|
||||
15: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
16: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
17: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
18: std::rt::lang_start_internal::{{closure}}
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:175:24
|
||||
19: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
20: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
21: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
22: std::rt::lang_start_internal
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:171:5
|
||||
23: main
|
||||
24: <unknown>
|
||||
25: __libc_start_main
|
||||
26: <unknown>
|
||||
1.043374140s INFO prepare_target{force=false package_id=arboard v3.6.1 target="arboard"}: cargo::core::compiler::fingerprint: fingerprint error for arboard v3.6.1/Check { test: false }/TargetInner { ..: lib_target("arboard", ["lib"], "/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/arboard-3.6.1/src/lib.rs", Edition2021) }
|
||||
1.043385493s INFO prepare_target{force=false package_id=arboard v3.6.1 target="arboard"}: cargo::core::compiler::fingerprint: err: failed to read `/home/zefad/Documents/Code/Rust/rklip/target/debug/.fingerprint/arboard-7ace66d9f3fc0fb8/lib-arboard`
|
||||
|
||||
Caused by:
|
||||
No such file or directory (os error 2)
|
||||
|
||||
Stack backtrace:
|
||||
0: cargo_util::paths::read_bytes
|
||||
1: cargo_util::paths::read
|
||||
2: cargo::core::compiler::fingerprint::_compare_old_fingerprint
|
||||
3: cargo::core::compiler::fingerprint::prepare_target
|
||||
4: cargo::core::compiler::compile
|
||||
5: cargo::core::compiler::compile
|
||||
6: <cargo::core::compiler::build_runner::BuildRunner>::compile
|
||||
7: cargo::ops::cargo_compile::compile_ws
|
||||
8: cargo::ops::cargo_compile::compile_with_exec
|
||||
9: cargo::ops::cargo_compile::compile
|
||||
10: cargo::commands::check::exec
|
||||
11: <cargo::cli::Exec>::exec
|
||||
12: cargo::main
|
||||
13: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
|
||||
14: std::rt::lang_start::<()>::{closure#0}
|
||||
15: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/core/src/ops/function.rs:287:21
|
||||
16: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
17: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
18: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
19: std::rt::lang_start_internal::{{closure}}
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:175:24
|
||||
20: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
21: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
22: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
23: std::rt::lang_start_internal
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:171:5
|
||||
24: main
|
||||
25: <unknown>
|
||||
26: __libc_start_main
|
||||
27: <unknown>
|
||||
1.046241787s INFO prepare_target{force=false package_id=x11rb v0.13.2 target="x11rb"}: cargo::core::compiler::fingerprint: fingerprint error for x11rb v0.13.2/Check { test: false }/TargetInner { ..: lib_target("x11rb", ["lib"], "/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/x11rb-0.13.2/src/lib.rs", Edition2021) }
|
||||
1.046251265s INFO prepare_target{force=false package_id=x11rb v0.13.2 target="x11rb"}: cargo::core::compiler::fingerprint: err: failed to read `/home/zefad/Documents/Code/Rust/rklip/target/debug/.fingerprint/x11rb-d7250961ceb78527/lib-x11rb`
|
||||
|
||||
Caused by:
|
||||
No such file or directory (os error 2)
|
||||
|
||||
Stack backtrace:
|
||||
0: cargo_util::paths::read_bytes
|
||||
1: cargo_util::paths::read
|
||||
2: cargo::core::compiler::fingerprint::_compare_old_fingerprint
|
||||
3: cargo::core::compiler::fingerprint::prepare_target
|
||||
4: cargo::core::compiler::compile
|
||||
5: cargo::core::compiler::compile
|
||||
6: cargo::core::compiler::compile
|
||||
7: <cargo::core::compiler::build_runner::BuildRunner>::compile
|
||||
8: cargo::ops::cargo_compile::compile_ws
|
||||
9: cargo::ops::cargo_compile::compile_with_exec
|
||||
10: cargo::ops::cargo_compile::compile
|
||||
11: cargo::commands::check::exec
|
||||
12: <cargo::cli::Exec>::exec
|
||||
13: cargo::main
|
||||
14: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
|
||||
15: std::rt::lang_start::<()>::{closure#0}
|
||||
16: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/core/src/ops/function.rs:287:21
|
||||
17: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
18: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
19: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
20: std::rt::lang_start_internal::{{closure}}
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:175:24
|
||||
21: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
22: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
23: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
24: std::rt::lang_start_internal
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:171:5
|
||||
25: main
|
||||
26: <unknown>
|
||||
27: __libc_start_main
|
||||
28: <unknown>
|
||||
1.046568941s INFO prepare_target{force=false package_id=x11rb-protocol v0.13.2 target="x11rb_protocol"}: cargo::core::compiler::fingerprint: fingerprint error for x11rb-protocol v0.13.2/Check { test: false }/TargetInner { ..: lib_target("x11rb_protocol", ["lib"], "/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/x11rb-protocol-0.13.2/src/lib.rs", Edition2021) }
|
||||
1.046575593s INFO prepare_target{force=false package_id=x11rb-protocol v0.13.2 target="x11rb_protocol"}: cargo::core::compiler::fingerprint: err: failed to read `/home/zefad/Documents/Code/Rust/rklip/target/debug/.fingerprint/x11rb-protocol-22f581df66f49a57/lib-x11rb_protocol`
|
||||
|
||||
Caused by:
|
||||
No such file or directory (os error 2)
|
||||
|
||||
Stack backtrace:
|
||||
0: cargo_util::paths::read_bytes
|
||||
1: cargo_util::paths::read
|
||||
2: cargo::core::compiler::fingerprint::_compare_old_fingerprint
|
||||
3: cargo::core::compiler::fingerprint::prepare_target
|
||||
4: cargo::core::compiler::compile
|
||||
5: cargo::core::compiler::compile
|
||||
6: cargo::core::compiler::compile
|
||||
7: cargo::core::compiler::compile
|
||||
8: <cargo::core::compiler::build_runner::BuildRunner>::compile
|
||||
9: cargo::ops::cargo_compile::compile_ws
|
||||
10: cargo::ops::cargo_compile::compile_with_exec
|
||||
11: cargo::ops::cargo_compile::compile
|
||||
12: cargo::commands::check::exec
|
||||
13: <cargo::cli::Exec>::exec
|
||||
14: cargo::main
|
||||
15: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
|
||||
16: std::rt::lang_start::<()>::{closure#0}
|
||||
17: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/core/src/ops/function.rs:287:21
|
||||
18: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
19: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
20: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
21: std::rt::lang_start_internal::{{closure}}
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:175:24
|
||||
22: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
23: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
24: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
25: std::rt::lang_start_internal
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:171:5
|
||||
26: main
|
||||
27: <unknown>
|
||||
28: __libc_start_main
|
||||
29: <unknown>
|
||||
1.046717135s INFO prepare_target{force=false package_id=rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip) target="rklip"}: cargo::core::compiler::fingerprint: fingerprint error for rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip)/Check { test: true }/TargetInner { name: "rklip", doc: true, ..: with_path("/home/zefad/Documents/Code/Rust/rklip/src/main.rs", Edition2024) }
|
||||
1.046723009s INFO prepare_target{force=false package_id=rklip v0.1.0 (/home/zefad/Documents/Code/Rust/rklip) target="rklip"}: cargo::core::compiler::fingerprint: err: failed to read `/home/zefad/Documents/Code/Rust/rklip/target/debug/.fingerprint/rklip-ae964ff7a6bec145/test-bin-rklip`
|
||||
|
||||
Caused by:
|
||||
No such file or directory (os error 2)
|
||||
|
||||
Stack backtrace:
|
||||
0: cargo_util::paths::read_bytes
|
||||
1: cargo_util::paths::read
|
||||
2: cargo::core::compiler::fingerprint::_compare_old_fingerprint
|
||||
3: cargo::core::compiler::fingerprint::prepare_target
|
||||
4: cargo::core::compiler::compile
|
||||
5: <cargo::core::compiler::build_runner::BuildRunner>::compile
|
||||
6: cargo::ops::cargo_compile::compile_ws
|
||||
7: cargo::ops::cargo_compile::compile_with_exec
|
||||
8: cargo::ops::cargo_compile::compile
|
||||
9: cargo::commands::check::exec
|
||||
10: <cargo::cli::Exec>::exec
|
||||
11: cargo::main
|
||||
12: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
|
||||
13: std::rt::lang_start::<()>::{closure#0}
|
||||
14: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/core/src/ops/function.rs:287:21
|
||||
15: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
16: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
17: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
18: std::rt::lang_start_internal::{{closure}}
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:175:24
|
||||
19: std::panicking::catch_unwind::do_call
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:590:40
|
||||
20: std::panicking::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panicking.rs:553:19
|
||||
21: std::panic::catch_unwind
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/panic.rs:359:14
|
||||
22: std::rt::lang_start_internal
|
||||
at /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234/library/std/src/rt.rs:171:5
|
||||
23: main
|
||||
24: <unknown>
|
||||
25: __libc_start_main
|
||||
26: <unknown>
|
||||
Checking x11rb-protocol v0.13.2
|
||||
126
src/target/rust-analyzer/flycheck0/stdout
Normal file
126
src/target/rust-analyzer/flycheck0/stdout
Normal file
@ -0,0 +1,126 @@
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.106/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.106/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/proc-macro2-94cad8ea6a6e7259/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106","linked_libs":[],"linked_paths":[],"cfgs":["wrap_proc_macro","proc_macro_span_location","proc_macro_span_file"],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/proc-macro2-189e8840540e2699/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.45/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.45/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/quote-7f25ee22a5499f38/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"unicode_ident","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.24/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libunicode_ident-61533c72d8f4e5f7.rlib","/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libunicode_ident-61533c72d8f4e5f7.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.106/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"proc_macro2","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.106/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libproc_macro2-00cf4115453f001d.rlib","/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libproc_macro2-00cf4115453f001d.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/quote-e44ca39061eb9357/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#autocfg@1.5.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/autocfg-1.5.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"autocfg","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/autocfg-1.5.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libautocfg-38d501c17e68b38c.rlib","/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libautocfg-38d501c17e68b38c.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cfg-if-1.0.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cfg_if","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cfg-if-1.0.4/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libcfg_if-e720413b8edafa0a.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.21","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.21/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.21/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/crossbeam-utils-3facd263110404aa/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.13.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/rayon-core-1bc1ef21b7aae4c0/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.8","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/simd-adler32-0.3.8/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"simd_adler32","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/simd-adler32-0.3.8/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["const-generics","default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libsimd_adler32-4959f318fd01020d.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.182","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.182/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.182/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/libc-8ca02912241efd47/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.45/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quote","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.45/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libquote-d8a1cc7f7ad6cf38.rlib","/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libquote-d8a1cc7f7ad6cf38.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-traits-0.2.19/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-traits-0.2.19/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","i128","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/num-traits-d33e8f76298fe0c5/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.21","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/crossbeam-utils-5593ca2e94a79bb0/out"}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.13.0","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/rayon-core-c8581eef8cc27647/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#either@1.15.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.15.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"either","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/either-1.15.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std","use_std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libeither-d5303fd443e6a93b.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/memchr-2.8.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"memchr","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/memchr-2.8.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libmemchr-6abce47e4deee6c4.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.101","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.101/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.101/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/anyhow-cb58e527b1a0d161/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.182","linked_libs":[],"linked_paths":[],"cfgs":["freebsd12"],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/libc-6ff60029f4d68dcc/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-2.0.117/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"syn","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/syn-2.0.117/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["clone-impls","default","derive","extra-traits","full","parsing","printing","proc-macro"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libsyn-9c5eefb1491f644f.rlib","/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libsyn-9c5eefb1491f644f.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19","linked_libs":[],"linked_paths":[],"cfgs":["has_total_cmp"],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/num-traits-a87aa9d62b9c616b/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.21","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.21/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crossbeam_utils","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-utils-0.8.21/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libcrossbeam_utils-30ff13b172e89a74.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.101","linked_libs":[],"linked_paths":[],"cfgs":["std_backtrace"],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/anyhow-11e19e6de96d8cd5/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/stable_deref_trait-1.2.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"stable_deref_trait","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/stable_deref_trait-1.2.1/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libstable_deref_trait-c1b563a78da1ff57.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.6","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/arrayvec-0.7.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"arrayvec","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/arrayvec-0.7.6/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libarrayvec-6c21595d89b6025f.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.18/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.18/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/thiserror-a345b3ae36f46eea/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.8.40","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zerocopy-0.8.40/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zerocopy-0.8.40/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["derive","simd","zerocopy-derive"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/zerocopy-5bfeaf2b807d02aa/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-traits-0.2.19/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"num_traits","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-traits-0.2.19/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","i128","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libnum_traits-09c751addfdfe33b.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#crossbeam-epoch@0.9.18","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.18/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crossbeam_epoch","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-epoch-0.9.18/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libcrossbeam_epoch-898edee4616dca8c.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#equator-macro@0.4.2","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/equator-macro-0.4.2/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"equator_macro","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/equator-macro-0.4.2/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libequator_macro-ce34652d644ade57.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crc32fast-1.5.0/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crc32fast-1.5.0/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/crc32fast-a1aeaaf666d10f76/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/adler2-2.0.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"adler2","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/adler2-2.0.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libadler2-8ddbfa9477eb984f.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.8.40","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zerocopy-derive-0.8.40/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"zerocopy_derive","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zerocopy-derive-0.8.40/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libzerocopy_derive-5894c5eea629596d.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#as-slice@0.2.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/as-slice-0.2.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"as_slice","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/as-slice-0.2.1/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libas_slice-1069b17130cc449f.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.18","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.18/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"thiserror_impl","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-impl-2.0.18/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libthiserror_impl-ddbff05c8c6afe5d.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.6","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crossbeam_deque","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crossbeam-deque-0.8.6/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libcrossbeam_deque-39548485313d4b56.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#equator@0.4.2","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/equator-0.4.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"equator","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/equator-0.4.2/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libequator-51650a9e80c59ae5.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.46","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-integer-0.1.46/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"num_integer","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-integer-0.1.46/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["i128","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libnum_integer-2f709f34659e5cfd.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.9","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/miniz_oxide-0.8.9/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"miniz_oxide","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/miniz_oxide-0.8.9/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","simd","simd-adler32","with-alloc"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libminiz_oxide-8ad7235a9edc0a11.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0","linked_libs":[],"linked_paths":[],"cfgs":["stable_arm_crc32_intrinsics"],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/crc32fast-b09c59eae4a5ad74/out"}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.8.40","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/zerocopy-b68dff16f8d09d31/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.101","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.101/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"anyhow","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/anyhow-1.0.101/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libanyhow-5db4dce135aba3fe.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/thiserror-f120952bacb12ece/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.13.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rayon_core","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-core-1.13.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/librayon_core-683451861a2493f2.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#num-bigint@0.4.6","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-bigint-0.4.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"num_bigint","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-bigint-0.4.6/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libnum_bigint-221718d152836be1.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#aligned-vec@0.6.4","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aligned-vec-0.6.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"aligned_vec","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aligned-vec-0.6.4/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libaligned_vec-8463edaf5f8c52aa.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.182","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.182/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"libc","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.182/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/liblibc-82529cab63a4719e.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bitflags-2.11.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bitflags","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bitflags-2.11.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libbitflags-218d0fd2bf1b120c.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/paste-1.0.15/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/paste-1.0.15/build.rs","edition":"2018","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/paste-c090709b5b0c5de5/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#av-scenechange@0.14.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/av-scenechange-0.14.1/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/av-scenechange-0.14.1/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/av-scenechange-b0718666adf1fefd/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#built@0.8.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/built-0.8.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"built","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/built-0.8.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libbuilt-3504150f55669f03.rlib","/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libbuilt-3504150f55669f03.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#v_frame@0.3.9","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/v_frame-0.3.9/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"v_frame","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/v_frame-0.3.9/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libv_frame-1156faa230269145.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#num-rational@0.4.2","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-rational-0.4.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"num_rational","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-rational-0.4.2/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","num-bigint","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libnum_rational-8dc5800ee90aed29.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rayon@1.11.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.11.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rayon","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rayon-1.11.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/librayon-e9a86bef7e7f0816.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#log@0.4.29","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/log-0.4.29/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"log","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/log-0.4.29/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/liblog-f1ce6776ce589c15.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#av-scenechange@0.14.1","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[["PROFILE","debug"],["CARGO_CFG_TARGET_FEATURE","fxsr,sse,sse2"],["CARGO_ENCODED_RUSTFLAGS",""]],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/av-scenechange-d80936c8f13e0512/out"}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/paste-7a04688c692878f0/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rav1e@0.8.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rav1e-0.8.1/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rav1e-0.8.1/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["threading"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/rav1e-83365e3fb08a807f/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.8.40","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zerocopy-0.8.40/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"zerocopy","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zerocopy-0.8.40/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["derive","simd","zerocopy-derive"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libzerocopy-deed186ddce2b9ba.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crc32fast-1.5.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"crc32fast","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/crc32fast-1.5.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libcrc32fast-69ea6609f925bfa8.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.18/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"thiserror","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/thiserror-2.0.18/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libthiserror-d6760243bf830594.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#aligned@0.4.3","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aligned-0.4.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"aligned","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/aligned-0.4.3/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libaligned-57c432200ef7aa3e.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#arg_enum_proc_macro@0.3.4","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/arg_enum_proc_macro-0.3.4/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"arg_enum_proc_macro","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/arg_enum_proc_macro-0.3.4/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libarg_enum_proc_macro-20f69d5a87e1bd5b.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#profiling-procmacros@1.0.17","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/profiling-procmacros-1.0.17/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"profiling_procmacros","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/profiling-procmacros-1.0.17/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libprofiling_procmacros-15972ea06dd62341.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#core2@0.4.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/core2-0.4.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"core2","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/core2-0.4.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libcore2-58ba9b4b0cfd9718.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#nom@8.0.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nom-8.0.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"nom","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/nom-8.0.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libnom-eb14b95ddda5263e.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#pastey@0.1.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pastey-0.1.1/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"pastey","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pastey-0.1.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libpastey-076ae5a4ca7f87b1.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#y4m@0.8.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/y4m-0.8.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"y4m","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/y4m-0.8.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/liby4m-894507e38c6e6f85.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.4","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","event","fs","net","std","system"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/rustix-e0b5ea39b95a2339/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/smallvec-1.15.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"smallvec","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/smallvec-1.15.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libsmallvec-4036188a1e309e0f.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#quick-error@2.0.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quick-error-2.0.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quick_error","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quick-error-2.0.1/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libquick_error-16937569b7bf55f9.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#profiling@1.0.17","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/profiling-1.0.17/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"profiling","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/profiling-1.0.17/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","procmacros","profiling-procmacros"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libprofiling-ba6aad9ed6763dad.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#flate2@1.1.9","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/flate2-1.1.9/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"flate2","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/flate2-1.1.9/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["any_impl","default","miniz_oxide","rust_backend"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libflate2-3db1f10dc39293c0.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#bitstream-io@4.9.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bitstream-io-4.9.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bitstream_io","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bitstream-io-4.9.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libbitstream_io-e19054d9edfa0c3f.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#av1-grain@0.2.5","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/av1-grain-0.2.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"av1_grain","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/av1-grain-0.2.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["create","default","diff","estimate","nom","num-rational","parse","v_frame"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libav1_grain-fba80771774657b6.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.4","linked_libs":[],"linked_paths":[],"cfgs":["static_assertions","lower_upper_exp_for_non_zero","rustc_diagnostics","linux_raw_dep","linux_raw","linux_like","linux_kernel"],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/rustix-204c43a58d13de92/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#av-scenechange@0.14.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/av-scenechange-0.14.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"av_scenechange","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/av-scenechange-0.14.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libav_scenechange-f3333e00b652f67d.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#half@2.7.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/half-2.7.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"half","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/half-2.7.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libhalf-fdae697aaf91859e.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#maybe-rayon@0.1.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/maybe-rayon-0.1.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"maybe_rayon","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/maybe-rayon-0.1.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["rayon","threads"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libmaybe_rayon-ca32aa8e7701a603.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/paste-1.0.15/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"paste","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/paste-1.0.15/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libpaste-577a69ce6fdefcf7.so"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#rav1e@0.8.1","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[["PROFILE","debug"],["CARGO_CFG_TARGET_FEATURE","fxsr,sse,sse2"],["CARGO_ENCODED_RUSTFLAGS",""]],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/rav1e-dcbc355ff43b645c/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#num-derive@0.4.2","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-derive-0.4.2/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"num_derive","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/num-derive-0.4.2/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libnum_derive-7c25d2e70035aa41.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#fax_derive@0.2.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/fax_derive-0.2.0/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"fax_derive","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/fax_derive-0.2.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libfax_derive-4e5bdade57e6eaf6.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#simd_helpers@0.1.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/simd_helpers-0.1.0/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"simd_helpers","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/simd_helpers-0.1.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libsimd_helpers-8850d3c4bf61a7b0.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#itertools@0.14.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/itertools-0.14.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"itertools","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/itertools-0.14.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":false},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","use_alloc","use_std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libitertools-f8fabf0fac410047.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.12.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/linux-raw-sys-0.12.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"linux_raw_sys","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/linux-raw-sys-0.12.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["auxvec","elf","errno","general","if_ether","ioctl","net","netlink","no_std","system","xdp"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/liblinux_raw_sys-de78d14e078030e0.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#weezl@0.1.12","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/weezl-0.1.12/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"weezl","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/weezl-0.1.12/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libweezl-a4353494f8a0a7cf.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.12/Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.12/build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/build/parking_lot_core-a1f54925c42d264a/build-script-build"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#new_debug_unreachable@1.0.6","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/new_debug_unreachable-1.0.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"debug_unreachable","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/new_debug_unreachable-1.0.6/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libdebug_unreachable-51d43fcbdae7043b.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#noop_proc_macro@0.3.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/noop_proc_macro-0.3.0/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"noop_proc_macro","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/noop_proc_macro-0.3.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libnoop_proc_macro-ce03a6fe81b5df14.so"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zune-core@0.4.12","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-core-0.4.12/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"zune_core","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-core-0.4.12/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libzune_core-757da2fe39e7dcf1.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#imgref@1.12.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/imgref-1.12.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"imgref","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/imgref-1.12.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","deprecated"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libimgref-6276481e74629e43.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.4","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rustix","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","event","fs","net","std","system"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/librustix-49c45b9740736b85.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zune-jpeg@0.4.21","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-jpeg-0.4.21/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"zune_jpeg","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-jpeg-0.4.21/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","neon","std","x86"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libzune_jpeg-3c4400efc9a96049.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rav1e@0.8.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rav1e-0.8.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rav1e","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rav1e-0.8.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["threading"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/librav1e-1a52c783ad0f68b9.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"/home/zefad/Documents/Code/Rust/rklip/target/debug/build/parking_lot_core-ffcaa5490a92af7a/out"}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#fax@0.2.6","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/fax-0.2.6/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"fax","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/fax-0.2.6/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libfax-4c6e7b31ea0d1eb2.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#avif-serialize@0.8.8","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/avif-serialize-0.8.8/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"avif_serialize","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/avif-serialize-0.8.8/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libavif_serialize-ca2373192a027c75.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zune-inflate@0.2.54","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-inflate-0.2.54/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"zune_inflate","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-inflate-0.2.54/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["simd-adler32","zlib"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libzune_inflate-94a975eb7785c702.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#loop9@0.1.5","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/loop9-0.1.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"loop9","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/loop9-0.1.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libloop9-cf58aa244acff4fb.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.7","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/fdeflate-0.3.7/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"fdeflate","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/fdeflate-0.3.7/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libfdeflate-4724e495799331e7.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#color_quant@1.1.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/color_quant-1.1.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"color_quant","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/color_quant-1.1.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libcolor_quant-a7170cf0bea17c8e.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/scopeguard-1.2.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"scopeguard","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/scopeguard-1.2.0/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libscopeguard-881bf68c2f011ad5.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.25.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytemuck-1.25.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bytemuck","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bytemuck-1.25.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["extern_crate_alloc"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libbytemuck-749e2a0e58f951f2.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#lebe@0.5.3","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lebe-0.5.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"lebe","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lebe-0.5.3/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/liblebe-09f462e0dae69152.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#byteorder-lite@0.1.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/byteorder-lite-0.1.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"byteorder_lite","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/byteorder-lite-0.1.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libbyteorder_lite-b23754f5d3aa0269.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#bit_field@0.10.3","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bit_field-0.10.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"bit_field","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bit_field-0.10.3/src/lib.rs","edition":"2015","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libbit_field-3f37887804c70baf.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#rgb@0.8.53","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rgb-0.8.53/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"rgb","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rgb-0.8.53/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/librgb-82283d2f5e74b225.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zune-core@0.5.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-core-0.5.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"zune_core","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-core-0.5.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libzune_core-21c04c6d223e5db8.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#pxfm@0.1.28","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.28/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"pxfm","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.28/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libpxfm-0bc2e9bbc61b1421.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.14","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lock_api-0.4.14/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"lock_api","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lock_api-0.4.14/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["atomic_usize","default"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/liblock_api-6f7c20f2ce02eb24.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#image-webp@0.2.4","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/image-webp-0.2.4/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"image_webp","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/image-webp-0.2.4/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libimage_webp-e6266960e6d130f7.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#exr@1.74.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/exr-1.74.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"exr","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/exr-1.74.0/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["rayon"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libexr-90bc44ffe544e72c.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#gif@0.14.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/gif-0.14.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"gif","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/gif-0.14.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["color_quant","default","raii_no_panic","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libgif-3882322f61f4d93a.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#png@0.18.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/png-0.18.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"png","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/png-0.18.1/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libpng-cdb7288755c57a42.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#ravif@0.12.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ravif-0.12.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"ravif","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ravif-0.12.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["threading"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libravif-c0c9f18240d9b9cc.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zune-jpeg@0.5.12","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-jpeg-0.5.12/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"zune_jpeg","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/zune-jpeg-0.5.12/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","neon","std","x86"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libzune_jpeg-75dd85a6d3e06012.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#moxcms@0.7.11","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.11/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"moxcms","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.11/src/lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["avx","default","neon","sse"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libmoxcms-f06522a8790573ab.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#qoi@0.4.1","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/qoi-0.4.1/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"qoi","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/qoi-0.4.1/src/lib.rs","edition":"2021","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libqoi-74be0481997fbc87.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.12/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"parking_lot_core","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot_core-0.9.12/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libparking_lot_core-3bb925a6fab39d8d.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#tiff@0.10.3","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tiff-0.10.3/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"tiff","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tiff-0.10.3/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","deflate","fax","jpeg","lzw"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libtiff-27787da440b3ba24.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#gethostname@1.1.0","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/gethostname-1.1.0/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"gethostname","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/gethostname-1.1.0/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libgethostname-c98cca82181ff837.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#image@0.25.9","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/image-0.25.9/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"image","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/image-0.25.9/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["avif","bmp","dds","default","default-formats","exr","ff","gif","hdr","ico","jpeg","png","pnm","qoi","rayon","tga","tiff","webp"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libimage-7407a10e28322477.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.5","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot-0.12.5/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"parking_lot","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/parking_lot-0.12.5/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libparking_lot-00df4fec9a246298.rmeta"],"executable":null,"fresh":true}
|
||||
{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2","manifest_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/percent-encoding-2.3.2/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"percent_encoding","src_path":"/home/zefad/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/percent-encoding-2.3.2/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["/home/zefad/Documents/Code/Rust/rklip/target/debug/deps/libpercent_encoding-2effb2ec806b20a0.rmeta"],"executable":null,"fresh":true}
|
||||
440
src/ui.rs
Normal file
440
src/ui.rs
Normal file
@ -0,0 +1,440 @@
|
||||
use crate::app::{App, Mode, detect_lang, highlight_code, is_image, is_url_only};
|
||||
use crate::crypto::Crypto;
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, BorderType, Borders, Clear, List, ListItem, Padding, Paragraph},
|
||||
};
|
||||
use ratatui_image::StatefulImage;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Point d'entrée
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn render(f: &mut Frame, app: &mut App) {
|
||||
let outer = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Length(1)])
|
||||
.split(f.area());
|
||||
|
||||
let panels = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Length(46), Constraint::Min(0)])
|
||||
.split(outer[0]);
|
||||
|
||||
// ---- Liste ----
|
||||
render_list(f, app, panels[0]);
|
||||
|
||||
// ---- Prévisualisation ----
|
||||
render_preview(f, app, panels[1]);
|
||||
|
||||
// ---- Barre de statut ----
|
||||
render_statusbar(f, app, outer[1]);
|
||||
|
||||
// ---- Overlay aide (par-dessus tout le reste) ----
|
||||
if app.mode == Mode::Help {
|
||||
render_help_overlay(f, f.area());
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Liste
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn render_list(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
let items: Vec<ListItem> = app
|
||||
.filtered_items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let ts = App::format_timestamp(item.timestamp);
|
||||
let ts_span = Span::styled(
|
||||
format!(" {} ", ts),
|
||||
Style::default().fg(Color::Rgb(90, 90, 110)),
|
||||
);
|
||||
|
||||
// Indicateur d'épingle (largeur fixe pour garder l'alignement)
|
||||
let pin_span = if item.pinned {
|
||||
Span::styled(
|
||||
"★ ",
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
} else {
|
||||
Span::raw(" ")
|
||||
};
|
||||
|
||||
if Crypto::is_any_encrypted(&item.content) {
|
||||
ListItem::new(Line::from(vec![
|
||||
pin_span,
|
||||
ts_span,
|
||||
Span::styled(
|
||||
"🔒 [Chiffré]",
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::ITALIC),
|
||||
),
|
||||
]))
|
||||
} else if is_image(&item.content) {
|
||||
ListItem::new(Line::from(vec![
|
||||
pin_span,
|
||||
ts_span,
|
||||
Span::styled(
|
||||
format!("🖼 {}", &item.content),
|
||||
Style::default()
|
||||
.fg(Color::Magenta)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
]))
|
||||
} else if is_url_only(&item.content) {
|
||||
let preview: String = item.content.chars().take(26).collect();
|
||||
ListItem::new(Line::from(vec![
|
||||
pin_span,
|
||||
ts_span,
|
||||
Span::styled(
|
||||
"[URL] ",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(preview, Style::default().fg(Color::Rgb(100, 180, 255))),
|
||||
]))
|
||||
} else {
|
||||
let preview: String = item
|
||||
.content
|
||||
.lines()
|
||||
.find(|l| !l.trim().is_empty())
|
||||
.unwrap_or("")
|
||||
.chars()
|
||||
.take(26)
|
||||
.collect();
|
||||
ListItem::new(Line::from(vec![pin_span, ts_span, Span::raw(preview)]))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let list = List::new(items)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Color::DarkGray))
|
||||
.title(Span::styled(
|
||||
" Historique ",
|
||||
Style::default()
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
))
|
||||
.title_alignment(Alignment::Center),
|
||||
)
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Rgb(40, 44, 52))
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.highlight_symbol("▶ ");
|
||||
|
||||
f.render_stateful_widget(list, area, &mut app.list_state);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Prévisualisation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn render_preview(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
let selected_content = app.get_selected_item().map(|i| i.content.clone());
|
||||
|
||||
let preview_title = match &app.preview_lang {
|
||||
Some(l) => format!(" Prévisualisation — {} ", l),
|
||||
None => " Prévisualisation ".to_string(),
|
||||
};
|
||||
|
||||
let preview_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(Color::DarkGray))
|
||||
.title(Span::styled(
|
||||
preview_title,
|
||||
Style::default()
|
||||
.fg(Color::White)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
))
|
||||
.title_alignment(Alignment::Center)
|
||||
.padding(Padding::uniform(1));
|
||||
|
||||
let inner = preview_block.inner(area);
|
||||
f.render_widget(preview_block, area);
|
||||
|
||||
let scroll = (app.preview_scroll, 0);
|
||||
|
||||
if let Some(state) = app.current_image.as_mut() {
|
||||
f.render_stateful_widget(StatefulImage::default(), inner, state);
|
||||
} else if let Some(content) = &selected_content {
|
||||
if Crypto::is_any_encrypted(content) {
|
||||
f.render_widget(
|
||||
Paragraph::new("🔒 Contenu chiffré\n\nAppuyez sur [e] pour déchiffrer.")
|
||||
.scroll(scroll),
|
||||
inner,
|
||||
);
|
||||
} else if is_url_only(content) {
|
||||
// Affiche l'URL complète + hint
|
||||
let lines = vec![
|
||||
Line::from(Span::styled(
|
||||
content.trim(),
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::UNDERLINED),
|
||||
)),
|
||||
Line::from(""),
|
||||
Line::from(Span::styled(
|
||||
" [o] Ouvrir dans le navigateur",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
)),
|
||||
];
|
||||
f.render_widget(Paragraph::new(lines).scroll(scroll), inner);
|
||||
} else if let Some(lines) = &app.preview_highlighted {
|
||||
f.render_widget(Paragraph::new(lines.clone()).scroll(scroll), inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Barre de statut
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn render_statusbar(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
let (mode_label, mode_color) = match &app.mode {
|
||||
Mode::Normal => (" NORMAL ", Color::Green),
|
||||
Mode::Search => (" RECHERCHE ", Color::Cyan),
|
||||
Mode::Command => (" COMMANDE ", Color::Yellow),
|
||||
Mode::ConfirmDelete => (" SUPPRIMER ? y/n ", Color::Red),
|
||||
Mode::PasswordInput => (" MOT DE PASSE ", Color::Magenta),
|
||||
Mode::Help => (" AIDE ", Color::Blue),
|
||||
};
|
||||
|
||||
let filter_hint = match app.type_filter {
|
||||
crate::app::TypeFilter::All => String::new(),
|
||||
f => format!(" [{}]", f.label()),
|
||||
};
|
||||
|
||||
let msg_span = if let Some((msg, _)) = &app.error_message {
|
||||
Span::styled(format!(" ⚠ {msg}"), Style::default().fg(Color::Red))
|
||||
} else if let Some((msg, _)) = &app.status_message {
|
||||
Span::styled(format!(" ✓ {msg}"), Style::default().fg(Color::Green))
|
||||
} else {
|
||||
let extra = match &app.mode {
|
||||
Mode::Search => {
|
||||
let mode_hint = if app.input_buffer.trim_start().starts_with('/') {
|
||||
"re"
|
||||
} else {
|
||||
"~"
|
||||
};
|
||||
format!(" [{}] /{}{}", mode_hint, app.input_buffer, filter_hint)
|
||||
}
|
||||
Mode::Command => format!(" :{}", app.input_buffer),
|
||||
Mode::PasswordInput => format!(" {}", "●".repeat(app.input_buffer.len())),
|
||||
Mode::Help => " ? ou Esc pour fermer".to_string(),
|
||||
_ => filter_hint,
|
||||
};
|
||||
Span::raw(extra)
|
||||
};
|
||||
|
||||
let total = app.filtered_items.len();
|
||||
let current = if total == 0 {
|
||||
0
|
||||
} else {
|
||||
app.list_state.selected().unwrap_or(0) + 1
|
||||
};
|
||||
let counter = if app.has_more {
|
||||
format!(" {}/{}+ ", current, total)
|
||||
} else {
|
||||
format!(" {}/{} ", current, total)
|
||||
};
|
||||
let clen = counter.len() as u16;
|
||||
|
||||
let status_cols = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Min(0), Constraint::Length(clen)])
|
||||
.split(area);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(Line::from(vec![
|
||||
Span::styled(
|
||||
mode_label,
|
||||
Style::default()
|
||||
.bg(mode_color)
|
||||
.fg(Color::Black)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
msg_span,
|
||||
])),
|
||||
status_cols[0],
|
||||
);
|
||||
|
||||
f.render_widget(
|
||||
Paragraph::new(Line::from(Span::styled(
|
||||
counter,
|
||||
Style::default()
|
||||
.fg(Color::DarkGray)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)))
|
||||
.alignment(Alignment::Right),
|
||||
status_cols[1],
|
||||
);
|
||||
}
|
||||
|
||||
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
|
||||
let x = area.x + (area.width.saturating_sub(width)) / 2;
|
||||
let y = area.y + (area.height.saturating_sub(height)) / 2;
|
||||
Rect::new(x, y, width.min(area.width), height.min(area.height))
|
||||
}
|
||||
|
||||
fn help_lines() -> Vec<Line<'static>> {
|
||||
let key = |s: &'static str| {
|
||||
Span::styled(
|
||||
s,
|
||||
Style::default()
|
||||
.fg(Color::Rgb(255, 215, 100))
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
};
|
||||
let desc = |s: &'static str| Span::styled(s, Style::default().fg(Color::Rgb(200, 205, 220)));
|
||||
let sep = || Span::raw(" ");
|
||||
let header = |s: &'static str| {
|
||||
Span::styled(
|
||||
s,
|
||||
Style::default()
|
||||
.fg(Color::Rgb(130, 190, 255))
|
||||
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
|
||||
)
|
||||
};
|
||||
let dim = |s: &'static str| Span::styled(s, Style::default().fg(Color::Rgb(100, 105, 130)));
|
||||
let divider = || {
|
||||
Line::from(Span::styled(
|
||||
" ─────────────────────────────────────────────────────────────",
|
||||
Style::default().fg(Color::Rgb(55, 60, 90)),
|
||||
))
|
||||
};
|
||||
|
||||
vec![
|
||||
Line::from(vec![header(" Navigation")]),
|
||||
divider(),
|
||||
Line::from(vec![
|
||||
key(" j / ↓"),
|
||||
sep(),
|
||||
desc("Bas"),
|
||||
sep(),
|
||||
sep(),
|
||||
key("k / ↑"),
|
||||
sep(),
|
||||
desc("Haut"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
key(" g g"),
|
||||
sep(),
|
||||
desc("Premier"),
|
||||
sep(),
|
||||
sep(),
|
||||
key("G"),
|
||||
sep(),
|
||||
desc("Dernier"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
key(" Ctrl+j"),
|
||||
sep(),
|
||||
desc("Scroll prévisualisation ↓"),
|
||||
sep(),
|
||||
key("Ctrl+k"),
|
||||
sep(),
|
||||
desc("↑"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![header(" Actions")]),
|
||||
divider(),
|
||||
Line::from(vec![key(" Entrée"), sep(), desc("Coller et quitter")]),
|
||||
Line::from(vec![key(" d d"), sep(), desc("Supprimer (confirmation)")]),
|
||||
Line::from(vec![key(" u"), sep(), desc("Annuler la suppression")]),
|
||||
Line::from(vec![key(" p"), sep(), desc("★ Épingler / désépingler")]),
|
||||
Line::from(vec![key(" e"), sep(), desc("🔒 Chiffrer / déchiffrer")]),
|
||||
Line::from(vec![
|
||||
key(" o"),
|
||||
sep(),
|
||||
desc("Ouvrir l'URL dans le navigateur"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
key(" t"),
|
||||
sep(),
|
||||
desc("Filtrer : Tous → Texte → Image"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![header(" Recherche ( / )")]),
|
||||
divider(),
|
||||
Line::from(vec![
|
||||
key(" /texte"),
|
||||
sep(),
|
||||
desc("Fuzzy"),
|
||||
sep(),
|
||||
key("//regex"),
|
||||
sep(),
|
||||
desc("Regex"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
key(" after:YYYY-MM-DD"),
|
||||
sep(),
|
||||
desc("Après date"),
|
||||
sep(),
|
||||
key("before:…"),
|
||||
sep(),
|
||||
desc("Avant date"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![header(" Commandes ( : )")]),
|
||||
divider(),
|
||||
Line::from(vec![
|
||||
key(" :clear"),
|
||||
sep(),
|
||||
desc("Tout effacer"),
|
||||
sep(),
|
||||
key(":password"),
|
||||
sep(),
|
||||
desc("Mot de passe session"),
|
||||
sep(),
|
||||
key(":q"),
|
||||
sep(),
|
||||
desc("Quitter"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![dim(" Appuyez sur ? ou Esc pour fermer")]),
|
||||
]
|
||||
}
|
||||
|
||||
fn render_help_overlay(f: &mut Frame, area: Rect) {
|
||||
let popup = centered_rect(70, 27, area);
|
||||
f.render_widget(Clear, popup);
|
||||
|
||||
let bg = Color::Rgb(22, 26, 50);
|
||||
let border_color = Color::Rgb(80, 130, 220);
|
||||
|
||||
let block = Block::default()
|
||||
.title(Span::styled(
|
||||
" ? Raccourcis clavier ",
|
||||
Style::default()
|
||||
.fg(Color::Rgb(200, 220, 255))
|
||||
.add_modifier(Modifier::BOLD),
|
||||
))
|
||||
.title_alignment(Alignment::Center)
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(border_color))
|
||||
.style(Style::default().bg(bg));
|
||||
|
||||
let inner = block.inner(popup);
|
||||
f.render_widget(block, popup);
|
||||
f.render_widget(
|
||||
Paragraph::new(help_lines()).style(Style::default().bg(bg)),
|
||||
inner,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user