feat: add native clipboard module with arboard backend (#228)
Cross-platform clipboard access (text read/write, image read) via the arboard Rust crate. No external tools (pbcopy, xclip, etc.) required. Ported from Oh My Pi's clipboard module with adaptations for GSD's architecture (direct AsyncTask instead of task::blocking wrapper). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d64575cd3c
commit
daca368ba2
10 changed files with 821 additions and 3 deletions
566
native/Cargo.lock
generated
566
native/Cargo.lock
generated
|
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
|
|
@ -11,6 +17,56 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"image",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.60.2",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
|
|
@ -28,12 +84,33 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
|
|
@ -43,6 +120,15 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
|
|
@ -68,6 +154,12 @@ version = "0.8.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.9"
|
||||
|
|
@ -92,6 +184,16 @@ dependencies = [
|
|||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
|
|
@ -116,6 +218,88 @@ dependencies = [
|
|||
"encoding_rs",
|
||||
]
|
||||
|
||||
[[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 = "error-code"
|
||||
version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fax"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
|
||||
dependencies = [
|
||||
"fax_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fax_derive"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.18"
|
||||
|
|
@ -170,14 +354,17 @@ dependencies = [
|
|||
name = "gsd-engine"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"dashmap",
|
||||
"globset",
|
||||
"gsd-grep",
|
||||
"ignore",
|
||||
"image",
|
||||
"libc",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"syntect",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -191,6 +378,17 @@ dependencies = [
|
|||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
|
|
@ -213,6 +411,20 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
|
|
@ -229,6 +441,12 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
|
|
@ -259,6 +477,26 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.16.17"
|
||||
|
|
@ -316,12 +554,104 @@ dependencies = [
|
|||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-graphics"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
|
|
@ -335,6 +665,25 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
|
|
@ -344,6 +693,18 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
|
|
@ -411,6 +772,19 @@ version = "0.8.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[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 = "same-file"
|
||||
version = "1.0.6"
|
||||
|
|
@ -461,6 +835,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
|
|
@ -478,6 +858,58 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntect"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "656b45c05d95a5704399aeef6bd0ddec7b2b3531b7c9e900abbf7c4d2190c925"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"fancy-regex",
|
||||
"flate2",
|
||||
"fnv",
|
||||
"once_cell",
|
||||
"regex-syntax",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"flate2",
|
||||
"half",
|
||||
"quick-error",
|
||||
"weezl",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
@ -500,13 +932,19 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -515,6 +953,15 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
|
|
@ -523,3 +970,120 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ crate-type = ["cdylib"]
|
|||
|
||||
[dependencies]
|
||||
gsd-grep = { path = "../grep" }
|
||||
arboard = "3"
|
||||
dashmap = "6"
|
||||
globset = "0.4"
|
||||
ignore = "0.4"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
napi = { version = "2", features = ["napi8"] }
|
||||
napi-derive = "2"
|
||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "regex-fancy"] }
|
||||
|
|
|
|||
110
native/crates/engine/src/clipboard.rs
Normal file
110
native/crates/engine/src/clipboard.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//! Clipboard utilities backed by arboard.
|
||||
//!
|
||||
//! Provides text copy/read and image read support across Linux, macOS, and Windows.
|
||||
//! Text copy runs synchronously so macOS writes execute on the caller thread,
|
||||
//! avoiding worker-thread `AppKit` pasteboard warnings in CLI contexts.
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use arboard::{Clipboard, Error as ClipboardError, ImageData};
|
||||
use image::{DynamicImage, ImageFormat, RgbaImage};
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi::{Env, Error, Result, Task};
|
||||
use napi_derive::napi;
|
||||
|
||||
/// Clipboard image payload encoded as PNG bytes.
|
||||
#[napi(object)]
|
||||
pub struct ClipboardImage {
|
||||
/// PNG-encoded image bytes.
|
||||
pub data: Uint8Array,
|
||||
#[napi(js_name = "mimeType")]
|
||||
/// MIME type for the encoded image payload.
|
||||
pub mime_type: String,
|
||||
}
|
||||
|
||||
fn encode_png(image: ImageData<'_>) -> Result<Vec<u8>> {
|
||||
let width = u32::try_from(image.width)
|
||||
.map_err(|_| Error::from_reason("Clipboard image width overflow"))?;
|
||||
let height = u32::try_from(image.height)
|
||||
.map_err(|_| Error::from_reason("Clipboard image height overflow"))?;
|
||||
let bytes = image.bytes.into_owned();
|
||||
let buffer = RgbaImage::from_raw(width, height, bytes)
|
||||
.ok_or_else(|| Error::from_reason("Clipboard image buffer size mismatch"))?;
|
||||
let capacity = width.saturating_mul(height).saturating_mul(4) as usize;
|
||||
let mut output = Vec::with_capacity(capacity);
|
||||
DynamicImage::ImageRgba8(buffer)
|
||||
.write_to(&mut Cursor::new(&mut output), ImageFormat::Png)
|
||||
.map_err(|err| Error::from_reason(format!("Failed to encode clipboard image: {err}")))?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Copy plain text to the system clipboard.
|
||||
///
|
||||
/// Runs synchronously to avoid macOS AppKit pasteboard warnings
|
||||
/// when writing from worker threads.
|
||||
#[napi(js_name = "copyToClipboard")]
|
||||
pub fn copy_to_clipboard(text: String) -> Result<()> {
|
||||
let mut clipboard = Clipboard::new()
|
||||
.map_err(|err| Error::from_reason(format!("Failed to access clipboard: {err}")))?;
|
||||
clipboard
|
||||
.set_text(text)
|
||||
.map_err(|err| Error::from_reason(format!("Failed to copy to clipboard: {err}")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read plain text from the system clipboard.
|
||||
///
|
||||
/// Returns `None` when no text data is available.
|
||||
#[napi(js_name = "readTextFromClipboard")]
|
||||
pub fn read_text_from_clipboard() -> Result<Option<String>> {
|
||||
let mut clipboard = Clipboard::new()
|
||||
.map_err(|err| Error::from_reason(format!("Failed to access clipboard: {err}")))?;
|
||||
match clipboard.get_text() {
|
||||
Ok(text) => Ok(Some(text)),
|
||||
Err(ClipboardError::ContentNotAvailable) => Ok(None),
|
||||
Err(err) => Err(Error::from_reason(format!(
|
||||
"Failed to read clipboard text: {err}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Async image read task ────────────────────────────────────────────
|
||||
|
||||
pub(crate) struct ReadImageTask;
|
||||
|
||||
impl Task for ReadImageTask {
|
||||
type JsValue = Option<ClipboardImage>;
|
||||
type Output = Option<ClipboardImage>;
|
||||
|
||||
fn compute(&mut self) -> Result<Self::Output> {
|
||||
let mut clipboard = Clipboard::new()
|
||||
.map_err(|err| Error::from_reason(format!("Failed to access clipboard: {err}")))?;
|
||||
match clipboard.get_image() {
|
||||
Ok(image) => {
|
||||
let bytes = encode_png(image)?;
|
||||
Ok(Some(ClipboardImage {
|
||||
data: Uint8Array::from(bytes),
|
||||
mime_type: "image/png".to_string(),
|
||||
}))
|
||||
}
|
||||
Err(ClipboardError::ContentNotAvailable) => Ok(None),
|
||||
Err(err) => Err(Error::from_reason(format!(
|
||||
"Failed to read clipboard image: {err}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve(&mut self, _env: Env, output: Self::Output) -> Result<Self::JsValue> {
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read an image from the system clipboard.
|
||||
///
|
||||
/// Returns a Promise that resolves to a `ClipboardImage` (PNG-encoded bytes)
|
||||
/// or `null` when no image data is available. Runs on libuv's thread pool
|
||||
/// to avoid blocking the main JS thread during PNG encoding.
|
||||
#[napi(js_name = "readImageFromClipboard")]
|
||||
pub fn read_image_from_clipboard() -> AsyncTask<ReadImageTask> {
|
||||
AsyncTask::new(ReadImageTask)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#![allow(clippy::needless_pass_by_value)]
|
||||
|
||||
mod clipboard;
|
||||
mod fs_cache;
|
||||
mod glob;
|
||||
mod glob_util;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"name": "@gsd/native",
|
||||
"version": "0.1.0",
|
||||
"description": "Native Rust bindings for GSD — high-performance grep, glob, and process management via N-API",
|
||||
"description": "Native Rust bindings for GSD — high-performance native modules via N-API",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build:native": "node ../../native/scripts/build.js",
|
||||
"build:native:dev": "node ../../native/scripts/build.js --dev",
|
||||
"test": "node --test src/__tests__/grep.test.mjs src/__tests__/ps.test.mjs src/__tests__/glob.test.mjs"
|
||||
"test": "node --test src/__tests__/grep.test.mjs src/__tests__/ps.test.mjs src/__tests__/glob.test.mjs src/__tests__/clipboard.test.mjs"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
|
@ -26,6 +26,10 @@
|
|||
"./glob": {
|
||||
"types": "./src/glob/index.ts",
|
||||
"import": "./src/glob/index.ts"
|
||||
},
|
||||
"./clipboard": {
|
||||
"types": "./src/clipboard/index.ts",
|
||||
"import": "./src/clipboard/index.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
|
|
|
|||
79
packages/native/src/__tests__/clipboard.test.mjs
Normal file
79
packages/native/src/__tests__/clipboard.test.mjs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { test, describe } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createRequire } from "node:module";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const addonDir = path.resolve(__dirname, "..", "..", "..", "..", "native", "addon");
|
||||
const platformTag = `${process.platform}-${process.arch}`;
|
||||
const candidates = [
|
||||
path.join(addonDir, `gsd_engine.${platformTag}.node`),
|
||||
path.join(addonDir, "gsd_engine.dev.node"),
|
||||
];
|
||||
|
||||
let native;
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
native = require(candidate);
|
||||
break;
|
||||
} catch {
|
||||
// try next
|
||||
}
|
||||
}
|
||||
|
||||
if (!native) {
|
||||
console.error("Native addon not found. Run build:native first.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
describe("native clipboard: copyToClipboard()", () => {
|
||||
test("copies text without throwing", () => {
|
||||
assert.doesNotThrow(() => {
|
||||
native.copyToClipboard("GSD clipboard test");
|
||||
});
|
||||
});
|
||||
|
||||
test("accepts empty string", () => {
|
||||
assert.doesNotThrow(() => {
|
||||
native.copyToClipboard("");
|
||||
});
|
||||
});
|
||||
|
||||
test("accepts unicode text", () => {
|
||||
assert.doesNotThrow(() => {
|
||||
native.copyToClipboard("Hello 世界");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("native clipboard: readTextFromClipboard()", () => {
|
||||
test("reads back text that was copied", () => {
|
||||
const testText = `GSD clipboard roundtrip ${Date.now()}`;
|
||||
native.copyToClipboard(testText);
|
||||
const result = native.readTextFromClipboard();
|
||||
assert.equal(result, testText);
|
||||
});
|
||||
|
||||
test("returns a string or null", () => {
|
||||
const result = native.readTextFromClipboard();
|
||||
assert.ok(result === null || typeof result === "string");
|
||||
});
|
||||
});
|
||||
|
||||
describe("native clipboard: readImageFromClipboard()", () => {
|
||||
test("returns a promise", () => {
|
||||
const result = native.readImageFromClipboard();
|
||||
assert.ok(result instanceof Promise);
|
||||
});
|
||||
|
||||
test("resolves to ClipboardImage or null", async () => {
|
||||
const result = await native.readImageFromClipboard();
|
||||
if (result !== null) {
|
||||
assert.ok(result.data instanceof Uint8Array, "data should be Uint8Array");
|
||||
assert.equal(result.mimeType, "image/png");
|
||||
}
|
||||
});
|
||||
});
|
||||
40
packages/native/src/clipboard/index.ts
Normal file
40
packages/native/src/clipboard/index.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Native clipboard access using N-API.
|
||||
*
|
||||
* Cross-platform clipboard read/write backed by the `arboard` Rust crate.
|
||||
* No external tools (pbcopy, xclip, etc.) required.
|
||||
*/
|
||||
|
||||
import { native } from "../native.js";
|
||||
import type { ClipboardImage } from "./types.js";
|
||||
|
||||
export type { ClipboardImage };
|
||||
|
||||
/**
|
||||
* Copy plain text to the system clipboard.
|
||||
*
|
||||
* Runs synchronously to avoid macOS AppKit pasteboard warnings
|
||||
* when writing from worker threads.
|
||||
*/
|
||||
export function copyToClipboard(text: string): void {
|
||||
native.copyToClipboard(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read plain text from the system clipboard.
|
||||
*
|
||||
* Returns `null` when no text data is available.
|
||||
*/
|
||||
export function readTextFromClipboard(): string | null {
|
||||
return native.readTextFromClipboard() as string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an image from the system clipboard.
|
||||
*
|
||||
* Returns a Promise that resolves to a `ClipboardImage` (PNG-encoded bytes)
|
||||
* or `null` when no image data is available.
|
||||
*/
|
||||
export function readImageFromClipboard(): Promise<ClipboardImage | null> {
|
||||
return native.readImageFromClipboard() as Promise<ClipboardImage | null>;
|
||||
}
|
||||
7
packages/native/src/clipboard/types.ts
Normal file
7
packages/native/src/clipboard/types.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/** Clipboard image payload encoded as PNG bytes. */
|
||||
export interface ClipboardImage {
|
||||
/** PNG-encoded image bytes. */
|
||||
data: Uint8Array;
|
||||
/** MIME type for the encoded image payload (always "image/png"). */
|
||||
mimeType: string;
|
||||
}
|
||||
|
|
@ -2,12 +2,20 @@
|
|||
* @gsd/native — High-performance Rust modules exposed via N-API.
|
||||
*
|
||||
* Modules:
|
||||
* - clipboard: native clipboard access (text + image)
|
||||
* - grep: ripgrep-backed regex search (content + filesystem)
|
||||
* - ps: cross-platform process tree management
|
||||
* - glob: gitignore-respecting filesystem discovery with scan caching
|
||||
* - highlight: syntect-based syntax highlighting
|
||||
*/
|
||||
|
||||
export {
|
||||
copyToClipboard,
|
||||
readTextFromClipboard,
|
||||
readImageFromClipboard,
|
||||
} from "./clipboard/index.js";
|
||||
export type { ClipboardImage } from "./clipboard/index.js";
|
||||
|
||||
export {
|
||||
highlightCode,
|
||||
supportsLanguage,
|
||||
|
|
|
|||
|
|
@ -55,4 +55,7 @@ export const native = loadNative() as {
|
|||
highlightCode: (code: string, lang: string | null, colors: unknown) => unknown;
|
||||
supportsLanguage: (lang: string) => unknown;
|
||||
getSupportedLanguages: () => unknown;
|
||||
copyToClipboard: (text: string) => void;
|
||||
readTextFromClipboard: () => string | null;
|
||||
readImageFromClipboard: () => Promise<unknown>;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue