Compare commits
No commits in common. "e03b5ab8cb0f44f52f6a9517979c4bbf470413d9" and "b58c2df1d9b268ba70ad2e00c8ef6cd9bb599285" have entirely different histories.
e03b5ab8cb
...
b58c2df1d9
15 changed files with 82 additions and 778 deletions
211
Cargo.lock
generated
211
Cargo.lock
generated
|
|
@ -127,15 +127,6 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-activity"
|
name = "android-activity"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
|
@ -477,28 +468,6 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bindgen"
|
|
||||||
version = "0.64.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"cexpr",
|
|
||||||
"clang-sys",
|
|
||||||
"lazy_static",
|
|
||||||
"lazycell",
|
|
||||||
"log",
|
|
||||||
"peeking_take_while",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"regex",
|
|
||||||
"rustc-hash 1.1.0",
|
|
||||||
"shlex",
|
|
||||||
"syn 1.0.109",
|
|
||||||
"which",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
|
@ -540,7 +509,6 @@ dependencies = [
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
"gbm",
|
"gbm",
|
||||||
"image",
|
"image",
|
||||||
"leptess",
|
|
||||||
"libc",
|
"libc",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"png 0.17.16",
|
"png 0.17.16",
|
||||||
|
|
@ -701,15 +669,6 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cexpr"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
|
||||||
dependencies = [
|
|
||||||
"nom 7.1.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -737,17 +696,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clang-sys"
|
|
||||||
version = "1.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
|
||||||
dependencies = [
|
|
||||||
"glob",
|
|
||||||
"libc",
|
|
||||||
"libloading",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.60"
|
version = "4.5.60"
|
||||||
|
|
@ -1208,12 +1156,6 @@ dependencies = [
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "emath"
|
name = "emath"
|
||||||
version = "0.29.1"
|
version = "0.29.1"
|
||||||
|
|
@ -1571,12 +1513,6 @@ dependencies = [
|
||||||
"xml-rs",
|
"xml-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "glob"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glow"
|
name = "glow"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
|
|
@ -1773,15 +1709,6 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "home"
|
|
||||||
version = "0.5.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.61.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
|
@ -1995,56 +1922,12 @@ version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy_static"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazycell"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leb128fmt"
|
name = "leb128fmt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "leptess"
|
|
||||||
version = "0.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae8964e3d3270be667dda2d0026e8c77011bafaad33936011b93750489987513"
|
|
||||||
dependencies = [
|
|
||||||
"tesseract-plumbing",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "leptonica-plumbing"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cc7a74c43d6f090d39158d233f326f47cd8bba545217595c93662b4e31156f42"
|
|
||||||
dependencies = [
|
|
||||||
"leptonica-sys",
|
|
||||||
"libc",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "leptonica-sys"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da627c72b2499a8106f4dd33143843015e4a631f445d561f3481f7fba35b6151"
|
|
||||||
dependencies = [
|
|
||||||
"bindgen",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.182"
|
version = "0.2.182"
|
||||||
|
|
@ -2184,12 +2067,6 @@ dependencies = [
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minimal-lexical"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
|
|
@ -2289,16 +2166,6 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "7.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"minimal-lexical",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "8.0.0"
|
version = "8.0.0"
|
||||||
|
|
@ -2711,12 +2578,6 @@ version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "peeking_take_while"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -3045,35 +2906,6 @@ dependencies = [
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "renderdoc-sys"
|
name = "renderdoc-sys"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -3438,29 +3270,6 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tesseract-plumbing"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a25fbbb95169954a9262a565fbfb001c4d9dad271d48142e6632a3e2b7314b35"
|
|
||||||
dependencies = [
|
|
||||||
"leptonica-plumbing",
|
|
||||||
"tesseract-sys",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tesseract-sys"
|
|
||||||
version = "0.5.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bd33f6f216124cfaf0fa86c2c0cdf04da39b6257bd78c5e44fa4fa98c3a5857b"
|
|
||||||
dependencies = [
|
|
||||||
"bindgen",
|
|
||||||
"leptonica-sys",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
|
|
@ -3605,7 +3414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"nom 8.0.0",
|
"nom",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3695,12 +3504,6 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
|
|
@ -4146,18 +3949,6 @@ dependencies = [
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "which"
|
|
||||||
version = "4.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"home",
|
|
||||||
"once_cell",
|
|
||||||
"rustix 0.38.44",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ required-features = ["gui"]
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
gui = ["dep:eframe", "dep:egui", "dep:egui_extras", "dep:ab_glyph"]
|
gui = ["dep:eframe", "dep:egui", "dep:egui_extras", "dep:ab_glyph"]
|
||||||
ocr = ["dep:leptess"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|
@ -45,7 +44,6 @@ ab_glyph = { version = "0.2", optional = true }
|
||||||
eframe = { version = "0.29", features = ["wayland"], optional = true }
|
eframe = { version = "0.29", features = ["wayland"], optional = true }
|
||||||
egui = { version = "0.29", optional = true }
|
egui = { version = "0.29", optional = true }
|
||||||
egui_extras = { version = "0.29", features = ["image"], optional = true }
|
egui_extras = { version = "0.29", features = ["image"], optional = true }
|
||||||
leptess = { version = "0.14", optional = true }
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,8 @@ impl AppState {
|
||||||
// Capture all monitors in parallel — each thread opens its own
|
// Capture all monitors in parallel — each thread opens its own
|
||||||
// Wayland connection. This turns N sequential captures (each
|
// Wayland connection. This turns N sequential captures (each
|
||||||
// gated on a compositor roundtrip) into N concurrent ones.
|
// gated on a compositor roundtrip) into N concurrent ones.
|
||||||
let captures: Vec<Result<Option<image::RgbaImage>>> = std::thread::scope(|s| {
|
let captures: Vec<Result<Option<image::RgbaImage>>> =
|
||||||
|
std::thread::scope(|s| {
|
||||||
let handles: Vec<_> = mons
|
let handles: Vec<_> = mons
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mon| {
|
.map(|mon| {
|
||||||
|
|
@ -672,8 +673,7 @@ fn capture_and_resize(
|
||||||
|
|
||||||
let mut state = state;
|
let mut state = state;
|
||||||
let overlay = if opts.include_cursor { 1 } else { 0 };
|
let overlay = if opts.include_cursor { 1 } else { 0 };
|
||||||
let frame =
|
let frame = state
|
||||||
state
|
|
||||||
.screencopy_mgr
|
.screencopy_mgr
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,3 @@ pub fn copy_png(png_bytes: Vec<u8>) -> Result<()> {
|
||||||
thread::sleep(std::time::Duration::from_millis(50));
|
thread::sleep(std::time::Duration::from_millis(50));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn copy_text(text: String) -> Result<()> {
|
|
||||||
Options::new()
|
|
||||||
.copy(Source::Bytes(text.into_bytes().into()), MimeType::Text)
|
|
||||||
.map_err(|e| BlastError::Clipboard(format!("{e}")))?;
|
|
||||||
thread::sleep(std::time::Duration::from_millis(50));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -213,15 +213,7 @@ impl Annotation for ArrowAnn {
|
||||||
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
||||||
let c = to_rgba(self.color);
|
let c = to_rgba(self.color);
|
||||||
draw_line_img(
|
draw_line_img(
|
||||||
img,
|
img, self.from[0], self.from[1], self.to[0], self.to[1], self.thickness, c, iw, ih,
|
||||||
self.from[0],
|
|
||||||
self.from[1],
|
|
||||||
self.to[0],
|
|
||||||
self.to[1],
|
|
||||||
self.thickness,
|
|
||||||
c,
|
|
||||||
iw,
|
|
||||||
ih,
|
|
||||||
);
|
);
|
||||||
draw_arrowhead(
|
draw_arrowhead(
|
||||||
img,
|
img,
|
||||||
|
|
@ -290,14 +282,7 @@ impl Annotation for TextAnn {
|
||||||
}
|
}
|
||||||
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
||||||
draw_text_img(
|
draw_text_img(
|
||||||
img,
|
img, self.pos[0], self.pos[1], &self.text, self.size, self.color, iw, ih,
|
||||||
self.pos[0],
|
|
||||||
self.pos[1],
|
|
||||||
&self.text,
|
|
||||||
self.size,
|
|
||||||
self.color,
|
|
||||||
iw,
|
|
||||||
ih,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn as_text(&self) -> Option<&TextAnn> {
|
fn as_text(&self) -> Option<&TextAnn> {
|
||||||
|
|
@ -355,9 +340,7 @@ impl Annotation for PixelateAnn {
|
||||||
rect_selection(&self.r, painter, to_screen);
|
rect_selection(&self.r, painter, to_screen);
|
||||||
}
|
}
|
||||||
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
||||||
pixelate_region(
|
pixelate_region(img, self.r[0], self.r[1], self.r[2], self.r[3], self.block, iw, ih);
|
||||||
img, self.r[0], self.r[1], self.r[2], self.r[3], self.block, iw, ih,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -403,17 +386,7 @@ impl Annotation for PencilAnn {
|
||||||
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
fn flatten(&self, img: &mut image::RgbaImage, iw: u32, ih: u32) {
|
||||||
let c = to_rgba(self.color);
|
let c = to_rgba(self.color);
|
||||||
for w in self.points.windows(2) {
|
for w in self.points.windows(2) {
|
||||||
draw_line_img(
|
draw_line_img(img, w[0][0], w[0][1], w[1][0], w[1][1], self.thickness, c, iw, ih);
|
||||||
img,
|
|
||||||
w[0][0],
|
|
||||||
w[0][1],
|
|
||||||
w[1][0],
|
|
||||||
w[1][1],
|
|
||||||
self.thickness,
|
|
||||||
c,
|
|
||||||
iw,
|
|
||||||
ih,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,8 @@ use crate::{
|
||||||
capture::{self, CaptureOptions},
|
capture::{self, CaptureOptions},
|
||||||
clipboard,
|
clipboard,
|
||||||
error::BlastError,
|
error::BlastError,
|
||||||
freeze, hyprland, paths, region, select,
|
freeze, hyprland, paths, region, select, wayland_windows,
|
||||||
shhh::{self, ShadowOptions},
|
shhh::{self, ShadowOptions},
|
||||||
wayland_windows,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
|
@ -205,25 +204,23 @@ impl BlastApp {
|
||||||
Subject::Area => {
|
Subject::Area => {
|
||||||
let geom = hyprland::with_animations_disabled(|bs| {
|
let geom = hyprland::with_animations_disabled(|bs| {
|
||||||
let boxes = wayland_windows::visible_window_hints().unwrap_or_else(|| {
|
let boxes = wayland_windows::visible_window_hints().unwrap_or_else(|| {
|
||||||
let bar =
|
let bar = hyprland::get_option_int("plugin:hyprbars:bar_height")
|
||||||
hyprland::get_option_int("plugin:hyprbars:bar_height").unwrap_or(0);
|
.unwrap_or(0);
|
||||||
hyprland::visible_windows()
|
hyprland::visible_windows()
|
||||||
.map(|wins| {
|
.map(|wins| {
|
||||||
wins.iter()
|
wins.iter()
|
||||||
.map(|w| {
|
.map(|w| {
|
||||||
let g = w.to_geometry(bs, bar);
|
let g = w.to_geometry(bs, bar);
|
||||||
select::HintBox {
|
select::HintBox {
|
||||||
x: g.x as i32,
|
x: g.x as i32, y: g.y as i32,
|
||||||
y: g.y as i32,
|
w: g.w as i32, h: g.h as i32,
|
||||||
w: g.w as i32,
|
|
||||||
h: g.h as i32,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
region::select_area_boxes(boxes, None, true)
|
region::select_area_boxes(boxes, None)
|
||||||
})
|
})
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
freeze_guard.kill();
|
freeze_guard.kill();
|
||||||
|
|
@ -231,7 +228,7 @@ impl BlastApp {
|
||||||
(img, "Area".into())
|
(img, "Area".into())
|
||||||
}
|
}
|
||||||
Subject::Region => {
|
Subject::Region => {
|
||||||
let geom = region::select_free_region(None, true).map_err(|e| e.to_string())?;
|
let geom = region::select_free_region(None).map_err(|e| e.to_string())?;
|
||||||
freeze_guard.kill();
|
freeze_guard.kill();
|
||||||
let img = capture::capture_region(geom, &opts).map_err(|e| e.to_string())?;
|
let img = capture::capture_region(geom, &opts).map_err(|e| e.to_string())?;
|
||||||
(img, "Region".into())
|
(img, "Region".into())
|
||||||
|
|
|
||||||
134
src/main.rs
134
src/main.rs
|
|
@ -4,8 +4,6 @@ mod error;
|
||||||
mod freeze;
|
mod freeze;
|
||||||
mod hyprland;
|
mod hyprland;
|
||||||
mod notify;
|
mod notify;
|
||||||
#[cfg(feature = "ocr")]
|
|
||||||
mod ocr;
|
|
||||||
mod paths;
|
mod paths;
|
||||||
mod region;
|
mod region;
|
||||||
mod select;
|
mod select;
|
||||||
|
|
@ -48,21 +46,6 @@ struct Cli {
|
||||||
#[arg(long = "hint-color", value_name = "RRGGBBAA", value_parser = parse_color)]
|
#[arg(long = "hint-color", value_name = "RRGGBBAA", value_parser = parse_color)]
|
||||||
hint_color: Option<u32>,
|
hint_color: Option<u32>,
|
||||||
|
|
||||||
/// Hide the border around the active selection / hovered hint box.
|
|
||||||
/// Useful when the outline bleeds into the captured image.
|
|
||||||
#[arg(long = "no-outline")]
|
|
||||||
no_outline: bool,
|
|
||||||
|
|
||||||
/// Reuse the geometry from the most recent `region` capture (skips interactive selection).
|
|
||||||
#[arg(long)]
|
|
||||||
last: bool,
|
|
||||||
|
|
||||||
/// Send notification with clickable actions (requires --notify).
|
|
||||||
/// On save/copysave, exposes a "Copy path" button. Process briefly blocks
|
|
||||||
/// awaiting a click or notification timeout.
|
|
||||||
#[arg(long, requires = "notify")]
|
|
||||||
actions: bool,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
shadow: ShadowArgs,
|
shadow: ShadowArgs,
|
||||||
|
|
||||||
|
|
@ -170,19 +153,6 @@ enum Action {
|
||||||
/// Output file path (defaults to /tmp/<timestamp>.png).
|
/// Output file path (defaults to /tmp/<timestamp>.png).
|
||||||
file: Option<String>,
|
file: Option<String>,
|
||||||
},
|
},
|
||||||
/// OCR the captured region and copy the recognized text to the clipboard.
|
|
||||||
#[cfg(feature = "ocr")]
|
|
||||||
Ocr {
|
|
||||||
#[arg(value_enum, default_value_t = Subject::Region)]
|
|
||||||
subject: Subject,
|
|
||||||
/// Tesseract language code(s), e.g. "eng" or "eng+deu" (default "eng").
|
|
||||||
#[arg(long, default_value = "eng")]
|
|
||||||
lang: String,
|
|
||||||
/// Write text to this file instead of (or in addition to) clipboard.
|
|
||||||
/// Use '-' for stdout.
|
|
||||||
#[arg(short = 'o', long)]
|
|
||||||
output: Option<String>,
|
|
||||||
},
|
|
||||||
Check,
|
Check,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,9 +183,6 @@ fn main() {
|
||||||
wait_secs: cli.wait,
|
wait_secs: cli.wait,
|
||||||
scale: cli.scale,
|
scale: cli.scale,
|
||||||
hint_color: cli.hint_color,
|
hint_color: cli.hint_color,
|
||||||
no_outline: cli.no_outline,
|
|
||||||
last: cli.last,
|
|
||||||
actions: cli.actions,
|
|
||||||
shadow: cli.shadow,
|
shadow: cli.shadow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -244,7 +211,6 @@ fn print_usage() {
|
||||||
println!(" -w, --wait N Wait N seconds before capture");
|
println!(" -w, --wait N Wait N seconds before capture");
|
||||||
println!(" -s, --scale SCALE Scale factor");
|
println!(" -s, --scale SCALE Scale factor");
|
||||||
println!(" --hint-color RRGGBBAA Hint-box colour for area/region (e.g. ff550080)");
|
println!(" --hint-color RRGGBBAA Hint-box colour for area/region (e.g. ff550080)");
|
||||||
println!(" --no-outline Hide the selection border (avoids outline in captures)");
|
|
||||||
println!(" --no-shadow Disable drop shadow and corner rounding");
|
println!(" --no-shadow Disable drop shadow and corner rounding");
|
||||||
println!(" --radius px Corner radius (default: from Hyprland config or 0)");
|
println!(" --radius px Corner radius (default: from Hyprland config or 0)");
|
||||||
println!(" --shadow-offset x,y Shadow offset (default: -20,-20)");
|
println!(" --shadow-offset x,y Shadow offset (default: -20,-20)");
|
||||||
|
|
@ -260,9 +226,6 @@ struct Context {
|
||||||
wait_secs: Option<u64>,
|
wait_secs: Option<u64>,
|
||||||
scale: Option<f64>,
|
scale: Option<f64>,
|
||||||
hint_color: Option<u32>,
|
hint_color: Option<u32>,
|
||||||
no_outline: bool,
|
|
||||||
last: bool,
|
|
||||||
actions: bool,
|
|
||||||
shadow: ShadowArgs,
|
shadow: ShadowArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,31 +257,6 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notify after a successful save. If `--actions` is enabled, expose a
|
|
||||||
/// "Copy path" button; on click, copy `saved_path` to the clipboard.
|
|
||||||
fn notify_saved(&self, summary: &str, saved_path: &str) {
|
|
||||||
if !self.notify {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if self.actions {
|
|
||||||
let actions = [("copy-path", "Copy path")];
|
|
||||||
match notify::notify_with_actions(
|
|
||||||
summary,
|
|
||||||
Some(saved_path),
|
|
||||||
Some(saved_path),
|
|
||||||
&actions,
|
|
||||||
3000,
|
|
||||||
) {
|
|
||||||
Ok(Some(key)) if key == "copy-path" => {
|
|
||||||
let _ = clipboard::copy_text(saved_path.to_string());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = notify::notify_ok(summary, Some(saved_path), Some(saved_path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encode image to PNG, applying shadow/rounding if enabled.
|
/// Encode image to PNG, applying shadow/rounding if enabled.
|
||||||
fn finalize_png(&self, img: &DynamicImage) -> Result<Vec<u8>> {
|
fn finalize_png(&self, img: &DynamicImage) -> Result<Vec<u8>> {
|
||||||
match self.shadow.resolve() {
|
match self.shadow.resolve() {
|
||||||
|
|
@ -342,55 +280,6 @@ fn run(action: Action, ctx: Context) -> Result<()> {
|
||||||
run_copysave(subject, output.or(file), ctx)
|
run_copysave(subject, output.or(file), ctx)
|
||||||
}
|
}
|
||||||
Action::Edit { subject, file } => run_edit(subject, file, ctx),
|
Action::Edit { subject, file } => run_edit(subject, file, ctx),
|
||||||
#[cfg(feature = "ocr")]
|
|
||||||
Action::Ocr {
|
|
||||||
subject,
|
|
||||||
lang,
|
|
||||||
output,
|
|
||||||
} => run_ocr(subject, lang, output, ctx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ocr")]
|
|
||||||
fn run_ocr(subject: Subject, lang: String, output: Option<String>, ctx: Context) -> Result<()> {
|
|
||||||
let (img, _what) = capture_subject(&subject, &ctx)?;
|
|
||||||
let text = ocr::recognize(&img, &lang)?;
|
|
||||||
|
|
||||||
if text.is_empty() {
|
|
||||||
ctx.notify_err("OCR: no text recognized", None);
|
|
||||||
return Err(BlastError::Other("no text recognized".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
match output.as_deref() {
|
|
||||||
Some("-") => {
|
|
||||||
print!("{text}");
|
|
||||||
}
|
|
||||||
Some(path) => {
|
|
||||||
std::fs::write(path, &text).map_err(BlastError::Io)?;
|
|
||||||
ctx.notify_ok("Text saved", Some(path), None);
|
|
||||||
println!("{path}");
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
clipboard::copy_text(text.clone()).map_err(|e| {
|
|
||||||
ctx.notify_err("Clipboard error", Some(&e.to_string()));
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
ctx.notify_ok("Text copied", Some(&summarize_text(&text)), None);
|
|
||||||
// also print to stdout so it's pipe-able
|
|
||||||
println!("{text}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ocr")]
|
|
||||||
fn summarize_text(t: &str) -> String {
|
|
||||||
let one_line: String = t.split_whitespace().collect::<Vec<_>>().join(" ");
|
|
||||||
if one_line.len() <= 80 {
|
|
||||||
one_line
|
|
||||||
} else {
|
|
||||||
format!("{}…", &one_line[..77])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,7 +340,7 @@ fn run_save(subject: Subject, file: Option<String>, ctx: Context) -> Result<()>
|
||||||
let png = ctx.finalize_png(&img)?;
|
let png = ctx.finalize_png(&img)?;
|
||||||
std::fs::write(&dest, &png).map_err(BlastError::Io)?;
|
std::fs::write(&dest, &png).map_err(BlastError::Io)?;
|
||||||
let name = dest.display().to_string();
|
let name = dest.display().to_string();
|
||||||
ctx.notify_saved(&format!("Screenshot of {what}"), &name);
|
ctx.notify_ok(&format!("Screenshot of {what}"), Some(&name), Some(&name));
|
||||||
println!("{name}");
|
println!("{name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -474,7 +363,11 @@ fn run_copysave(subject: Subject, file: Option<String>, ctx: Context) -> Result<
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let name = dest.display().to_string();
|
let name = dest.display().to_string();
|
||||||
ctx.notify_saved(&format!("{what} copied and saved"), &name);
|
ctx.notify_ok(
|
||||||
|
&format!("{what} copied and saved"),
|
||||||
|
Some(&name),
|
||||||
|
Some(&name),
|
||||||
|
);
|
||||||
println!("{name}");
|
println!("{name}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -584,11 +477,11 @@ fn capture_area(ctx: &Context) -> Result<(DynamicImage, String)> {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
region::select_area_boxes(boxes, ctx.hint_color, !ctx.no_outline)
|
region::select_area_boxes(boxes, ctx.hint_color)
|
||||||
})?
|
})?
|
||||||
} else {
|
} else {
|
||||||
let boxes = wayland_windows::visible_window_hints().unwrap_or_default();
|
let boxes = wayland_windows::visible_window_hints().unwrap_or_default();
|
||||||
region::select_area_boxes(boxes, ctx.hint_color, !ctx.no_outline)?
|
region::select_area_boxes(boxes, ctx.hint_color)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the selection snapped to a known Hyprland window, ask the compositor
|
// If the selection snapped to a known Hyprland window, ask the compositor
|
||||||
|
|
@ -613,22 +506,13 @@ fn capture_area(ctx: &Context) -> Result<(DynamicImage, String)> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capture_region_free(ctx: &Context) -> Result<(DynamicImage, String)> {
|
fn capture_region_free(ctx: &Context) -> Result<(DynamicImage, String)> {
|
||||||
if ctx.last {
|
|
||||||
let geom = paths::load_last_region().ok_or_else(|| {
|
|
||||||
BlastError::Selection("no previous region cached (run `region` once first)".into())
|
|
||||||
})?;
|
|
||||||
let img = capture::capture_region(geom, &ctx.capture_opts())?;
|
|
||||||
return Ok((img, "Region".into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut freeze_guard = if ctx.freeze {
|
let mut freeze_guard = if ctx.freeze {
|
||||||
freeze::FreezeGuard::spawn()?
|
freeze::FreezeGuard::spawn()?
|
||||||
} else {
|
} else {
|
||||||
freeze::FreezeGuard::none()
|
freeze::FreezeGuard::none()
|
||||||
};
|
};
|
||||||
|
|
||||||
let geom = region::select_free_region(ctx.hint_color, !ctx.no_outline)?;
|
let geom = region::select_free_region(ctx.hint_color)?;
|
||||||
paths::save_last_region(&geom);
|
|
||||||
|
|
||||||
let img = capture::capture_region(geom, &ctx.capture_opts())?;
|
let img = capture::capture_region(geom, &ctx.capture_opts())?;
|
||||||
freeze_guard.kill();
|
freeze_guard.kill();
|
||||||
|
|
|
||||||
295
src/notify.rs
295
src/notify.rs
|
|
@ -8,7 +8,7 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::error::{BlastError, Result};
|
use crate::error::{BlastError, Result};
|
||||||
|
|
||||||
|
|
@ -28,7 +28,13 @@ pub fn notify_ok(summary: &str, body: Option<&str>, icon: Option<&str>) -> Resul
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn notify_error(summary: &str, body: Option<&str>) -> Result<()> {
|
pub fn notify_error(summary: &str, body: Option<&str>) -> Result<()> {
|
||||||
send_notify(summary, body.unwrap_or(""), "", URGENCY_CRITICAL, 5000)
|
send_notify(
|
||||||
|
summary,
|
||||||
|
body.unwrap_or(""),
|
||||||
|
"",
|
||||||
|
URGENCY_CRITICAL,
|
||||||
|
5000,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn err<S: Into<String>>(s: S) -> BlastError {
|
fn err<S: Into<String>>(s: S) -> BlastError {
|
||||||
|
|
@ -39,7 +45,13 @@ fn io_err(e: std::io::Error) -> BlastError {
|
||||||
BlastError::Notify(e.to_string())
|
BlastError::Notify(e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_notify(summary: &str, body: &str, icon: &str, urgency: u8, timeout_ms: i32) -> Result<()> {
|
fn send_notify(
|
||||||
|
summary: &str,
|
||||||
|
body: &str,
|
||||||
|
icon: &str,
|
||||||
|
urgency: u8,
|
||||||
|
timeout_ms: i32,
|
||||||
|
) -> Result<()> {
|
||||||
let path = session_bus_path().ok_or_else(|| err("no DBus session bus address"))?;
|
let path = session_bus_path().ok_or_else(|| err("no DBus session bus address"))?;
|
||||||
let mut sock = UnixStream::connect(&path).map_err(io_err)?;
|
let mut sock = UnixStream::connect(&path).map_err(io_err)?;
|
||||||
sock.set_read_timeout(Some(Duration::from_secs(2))).ok();
|
sock.set_read_timeout(Some(Duration::from_secs(2))).ok();
|
||||||
|
|
@ -189,20 +201,6 @@ fn write_empty_string_array(buf: &mut Vec<u8>) {
|
||||||
buf.extend_from_slice(&0u32.to_le_bytes());
|
buf.extend_from_slice(&0u32.to_le_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marshal an array of strings: u32 byte-length + N (string) entries.
|
|
||||||
fn write_string_array(buf: &mut Vec<u8>, items: &[&str]) {
|
|
||||||
align_to(buf, 4);
|
|
||||||
let len_pos = buf.len();
|
|
||||||
buf.extend_from_slice(&[0u8; 4]);
|
|
||||||
// 's' element alignment is 4 (already at 4-aligned position).
|
|
||||||
let start = buf.len();
|
|
||||||
for s in items {
|
|
||||||
write_string(buf, s);
|
|
||||||
}
|
|
||||||
let len = (buf.len() - start) as u32;
|
|
||||||
buf[len_pos..len_pos + 4].copy_from_slice(&len.to_le_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Marshal `a{sv}` containing a single "urgency" -> byte entry.
|
/// Marshal `a{sv}` containing a single "urgency" -> byte entry.
|
||||||
fn write_urgency_hint(buf: &mut Vec<u8>, urgency: u8) {
|
fn write_urgency_hint(buf: &mut Vec<u8>, urgency: u8) {
|
||||||
align_to(buf, 4);
|
align_to(buf, 4);
|
||||||
|
|
@ -299,173 +297,10 @@ mod tests {
|
||||||
notify_ok("blast notify test", Some("hand-rolled DBus works"), None)
|
notify_ok("blast notify test", Some("hand-rolled DBus works"), None)
|
||||||
.expect("notify failed");
|
.expect("notify failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Live test for the actions path. Pops a notification with a "Copy path"
|
|
||||||
/// button. Returns Some("copy-path") if you click it, None on timeout.
|
|
||||||
/// Run with: cargo test --bin blast notify::tests::live_actions -- --ignored --nocapture
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn live_actions() {
|
|
||||||
let result = notify_with_actions(
|
|
||||||
"blast actions test",
|
|
||||||
Some("click \"Copy path\" within 5 seconds"),
|
|
||||||
None,
|
|
||||||
&[("copy-path", "Copy path")],
|
|
||||||
5000,
|
|
||||||
)
|
|
||||||
.expect("notify_with_actions failed");
|
|
||||||
println!("result: {:?}", result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read and discard one DBus message (used to drain the Hello reply).
|
/// Read and discard one DBus message (used to drain the Hello reply).
|
||||||
fn drain_message(sock: &mut UnixStream) -> Result<()> {
|
fn drain_message(sock: &mut UnixStream) -> Result<()> {
|
||||||
let _ = read_message(sock)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Interactive notifications (actions) ===
|
|
||||||
|
|
||||||
/// Send a notification with clickable actions, then wait up to `expire_ms`+500ms for the
|
|
||||||
/// user to click one. Returns `Some(action_key)` on click, `None` on dismissal or timeout.
|
|
||||||
///
|
|
||||||
/// `actions` is a list of `(key, label)` pairs — e.g. `[("open", "Open"), ("copy-path", "Copy path")]`.
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn notify_with_actions(
|
|
||||||
summary: &str,
|
|
||||||
body: Option<&str>,
|
|
||||||
icon: Option<&str>,
|
|
||||||
actions: &[(&str, &str)],
|
|
||||||
expire_ms: i32,
|
|
||||||
) -> Result<Option<String>> {
|
|
||||||
let path = session_bus_path().ok_or_else(|| err("no DBus session bus address"))?;
|
|
||||||
let mut sock = UnixStream::connect(&path).map_err(io_err)?;
|
|
||||||
sock.set_write_timeout(Some(Duration::from_secs(2))).ok();
|
|
||||||
|
|
||||||
sasl_auth(&mut sock)?;
|
|
||||||
hello(&mut sock)?;
|
|
||||||
add_match(
|
|
||||||
&mut sock,
|
|
||||||
"type='signal',interface='org.freedesktop.Notifications'",
|
|
||||||
)?;
|
|
||||||
let notif_id = do_notify_with_actions(
|
|
||||||
&mut sock,
|
|
||||||
summary,
|
|
||||||
body.unwrap_or(""),
|
|
||||||
icon.unwrap_or(""),
|
|
||||||
URGENCY_NORMAL,
|
|
||||||
expire_ms,
|
|
||||||
actions,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Wait for ActionInvoked or NotificationClosed for our id, or timeout.
|
|
||||||
// Bus may interleave other signals (NameAcquired etc) — we just skip them.
|
|
||||||
let deadline = Instant::now() + Duration::from_millis(expire_ms.max(0) as u64 + 500);
|
|
||||||
loop {
|
|
||||||
let remaining = deadline.saturating_duration_since(Instant::now());
|
|
||||||
if remaining.is_zero() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
sock.set_read_timeout(Some(remaining)).ok();
|
|
||||||
|
|
||||||
let msg = match read_message(&mut sock) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(_) => return Ok(None), // timed out or socket error
|
|
||||||
};
|
|
||||||
|
|
||||||
match msg.member.as_deref() {
|
|
||||||
Some("ActionInvoked") => {
|
|
||||||
if let Some((id, key)) = parse_us_body(&msg.body) {
|
|
||||||
if id == notif_id {
|
|
||||||
return Ok(Some(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some("NotificationClosed") => {
|
|
||||||
if msg.body.len() >= 4 {
|
|
||||||
let id = u32::from_le_bytes(msg.body[0..4].try_into().unwrap());
|
|
||||||
if id == notif_id {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_match(sock: &mut UnixStream, rule: &str) -> Result<()> {
|
|
||||||
let mut body = Vec::new();
|
|
||||||
write_string(&mut body, rule);
|
|
||||||
let msg = build_method_call(
|
|
||||||
3,
|
|
||||||
"/org/freedesktop/DBus",
|
|
||||||
"org.freedesktop.DBus",
|
|
||||||
"AddMatch",
|
|
||||||
"org.freedesktop.DBus",
|
|
||||||
Some("s"),
|
|
||||||
&body,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
sock.write_all(&msg).map_err(io_err)?;
|
|
||||||
drain_message(sock)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_notify_with_actions(
|
|
||||||
sock: &mut UnixStream,
|
|
||||||
summary: &str,
|
|
||||||
body: &str,
|
|
||||||
icon: &str,
|
|
||||||
urgency: u8,
|
|
||||||
timeout_ms: i32,
|
|
||||||
actions: &[(&str, &str)],
|
|
||||||
) -> Result<u32> {
|
|
||||||
let mut payload = Vec::new();
|
|
||||||
write_string(&mut payload, "blast");
|
|
||||||
write_u32(&mut payload, 0);
|
|
||||||
write_string(&mut payload, icon);
|
|
||||||
write_string(&mut payload, summary);
|
|
||||||
write_string(&mut payload, body);
|
|
||||||
|
|
||||||
// Flatten (key,label) pairs into a flat string array.
|
|
||||||
let mut flat: Vec<&str> = Vec::with_capacity(actions.len() * 2);
|
|
||||||
for (k, l) in actions {
|
|
||||||
flat.push(k);
|
|
||||||
flat.push(l);
|
|
||||||
}
|
|
||||||
write_string_array(&mut payload, &flat);
|
|
||||||
write_urgency_hint(&mut payload, urgency);
|
|
||||||
align_to(&mut payload, 4);
|
|
||||||
payload.extend_from_slice(&timeout_ms.to_le_bytes());
|
|
||||||
|
|
||||||
let msg = build_method_call(
|
|
||||||
4,
|
|
||||||
"/org/freedesktop/Notifications",
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"Notify",
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
Some("susssasa{sv}i"),
|
|
||||||
&payload,
|
|
||||||
0, // NOT fire-and-forget — we need the reply to learn our notification id.
|
|
||||||
);
|
|
||||||
sock.write_all(&msg).map_err(io_err)?;
|
|
||||||
|
|
||||||
let reply = read_message(sock)?;
|
|
||||||
if reply.body.len() < 4 {
|
|
||||||
return Err(err("notify reply: body too short"));
|
|
||||||
}
|
|
||||||
Ok(u32::from_le_bytes(reply.body[0..4].try_into().unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Message unmarshalling ===
|
|
||||||
|
|
||||||
struct Message {
|
|
||||||
member: Option<String>,
|
|
||||||
body: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_message(sock: &mut UnixStream) -> Result<Message> {
|
|
||||||
let mut fixed = [0u8; 16];
|
let mut fixed = [0u8; 16];
|
||||||
sock.read_exact(&mut fixed).map_err(io_err)?;
|
sock.read_exact(&mut fixed).map_err(io_err)?;
|
||||||
if fixed[0] != b'l' {
|
if fixed[0] != b'l' {
|
||||||
|
|
@ -473,100 +308,10 @@ fn read_message(sock: &mut UnixStream) -> Result<Message> {
|
||||||
}
|
}
|
||||||
let body_len = u32::from_le_bytes(fixed[4..8].try_into().unwrap()) as usize;
|
let body_len = u32::from_le_bytes(fixed[4..8].try_into().unwrap()) as usize;
|
||||||
let fields_len = u32::from_le_bytes(fixed[12..16].try_into().unwrap()) as usize;
|
let fields_len = u32::from_le_bytes(fixed[12..16].try_into().unwrap()) as usize;
|
||||||
|
// Header is always padded to 8 before body (whether body is empty or not).
|
||||||
let header_pad = (8 - ((16 + fields_len) % 8)) % 8;
|
let header_pad = (8 - ((16 + fields_len) % 8)) % 8;
|
||||||
|
let total_after = fields_len + header_pad + body_len;
|
||||||
let mut fields = vec![0u8; fields_len];
|
let mut discard = vec![0u8; total_after];
|
||||||
sock.read_exact(&mut fields).map_err(io_err)?;
|
sock.read_exact(&mut discard).map_err(io_err)?;
|
||||||
let mut pad = vec![0u8; header_pad];
|
Ok(())
|
||||||
if !pad.is_empty() {
|
|
||||||
sock.read_exact(&mut pad).map_err(io_err)?;
|
|
||||||
}
|
|
||||||
let mut body = vec![0u8; body_len];
|
|
||||||
if !body.is_empty() {
|
|
||||||
sock.read_exact(&mut body).map_err(io_err)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let member = parse_member(&fields);
|
|
||||||
Ok(Message { member, body })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Walk the header fields array looking for the MEMBER field (code=3, sig "s").
|
|
||||||
/// Returns None if not present or if parsing falls off the end of a known type.
|
|
||||||
fn parse_member(fields: &[u8]) -> Option<String> {
|
|
||||||
let mut pos = 0;
|
|
||||||
while pos < fields.len() {
|
|
||||||
pos = align_up(pos, 8);
|
|
||||||
if pos >= fields.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let code = fields[pos];
|
|
||||||
pos += 1;
|
|
||||||
if pos >= fields.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let sig_len = fields[pos] as usize;
|
|
||||||
pos += 1;
|
|
||||||
if pos + sig_len + 1 > fields.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let sig = fields[pos..pos + sig_len].to_vec();
|
|
||||||
pos += sig_len + 1; // include trailing NUL
|
|
||||||
|
|
||||||
if code == 3 && sig.first() == Some(&b's') {
|
|
||||||
pos = align_up(pos, 4);
|
|
||||||
if pos + 4 > fields.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let slen = u32::from_le_bytes(fields[pos..pos + 4].try_into().ok()?) as usize;
|
|
||||||
pos += 4;
|
|
||||||
if pos + slen > fields.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
return Some(String::from_utf8_lossy(&fields[pos..pos + slen]).into_owned());
|
|
||||||
}
|
|
||||||
pos = skip_variant_value(&sig, fields, pos)?;
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skip_variant_value(sig: &[u8], fields: &[u8], mut pos: usize) -> Option<usize> {
|
|
||||||
match sig.first()? {
|
|
||||||
b'o' | b's' => {
|
|
||||||
pos = align_up(pos, 4);
|
|
||||||
if pos + 4 > fields.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let slen = u32::from_le_bytes(fields[pos..pos + 4].try_into().ok()?) as usize;
|
|
||||||
Some(pos + 4 + slen + 1)
|
|
||||||
}
|
|
||||||
b'g' => {
|
|
||||||
if pos >= fields.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let glen = fields[pos] as usize;
|
|
||||||
Some(pos + 1 + glen + 1)
|
|
||||||
}
|
|
||||||
b'u' | b'i' => {
|
|
||||||
pos = align_up(pos, 4);
|
|
||||||
Some(pos + 4)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn align_up(pos: usize, n: usize) -> usize {
|
|
||||||
(pos + n - 1) & !(n - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a DBus body with signature "us": u32 id + string. Used for ActionInvoked.
|
|
||||||
fn parse_us_body(body: &[u8]) -> Option<(u32, String)> {
|
|
||||||
if body.len() < 8 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let id = u32::from_le_bytes(body[0..4].try_into().ok()?);
|
|
||||||
let slen = u32::from_le_bytes(body[4..8].try_into().ok()?) as usize;
|
|
||||||
if body.len() < 8 + slen {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some((id, String::from_utf8_lossy(&body[8..8 + slen]).into_owned()))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
src/ocr.rs
32
src/ocr.rs
|
|
@ -1,32 +0,0 @@
|
||||||
//! OCR via libtesseract (dynamic link, gated by the `ocr` cargo feature).
|
|
||||||
//!
|
|
||||||
//! `leptess` wraps `tesseract-sys` (bindgen-generated bindings to libtesseract +
|
|
||||||
//! libleptonica). At build time bindgen reads /usr/include/tesseract/*.h and emits
|
|
||||||
//! the Rust extern declarations; at runtime we dynamic-link against
|
|
||||||
//! `libtesseract.so` / `libleptonica.so` already present on the system.
|
|
||||||
|
|
||||||
use image::DynamicImage;
|
|
||||||
use leptess::LepTess;
|
|
||||||
|
|
||||||
use crate::error::{BlastError, Result};
|
|
||||||
use crate::shhh::encode_png;
|
|
||||||
|
|
||||||
/// Run OCR on `img` using the given language tag (e.g. "eng", "eng+deu").
|
|
||||||
/// Returns the recognized text with trailing whitespace trimmed.
|
|
||||||
///
|
|
||||||
/// leptess only exposes `set_image_from_mem` (decodes via leptonica), so we
|
|
||||||
/// round-trip through PNG. Re-encoding is cheap relative to OCR runtime.
|
|
||||||
pub fn recognize(img: &DynamicImage, language: &str) -> Result<String> {
|
|
||||||
let mut lt = LepTess::new(None, language)
|
|
||||||
.map_err(|e| BlastError::Other(format!("tesseract init: {e}")))?;
|
|
||||||
|
|
||||||
let png = encode_png(img).map_err(|e| BlastError::Image(e.to_string()))?;
|
|
||||||
lt.set_image_from_mem(&png)
|
|
||||||
.map_err(|e| BlastError::Other(format!("tesseract set_image: {e}")))?;
|
|
||||||
|
|
||||||
let text = lt
|
|
||||||
.get_utf8_text()
|
|
||||||
.map_err(|e| BlastError::Other(format!("tesseract recognize: {e}")))?;
|
|
||||||
|
|
||||||
Ok(text.trim().to_string())
|
|
||||||
}
|
|
||||||
32
src/paths.rs
32
src/paths.rs
|
|
@ -1,11 +1,9 @@
|
||||||
//! File path helpers: XDG screenshot dir, random names, editor tmp dir.
|
//! File path helpers: XDG screenshot dir, random names, editor tmp dir.
|
||||||
|
|
||||||
use std::{env, fs, path::PathBuf};
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
|
||||||
use crate::hyprland::Geometry;
|
|
||||||
|
|
||||||
/// Resolve the target directory for saving screenshots.
|
/// Resolve the target directory for saving screenshots.
|
||||||
///
|
///
|
||||||
/// Priority: $XDG_SCREENSHOTS_DIR -> $XDG_PICTURES_DIR -> $HOME.
|
/// Priority: $XDG_SCREENSHOTS_DIR -> $XDG_PICTURES_DIR -> $HOME.
|
||||||
|
|
@ -62,31 +60,3 @@ pub fn default_editor_path() -> PathBuf {
|
||||||
pub fn resolve_editor() -> String {
|
pub fn resolve_editor() -> String {
|
||||||
env::var("BLAST_EDITOR").unwrap_or_else(|_| "gimp".into())
|
env::var("BLAST_EDITOR").unwrap_or_else(|_| "gimp".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn last_region_path() -> Option<PathBuf> {
|
|
||||||
dirs::cache_dir().map(|d| d.join("blast").join("last-region"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_last_region(g: &Geometry) {
|
|
||||||
let Some(path) = last_region_path() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
let _ = fs::create_dir_all(parent);
|
|
||||||
}
|
|
||||||
let _ = fs::write(path, format!("{} {} {} {}\n", g.x, g.y, g.w, g.h));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_last_region() -> Option<Geometry> {
|
|
||||||
let path = last_region_path()?;
|
|
||||||
let s = fs::read_to_string(path).ok()?;
|
|
||||||
let mut parts = s.trim().split_whitespace();
|
|
||||||
let x = parts.next()?.parse().ok()?;
|
|
||||||
let y = parts.next()?.parse().ok()?;
|
|
||||||
let w = parts.next()?.parse().ok()?;
|
|
||||||
let h = parts.next()?.parse().ok()?;
|
|
||||||
if w <= 0 || h <= 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(Geometry { x, y, w, h })
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,16 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Area selection from a pre-built list of hint boxes (logical coords).
|
/// Area selection from a pre-built list of hint boxes (logical coords).
|
||||||
pub fn select_area_boxes(
|
pub fn select_area_boxes(boxes: Vec<HintBox>, hint_rgba: Option<u32>) -> Result<Geometry> {
|
||||||
boxes: Vec<HintBox>,
|
|
||||||
hint_rgba: Option<u32>,
|
|
||||||
outline: bool,
|
|
||||||
) -> Result<Geometry> {
|
|
||||||
if boxes.is_empty() {
|
if boxes.is_empty() {
|
||||||
select::select_region(vec![], false, hint_rgba, outline)
|
select::select_region(vec![], false, hint_rgba)
|
||||||
} else {
|
} else {
|
||||||
select::select_region(boxes, true, hint_rgba, outline)
|
select::select_region(boxes, true, hint_rgba)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_free_region(hint_rgba: Option<u32>, outline: bool) -> Result<Geometry> {
|
pub fn select_free_region(hint_rgba: Option<u32>) -> Result<Geometry> {
|
||||||
select::select_region(vec![], false, hint_rgba, outline)
|
select::select_region(vec![], false, hint_rgba)
|
||||||
}
|
}
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn select_area(
|
pub fn select_area(
|
||||||
|
|
@ -26,10 +22,9 @@ pub fn select_area(
|
||||||
border_size: i64,
|
border_size: i64,
|
||||||
bar_height: i64,
|
bar_height: i64,
|
||||||
hint_rgba: Option<u32>,
|
hint_rgba: Option<u32>,
|
||||||
outline: bool,
|
|
||||||
) -> Result<Geometry> {
|
) -> Result<Geometry> {
|
||||||
if windows.is_empty() {
|
if windows.is_empty() {
|
||||||
return select::select_region(vec![], false, hint_rgba, outline);
|
return select::select_region(vec![], false, hint_rgba);
|
||||||
}
|
}
|
||||||
let boxes = windows
|
let boxes = windows
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -43,5 +38,5 @@ pub fn select_area(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
select::select_region(boxes, true, hint_rgba, outline)
|
select::select_region(boxes, true, hint_rgba)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,6 @@ struct St {
|
||||||
hover: Option<usize>,
|
hover: Option<usize>,
|
||||||
restrict: bool,
|
restrict: bool,
|
||||||
c_hint: [u8; 4],
|
c_hint: [u8; 4],
|
||||||
outline: bool,
|
|
||||||
|
|
||||||
running: bool,
|
running: bool,
|
||||||
result: Option<Geometry>,
|
result: Option<Geometry>,
|
||||||
|
|
@ -346,7 +345,6 @@ impl St {
|
||||||
let buf_size = s.buf_size;
|
let buf_size = s.buf_size;
|
||||||
|
|
||||||
let c_hint = self.c_hint;
|
let c_hint = self.c_hint;
|
||||||
let outline = self.outline;
|
|
||||||
let box_data: Vec<(i32, i32, i32, i32, bool)> = self
|
let box_data: Vec<(i32, i32, i32, i32, bool)> = self
|
||||||
.boxes
|
.boxes
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -371,9 +369,7 @@ impl St {
|
||||||
if bx0 < bx1 && by0 < by1 {
|
if bx0 < bx1 && by0 < by1 {
|
||||||
if *is_hov {
|
if *is_hov {
|
||||||
fill(data, stride, bx0, by0, bx1, by1, C_SEL);
|
fill(data, stride, bx0, by0, bx1, by1, C_SEL);
|
||||||
if outline {
|
|
||||||
draw_border(data, stride, pw, ph, bx0, by0, bx1, by1, BDR * scale, C_BDR);
|
draw_border(data, stride, pw, ph, bx0, by0, bx1, by1, BDR * scale, C_BDR);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fill(data, stride, bx0, by0, bx1, by1, c_hint);
|
fill(data, stride, bx0, by0, bx1, by1, c_hint);
|
||||||
}
|
}
|
||||||
|
|
@ -387,11 +383,9 @@ impl St {
|
||||||
let y1 = ((gy + gh - ly) * scale).max(0).min(ph);
|
let y1 = ((gy + gh - ly) * scale).max(0).min(ph);
|
||||||
if x0 < x1 && y0 < y1 {
|
if x0 < x1 && y0 < y1 {
|
||||||
fill(data, stride, x0, y0, x1, y1, C_SEL);
|
fill(data, stride, x0, y0, x1, y1, C_SEL);
|
||||||
if outline {
|
|
||||||
draw_border(data, stride, pw, ph, x0, y0, x1, y1, BDR * scale, C_BDR);
|
draw_border(data, stride, pw, ph, x0, y0, x1, y1, BDR * scale, C_BDR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Attach buffer, damage the full surface, and commit.
|
// Attach buffer, damage the full surface, and commit.
|
||||||
s.wl.attach(Some(s.bufs[buf_slot].as_ref().unwrap()), 0, 0);
|
s.wl.attach(Some(s.bufs[buf_slot].as_ref().unwrap()), 0, 0);
|
||||||
|
|
@ -760,7 +754,6 @@ pub fn select_region(
|
||||||
boxes: Vec<HintBox>,
|
boxes: Vec<HintBox>,
|
||||||
restrict: bool,
|
restrict: bool,
|
||||||
hint_rgba: Option<u32>,
|
hint_rgba: Option<u32>,
|
||||||
outline: bool,
|
|
||||||
) -> Result<Geometry> {
|
) -> Result<Geometry> {
|
||||||
let conn =
|
let conn =
|
||||||
Connection::connect_to_env().map_err(|e| BlastError::Selection(format!("connect: {e}")))?;
|
Connection::connect_to_env().map_err(|e| BlastError::Selection(format!("connect: {e}")))?;
|
||||||
|
|
@ -793,9 +786,12 @@ pub fn select_region(
|
||||||
let pending: Vec<PendingOut> = out_globals
|
let pending: Vec<PendingOut> = out_globals
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(gname, ver)| PendingOut {
|
.map(|&(gname, ver)| PendingOut {
|
||||||
wl: globals
|
wl: globals.registry().bind::<wl_output::WlOutput, _, _>(
|
||||||
.registry()
|
gname,
|
||||||
.bind::<wl_output::WlOutput, _, _>(gname, ver.min(4), &qh, ()),
|
ver.min(4),
|
||||||
|
&qh,
|
||||||
|
(),
|
||||||
|
),
|
||||||
name: None,
|
name: None,
|
||||||
wl_name: gname,
|
wl_name: gname,
|
||||||
ox: 0,
|
ox: 0,
|
||||||
|
|
@ -838,7 +834,6 @@ pub fn select_region(
|
||||||
hover: None,
|
hover: None,
|
||||||
restrict,
|
restrict,
|
||||||
c_hint,
|
c_hint,
|
||||||
outline,
|
|
||||||
running: true,
|
running: true,
|
||||||
result: None,
|
result: None,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -237,8 +237,8 @@ fn create_shadow(
|
||||||
for (i, &a) in mask.iter().enumerate() {
|
for (i, &a) in mask.iter().enumerate() {
|
||||||
out_rgba[i * 4 + 3] = lut[a as usize];
|
out_rgba[i * 4 + 3] = lut[a as usize];
|
||||||
}
|
}
|
||||||
let buf: ImageBuffer<Rgba<u8>, Vec<u8>> =
|
let buf: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::from_raw(new_w, new_h, out_rgba)
|
||||||
ImageBuffer::from_raw(new_w, new_h, out_rgba).expect("shadow buffer construction");
|
.expect("shadow buffer construction");
|
||||||
DynamicImage::ImageRgba8(buf)
|
DynamicImage::ImageRgba8(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,7 @@ use wayland_protocols::wp::linux_dmabuf::zv1::client::{
|
||||||
zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||||
};
|
};
|
||||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
||||||
zwlr_foreign_toplevel_handle_v1::{
|
zwlr_foreign_toplevel_handle_v1::{self, State as WlrToplevelState, ZwlrForeignToplevelHandleV1},
|
||||||
self, State as WlrToplevelState, ZwlrForeignToplevelHandleV1,
|
|
||||||
},
|
|
||||||
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -696,7 +694,8 @@ impl Dispatch<HyprlandToplevelExportFrameV1, ()> for St {
|
||||||
}
|
}
|
||||||
hyprland_toplevel_export_frame_v1::Event::Flags { flags } => {
|
hyprland_toplevel_export_frame_v1::Event::Flags { flags } => {
|
||||||
if let wayland_client::WEnum::Value(f) = flags {
|
if let wayland_client::WEnum::Value(f) = flags {
|
||||||
state.y_invert = f.contains(hyprland_toplevel_export_frame_v1::Flags::YInvert);
|
state.y_invert = f
|
||||||
|
.contains(hyprland_toplevel_export_frame_v1::Flags::YInvert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hyprland_toplevel_export_frame_v1::Event::Ready { .. } => {
|
hyprland_toplevel_export_frame_v1::Event::Ready { .. } => {
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ use wayland_protocols::xdg::xdg_output::zv1::client::{
|
||||||
zxdg_output_v1::{self, ZxdgOutputV1},
|
zxdg_output_v1::{self, ZxdgOutputV1},
|
||||||
};
|
};
|
||||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
||||||
zwlr_foreign_toplevel_handle_v1::{
|
zwlr_foreign_toplevel_handle_v1::{self, State as WlrToplevelState, ZwlrForeignToplevelHandleV1},
|
||||||
self, State as WlrToplevelState, ZwlrForeignToplevelHandleV1,
|
|
||||||
},
|
|
||||||
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue