Switch to tag-pinned bread-ecosystem deps; bump version to v0.2.0
This commit is contained in:
parent
e16c1b461c
commit
7634af0b4f
8 changed files with 708 additions and 102 deletions
299
Cargo.lock
generated
299
Cargo.lock
generated
|
|
@ -22,9 +22,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
|
|
@ -35,6 +35,17 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bread-shared"
|
||||
version = "0.7.0"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread?tag=v0.7.0#22e34e2cf2202305d7960759dfccb54dc79f948b"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.8.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bread-theme"
|
||||
version = "0.2.3"
|
||||
|
|
@ -48,8 +59,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bread-utils"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bread-shared",
|
||||
"dirs",
|
||||
"gtk4",
|
||||
"gtk4-layer-shell",
|
||||
|
|
@ -78,13 +90,17 @@ dependencies = [
|
|||
"hex",
|
||||
"rusqlite",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "breadclipd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bread-utils",
|
||||
"breadclip-core",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -112,9 +128,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.65"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
||||
checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
|
|
@ -192,6 +208,16 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
|
|
@ -204,6 +230,12 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "field-offset"
|
||||
version = "0.3.6"
|
||||
|
|
@ -222,24 +254,24 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
|||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
|
||||
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
|
|
@ -248,15 +280,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -265,15 +297,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
|
|
@ -309,9 +341,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4"
|
||||
version = "0.11.2"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e"
|
||||
checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk-pixbuf",
|
||||
|
|
@ -325,9 +357,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4-sys"
|
||||
version = "0.11.2"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c"
|
||||
checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
|
@ -362,10 +394,21 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.22.6"
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.22.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
|
@ -380,9 +423,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.22.0"
|
||||
version = "0.22.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1"
|
||||
checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
|
@ -413,9 +456,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.22.7"
|
||||
version = "0.22.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
|
||||
checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
|
|
@ -446,9 +489,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.22.6"
|
||||
version = "0.22.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445"
|
||||
checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
|
|
@ -467,32 +510,30 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "graphene-rs"
|
||||
version = "0.22.0"
|
||||
version = "0.22.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1"
|
||||
checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"graphene-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-sys"
|
||||
version = "0.22.0"
|
||||
version = "0.22.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c"
|
||||
checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gsk4"
|
||||
version = "0.11.1"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62"
|
||||
checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"gdk4",
|
||||
|
|
@ -505,9 +546,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gsk4-sys"
|
||||
version = "0.11.1"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a"
|
||||
checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk4-sys",
|
||||
|
|
@ -521,9 +562,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4"
|
||||
version = "0.11.3"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2"
|
||||
checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"field-offset",
|
||||
|
|
@ -570,9 +611,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-macros"
|
||||
version = "0.11.0"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87"
|
||||
checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
|
|
@ -582,9 +623,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-sys"
|
||||
version = "0.11.3"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b"
|
||||
checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
|
@ -665,9 +706,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.17"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
||||
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -683,6 +724,12 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.33"
|
||||
|
|
@ -691,9 +738,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
|
|
@ -718,13 +765,12 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
|||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.22.6"
|
||||
version = "0.22.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438"
|
||||
checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c"
|
||||
dependencies = [
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"pango-sys",
|
||||
]
|
||||
|
||||
|
|
@ -758,7 +804,7 @@ version = "3.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
"toml_edit 0.25.13+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -779,13 +825,19 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
@ -813,6 +865,19 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
|
|
@ -862,6 +927,15 @@ dependencies = [
|
|||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
|
|
@ -902,9 +976,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.118"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -920,7 +994,7 @@ dependencies = [
|
|||
"cfg-expr",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"toml 1.1.3+spec-1.1.0",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
|
|
@ -930,6 +1004,19 @@ version = "0.13.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
|
|
@ -952,17 +1039,38 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.3+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"serde_spanned 1.1.1",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -976,14 +1084,28 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.12+spec-1.1.0"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_write",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.13+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime 1.1.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -992,14 +1114,20 @@ version = "1.1.2+spec-1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
"winnow 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
|
|
@ -1120,9 +1248,18 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.3"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -1135,18 +1272,18 @@ checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
|||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.52"
|
||||
version = "0.8.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
||||
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.52"
|
||||
version = "0.8.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
|
||||
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1155,6 +1292,6 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
|
|
|||
53
EVENTS.md
Normal file
53
EVENTS.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# breadclip — bread event integration
|
||||
|
||||
breadclip is a standalone clipboard manager: it works exactly the same with
|
||||
or without `breadd` running. When breadd *is* present, `breadclipd` publishes
|
||||
events into the shared bread automation fabric and listens for a small set
|
||||
of commands. See the parent `bread` repo's `Documentation.md` — specifically
|
||||
its "Namespaces" and "Integrating a bread\* app" sections — for the general
|
||||
convention this follows.
|
||||
|
||||
App id: **`clip`**. Transport: `bread-utils`'s `bread_client` module
|
||||
(feature `bread-client`) — `breadclipd` links it directly rather than
|
||||
shelling out, since it's a long-running process for the command-subscription
|
||||
half (though each `emit` call is still its own short-lived connection, since
|
||||
`capture_once()` — the actual clipboard-read code — runs as a fresh
|
||||
per-clipboard-change process invocation, not inside a persistent loop).
|
||||
|
||||
## Events published (`bread.clip.*`)
|
||||
|
||||
| Event | Data | When |
|
||||
|-------|------|------|
|
||||
| `bread.clip.copied` | `{ "kind": "url" \| "error" \| "code" \| "path" \| "plain", "len": <bytes or chars> }` | Every successful clipboard capture (text or image — images always get `kind: "image"`). `kind` is a heuristic classification (see `breadclipd/src/content_kind.rs`), not a guarantee — don't build a security decision on it. |
|
||||
| `bread.clip.clear.done` | `{}` | `bread.command.clip.clear` was received and history was successfully cleared. |
|
||||
| `bread.clip.clear.failed` | `{ "error": "<message>" }` | `bread.command.clip.clear` was received but clearing failed (e.g. DB error). |
|
||||
|
||||
Content is never included in the payload — only its detected kind and length. History (including the actual copied content) stays local to breadclip's own SQLite database; the event bus is for *notifications about* clipboard activity, not a channel for clipboard content itself.
|
||||
|
||||
## Commands honored (`bread.command.clip.*`)
|
||||
|
||||
| Verb | Effect |
|
||||
|------|--------|
|
||||
| `clear` | Deletes all clipboard history (text entries and stored image files). Emits `bread.clip.clear.done`/`.failed`. |
|
||||
|
||||
### Not implemented: `pin` / `select`
|
||||
|
||||
An earlier draft of this integration planned `pin`/`select` verbs, but
|
||||
breadclip's history schema has no "pinned" concept at all today — there's no
|
||||
column for it, and the GTK popup UI has no corresponding affordance. Adding
|
||||
real pin/select support is a product decision for breadclip itself (does it
|
||||
want pinning, and what should the UI look like?), not something to fabricate
|
||||
as a side effect of wiring up the event bus. If/when breadclip grows that
|
||||
feature, the corresponding `bread.command.clip.pin`/`.select` verbs (and
|
||||
matching `bread.clip.pinned`/`.selected` events) should be added at the same
|
||||
time, not stubbed out ahead of it.
|
||||
|
||||
## Fail-safe behavior
|
||||
|
||||
- If breadd isn't installed or isn't running, `emit` is a silent no-op
|
||||
(`BreadClient::emit` never blocks or errors the caller) and the
|
||||
command subscription simply never receives anything — breadclip's actual
|
||||
clipboard-history functionality is entirely unaffected either way.
|
||||
- If breadd restarts, the command subscription reconnects automatically
|
||||
(`BreadClient::subscribe`'s background thread has its own backoff loop);
|
||||
no restart of breadclipd is needed.
|
||||
|
|
@ -8,5 +8,9 @@ rusqlite = { version = "0.31", features = ["bundled"] }
|
|||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
dirs = "5"
|
||||
# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern
|
||||
bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils" }
|
||||
# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern.
|
||||
# (Path corrected: previously pointed at a since-cleaned-up "bread-ecosystem-fix-worktree" checkout that no longer exists on disk.)
|
||||
bread-utils = { path = "../../bread-ecosystem/bread-utils" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ impl HistoryDb {
|
|||
"SELECT id, timestamp, mime_type, content, image_path, content_hash
|
||||
FROM history ORDER BY timestamp DESC LIMIT ?1",
|
||||
)?;
|
||||
let rows = stmt.query_map([limit as i64], |row| {
|
||||
let rows = stmt
|
||||
.query_map([limit as i64], |row| {
|
||||
Ok(ClipEntry {
|
||||
id: row.get(0)?,
|
||||
timestamp: row.get(1)?,
|
||||
|
|
@ -92,6 +93,27 @@ impl HistoryDb {
|
|||
rows
|
||||
}
|
||||
|
||||
/// Deletes every history entry and every stored image file. Used by the
|
||||
/// `bread.command.clip.clear` handler (see breadclipd's bread-client
|
||||
/// subscription) as well as anything else that wants a hard reset of
|
||||
/// clipboard history.
|
||||
pub fn clear_all(&self) -> SqlResult<()> {
|
||||
let image_paths: Vec<String> = {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT image_path FROM history WHERE image_path IS NOT NULL")?;
|
||||
let paths = stmt
|
||||
.query_map([], |row| row.get(0))?
|
||||
.collect::<SqlResult<Vec<_>>>()?;
|
||||
paths
|
||||
};
|
||||
for path in image_paths {
|
||||
let _ = std::fs::remove_file(path);
|
||||
}
|
||||
self.conn.execute("DELETE FROM history", [])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_entry(&self, id: i64) -> SqlResult<()> {
|
||||
// Clean up image file if present
|
||||
let image_path: Option<String> = self
|
||||
|
|
@ -106,7 +128,8 @@ impl HistoryDb {
|
|||
if let Some(p) = image_path {
|
||||
let _ = std::fs::remove_file(p);
|
||||
}
|
||||
self.conn.execute("DELETE FROM history WHERE id = ?1", [id])?;
|
||||
self.conn
|
||||
.execute("DELETE FROM history WHERE id = ?1", [id])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +154,8 @@ impl HistoryDb {
|
|||
ORDER BY timestamp DESC LIMIT ?1
|
||||
)",
|
||||
)?;
|
||||
let paths = stmt.query_map([max_images as i64], |row| row.get(0))?
|
||||
let paths = stmt
|
||||
.query_map([max_images as i64], |row| row.get(0))?
|
||||
.collect::<SqlResult<Vec<_>>>()?;
|
||||
paths
|
||||
};
|
||||
|
|
@ -184,3 +208,53 @@ fn unix_now() -> i64 {
|
|||
.unwrap_or_default()
|
||||
.as_secs() as i64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// `HistoryDb::open` resolves its path via `data_dir()`, which follows
|
||||
// `$XDG_DATA_HOME` — redirecting it to a fresh temp dir per test keeps
|
||||
// this isolated from a real `~/.local/share/breadclip` and from other
|
||||
// tests. Safe without a lock: this is currently the only test in the
|
||||
// crate that touches XDG_DATA_HOME.
|
||||
fn open_test_db() -> (tempfile::TempDir, HistoryDb) {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
std::env::set_var("XDG_DATA_HOME", dir.path());
|
||||
let db = HistoryDb::open().expect("open history db");
|
||||
(dir, db)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_all_removes_every_entry_and_image_file() {
|
||||
let (_dir, db) = open_test_db();
|
||||
db.insert_text("first").unwrap();
|
||||
db.insert_text("second").unwrap();
|
||||
db.insert_image(b"not really a png, just bytes for the test")
|
||||
.unwrap();
|
||||
|
||||
let before = db.list_entries(10).unwrap();
|
||||
assert_eq!(before.len(), 3);
|
||||
let image_path = before
|
||||
.iter()
|
||||
.find_map(|e| e.image_path.clone())
|
||||
.expect("one entry should be the image");
|
||||
assert!(Path::new(&image_path).exists());
|
||||
|
||||
db.clear_all().unwrap();
|
||||
|
||||
let after = db.list_entries(10).unwrap();
|
||||
assert!(after.is_empty(), "expected no entries after clear_all");
|
||||
assert!(
|
||||
!Path::new(&image_path).exists(),
|
||||
"image file should be removed by clear_all"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_all_on_empty_history_is_a_harmless_no_op() {
|
||||
let (_dir, db) = open_test_db();
|
||||
db.clear_all().unwrap();
|
||||
assert!(db.list_entries(10).unwrap().is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ path = "src/main.rs"
|
|||
[dependencies]
|
||||
breadclip-core = { path = "../breadclip-core" }
|
||||
bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.10", features = ["gtk"] }
|
||||
# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern
|
||||
bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils", features = ["gtk"] }
|
||||
# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern.
|
||||
# (Path corrected: previously pointed at a since-cleaned-up "bread-ecosystem-fix-worktree" checkout that no longer exists on disk.)
|
||||
bread-utils = { path = "../../bread-ecosystem/bread-utils", features = ["gtk"] }
|
||||
gtk4 = { version = "0.11", features = ["v4_12"] }
|
||||
gtk4-layer-shell = "0.8"
|
||||
serde_json = "1"
|
||||
|
|
|
|||
|
|
@ -9,3 +9,11 @@ path = "src/main.rs"
|
|||
|
||||
[dependencies]
|
||||
breadclip-core = { path = "../breadclip-core" }
|
||||
# TODO(owner): switch to a tag-pinned git dependency once bread-shared is
|
||||
# released/tagged for external consumption, matching the bread-theme pattern —
|
||||
# see the same stopgap already in place for bread-utils's own dependents.
|
||||
bread-utils = { path = "../../bread-ecosystem/bread-utils", features = ["bread-client"] }
|
||||
serde_json = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
|
|
|||
186
breadclipd/src/content_kind.rs
Normal file
186
breadclipd/src/content_kind.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
//! Heuristic classification of clipboard text, used to tag the
|
||||
//! `bread.clip.copied` event's `kind` field (see `bread-shared`'s namespace
|
||||
//! docs). Deliberately simple pattern matching, not a real parser or ML
|
||||
//! classifier — good enough to auto-tag "you just copied a stack trace" or
|
||||
//! "you just copied a URL" for a Lua automation module to react to, not a
|
||||
//! source of truth anyone should build a security decision on.
|
||||
|
||||
/// Checked in this order because a false-not-`error` or false-not-`url` is
|
||||
/// cheap to live with, while classifying a URL as generic `code` because it
|
||||
/// contains a `/` would be a worse user-facing miss.
|
||||
pub fn detect(text: &str) -> &'static str {
|
||||
let trimmed = text.trim();
|
||||
if trimmed.is_empty() {
|
||||
return "plain";
|
||||
}
|
||||
if looks_like_error(trimmed) {
|
||||
"error"
|
||||
} else if looks_like_url(trimmed) {
|
||||
"url"
|
||||
} else if looks_like_path(trimmed) {
|
||||
"path"
|
||||
} else if looks_like_code(trimmed) {
|
||||
"code"
|
||||
} else {
|
||||
"plain"
|
||||
}
|
||||
}
|
||||
|
||||
fn looks_like_url(text: &str) -> bool {
|
||||
// Single "word" (no internal whitespace) is required — a sentence that
|
||||
// merely contains a URL should classify as plain/error/code on its own
|
||||
// merits, not get tagged "url" just because a link appears in it.
|
||||
if text.split_whitespace().count() != 1 {
|
||||
return false;
|
||||
}
|
||||
let lower = text.to_ascii_lowercase();
|
||||
lower.starts_with("http://")
|
||||
|| lower.starts_with("https://")
|
||||
|| lower.starts_with("ftp://")
|
||||
|| lower.starts_with("file://")
|
||||
|| lower.starts_with("mailto:")
|
||||
}
|
||||
|
||||
fn looks_like_error(text: &str) -> bool {
|
||||
const MARKERS: [&str; 8] = [
|
||||
"traceback (most recent call last)",
|
||||
"panicked at",
|
||||
"exception in thread",
|
||||
"unhandled exception",
|
||||
"stack trace:",
|
||||
"at java.",
|
||||
"uncaught exception",
|
||||
"fatal error:",
|
||||
];
|
||||
let lower = text.to_ascii_lowercase();
|
||||
if MARKERS.iter().any(|m| lower.contains(m)) {
|
||||
return true;
|
||||
}
|
||||
// A line starting "Error:"/"error:" combined with more than one line is
|
||||
// a common shape for compiler/runtime error output; a single bare line
|
||||
// like "error: 42" out of context is more likely just plain text.
|
||||
let first_line = lower.lines().next().unwrap_or("");
|
||||
text.lines().count() > 1
|
||||
&& (first_line.starts_with("error:") || first_line.starts_with("error["))
|
||||
}
|
||||
|
||||
fn looks_like_path(text: &str) -> bool {
|
||||
if text.split_whitespace().count() != 1 {
|
||||
return false;
|
||||
}
|
||||
let has_extension_or_depth = text
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.map(|s| s.contains('.'))
|
||||
.unwrap_or(false)
|
||||
|| text.matches('/').count() >= 2;
|
||||
(text.starts_with('/') || text.starts_with("~/") || text.starts_with("./"))
|
||||
&& has_extension_or_depth
|
||||
}
|
||||
|
||||
fn looks_like_code(text: &str) -> bool {
|
||||
const CODE_TOKENS: [&str; 14] = [
|
||||
"fn ",
|
||||
"function ",
|
||||
"def ",
|
||||
"class ",
|
||||
"const ",
|
||||
"let ",
|
||||
"import ",
|
||||
"#include",
|
||||
"public ",
|
||||
"private ",
|
||||
"=> {",
|
||||
"};",
|
||||
"</",
|
||||
"><",
|
||||
];
|
||||
let lines: Vec<&str> = text.lines().collect();
|
||||
let token_hits = CODE_TOKENS.iter().filter(|t| text.contains(*t)).count();
|
||||
let brace_or_semicolon_lines = lines
|
||||
.iter()
|
||||
.filter(|l| {
|
||||
let t = l.trim_end();
|
||||
t.ends_with(';') || t.ends_with('{') || t.ends_with('}')
|
||||
})
|
||||
.count();
|
||||
|
||||
// Multi-line with a meaningful fraction of "code-shaped" lines, or an
|
||||
// unambiguous single-line token (import/#include/fn signature), counts
|
||||
// as code. A single short line of prose won't hit either bar.
|
||||
token_hits >= 2
|
||||
|| (lines.len() > 1 && brace_or_semicolon_lines * 2 >= lines.len())
|
||||
|| (lines.len() == 1 && token_hits >= 1 && text.len() < 200)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn detects_url() {
|
||||
assert_eq!(detect("https://example.com/path?query=1"), "url");
|
||||
assert_eq!(detect("http://localhost:8080"), "url");
|
||||
assert_eq!(detect(" https://example.com "), "url");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_tag_sentence_containing_a_url_as_url() {
|
||||
assert_eq!(detect("check out https://example.com later"), "plain");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_python_traceback() {
|
||||
let text = "Traceback (most recent call last):\n File \"x.py\", line 1\nValueError: bad";
|
||||
assert_eq!(detect(text), "error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_rust_panic() {
|
||||
assert_eq!(
|
||||
detect("thread 'main' panicked at src/main.rs:10:5:\nindex out of bounds"),
|
||||
"error"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_multiline_error_prefixed_output() {
|
||||
assert_eq!(detect("error: expected `;`\n --> src/main.rs:2:1"), "error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_error_word_without_context_is_plain() {
|
||||
assert_eq!(detect("error: something"), "plain");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_absolute_path() {
|
||||
assert_eq!(detect("/home/user/.config/bread/init.lua"), "path");
|
||||
assert_eq!(detect("~/Projects/bread/README.md"), "path");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_multiline_code() {
|
||||
let text = "fn main() {\n let x = 1;\n println!(\"{}\", x);\n}";
|
||||
assert_eq!(detect(text), "code");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_single_line_import() {
|
||||
assert_eq!(detect("import numpy as np"), "code");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_prose_is_plain() {
|
||||
assert_eq!(
|
||||
detect("just a normal sentence someone copied from an email"),
|
||||
"plain"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_or_whitespace_is_plain() {
|
||||
assert_eq!(detect(""), "plain");
|
||||
assert_eq!(detect(" \n "), "plain");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
mod content_kind;
|
||||
|
||||
use bread_utils::bread_client::BreadClient;
|
||||
use breadclip_core::HistoryDb;
|
||||
use std::{env, fs, path::PathBuf, process::Command, thread, time::Duration};
|
||||
|
||||
/// This app's id in bread's sibling-app namespace registry
|
||||
/// (`bread_shared::apps::KNOWN_APPS`) — events are published as
|
||||
/// `bread.clip.*`, commands are received on `bread.command.clip.*`.
|
||||
const APP_ID: &str = "clip";
|
||||
|
||||
/// Argument used to invoke this same binary as the one-shot handler for
|
||||
/// `wl-paste --watch`. Kept as a plain positional arg (no clap dependency
|
||||
/// needed for a single internal flag).
|
||||
|
|
@ -99,19 +107,69 @@ fn capture_once() {
|
|||
|
||||
if has_image && !has_text {
|
||||
if let Some(bytes) = get_clipboard_image() {
|
||||
if let Err(e) = db.insert_image(&bytes) {
|
||||
eprintln!("breadclipd: insert image: {e}");
|
||||
match db.insert_image(&bytes) {
|
||||
Ok(()) => emit_copied("image", bytes.len()),
|
||||
Err(e) => eprintln!("breadclipd: insert image: {e}"),
|
||||
}
|
||||
}
|
||||
} else if has_text {
|
||||
if let Some(text) = get_clipboard_text() {
|
||||
if let Err(e) = db.insert_text(&text) {
|
||||
eprintln!("breadclipd: insert text: {e}");
|
||||
match db.insert_text(&text) {
|
||||
Ok(()) => emit_copied(content_kind::detect(&text), text.len()),
|
||||
Err(e) => eprintln!("breadclipd: insert text: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Publishes `bread.clip.copied` into the bread event fabric. Fire-and-forget
|
||||
/// and non-fatal by design (`BreadClient::emit` never blocks or errors this
|
||||
/// caller) — breadd being absent or not installed must never affect
|
||||
/// breadclip's own clipboard-history functionality, only mean this one
|
||||
/// notification doesn't go anywhere.
|
||||
fn emit_copied(kind: &str, len: usize) {
|
||||
BreadClient::connect(APP_ID).emit(
|
||||
"bread.clip.copied",
|
||||
serde_json::json!({ "kind": kind, "len": len }),
|
||||
);
|
||||
}
|
||||
|
||||
/// Reacts to `bread.command.clip.*` verbs. Only `clear` maps to real,
|
||||
/// existing breadclip functionality today — `pin`/`select` would need a new
|
||||
/// "pinned" concept that doesn't exist anywhere in the history DB schema,
|
||||
/// which is a real product decision for breadclip itself (does it want
|
||||
/// pinning at all, and what would the GTK UI for it look like?), not
|
||||
/// something to fabricate as a side effect of wiring up the event bus. See
|
||||
/// `breadclip/EVENTS.md` for the honest current status.
|
||||
///
|
||||
/// Emits `bread.clip.<verb>.done`/`.failed` per the confirmation convention
|
||||
/// in bread's Documentation.md — a module that started this command via
|
||||
/// `bread.wait`/`bread.wait_any` can await the real outcome instead of
|
||||
/// assuming success the moment it publishes the command.
|
||||
fn handle_command(event_name: &str) {
|
||||
let Some(verb) = event_name.strip_prefix("bread.command.clip.") else {
|
||||
return;
|
||||
};
|
||||
match verb {
|
||||
"clear" => match HistoryDb::open().and_then(|db| db.clear_all()) {
|
||||
Ok(()) => {
|
||||
eprintln!("breadclipd: cleared history via bread.command.clip.clear");
|
||||
BreadClient::connect(APP_ID).emit("bread.clip.clear.done", serde_json::json!({}));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("breadclipd: bread.command.clip.clear failed: {e}");
|
||||
BreadClient::connect(APP_ID).emit(
|
||||
"bread.clip.clear.failed",
|
||||
serde_json::json!({ "error": e.to_string() }),
|
||||
);
|
||||
}
|
||||
},
|
||||
other => {
|
||||
eprintln!("breadclipd: ignoring unrecognized command verb '{other}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.get(1).map(String::as_str) == Some(CAPTURE_FLAG) {
|
||||
|
|
@ -153,6 +211,17 @@ fn main() {
|
|||
|
||||
eprintln!("breadclipd: started (pid {})", std::process::id());
|
||||
|
||||
// Long-lived, so this uses BreadClient::subscribe (a persistent
|
||||
// background thread with its own reconnect/backoff loop) rather than a
|
||||
// one-shot connection — the same client type capture_once() uses for
|
||||
// `emit`, just the other half of it. breadd being absent or restarting
|
||||
// is transparent here too: the subscription just quietly stops
|
||||
// delivering commands until it reconnects.
|
||||
let command_client = BreadClient::connect(APP_ID);
|
||||
let _commands = command_client.subscribe("bread.command.clip.**", |event| {
|
||||
handle_command(&event.event);
|
||||
});
|
||||
|
||||
// `wl-paste --watch <cmd>` runs <cmd> once per clipboard-change event
|
||||
// instead of polling — zero idle cost between changes, and no more
|
||||
// forking wl-paste 4-6 times a second. Each event re-invokes this same
|
||||
|
|
@ -175,3 +244,77 @@ fn main() {
|
|||
thread::sleep(Duration::from_secs(2));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Isolates HistoryDb::open() to a fresh temp dir per test. `cargo test`
|
||||
// runs tests in parallel threads within one process by default, and
|
||||
// `XDG_DATA_HOME` is process-global — three tests in this module all
|
||||
// set it, so without this lock they race each other (confirmed: an
|
||||
// earlier version of this file had that exact bug, caught by a flaky
|
||||
// `len() == 2` instead of `1` failure).
|
||||
fn env_test_lock() -> &'static std::sync::Mutex<()> {
|
||||
static LOCK: std::sync::OnceLock<std::sync::Mutex<()>> = std::sync::OnceLock::new();
|
||||
LOCK.get_or_init(|| std::sync::Mutex::new(()))
|
||||
}
|
||||
|
||||
fn with_isolated_history<F: FnOnce()>(f: F) {
|
||||
let _guard = env_test_lock().lock().unwrap_or_else(|p| p.into_inner());
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
std::env::set_var("XDG_DATA_HOME", dir.path());
|
||||
f();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_command_clear_empties_history_even_with_no_daemon_reachable() {
|
||||
// The point of this test: handle_command's actual effect (clearing
|
||||
// the DB) must not depend on breadd being reachable — `emit` inside
|
||||
// it is fire-and-forget and must never gate the real work. No test
|
||||
// daemon is running here, so if clearing the DB were accidentally
|
||||
// made contingent on the emit succeeding, this test would fail.
|
||||
with_isolated_history(|| {
|
||||
let db = HistoryDb::open().expect("open history db");
|
||||
db.insert_text("something to clear").unwrap();
|
||||
assert_eq!(db.list_entries(10).unwrap().len(), 1);
|
||||
|
||||
handle_command("bread.command.clip.clear");
|
||||
|
||||
let db = HistoryDb::open().expect("reopen history db");
|
||||
assert!(db.list_entries(10).unwrap().is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_command_ignores_unrecognized_verb() {
|
||||
with_isolated_history(|| {
|
||||
let db = HistoryDb::open().expect("open history db");
|
||||
db.insert_text("should survive").unwrap();
|
||||
|
||||
handle_command("bread.command.clip.pin");
|
||||
|
||||
let db = HistoryDb::open().expect("reopen history db");
|
||||
assert_eq!(
|
||||
db.list_entries(10).unwrap().len(),
|
||||
1,
|
||||
"an unrecognized verb must not touch history"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_command_ignores_events_outside_its_own_command_namespace() {
|
||||
with_isolated_history(|| {
|
||||
let db = HistoryDb::open().expect("open history db");
|
||||
db.insert_text("should survive").unwrap();
|
||||
|
||||
// Not a `bread.command.clip.*` event at all — must be a no-op.
|
||||
handle_command("bread.command.pad.clear");
|
||||
handle_command("bread.clip.copied");
|
||||
|
||||
let db = HistoryDb::open().expect("reopen history db");
|
||||
assert_eq!(db.list_entries(10).unwrap().len(), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue