breadclip: add --screenshot CLI mode for automated capture
Same pattern as breadbar/breadbox: render the clipboard-history panel,
capture it via bread-screenshots, then exit. One view ("history"), full
known-size canvas capture since the panel isn't its own layer surface.
active_window()/focused_monitor() (used to anchor the panel next to the
real focused window) both resolve to None inside bread-capture's isolated
environment (HYPRLAND_INSTANCE_SIGNATURE is deliberately unset there), so
the panel falls back to its already-existing centered layout — exactly
the deterministic behavior a screenshot needs, with no screenshot-mode-
specific positioning logic required.
Also fixes the same singleton footgun as breadbox: toggle_or_kill()
kills whatever's holding breadclip's single-instance lock, which is
typically the real running instance. A screenshot run now skips it
entirely instead of fighting over (and killing) the operator's real
clipboard panel.
This commit is contained in:
parent
b95e4c9466
commit
b125facc67
4 changed files with 348 additions and 19 deletions
205
Cargo.lock
generated
205
Cargo.lock
generated
|
|
@ -14,6 +14,62 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
|
|
@ -35,6 +91,16 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bread-screenshots"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=dev#1a3475bd2358202f60e29c9bd27d06b1428b1a27"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bread-utils 0.3.1 (git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=dev)",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-shared"
|
name = "bread-shared"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
@ -80,13 +146,26 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bread-utils"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?branch=dev#1a3475bd2358202f60e29c9bd27d06b1428b1a27"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "breadclip"
|
name = "breadclip"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bread-screenshots",
|
||||||
"bread-theme",
|
"bread-theme",
|
||||||
"bread-utils 0.3.0",
|
"bread-utils 0.3.0",
|
||||||
"breadclip-core",
|
"breadclip-core",
|
||||||
|
"clap",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"gtk4-layer-shell",
|
"gtk4-layer-shell",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
@ -108,7 +187,7 @@ dependencies = [
|
||||||
name = "breadclipd"
|
name = "breadclipd"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bread-utils 0.3.1",
|
"bread-utils 0.3.1 (git+https://github.com/Breadway/bread-ecosystem?tag=v0.3.1)",
|
||||||
"breadclip-core",
|
"breadclip-core",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
@ -163,6 +242,52 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
|
@ -303,7 +428,7 @@ checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -495,7 +620,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -629,7 +754,7 @@ dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -697,6 +822,12 @@ dependencies = [
|
||||||
"hashbrown 0.17.1",
|
"hashbrown 0.17.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|
@ -768,6 +899,12 @@ version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "option-ext"
|
name = "option-ext"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -922,7 +1059,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -985,6 +1122,12 @@ version = "1.15.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.119"
|
version = "2.0.119"
|
||||||
|
|
@ -996,6 +1139,17 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "7.0.8"
|
version = "7.0.8"
|
||||||
|
|
@ -1045,7 +1199,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1140,6 +1294,37 @@ version = "1.1.2+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.20.1"
|
version = "1.20.1"
|
||||||
|
|
@ -1152,6 +1337,12 @@ version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
@ -1298,7 +1489,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ breadclip-core = { path = "../breadclip-core" }
|
||||||
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0", features = ["gtk"] }
|
bread-theme = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0", features = ["gtk"] }
|
||||||
# (Path corrected: previously pointed at a since-cleaned-up "bread-ecosystem-fix-worktree" checkout that no longer exists on disk.)
|
# (Path corrected: previously pointed at a since-cleaned-up "bread-ecosystem-fix-worktree" checkout that no longer exists on disk.)
|
||||||
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0", features = ["gtk"] }
|
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0", features = ["gtk"] }
|
||||||
|
# Capture primitives for `--screenshot` mode — see src/screenshot.rs. Not
|
||||||
|
# tag-pinned like the deps above since it doesn't have a tagged release yet.
|
||||||
|
bread-screenshots = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", branch = "dev" }
|
||||||
gtk4 = { version = "0.11", features = ["v4_12"] }
|
gtk4 = { version = "0.11", features = ["v4_12"] }
|
||||||
gtk4-layer-shell = "0.8"
|
gtk4-layer-shell = "0.8"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
anyhow = "1"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod css;
|
mod css;
|
||||||
|
mod screenshot;
|
||||||
|
|
||||||
use breadclip_core::{ClipEntry, HistoryDb};
|
use breadclip_core::{ClipEntry, HistoryDb};
|
||||||
use bread_theme::{load_palette};
|
use bread_theme::{load_palette};
|
||||||
|
|
@ -184,10 +185,17 @@ fn do_copy(entry: &ClipEntry) {
|
||||||
|
|
||||||
// ---- UI ---------------------------------------------------------------------
|
// ---- UI ---------------------------------------------------------------------
|
||||||
|
|
||||||
fn run_ui(entries: Vec<ClipEntry>) {
|
fn run_ui(entries: Vec<ClipEntry>, screenshot_req: Option<screenshot::ScreenshotRequest>) {
|
||||||
let app = Application::builder()
|
let mut builder = Application::builder().application_id("com.breadway.breadclip");
|
||||||
.application_id("com.breadway.breadclip")
|
if screenshot_req.is_some() {
|
||||||
.build();
|
// GApplication is single-instance by default; this machine typically
|
||||||
|
// already has a real breadclip instance, so without this a
|
||||||
|
// screenshot run would just message the *existing* instance instead
|
||||||
|
// of starting a fresh one that ever sees `screenshot_req`.
|
||||||
|
builder = builder.flags(gtk4::gio::ApplicationFlags::NON_UNIQUE);
|
||||||
|
}
|
||||||
|
let app = builder.build();
|
||||||
|
let is_screenshot_run = screenshot_req.is_some();
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
bread_theme::gtk::apply_shared();
|
bread_theme::gtk::apply_shared();
|
||||||
|
|
@ -408,25 +416,50 @@ fn run_ui(entries: Vec<ClipEntry>) {
|
||||||
bread_utils::gtk_popup::close_on_outside_click(&window, &panel, move || close_outside());
|
bread_utils::gtk_popup::close_on_outside_click(&window, &panel, move || close_outside());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(req) = screenshot_req.clone() {
|
||||||
|
screenshot::dispatch(&window, req);
|
||||||
|
}
|
||||||
|
|
||||||
window.present();
|
window.present();
|
||||||
search.grab_focus();
|
search.grab_focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.run();
|
if is_screenshot_run {
|
||||||
|
// GLib's own option parser otherwise rejects --screenshot/--output
|
||||||
|
// before clap ever sees them (`Cli::parse()` already ran in `main`,
|
||||||
|
// over the real argv).
|
||||||
|
app.run_with_args(&[] as &[&str]);
|
||||||
|
} else {
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Main -------------------------------------------------------------------
|
// ---- Main -------------------------------------------------------------------
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
use clap::Parser;
|
||||||
|
let cli = screenshot::Cli::parse();
|
||||||
|
let screenshot_req = cli.screenshot_request();
|
||||||
|
|
||||||
|
// `toggle_or_kill` kills whatever's holding the single-instance lock —
|
||||||
|
// a real, already-running breadclip included. A screenshot run must
|
||||||
|
// never touch it: it's a separate, disposable instance by design (same
|
||||||
|
// reasoning as breadbar's `allow_multiple_instances`), not a toggle of
|
||||||
|
// the operator's real clipboard panel.
|
||||||
|
//
|
||||||
// Kept alive for the rest of `main` — dropping it releases the
|
// Kept alive for the rest of `main` — dropping it releases the
|
||||||
// single-instance lock and removes the pid file, which happens
|
// single-instance lock and removes the pid file, which happens
|
||||||
// naturally once `run_ui` returns (after the window closes).
|
// naturally once `run_ui` returns (after the window closes).
|
||||||
let _singleton_guard = match bread_utils::singleton::toggle_or_kill("breadclip") {
|
let _singleton_guard = if screenshot_req.is_some() {
|
||||||
Ok(bread_utils::singleton::Toggle::Started(guard)) => Some(guard),
|
None
|
||||||
Ok(bread_utils::singleton::Toggle::KilledExisting) => return,
|
} else {
|
||||||
Err(e) => {
|
match bread_utils::singleton::toggle_or_kill("breadclip") {
|
||||||
eprintln!("breadclip: single-instance lock unavailable ({e}); continuing without it");
|
Ok(bread_utils::singleton::Toggle::Started(guard)) => Some(guard),
|
||||||
None
|
Ok(bread_utils::singleton::Toggle::KilledExisting) => return,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("breadclip: single-instance lock unavailable ({e}); continuing without it");
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -434,5 +467,5 @@ fn main() {
|
||||||
.and_then(|db| db.list_entries(MAX_ENTRIES))
|
.and_then(|db| db.list_entries(MAX_ENTRIES))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
run_ui(entries);
|
run_ui(entries, screenshot_req);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
100
breadclip/src/screenshot.rs
Normal file
100
breadclip/src/screenshot.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
//! `--screenshot` CLI mode: render breadclip's clipboard-history panel,
|
||||||
|
//! capture it via `bread-screenshots`, then exit — driven by
|
||||||
|
//! `bread-ecosystem`'s `bread-capture` orchestrator, or run standalone for
|
||||||
|
//! one-off captures.
|
||||||
|
//!
|
||||||
|
//! breadclip has one view worth capturing: the history panel itself. Like
|
||||||
|
//! breadbox, it's a `halign`/`valign`-positioned panel over a full-screen
|
||||||
|
//! transparent overlay rather than its own layer surface, so a full
|
||||||
|
//! known-size canvas capture is the simplest reliable option. In screenshot
|
||||||
|
//! mode there's never a focused window to anchor the panel next to (see
|
||||||
|
//! `bread_utils::hypr::active_window` in `main.rs`, which the isolated
|
||||||
|
//! capture environment always reports `None` for — deliberately, see
|
||||||
|
//! `bread-capture`'s isolation module), so the panel always falls back to
|
||||||
|
//! centered, which is exactly what we want for a consistent screenshot.
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use gtk4::prelude::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// Extra settle time after `map` for the first frame to actually paint
|
||||||
|
/// before grim runs — `map` fires once the surface exists, not once
|
||||||
|
/// anything has been drawn into it.
|
||||||
|
const SETTLE_DELAY: Duration = Duration::from_millis(300);
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "breadclip")]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Render the named view, capture it, then exit instead of running
|
||||||
|
/// normally. Known views: "history".
|
||||||
|
#[arg(long)]
|
||||||
|
pub screenshot: Option<String>,
|
||||||
|
|
||||||
|
/// PNG path to write the capture to. Required together with --screenshot.
|
||||||
|
#[arg(long)]
|
||||||
|
pub output: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Capture canvas width — matches the isolated compositor's output width
|
||||||
|
/// (`bread-capture --isolate-width`).
|
||||||
|
#[arg(long, default_value_t = 1920)]
|
||||||
|
pub width: u32,
|
||||||
|
|
||||||
|
/// Capture canvas height — see `width`.
|
||||||
|
#[arg(long, default_value_t = 1080)]
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ScreenshotRequest {
|
||||||
|
pub view: String,
|
||||||
|
pub output: PathBuf,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cli {
|
||||||
|
/// `None` for a normal run. Exits the process with an error if
|
||||||
|
/// `--screenshot` was given without `--output`, before any GTK setup
|
||||||
|
/// happens.
|
||||||
|
pub fn screenshot_request(&self) -> Option<ScreenshotRequest> {
|
||||||
|
let view = self.screenshot.clone()?;
|
||||||
|
let Some(output) = self.output.clone() else {
|
||||||
|
eprintln!("breadclip: --screenshot requires --output");
|
||||||
|
std::process::exit(1);
|
||||||
|
};
|
||||||
|
Some(ScreenshotRequest { view, output, width: self.width, height: self.height })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wire up the given view's screenshot sequence against an already-built,
|
||||||
|
/// not-yet-presented window. Every path here ends by exiting the process —
|
||||||
|
/// it never returns control to the normal history-panel UI.
|
||||||
|
pub fn dispatch(window: >k4::ApplicationWindow, req: ScreenshotRequest) {
|
||||||
|
match req.view.as_str() {
|
||||||
|
"history" => {
|
||||||
|
let output = req.output;
|
||||||
|
let (width, height) = (req.width as i32, req.height as i32);
|
||||||
|
window.connect_map(move |_| {
|
||||||
|
let output = output.clone();
|
||||||
|
gtk4::glib::timeout_add_local_once(SETTLE_DELAY, move || {
|
||||||
|
finish(bread_screenshots::capture_region(0, 0, width, height, &output));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
eprintln!("breadclip: unknown screenshot view '{other}' (known: history)");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(result: anyhow::Result<()>) {
|
||||||
|
match result {
|
||||||
|
Ok(()) => std::process::exit(0),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("breadclip: screenshot capture failed: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue