mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
feat: clipboard, multi formats (#8733)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
parent
8c91e5c5ca
commit
541d9c6b86
2
.github/workflows/flutter-build.yml
vendored
2
.github/workflows/flutter-build.yml
vendored
@ -33,7 +33,7 @@ env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# vcpkg version: 2024.07.12
|
||||
VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1"
|
||||
VERSION: "1.2.7"
|
||||
VERSION: "1.3.0"
|
||||
NDK_VERSION: "r26d"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
|
||||
2
.github/workflows/playground.yml
vendored
2
.github/workflows/playground.yml
vendored
@ -18,7 +18,7 @@ env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# vcpkg version: 2024.06.15
|
||||
VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625"
|
||||
VERSION: "1.2.7"
|
||||
VERSION: "1.3.0"
|
||||
NDK_VERSION: "r26d"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
|
||||
322
Cargo.lock
generated
322
Cargo.lock
generated
@ -224,7 +224,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.4.0"
|
||||
source = "git+https://github.com/rustdesk-org/arboard#27b4e503caa70ec6306e5270461429f2cf907ad6"
|
||||
source = "git+https://github.com/rustdesk-org/arboard#75166f255bf2fd6c662269029f5130b11d024f46"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"core-graphics 0.23.2",
|
||||
@ -234,24 +234,11 @@ dependencies = [
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"resvg",
|
||||
"windows-sys 0.48.0",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.5.1"
|
||||
@ -987,11 +974,14 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "clipboard-master"
|
||||
version = "4.0.0-beta.6"
|
||||
source = "git+https://github.com/rustdesk-org/clipboard-master#5268c7b3d7728699566ad863da0911f249706f8c"
|
||||
source = "git+https://github.com/rustdesk-org/clipboard-master#4fb62e5b62fb6350d82b571ec7ba94b3cd466695"
|
||||
dependencies = [
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"windows-win",
|
||||
"wl-clipboard-rs",
|
||||
"x11-clipboard 0.9.2",
|
||||
@ -1000,9 +990,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.3.1"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad"
|
||||
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
@ -1526,12 +1516,6 @@ dependencies = [
|
||||
"dasp_sample",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.7"
|
||||
@ -2081,12 +2065,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.0"
|
||||
@ -2143,29 +2121,6 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d"
|
||||
dependencies = [
|
||||
"roxmltree 0.19.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@ -3213,12 +3168,6 @@ dependencies = [
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
|
||||
|
||||
[[package]]
|
||||
name = "impersonate_system"
|
||||
version = "0.1.0"
|
||||
@ -3462,16 +3411,6 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@ -3777,15 +3716,6 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
@ -4732,15 +4662,9 @@ version = "0.7.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
|
||||
dependencies = [
|
||||
"siphasher 0.2.3",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.5"
|
||||
@ -5065,6 +4989,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
@ -5414,31 +5347,6 @@ dependencies = [
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051"
|
||||
dependencies = [
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"log",
|
||||
"pico-args",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"usvg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
@ -5463,18 +5371,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "2.1.0"
|
||||
@ -5572,7 +5468,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk"
|
||||
version = "1.2.7"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"android-wakelock",
|
||||
"android_logger",
|
||||
@ -5670,7 +5566,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.2.7"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"dirs 5.0.1",
|
||||
@ -5853,22 +5749,6 @@ version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bytemuck",
|
||||
"smallvec",
|
||||
"ttf-parser",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
@ -6159,27 +6039,12 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simplecss"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@ -6189,15 +6054,6 @@ dependencies = [
|
||||
"autocfg 1.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
@ -6268,15 +6124,6 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
|
||||
|
||||
[[package]]
|
||||
name = "strict-num"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
dependencies = [
|
||||
"float-cmp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
@ -6338,16 +6185,6 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"siphasher 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.15.44"
|
||||
@ -6687,32 +6524,6 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"bytemuck",
|
||||
"cfg-if 1.0.0",
|
||||
"log",
|
||||
"png",
|
||||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia-path"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.1"
|
||||
@ -7004,12 +6815,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
@ -7082,18 +6887,6 @@ version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
@ -7109,30 +6902,12 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-script"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-vo"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
@ -7195,33 +6970,6 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"fontdb",
|
||||
"imagesize",
|
||||
"kurbo",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree 0.20.0",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher 1.0.1",
|
||||
"strict-num",
|
||||
"svgtypes",
|
||||
"tiny-skia-path",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
"unicode-vo",
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf16string"
|
||||
version = "0.2.0"
|
||||
@ -7414,9 +7162,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.4"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34e9e6b6d4a2bb4e7e69433e0b35c7923b95d4dc8503a84d25ec917a4bbfdf07"
|
||||
checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
@ -7428,9 +7176,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.3"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e63801c85358a431f986cffa74ba9599ff571fc5774ac113ed3b490c19a1133"
|
||||
checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"rustix 0.38.34",
|
||||
@ -7440,9 +7188,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.1"
|
||||
version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83d0f1056570486e26a3773ec633885124d79ae03827de05ba6c85f79904026c"
|
||||
checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"wayland-backend",
|
||||
@ -7452,9 +7200,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.1"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7dab47671043d9f5397035975fe1cac639e5bca5cc0b3c32d09f01612e34d24"
|
||||
checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"wayland-backend",
|
||||
@ -7465,20 +7213,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.2"
|
||||
version = "0.31.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67da50b9f80159dec0ea4c11c13e24ef9e7574bd6ce24b01860a175010cea565"
|
||||
checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.86",
|
||||
"quick-xml 0.31.0",
|
||||
"quick-xml 0.34.0",
|
||||
"quote 1.0.36",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.2"
|
||||
version = "0.31.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "105b1842da6554f91526c14a2a2172897b7f745a805d62af4ce698706be79c12"
|
||||
checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
@ -8220,12 +7968,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlwriter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "3.15.2"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.2.7"
|
||||
version = "1.3.0"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
@ -90,7 +90,7 @@ enigo = { path = "libs/enigo", features = [ "with_serde" ] }
|
||||
clipboard = { path = "libs/clipboard" }
|
||||
ctrlc = "3.2"
|
||||
# arboard = { version = "3.4.0", features = ["wayland-data-control"] }
|
||||
arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control", "image-data"] }
|
||||
arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control"] }
|
||||
clipboard-master = { git = "https://github.com/rustdesk-org/clipboard-master" }
|
||||
|
||||
system_shutdown = "4.0"
|
||||
|
||||
@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.7
|
||||
version: 1.3.0
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.2.7
|
||||
version: 1.3.0
|
||||
exec: usr/lib/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.2.7+46
|
||||
version: 1.3.0+46
|
||||
|
||||
environment:
|
||||
sdk: '^3.1.0'
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use crate::{CliprdrError, CliprdrServiceContext};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@ -63,8 +64,10 @@ pub fn create_cliprdr_context(
|
||||
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
struct DummyCliprdrContext {}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl CliprdrServiceContext for DummyCliprdrContext {
|
||||
fn set_is_stopped(&mut self) -> Result<(), CliprdrError> {
|
||||
Ok(())
|
||||
|
||||
@ -81,6 +81,7 @@ message LoginRequest {
|
||||
uint64 session_id = 10;
|
||||
string version = 11;
|
||||
OSLogin os_login = 12;
|
||||
string my_platform = 13;
|
||||
}
|
||||
|
||||
message Auth2FA {
|
||||
@ -315,13 +316,25 @@ message Hash {
|
||||
string challenge = 2;
|
||||
}
|
||||
|
||||
enum ClipboardFormat {
|
||||
Text = 0;
|
||||
Rtf = 1;
|
||||
Html = 2;
|
||||
ImageRgba = 21;
|
||||
ImagePng = 22;
|
||||
ImageSvg = 23;
|
||||
}
|
||||
|
||||
message Clipboard {
|
||||
bool compress = 1;
|
||||
bytes content = 2;
|
||||
int32 width = 3;
|
||||
int32 height = 4;
|
||||
ClipboardFormat format = 5;
|
||||
}
|
||||
|
||||
message MultiClipboards { repeated Clipboard clipboards = 1; }
|
||||
|
||||
enum FileType {
|
||||
Dir = 0;
|
||||
DirLink = 2;
|
||||
@ -816,5 +829,6 @@ message Message {
|
||||
PeerInfo peer_info = 25;
|
||||
PointerDeviceEvent pointer_device_event = 26;
|
||||
Auth2FA auth_2fa = 27;
|
||||
MultiClipboards multi_clipboards = 28;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.2.7"
|
||||
version = "1.3.0"
|
||||
edition = "2021"
|
||||
description = "RustDesk Remote Desktop"
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.2.7
|
||||
pkgver=1.3.0
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.7
|
||||
Version: 1.3.0
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.7
|
||||
Version: 1.3.0
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.2.7
|
||||
Version: 1.3.0
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
177
src/client.rs
177
src/client.rs
@ -1,14 +1,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::c_void,
|
||||
net::SocketAddr,
|
||||
ops::Deref,
|
||||
str::FromStr,
|
||||
sync::{mpsc, Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use clipboard_master::{CallbackResult, ClipboardHandler};
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
use cpal::{
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
@ -19,6 +12,18 @@ use magnum_opus::{Channels::*, Decoder as AudioDecoder};
|
||||
#[cfg(not(any(target_os = "android", target_os = "linux")))]
|
||||
use ringbuf::{ring_buffer::RbBase, Rb};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::c_void,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
ops::Deref,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
mpsc::{self, RecvTimeoutError, Sender},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use file_trait::FileManager;
|
||||
@ -65,7 +70,7 @@ use crate::{
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::clipboard::{check_clipboard, CLIPBOARD_INTERVAL};
|
||||
use crate::clipboard::{check_clipboard, ClipboardSide, CLIPBOARD_INTERVAL};
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::ui_session_interface::SessionPermissionConfig;
|
||||
@ -136,18 +141,11 @@ lazy_static::lazy_static! {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref ENIGO: Arc<Mutex<enigo::Enigo>> = Arc::new(Mutex::new(enigo::Enigo::new()));
|
||||
static ref OLD_CLIPBOARD_DATA: Arc<Mutex<crate::clipboard::ClipboardData>> = Default::default();
|
||||
static ref TEXT_CLIPBOARD_STATE: Arc<Mutex<TextClipboardState>> = Arc::new(Mutex::new(TextClipboardState::new()));
|
||||
}
|
||||
|
||||
const PUBLIC_SERVER: &str = "public";
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_old_clipboard_text() -> Arc<Mutex<crate::clipboard::ClipboardData>> {
|
||||
OLD_CLIPBOARD_DATA.clone()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_key_state(key: enigo::Key) -> bool {
|
||||
use enigo::KeyboardControllable;
|
||||
@ -714,6 +712,13 @@ impl Client {
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn try_stop_clipboard() {
|
||||
// There's a bug here.
|
||||
// If session is closed by the peer, `has_sessions_running()` will always return true.
|
||||
// It's better to check if the active session number.
|
||||
// But it's not a problem, because the clipboard thread does not consume CPU.
|
||||
//
|
||||
// If we want to fix it, we can add a flag to indicate if session is active.
|
||||
// But I think it's not necessary to introduce complexity at this point.
|
||||
#[cfg(feature = "flutter")]
|
||||
if crate::flutter::sessions::has_sessions_running(ConnType::DEFAULT_CONN) {
|
||||
return;
|
||||
@ -727,18 +732,44 @@ impl Client {
|
||||
//
|
||||
// If clipboard update is detected, the text will be sent to all sessions by `send_text_clipboard_msg`.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn try_start_clipboard(_ctx: Option<ClientClipboardContext>) -> Option<UnboundedReceiver<()>> {
|
||||
fn try_start_clipboard(
|
||||
_client_clip_ctx: Option<ClientClipboardContext>,
|
||||
) -> Option<UnboundedReceiver<()>> {
|
||||
let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap();
|
||||
if clipboard_lock.running {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (tx_cb_result, rx_cb_result) = mpsc::channel();
|
||||
let handler = ClientClipboardHandler {
|
||||
ctx: None,
|
||||
tx_cb_result,
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
client_clip_ctx: _client_clip_ctx,
|
||||
};
|
||||
|
||||
let (tx_start_res, rx_start_res) = mpsc::channel();
|
||||
let h = crate::clipboard::start_clipbard_master_thread(handler, tx_start_res);
|
||||
let shutdown = match rx_start_res.recv() {
|
||||
Ok((Some(s), _)) => s,
|
||||
Ok((None, err)) => {
|
||||
log::error!("{}", err);
|
||||
return None;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to create clipboard listener: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
clipboard_lock.running = true;
|
||||
let (tx, rx) = unbounded_channel();
|
||||
|
||||
let (tx_started, rx_started) = unbounded_channel();
|
||||
|
||||
log::info!("Start text clipboard loop");
|
||||
std::thread::spawn(move || {
|
||||
let mut is_sent = false;
|
||||
let mut ctx = None;
|
||||
|
||||
loop {
|
||||
if !TEXT_CLIPBOARD_STATE.lock().unwrap().running {
|
||||
break;
|
||||
@ -748,39 +779,31 @@ impl Client {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(msg) = check_clipboard(&mut ctx, Some(OLD_CLIPBOARD_DATA.clone())) {
|
||||
#[cfg(feature = "flutter")]
|
||||
crate::flutter::send_text_clipboard_msg(msg);
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
if let Some(ctx) = &_ctx {
|
||||
if ctx.cfg.is_text_clipboard_required() {
|
||||
let _ = ctx.tx.send(Data::Message(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_sent {
|
||||
is_sent = true;
|
||||
tx.send(()).ok();
|
||||
tx_started.send(()).ok();
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL));
|
||||
match rx_cb_result.recv_timeout(Duration::from_millis(CLIPBOARD_INTERVAL)) {
|
||||
Ok(CallbackResult::Stop) => {
|
||||
log::debug!("Clipboard listener stopped");
|
||||
break;
|
||||
}
|
||||
Ok(CallbackResult::StopWithError(err)) => {
|
||||
log::error!("Clipboard listener stopped with error: {}", err);
|
||||
break;
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
log::info!("Stop text clipboard loop");
|
||||
shutdown.signal();
|
||||
h.join().ok();
|
||||
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
|
||||
});
|
||||
|
||||
Some(rx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn get_current_clipboard_msg() -> Option<Message> {
|
||||
let data = &*OLD_CLIPBOARD_DATA.lock().unwrap();
|
||||
if data.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(data.create_msg())
|
||||
}
|
||||
Some(rx_started)
|
||||
}
|
||||
}
|
||||
|
||||
@ -794,6 +817,65 @@ impl TextClipboardState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
struct ClientClipboardHandler {
|
||||
ctx: Option<crate::clipboard::ClipboardContext>,
|
||||
tx_cb_result: Sender<CallbackResult>,
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
client_clip_ctx: Option<ClientClipboardContext>,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
impl ClientClipboardHandler {
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter")]
|
||||
fn send_msg(&self, msg: Message) {
|
||||
crate::flutter::send_text_clipboard_msg(msg);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
fn send_msg(&self, msg: Message) {
|
||||
if let Some(ctx) = &self.client_clip_ctx {
|
||||
if ctx.cfg.is_text_clipboard_required() {
|
||||
if let Some(pi) = ctx.cfg.lc.read().unwrap().peer_info.as_ref() {
|
||||
if let Some(message::Union::MultiClipboards(multi_clipboards)) = &msg.union {
|
||||
if let Some(msg_out) = crate::clipboard::get_msg_if_not_support_multi_clip(
|
||||
&pi.version,
|
||||
&pi.platform,
|
||||
multi_clipboards,
|
||||
) {
|
||||
let _ = ctx.tx.send(Data::Message(msg_out));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = ctx.tx.send(Data::Message(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
impl ClipboardHandler for ClientClipboardHandler {
|
||||
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||
if TEXT_CLIPBOARD_STATE.lock().unwrap().running
|
||||
&& TEXT_CLIPBOARD_STATE.lock().unwrap().is_required
|
||||
{
|
||||
if let Some(msg) = check_clipboard(&mut self.ctx, ClipboardSide::Client, false) {
|
||||
self.send_msg(msg);
|
||||
}
|
||||
}
|
||||
CallbackResult::Next
|
||||
}
|
||||
|
||||
fn on_clipboard_error(&mut self, error: io::Error) -> CallbackResult {
|
||||
self.tx_cb_result
|
||||
.send(CallbackResult::StopWithError(error))
|
||||
.ok();
|
||||
CallbackResult::Next
|
||||
}
|
||||
}
|
||||
|
||||
/// Audio handler for the [`Client`].
|
||||
#[derive(Default)]
|
||||
pub struct AudioHandler {
|
||||
@ -2031,11 +2113,16 @@ impl LoginConfigHandler {
|
||||
if display_name.is_empty() {
|
||||
display_name = crate::username();
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let my_platform = whoami::platform().to_string();
|
||||
#[cfg(target_os = "android")]
|
||||
let my_platform = "Android".into();
|
||||
let mut lr = LoginRequest {
|
||||
username: pure_id,
|
||||
password: password.into(),
|
||||
my_id,
|
||||
my_name: display_name,
|
||||
my_platform,
|
||||
option: self.get_option_message(true).into(),
|
||||
session_id: self.session_id,
|
||||
version: crate::VERSION.to_string(),
|
||||
|
||||
@ -7,11 +7,21 @@ use std::{
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::clipboard::{update_clipboard, ClipboardSide, CLIPBOARD_INTERVAL};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::{audio_service, ConnInner, CLIENT_SERVER};
|
||||
use crate::{
|
||||
client::{
|
||||
self, new_voice_call_request, Client, Data, Interface, MediaData, MediaSender,
|
||||
QualityStatus, MILLI1, SEC30,
|
||||
},
|
||||
common::get_default_sound_input,
|
||||
ui_session_interface::{InvokeUiSession, Session},
|
||||
};
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use clipboard::ContextSend;
|
||||
use crossbeam_queue::ArrayQueue;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::sleep;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
|
||||
use hbb_common::{
|
||||
@ -37,17 +47,6 @@ use hbb_common::{
|
||||
use hbb_common::{tokio::sync::Mutex as TokioMutex, ResultType};
|
||||
use scrap::CodecFormat;
|
||||
|
||||
use crate::client::{
|
||||
self, new_voice_call_request, Client, MediaData, MediaSender, QualityStatus, MILLI1, SEC30,
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::clipboard::{update_clipboard, CLIPBOARD_INTERVAL};
|
||||
use crate::common::get_default_sound_input;
|
||||
use crate::ui_session_interface::{InvokeUiSession, Session};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::{audio_service, ConnInner, CLIENT_SERVER};
|
||||
use crate::{client::Data, client::Interface};
|
||||
|
||||
pub struct Remote<T: InvokeUiSession> {
|
||||
handler: Session<T>,
|
||||
video_queue_map: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
|
||||
@ -173,8 +172,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
crate::rustdesk_interval(time::interval(Duration::new(1, 0)));
|
||||
let mut fps_instant = Instant::now();
|
||||
|
||||
let _keep_it =
|
||||
client::hc_connection(feedback, rendezvous_server, token).await;
|
||||
let _keep_it = client::hc_connection(feedback, rendezvous_server, token).await;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@ -1123,6 +1121,8 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
let peer_version = pi.version.clone();
|
||||
let peer_platform = pi.platform.clone();
|
||||
self.handler.handle_peer_info(pi);
|
||||
self.check_clipboard_file_context();
|
||||
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
|
||||
@ -1144,12 +1144,14 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(msg_out) = Client::get_current_clipboard_msg() {
|
||||
if let Some(msg_out) = crate::clipboard::get_current_clipboard_msg(
|
||||
&peer_version,
|
||||
&peer_platform,
|
||||
crate::clipboard::ClipboardSide::Client,
|
||||
) {
|
||||
let sender = self.sender.clone();
|
||||
let permission_config = self.handler.get_permission_config();
|
||||
tokio::spawn(async move {
|
||||
// due to clipboard service interval time
|
||||
sleep(CLIPBOARD_INTERVAL as f32 / 1_000.).await;
|
||||
if permission_config.is_text_clipboard_required() {
|
||||
sender.send(Data::Message(msg_out)).ok();
|
||||
}
|
||||
@ -1185,7 +1187,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(cb, Some(crate::client::get_old_clipboard_text()));
|
||||
update_clipboard(vec![cb], ClipboardSide::Client);
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
let content = if cb.compress {
|
||||
@ -1199,6 +1201,12 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::MultiClipboards(_mcb)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(_mcb.clipboards, ClipboardSide::Client);
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
Some(message::Union::Cliprdr(clip)) => {
|
||||
self.handle_cliprdr_msg(clip);
|
||||
|
||||
556
src/clipboard.rs
556
src/clipboard.rs
@ -1,26 +1,35 @@
|
||||
use std::sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc, Mutex,
|
||||
};
|
||||
|
||||
use clipboard_master::{CallbackResult, ClipboardHandler, Master, Shutdown};
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
compress::{compress as compress_func, decompress},
|
||||
log,
|
||||
message_proto::*,
|
||||
ResultType,
|
||||
use arboard::{ClipboardData, ClipboardFormat};
|
||||
use clipboard_master::{ClipboardHandler, Master, Shutdown};
|
||||
use hbb_common::{log, message_proto::*, ResultType};
|
||||
use std::{
|
||||
sync::{mpsc::Sender, Arc, Mutex},
|
||||
thread,
|
||||
thread::JoinHandle,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub const CLIPBOARD_NAME: &'static str = "clipboard";
|
||||
pub const CLIPBOARD_INTERVAL: u64 = 333;
|
||||
const FAKE_SVG_WIDTH: usize = 999999;
|
||||
|
||||
// This format is used to store the flag in the clipboard.
|
||||
const RUSTDESK_CLIPBOARD_OWNER_FORMAT: &'static str = "dyn.com.rustdesk.owner";
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref CONTENT: Arc<Mutex<ClipboardData>> = Default::default();
|
||||
static ref ARBOARD_MTX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
|
||||
// cache the clipboard msg
|
||||
static ref LAST_MULTI_CLIPBOARDS: Arc<Mutex<MultiClipboards>> = Arc::new(Mutex::new(MultiClipboards::new()));
|
||||
}
|
||||
|
||||
const SUPPORTED_FORMATS: &[ClipboardFormat] = &[
|
||||
ClipboardFormat::Text,
|
||||
ClipboardFormat::Html,
|
||||
ClipboardFormat::Rtf,
|
||||
ClipboardFormat::ImageRgba,
|
||||
ClipboardFormat::ImagePng,
|
||||
ClipboardFormat::ImageSvg,
|
||||
ClipboardFormat::Special(RUSTDESK_CLIPBOARD_OWNER_FORMAT),
|
||||
];
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
|
||||
static X11_CLIPBOARD: once_cell::sync::OnceCell<x11_clipboard::Clipboard> =
|
||||
once_cell::sync::OnceCell::new();
|
||||
@ -61,7 +70,7 @@ fn parse_plain_uri_list(v: Vec<u8>) -> Result<String, String> {
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
|
||||
impl ClipboardContext {
|
||||
pub fn new(_listen: bool) -> Result<Self, String> {
|
||||
pub fn new() -> Result<Self, String> {
|
||||
let clipboard = get_clipboard()?;
|
||||
let string_getter = clipboard
|
||||
.getter
|
||||
@ -128,56 +137,42 @@ impl ClipboardContext {
|
||||
|
||||
pub fn check_clipboard(
|
||||
ctx: &mut Option<ClipboardContext>,
|
||||
old: Option<Arc<Mutex<ClipboardData>>>,
|
||||
side: ClipboardSide,
|
||||
force: bool,
|
||||
) -> Option<Message> {
|
||||
if ctx.is_none() {
|
||||
*ctx = ClipboardContext::new(true).ok();
|
||||
*ctx = ClipboardContext::new().ok();
|
||||
}
|
||||
let ctx2 = ctx.as_mut()?;
|
||||
let side = if old.is_none() { "host" } else { "client" };
|
||||
let old = if let Some(old) = old {
|
||||
old
|
||||
} else {
|
||||
CONTENT.clone()
|
||||
};
|
||||
let content = ctx2.get();
|
||||
let content = ctx2.get(side, force);
|
||||
if let Ok(content) = content {
|
||||
if !content.is_empty() {
|
||||
if matches!(content, ClipboardData::Text(_)) {
|
||||
// Skip the text if the last content is image-svg/html
|
||||
if ctx2.is_last_plain {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
let changed = content != *old.lock().unwrap();
|
||||
if changed {
|
||||
log::info!("{} update found on {}", CLIPBOARD_NAME, side);
|
||||
let msg = content.create_msg();
|
||||
*old.lock().unwrap() = content;
|
||||
return Some(msg);
|
||||
}
|
||||
let mut msg = Message::new();
|
||||
let clipboards = proto::create_multi_clipboards(content);
|
||||
msg.set_multi_clipboards(clipboards.clone());
|
||||
*LAST_MULTI_CLIPBOARDS.lock().unwrap() = clipboards;
|
||||
return Some(msg);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn update_clipboard_(clipboard: Clipboard, old: Option<Arc<Mutex<ClipboardData>>>) {
|
||||
let content = ClipboardData::from_msg(clipboard);
|
||||
if content.is_empty() {
|
||||
fn update_clipboard_(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
|
||||
let mut to_update_data = proto::from_multi_clipbards(multi_clipboards);
|
||||
if to_update_data.is_empty() {
|
||||
return;
|
||||
}
|
||||
match ClipboardContext::new(false) {
|
||||
match ClipboardContext::new() {
|
||||
Ok(mut ctx) => {
|
||||
let side = if old.is_none() { "host" } else { "client" };
|
||||
let old = if let Some(old) = old {
|
||||
old
|
||||
to_update_data.push(ClipboardData::Special((
|
||||
RUSTDESK_CLIPBOARD_OWNER_FORMAT.to_owned(),
|
||||
side.get_owner_data(),
|
||||
)));
|
||||
if let Err(e) = ctx.set(&to_update_data) {
|
||||
log::debug!("Failed to set clipboard: {}", e);
|
||||
} else {
|
||||
CONTENT.clone()
|
||||
};
|
||||
allow_err!(ctx.set(&content));
|
||||
*old.lock().unwrap() = content;
|
||||
log::debug!("{} updated on {}", CLIPBOARD_NAME, side);
|
||||
log::debug!("{} updated on {}", CLIPBOARD_NAME, side);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to create clipboard context: {}", err);
|
||||
@ -185,143 +180,21 @@ fn update_clipboard_(clipboard: Clipboard, old: Option<Arc<Mutex<ClipboardData>>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_clipboard(clipboard: Clipboard, old: Option<Arc<Mutex<ClipboardData>>>) {
|
||||
pub fn update_clipboard(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
|
||||
std::thread::spawn(move || {
|
||||
update_clipboard_(clipboard, old);
|
||||
update_clipboard_(multi_clipboards, side);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ClipboardData {
|
||||
Text(String),
|
||||
Image(arboard::ImageData<'static>, u64),
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Default for ClipboardData {
|
||||
fn default() -> Self {
|
||||
ClipboardData::Empty
|
||||
}
|
||||
}
|
||||
|
||||
impl ClipboardData {
|
||||
fn image(image: arboard::ImageData<'static>) -> ClipboardData {
|
||||
let hash = 0;
|
||||
/*
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
let mut hasher = DefaultHasher::new();
|
||||
image.bytes.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
*/
|
||||
ClipboardData::Image(image, hash)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
ClipboardData::Empty => true,
|
||||
ClipboardData::Text(s) => s.is_empty(),
|
||||
ClipboardData::Image(a, _) => a.bytes().is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_msg(clipboard: Clipboard) -> Self {
|
||||
let is_image = clipboard.width > 0;
|
||||
let data = if clipboard.compress {
|
||||
decompress(&clipboard.content)
|
||||
} else {
|
||||
clipboard.content.into()
|
||||
};
|
||||
if is_image {
|
||||
// We cannot use data.start_with(b"<svg") to check if it is svg image
|
||||
// because svg image may starts with other bytes
|
||||
let img = if clipboard.height == 0 && clipboard.width as usize == FAKE_SVG_WIDTH {
|
||||
arboard::ImageData::svg(std::str::from_utf8(&data).unwrap_or_default())
|
||||
} else {
|
||||
arboard::ImageData::rgba(clipboard.width as _, clipboard.height as _, data.into())
|
||||
};
|
||||
ClipboardData::Image(img, 0)
|
||||
} else {
|
||||
if let Ok(content) = String::from_utf8(data) {
|
||||
ClipboardData::Text(content)
|
||||
} else {
|
||||
ClipboardData::Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_msg(&self) -> Message {
|
||||
let mut msg = Message::new();
|
||||
|
||||
match self {
|
||||
ClipboardData::Text(s) => {
|
||||
let compressed = compress_func(s.as_bytes());
|
||||
let compress = compressed.len() < s.as_bytes().len();
|
||||
let content = if compress {
|
||||
compressed
|
||||
} else {
|
||||
s.clone().into_bytes()
|
||||
};
|
||||
msg.set_clipboard(Clipboard {
|
||||
compress,
|
||||
content: content.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
ClipboardData::Image(a, _) => {
|
||||
let compressed = compress_func(&a.bytes());
|
||||
let compress = compressed.len() < a.bytes().len();
|
||||
let content = if compress {
|
||||
compressed
|
||||
} else {
|
||||
a.bytes().to_vec()
|
||||
};
|
||||
let (w, h) = match a {
|
||||
arboard::ImageData::Rgba(a) => (a.width, a.height),
|
||||
arboard::ImageData::Svg(_) => (FAKE_SVG_WIDTH as _, 0 as _),
|
||||
};
|
||||
msg.set_clipboard(Clipboard {
|
||||
compress,
|
||||
content: content.into(),
|
||||
width: w as _,
|
||||
height: h as _,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ClipboardData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ClipboardData::Text(a), ClipboardData::Text(b)) => a == b,
|
||||
(ClipboardData::Image(a, _), ClipboardData::Image(b, _)) => match (a, b) {
|
||||
(arboard::ImageData::Rgba(a), arboard::ImageData::Rgba(b)) => {
|
||||
a.width == b.width && a.height == b.height && a.bytes == b.bytes
|
||||
}
|
||||
(arboard::ImageData::Svg(a), arboard::ImageData::Svg(b)) => a == b,
|
||||
_ => false,
|
||||
},
|
||||
(ClipboardData::Empty, ClipboardData::Empty) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))]
|
||||
pub struct ClipboardContext {
|
||||
inner: arboard::Clipboard,
|
||||
counter: (Arc<AtomicU64>, u64),
|
||||
shutdown: Option<Shutdown>,
|
||||
is_last_plain: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))]
|
||||
#[allow(unreachable_code)]
|
||||
impl ClipboardContext {
|
||||
pub fn new(listen: bool) -> ResultType<ClipboardContext> {
|
||||
pub fn new() -> ResultType<ClipboardContext> {
|
||||
let board;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
@ -351,94 +224,275 @@ impl ClipboardContext {
|
||||
}
|
||||
}
|
||||
|
||||
// starting from 1 so that we can always get initial clipboard data no matter if change
|
||||
let change_count: Arc<AtomicU64> = Arc::new(AtomicU64::new(1));
|
||||
let mut shutdown = None;
|
||||
if listen {
|
||||
struct Handler(Arc<AtomicU64>);
|
||||
impl ClipboardHandler for Handler {
|
||||
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||
self.0.fetch_add(1, Ordering::SeqCst);
|
||||
CallbackResult::Next
|
||||
}
|
||||
Ok(ClipboardContext { inner: board })
|
||||
}
|
||||
|
||||
fn on_clipboard_error(&mut self, error: std::io::Error) -> CallbackResult {
|
||||
log::trace!("Error of clipboard listener: {}", error);
|
||||
CallbackResult::Next
|
||||
}
|
||||
}
|
||||
let change_count_cloned = change_count.clone();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage#:~:text=The%20window%20must%20belong%20to%20the%20current%20thread.
|
||||
std::thread::spawn(move || match Master::new(Handler(change_count_cloned)) {
|
||||
Ok(mut master) => {
|
||||
tx.send(master.shutdown_channel()).ok();
|
||||
log::debug!("Clipboard listener started");
|
||||
if let Err(err) = master.run() {
|
||||
log::error!("Failed to run clipboard listener: {}", err);
|
||||
} else {
|
||||
log::debug!("Clipboard listener stopped");
|
||||
pub fn get(&mut self, side: ClipboardSide, force: bool) -> ResultType<Vec<ClipboardData>> {
|
||||
let _lock = ARBOARD_MTX.lock().unwrap();
|
||||
let data = self.inner.get_formats(SUPPORTED_FORMATS)?;
|
||||
if data.is_empty() {
|
||||
return Ok(data);
|
||||
}
|
||||
if !force {
|
||||
for c in data.iter() {
|
||||
if let ClipboardData::Special((_, d)) = c {
|
||||
if side.is_owner(d) {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to create clipboard listener: {}", err);
|
||||
}
|
||||
});
|
||||
if let Ok(st) = rx.recv() {
|
||||
shutdown = Some(st);
|
||||
}
|
||||
}
|
||||
Ok(ClipboardContext {
|
||||
inner: board,
|
||||
counter: (change_count, 0),
|
||||
shutdown,
|
||||
is_last_plain: false,
|
||||
})
|
||||
Ok(data
|
||||
.into_iter()
|
||||
.filter(|c| !matches!(c, ClipboardData::Special(_)))
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn change_count(&self) -> u64 {
|
||||
debug_assert!(self.shutdown.is_some());
|
||||
self.counter.0.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn get(&mut self) -> ResultType<ClipboardData> {
|
||||
let cn = self.change_count();
|
||||
fn set(&mut self, data: &[ClipboardData]) -> ResultType<()> {
|
||||
let _lock = ARBOARD_MTX.lock().unwrap();
|
||||
// only for image for the time being,
|
||||
// because I do not want to change behavior of text clipboard for the time being
|
||||
if cn != self.counter.1 {
|
||||
self.is_last_plain = false;
|
||||
self.counter.1 = cn;
|
||||
if let Ok(image) = self.inner.get_image() {
|
||||
// Both text and image svg may be set by some applications
|
||||
// But we only want to send the svg content.
|
||||
//
|
||||
// We can't call `get_text()` and store current text in `old` in outer scope,
|
||||
// because it may be updated later than svg.
|
||||
// Then the text will still be sent and replace the image svg content.
|
||||
self.is_last_plain = matches!(image, arboard::ImageData::Svg(_));
|
||||
return Ok(ClipboardData::image(image));
|
||||
}
|
||||
}
|
||||
Ok(ClipboardData::Text(self.inner.get_text()?))
|
||||
}
|
||||
|
||||
fn set(&mut self, data: &ClipboardData) -> ResultType<()> {
|
||||
let _lock = ARBOARD_MTX.lock().unwrap();
|
||||
match data {
|
||||
ClipboardData::Text(s) => self.inner.set_text(s)?,
|
||||
ClipboardData::Image(a, _) => self.inner.set_image(a.clone())?,
|
||||
_ => {}
|
||||
}
|
||||
self.inner.set_formats(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ClipboardContext {
|
||||
fn drop(&mut self) {
|
||||
if let Some(shutdown) = self.shutdown.take() {
|
||||
let _ = shutdown.signal();
|
||||
pub fn is_support_multi_clipboard(peer_version: &str, peer_platform: &str) -> bool {
|
||||
use hbb_common::get_version_number;
|
||||
get_version_number(peer_version) >= get_version_number("1.3.0")
|
||||
&& !["", "Android", &whoami::Platform::Ios.to_string()].contains(&peer_platform)
|
||||
}
|
||||
|
||||
pub fn get_current_clipboard_msg(
|
||||
peer_version: &str,
|
||||
peer_platform: &str,
|
||||
side: ClipboardSide,
|
||||
) -> Option<Message> {
|
||||
let mut multi_clipboards = LAST_MULTI_CLIPBOARDS.lock().unwrap();
|
||||
if multi_clipboards.clipboards.is_empty() {
|
||||
let mut ctx = ClipboardContext::new().ok()?;
|
||||
*multi_clipboards = proto::create_multi_clipboards(ctx.get(side, true).ok()?);
|
||||
}
|
||||
if multi_clipboards.clipboards.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_support_multi_clipboard(peer_version, peer_platform) {
|
||||
let mut msg = Message::new();
|
||||
msg.set_multi_clipboards(multi_clipboards.clone());
|
||||
Some(msg)
|
||||
} else {
|
||||
// Find the first text clipboard and send it.
|
||||
multi_clipboards
|
||||
.clipboards
|
||||
.iter()
|
||||
.find(|c| c.format.enum_value() == Ok(hbb_common::message_proto::ClipboardFormat::Text))
|
||||
.map(|c| {
|
||||
let mut msg = Message::new();
|
||||
msg.set_clipboard(c.clone());
|
||||
msg
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ClipboardSide {
|
||||
Host,
|
||||
Client,
|
||||
}
|
||||
|
||||
impl ClipboardSide {
|
||||
// 01: the clipboard is owned by the host
|
||||
// 10: the clipboard is owned by the client
|
||||
fn get_owner_data(&self) -> Vec<u8> {
|
||||
match self {
|
||||
ClipboardSide::Host => vec![0b01],
|
||||
ClipboardSide::Client => vec![0b10],
|
||||
}
|
||||
}
|
||||
|
||||
fn is_owner(&self, data: &[u8]) -> bool {
|
||||
if data.len() == 0 {
|
||||
return false;
|
||||
}
|
||||
data[0] & 0b11 != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ClipboardSide {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ClipboardSide::Host => write!(f, "host"),
|
||||
ClipboardSide::Client => write!(f, "client"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_clipbard_master_thread(
|
||||
handler: impl ClipboardHandler + Send + 'static,
|
||||
tx_start_res: Sender<(Option<Shutdown>, String)>,
|
||||
) -> JoinHandle<()> {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage#:~:text=The%20window%20must%20belong%20to%20the%20current%20thread.
|
||||
let h = std::thread::spawn(move || match Master::new(handler) {
|
||||
Ok(mut master) => {
|
||||
tx_start_res
|
||||
.send((Some(master.shutdown_channel()), "".to_owned()))
|
||||
.ok();
|
||||
log::debug!("Clipboard listener started");
|
||||
if let Err(err) = master.run() {
|
||||
log::error!("Failed to run clipboard listener: {}", err);
|
||||
} else {
|
||||
log::debug!("Clipboard listener stopped");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tx_start_res
|
||||
.send((
|
||||
None,
|
||||
format!("Failed to create clipboard listener: {}", err),
|
||||
))
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
h
|
||||
}
|
||||
|
||||
pub use proto::get_msg_if_not_support_multi_clip;
|
||||
mod proto {
|
||||
use arboard::ClipboardData;
|
||||
use hbb_common::{
|
||||
compress::{compress as compress_func, decompress},
|
||||
message_proto::{Clipboard, ClipboardFormat, Message, MultiClipboards},
|
||||
};
|
||||
|
||||
fn plain_to_proto(s: String, format: ClipboardFormat) -> Clipboard {
|
||||
let compressed = compress_func(s.as_bytes());
|
||||
let compress = compressed.len() < s.as_bytes().len();
|
||||
let content = if compress {
|
||||
compressed
|
||||
} else {
|
||||
s.bytes().collect::<Vec<u8>>()
|
||||
};
|
||||
Clipboard {
|
||||
compress,
|
||||
content: content.into(),
|
||||
format: format.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn image_to_proto(a: arboard::ImageData) -> Clipboard {
|
||||
match &a {
|
||||
arboard::ImageData::Rgba(rgba) => {
|
||||
let compressed = compress_func(&a.bytes());
|
||||
let compress = compressed.len() < a.bytes().len();
|
||||
let content = if compress {
|
||||
compressed
|
||||
} else {
|
||||
a.bytes().to_vec()
|
||||
};
|
||||
Clipboard {
|
||||
compress,
|
||||
content: content.into(),
|
||||
width: rgba.width as _,
|
||||
height: rgba.height as _,
|
||||
format: ClipboardFormat::ImageRgba.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
arboard::ImageData::Png(png) => Clipboard {
|
||||
compress: false,
|
||||
content: png.to_owned().to_vec().into(),
|
||||
format: ClipboardFormat::ImagePng.into(),
|
||||
..Default::default()
|
||||
},
|
||||
arboard::ImageData::Svg(_) => {
|
||||
let compressed = compress_func(&a.bytes());
|
||||
let compress = compressed.len() < a.bytes().len();
|
||||
let content = if compress {
|
||||
compressed
|
||||
} else {
|
||||
a.bytes().to_vec()
|
||||
};
|
||||
Clipboard {
|
||||
compress,
|
||||
content: content.into(),
|
||||
format: ClipboardFormat::ImageSvg.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clipboard_data_to_proto(data: ClipboardData) -> Option<Clipboard> {
|
||||
let d = match data {
|
||||
ClipboardData::Text(s) => plain_to_proto(s, ClipboardFormat::Text),
|
||||
ClipboardData::Rtf(s) => plain_to_proto(s, ClipboardFormat::Rtf),
|
||||
ClipboardData::Html(s) => plain_to_proto(s, ClipboardFormat::Html),
|
||||
ClipboardData::Image(a) => image_to_proto(a),
|
||||
_ => return None,
|
||||
};
|
||||
Some(d)
|
||||
}
|
||||
|
||||
pub fn create_multi_clipboards(vec_data: Vec<ClipboardData>) -> MultiClipboards {
|
||||
MultiClipboards {
|
||||
clipboards: vec_data
|
||||
.into_iter()
|
||||
.filter_map(clipboard_data_to_proto)
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_clipboard(clipboard: Clipboard) -> Option<ClipboardData> {
|
||||
let data = if clipboard.compress {
|
||||
decompress(&clipboard.content)
|
||||
} else {
|
||||
clipboard.content.into()
|
||||
};
|
||||
match clipboard.format.enum_value() {
|
||||
Ok(ClipboardFormat::Text) => String::from_utf8(data).ok().map(ClipboardData::Text),
|
||||
Ok(ClipboardFormat::Rtf) => String::from_utf8(data).ok().map(ClipboardData::Rtf),
|
||||
Ok(ClipboardFormat::Html) => String::from_utf8(data).ok().map(ClipboardData::Html),
|
||||
Ok(ClipboardFormat::ImageRgba) => Some(ClipboardData::Image(arboard::ImageData::rgba(
|
||||
clipboard.width as _,
|
||||
clipboard.height as _,
|
||||
data.into(),
|
||||
))),
|
||||
Ok(ClipboardFormat::ImagePng) => {
|
||||
Some(ClipboardData::Image(arboard::ImageData::png(data.into())))
|
||||
}
|
||||
Ok(ClipboardFormat::ImageSvg) => Some(ClipboardData::Image(arboard::ImageData::svg(
|
||||
std::str::from_utf8(&data).unwrap_or_default(),
|
||||
))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_multi_clipbards(multi_clipboards: Vec<Clipboard>) -> Vec<ClipboardData> {
|
||||
multi_clipboards
|
||||
.into_iter()
|
||||
.filter_map(from_clipboard)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_msg_if_not_support_multi_clip(
|
||||
version: &str,
|
||||
platform: &str,
|
||||
multi_clipboards: &MultiClipboards,
|
||||
) -> Option<Message> {
|
||||
if crate::clipboard::is_support_multi_clipboard(version, platform) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Find the first text clipboard and send it.
|
||||
multi_clipboards
|
||||
.clipboards
|
||||
.iter()
|
||||
.find(|c| c.format.enum_value() == Ok(ClipboardFormat::Text))
|
||||
.map(|c| {
|
||||
let mut msg = Message::new();
|
||||
msg.set_clipboard(c.clone());
|
||||
msg
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1256,6 +1256,19 @@ pub fn update_text_clipboard_required() {
|
||||
pub fn send_text_clipboard_msg(msg: Message) {
|
||||
for s in sessions::get_sessions() {
|
||||
if s.is_text_clipboard_required() {
|
||||
// Check if the client supports multi clipboards
|
||||
if let Some(message::Union::MultiClipboards(multi_clipboards)) = &msg.union {
|
||||
let version = s.ui_handler.peer_info.read().unwrap().version.clone();
|
||||
let platform = s.ui_handler.peer_info.read().unwrap().platform.clone();
|
||||
if let Some(msg_out) = crate::clipboard::get_msg_if_not_support_multi_clip(
|
||||
&version,
|
||||
&platform,
|
||||
multi_clipboards,
|
||||
) {
|
||||
s.send(Data::Message(msg_out));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
s.send(Data::Message(msg.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,49 +1,79 @@
|
||||
use super::*;
|
||||
pub use crate::clipboard::{
|
||||
check_clipboard, ClipboardContext, CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME,
|
||||
CONTENT,
|
||||
check_clipboard, ClipboardContext, ClipboardSide, CLIPBOARD_INTERVAL as INTERVAL,
|
||||
CLIPBOARD_NAME as NAME,
|
||||
};
|
||||
use clipboard_master::{CallbackResult, ClipboardHandler};
|
||||
use std::{
|
||||
io,
|
||||
sync::mpsc::{channel, RecvTimeoutError, Sender},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
struct Handler {
|
||||
sp: EmptyExtraFieldService,
|
||||
ctx: Option<ClipboardContext>,
|
||||
}
|
||||
|
||||
impl super::service::Reset for State {
|
||||
fn reset(&mut self) {
|
||||
*CONTENT.lock().unwrap() = Default::default();
|
||||
self.ctx = None;
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
let ctx = match ClipboardContext::new(true) {
|
||||
Ok(ctx) => Some(ctx),
|
||||
Err(err) => {
|
||||
log::error!("Failed to start {}: {}", NAME, err);
|
||||
None
|
||||
}
|
||||
};
|
||||
self.ctx = ctx;
|
||||
}
|
||||
tx_cb_result: Sender<CallbackResult>,
|
||||
}
|
||||
|
||||
pub fn new() -> GenericService {
|
||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||
GenericService::repeat::<State, _, _>(&svc.clone(), INTERVAL, run);
|
||||
GenericService::run(&svc.clone(), run);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
|
||||
if let Some(msg) = check_clipboard(&mut state.ctx, None) {
|
||||
sp.send(msg);
|
||||
}
|
||||
sp.snapshot(|sps| {
|
||||
let data = CONTENT.lock().unwrap().clone();
|
||||
if !data.is_empty() {
|
||||
let msg_out = data.create_msg();
|
||||
sps.send_shared(Arc::new(msg_out));
|
||||
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
let (tx_cb_result, rx_cb_result) = channel();
|
||||
let handler = Handler {
|
||||
sp: sp.clone(),
|
||||
ctx: Some(ClipboardContext::new()?),
|
||||
tx_cb_result,
|
||||
};
|
||||
|
||||
let (tx_start_res, rx_start_res) = channel();
|
||||
let h = crate::clipboard::start_clipbard_master_thread(handler, tx_start_res);
|
||||
let shutdown = match rx_start_res.recv() {
|
||||
Ok((Some(s), _)) => s,
|
||||
Ok((None, err)) => {
|
||||
bail!(err);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Err(e) => {
|
||||
bail!("Failed to create clipboard listener: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
while sp.ok() {
|
||||
match rx_cb_result.recv_timeout(Duration::from_millis(INTERVAL)) {
|
||||
Ok(CallbackResult::Stop) => {
|
||||
log::debug!("Clipboard listener stopped");
|
||||
break;
|
||||
}
|
||||
Ok(CallbackResult::StopWithError(err)) => {
|
||||
bail!("Clipboard listener stopped with error: {}", err);
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
shutdown.signal();
|
||||
h.join().ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ClipboardHandler for Handler {
|
||||
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||
self.sp.snapshot(|_sps| Ok(())).ok();
|
||||
if let Some(msg) = check_clipboard(&mut self.ctx, ClipboardSide::Host, false) {
|
||||
self.sp.send(msg);
|
||||
}
|
||||
CallbackResult::Next
|
||||
}
|
||||
|
||||
fn on_clipboard_error(&mut self, error: io::Error) -> CallbackResult {
|
||||
self.tx_cb_result
|
||||
.send(CallbackResult::StopWithError(error))
|
||||
.ok();
|
||||
CallbackResult::Next
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use super::{input_service::*, *};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::clipboard::update_clipboard;
|
||||
use crate::clipboard::{update_clipboard, ClipboardSide};
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use crate::clipboard_file::*;
|
||||
#[cfg(target_os = "android")]
|
||||
@ -682,8 +682,19 @@ impl Connection {
|
||||
msg = Arc::new(new_msg);
|
||||
}
|
||||
}
|
||||
Some(message::Union::MultiClipboards(_multi_clipboards)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(msg_out) = crate::clipboard::get_msg_if_not_support_multi_clip(&conn.lr.version, &conn.lr.my_platform, _multi_clipboards) {
|
||||
if let Err(err) = conn.stream.send(&msg_out).await {
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let msg: &Message = &msg;
|
||||
if let Err(err) = conn.stream.send(msg).await {
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
@ -2049,7 +2060,7 @@ impl Connection {
|
||||
Some(message::Union::Clipboard(cb)) => {
|
||||
if self.clipboard {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(cb, None);
|
||||
update_clipboard(vec![cb], ClipboardSide::Host);
|
||||
#[cfg(all(feature = "flutter", target_os = "android"))]
|
||||
{
|
||||
let content = if cb.compress {
|
||||
@ -2070,6 +2081,13 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message::Union::MultiClipboards(_mcb)) =>
|
||||
{
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.clipboard {
|
||||
update_clipboard(_mcb.clipboards, ClipboardSide::Host);
|
||||
}
|
||||
}
|
||||
Some(message::Union::Cliprdr(_clip)) =>
|
||||
{
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user