mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-06-07 18:02:48 +08:00
Merge pull request #5638 from ClSlaid/feat/x11/clipboard-file/init
[feat] x11 clipboard copy and paste file
This commit is contained in:
commit
46a363cce4
160
Cargo.lock
generated
160
Cargo.lock
generated
@ -243,7 +243,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"thiserror",
|
||||
"winapi 0.3.9",
|
||||
"x11rb",
|
||||
"x11rb 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -932,11 +932,21 @@ name = "clipboard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"dashmap",
|
||||
"fuser",
|
||||
"hbb_common",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"serde 1.0.163",
|
||||
"serde_derive",
|
||||
"thiserror",
|
||||
"utf16string",
|
||||
"x11-clipboard",
|
||||
"x11rb 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1375,6 +1385,19 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"hashbrown 0.14.0",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dasp"
|
||||
version = "0.11.0"
|
||||
@ -2221,6 +2244,21 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "fuser"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21370f84640642c8ea36dfb2a6bfc4c55941f476fcf431f6fef25a5ddcf0169b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"page_size",
|
||||
"pkg-config",
|
||||
"smallvec",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
@ -2431,6 +2469,16 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
@ -2851,6 +2899,12 @@ dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "hbb_common"
|
||||
version = "0.1.0"
|
||||
@ -3131,7 +3185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3385,9 +3439,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.144"
|
||||
version = "0.2.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@ -3568,9 +3622,9 @@ checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"scopeguard",
|
||||
@ -4150,7 +4204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4198,6 +4252,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pam"
|
||||
version = "0.7.0"
|
||||
@ -4288,15 +4352,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.7"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
|
||||
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"redox_syscall 0.3.5",
|
||||
"smallvec",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4342,9 +4406,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
@ -5193,9 +5257,11 @@ dependencies = [
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"objc_id",
|
||||
"once_cell",
|
||||
"os-version",
|
||||
"pam",
|
||||
"parity-tokio-ipc",
|
||||
"percent-encoding",
|
||||
"rdev",
|
||||
"repng",
|
||||
"reqwest",
|
||||
@ -5230,6 +5296,8 @@ dependencies = [
|
||||
"winreg 0.11.0",
|
||||
"winres",
|
||||
"wol-rs",
|
||||
"x11-clipboard",
|
||||
"x11rb 0.12.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@ -6221,7 +6289,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"tokio",
|
||||
@ -6510,6 +6578,15 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf16string"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b62a1e85e12d5d712bf47a85f426b73d303e2d00a90de5f3004df3596e9d216"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
@ -7258,6 +7335,14 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-clipboard"
|
||||
version = "0.8.1"
|
||||
source = "git+https://github.com/clslaid/x11-clipboard?branch=feat/store-batch#5fc2e73bc01ada3681159b34cf3ea8f0d14cd904"
|
||||
dependencies = [
|
||||
"x11rb 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
@ -7275,11 +7360,24 @@ version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"gethostname 0.2.3",
|
||||
"nix 0.24.3",
|
||||
"winapi 0.3.9",
|
||||
"winapi-wsapoll",
|
||||
"x11rb-protocol",
|
||||
"x11rb-protocol 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
|
||||
dependencies = [
|
||||
"gethostname 0.3.0",
|
||||
"nix 0.26.2",
|
||||
"winapi 0.3.9",
|
||||
"winapi-wsapoll",
|
||||
"x11rb-protocol 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7291,6 +7389,15 @@ dependencies = [
|
||||
"nix 0.24.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc"
|
||||
dependencies = [
|
||||
"nix 0.26.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xdg-home"
|
||||
version = "1.0.0"
|
||||
@ -7374,6 +7481,27 @@ dependencies = [
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3b9c234616391070b0b173963ebc65a9195068e7ed3731c6edac2ec45ebe106"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f7f3a471f98d0a61c34322fbbfd10c384b07687f680d4119813713f72308d91"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.63",
|
||||
"quote 1.0.27",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.5"
|
||||
|
11
Cargo.toml
11
Cargo.toml
@ -32,6 +32,13 @@ linux_headless = ["pam" ]
|
||||
virtual_display_driver = ["virtual_display"]
|
||||
plugin_framework = []
|
||||
linux-pkg-config = ["magnum-opus/linux-pkg-config", "scrap/linux-pkg-config"]
|
||||
unix-file-copy-paste = [
|
||||
"dep:x11-clipboard",
|
||||
"dep:x11rb",
|
||||
"dep:percent-encoding",
|
||||
"dep:once_cell",
|
||||
"clipboard/unix-file-copy-paste",
|
||||
]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -132,6 +139,10 @@ dbus = "0.9"
|
||||
dbus-crossroads = "0.5"
|
||||
pam = { git="https://github.com/fufesou/pam", optional = true }
|
||||
users = { version = "0.11" }
|
||||
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
|
||||
x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
|
||||
percent-encoding = {version = "2.3", optional = true}
|
||||
once_cell = {version = "1.18", optional = true}
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13"
|
||||
|
14
build.py
14
build.py
@ -126,6 +126,11 @@ def make_parser():
|
||||
action='store_true',
|
||||
help='Build windows portable'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--unix-file-copy-paste',
|
||||
action='store_true',
|
||||
help='Build with unix file copy paste feature'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--flatpak',
|
||||
action='store_true',
|
||||
@ -188,6 +193,7 @@ def download_extract_features(features, res_dir):
|
||||
import re
|
||||
|
||||
proxy = ''
|
||||
|
||||
def req(url):
|
||||
if not proxy:
|
||||
return url
|
||||
@ -275,6 +281,8 @@ def get_features(args):
|
||||
features.append('flatpak')
|
||||
if args.appimage:
|
||||
features.append('appimage')
|
||||
if args.unix_file_copy_paste:
|
||||
features.append('unix-file-copy-paste')
|
||||
print("features:", features)
|
||||
return features
|
||||
|
||||
@ -396,7 +404,8 @@ def build_deb_from_folder(version, binary_folder):
|
||||
def build_flutter_dmg(version, features):
|
||||
if not skip_cargo:
|
||||
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
||||
system2(f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
|
||||
system2(
|
||||
f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
|
||||
# copy dylib
|
||||
system2(
|
||||
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
||||
@ -562,7 +571,8 @@ def main():
|
||||
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/*
|
||||
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app
|
||||
'''.format(pa))
|
||||
system2('create-dmg "RustDesk %s.dmg" "target/release/bundle/osx/RustDesk.app"' % version)
|
||||
system2(
|
||||
'create-dmg "RustDesk %s.dmg" "target/release/bundle/osx/RustDesk.app"' % version)
|
||||
os.rename('RustDesk %s.dmg' %
|
||||
version, 'rustdesk-%s.dmg' % version)
|
||||
if pa:
|
||||
|
@ -436,9 +436,9 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
child: Text(translate('Mute'))));
|
||||
}
|
||||
// file copy and paste
|
||||
if (Platform.isWindows &&
|
||||
pi.platform == kPeerPlatformWindows &&
|
||||
perms['file'] != false) {
|
||||
if (perms['file'] != false &&
|
||||
bind.mainHasFileClipboard() &&
|
||||
pi.platformAdditions.containsKey(kPlatformAdditionsHasFileClipboard)) {
|
||||
final option = 'enable-file-transfer';
|
||||
final value =
|
||||
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||
|
@ -22,6 +22,7 @@ const String kPlatformAdditionsIsWayland = "is_wayland";
|
||||
const String kPlatformAdditionsHeadless = "headless";
|
||||
const String kPlatformAdditionsIsInstalled = "is_installed";
|
||||
const String kPlatformAdditionsVirtualDisplays = "virtual_displays";
|
||||
const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
|
||||
|
||||
const String kPeerPlatformWindows = "Windows";
|
||||
const String kPeerPlatformLinux = "Linux";
|
||||
|
@ -39,7 +39,7 @@ dependencies:
|
||||
package_info_plus: ^3.1.2
|
||||
url_launcher: ^6.0.9
|
||||
toggle_switch: ^2.1.0
|
||||
dash_chat_2:
|
||||
dash_chat_2:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/Dash-Chat-2
|
||||
draggable_float_widget: ^0.0.2
|
||||
@ -68,7 +68,7 @@ dependencies:
|
||||
get: ^4.6.5
|
||||
visibility_detector: ^0.4.0+2
|
||||
contextmenu: ^3.0.0
|
||||
desktop_drop:
|
||||
desktop_drop:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/flutter-plugins
|
||||
path: ./packages/desktop_drop
|
||||
@ -84,7 +84,7 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/flutter_improved_scrolling
|
||||
uni_links: ^0.5.1
|
||||
uni_links_desktop:
|
||||
uni_links_desktop:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/uni_links_desktop
|
||||
path: ^1.8.1
|
||||
|
@ -2,16 +2,42 @@
|
||||
name = "clipboard"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
unix-file-copy-paste = [
|
||||
"dep:x11rb",
|
||||
"dep:x11-clipboard",
|
||||
"dep:rand",
|
||||
"dep:fuser",
|
||||
"dep:libc",
|
||||
"dep:dashmap",
|
||||
"dep:percent-encoding",
|
||||
"dep:utf16string",
|
||||
"dep:once_cell"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0"
|
||||
lazy_static = "1.4"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
hbb_common = { path = "../hbb_common" }
|
||||
parking_lot = {version = "0.12"}
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
|
||||
once_cell = {version = "1.18", optional = true}
|
||||
x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
|
||||
rand = {version = "0.8", optional = true}
|
||||
fuser = {version = "0.13", optional = true}
|
||||
libc = {version = "0.2", optional = true}
|
||||
dashmap = {version ="5.5", optional = true}
|
||||
percent-encoding = {version ="2.3", optional = true}
|
||||
utf16string = {version = "0.2", optional = true}
|
||||
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
|
||||
|
@ -1,39 +1,37 @@
|
||||
use cc;
|
||||
|
||||
fn build_c_impl() {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let mut build = cc::Build::new();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
build.file("src/windows/wf_cliprdr.c");
|
||||
#[cfg(target_os = "linux")]
|
||||
build.file("src/X11/xf_cliprdr.c");
|
||||
#[cfg(target_os = "macos")]
|
||||
build.file("src/OSX/Clipboard.m");
|
||||
|
||||
build.flag_if_supported("-Wno-c++0x-extensions");
|
||||
build.flag_if_supported("-Wno-return-type-c-linkage");
|
||||
build.flag_if_supported("-Wno-invalid-offsetof");
|
||||
build.flag_if_supported("-Wno-unused-parameter");
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
build.flag_if_supported("-Wno-c++0x-extensions");
|
||||
build.flag_if_supported("-Wno-return-type-c-linkage");
|
||||
build.flag_if_supported("-Wno-invalid-offsetof");
|
||||
build.flag_if_supported("-Wno-unused-parameter");
|
||||
|
||||
if build.get_compiler().is_like_msvc() {
|
||||
build.define("WIN32", "");
|
||||
// build.define("_AMD64_", "");
|
||||
build.flag("-Z7");
|
||||
build.flag("-GR-");
|
||||
// build.flag("-std:c++11");
|
||||
} else {
|
||||
build.flag("-fPIC");
|
||||
// build.flag("-std=c++11");
|
||||
// build.flag("-include");
|
||||
// build.flag(&confdefs_path.to_string_lossy());
|
||||
if build.get_compiler().is_like_msvc() {
|
||||
build.define("WIN32", "");
|
||||
// build.define("_AMD64_", "");
|
||||
build.flag("-Z7");
|
||||
build.flag("-GR-");
|
||||
// build.flag("-std:c++11");
|
||||
} else {
|
||||
build.flag("-fPIC");
|
||||
// build.flag("-std=c++11");
|
||||
// build.flag("-include");
|
||||
// build.flag(&confdefs_path.to_string_lossy());
|
||||
}
|
||||
|
||||
build.compile("mycliprdr");
|
||||
}
|
||||
|
||||
build.compile("mycliprdr");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
println!("cargo:rerun-if-changed=src/windows/wf_cliprdr.c");
|
||||
#[cfg(target_os = "linux")]
|
||||
println!("cargo:rerun-if-changed=src/X11/xf_cliprdr.c");
|
||||
#[cfg(target_os = "macos")]
|
||||
println!("cargo:rerun-if-changed=src/OSX/Clipboard.m");
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
#include "../cliprdr.h"
|
||||
|
||||
void xf_cliprdr_init(CliprdrClientContext* cliprdr)
|
||||
{
|
||||
(void)cliprdr;
|
||||
}
|
||||
|
||||
void xf_cliprdr_uninit( CliprdrClientContext* cliprdr)
|
||||
{
|
||||
(void)cliprdr;
|
||||
}
|
@ -1,573 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(deref_nullptr)]
|
||||
|
||||
use std::{boxed::Box, result::Result};
|
||||
use thiserror::Error;
|
||||
|
||||
pub type size_t = ::std::os::raw::c_ulonglong;
|
||||
pub type __vcrt_bool = bool;
|
||||
pub type wchar_t = ::std::os::raw::c_ushort;
|
||||
|
||||
pub type POINTER_64_INT = ::std::os::raw::c_ulonglong;
|
||||
pub type INT8 = ::std::os::raw::c_schar;
|
||||
pub type PINT8 = *mut ::std::os::raw::c_schar;
|
||||
pub type INT16 = ::std::os::raw::c_short;
|
||||
pub type PINT16 = *mut ::std::os::raw::c_short;
|
||||
pub type INT32 = ::std::os::raw::c_int;
|
||||
pub type PINT32 = *mut ::std::os::raw::c_int;
|
||||
pub type INT64 = ::std::os::raw::c_longlong;
|
||||
pub type PINT64 = *mut ::std::os::raw::c_longlong;
|
||||
pub type UINT8 = ::std::os::raw::c_uchar;
|
||||
pub type PUINT8 = *mut ::std::os::raw::c_uchar;
|
||||
pub type UINT16 = ::std::os::raw::c_ushort;
|
||||
pub type PUINT16 = *mut ::std::os::raw::c_ushort;
|
||||
pub type UINT32 = ::std::os::raw::c_uint;
|
||||
pub type PUINT32 = *mut ::std::os::raw::c_uint;
|
||||
pub type UINT64 = ::std::os::raw::c_ulonglong;
|
||||
pub type PUINT64 = *mut ::std::os::raw::c_ulonglong;
|
||||
pub type LONG32 = ::std::os::raw::c_int;
|
||||
pub type PLONG32 = *mut ::std::os::raw::c_int;
|
||||
pub type ULONG32 = ::std::os::raw::c_uint;
|
||||
pub type PULONG32 = *mut ::std::os::raw::c_uint;
|
||||
pub type DWORD32 = ::std::os::raw::c_uint;
|
||||
pub type PDWORD32 = *mut ::std::os::raw::c_uint;
|
||||
pub type INT_PTR = ::std::os::raw::c_longlong;
|
||||
pub type PINT_PTR = *mut ::std::os::raw::c_longlong;
|
||||
pub type UINT_PTR = ::std::os::raw::c_ulonglong;
|
||||
pub type PUINT_PTR = *mut ::std::os::raw::c_ulonglong;
|
||||
pub type LONG_PTR = ::std::os::raw::c_longlong;
|
||||
pub type PLONG_PTR = *mut ::std::os::raw::c_longlong;
|
||||
pub type ULONG_PTR = ::std::os::raw::c_ulonglong;
|
||||
pub type PULONG_PTR = *mut ::std::os::raw::c_ulonglong;
|
||||
pub type SHANDLE_PTR = ::std::os::raw::c_longlong;
|
||||
pub type HANDLE_PTR = ::std::os::raw::c_ulonglong;
|
||||
pub type UHALF_PTR = ::std::os::raw::c_uint;
|
||||
pub type PUHALF_PTR = *mut ::std::os::raw::c_uint;
|
||||
pub type HALF_PTR = ::std::os::raw::c_int;
|
||||
pub type PHALF_PTR = *mut ::std::os::raw::c_int;
|
||||
pub type SIZE_T = ULONG_PTR;
|
||||
pub type PSIZE_T = *mut ULONG_PTR;
|
||||
pub type SSIZE_T = LONG_PTR;
|
||||
pub type PSSIZE_T = *mut LONG_PTR;
|
||||
pub type DWORD_PTR = ULONG_PTR;
|
||||
pub type PDWORD_PTR = *mut ULONG_PTR;
|
||||
pub type LONG64 = ::std::os::raw::c_longlong;
|
||||
pub type PLONG64 = *mut ::std::os::raw::c_longlong;
|
||||
pub type ULONG64 = ::std::os::raw::c_ulonglong;
|
||||
pub type PULONG64 = *mut ::std::os::raw::c_ulonglong;
|
||||
pub type DWORD64 = ::std::os::raw::c_ulonglong;
|
||||
pub type PDWORD64 = *mut ::std::os::raw::c_ulonglong;
|
||||
pub type KAFFINITY = ULONG_PTR;
|
||||
pub type PKAFFINITY = *mut KAFFINITY;
|
||||
pub type PVOID = *mut ::std::os::raw::c_void;
|
||||
pub type CHAR = ::std::os::raw::c_char;
|
||||
pub type SHORT = ::std::os::raw::c_short;
|
||||
pub type LONG = ::std::os::raw::c_long;
|
||||
pub type WCHAR = wchar_t;
|
||||
pub type PWCHAR = *mut WCHAR;
|
||||
pub type LPWCH = *mut WCHAR;
|
||||
pub type PWCH = *mut WCHAR;
|
||||
pub type LPCWCH = *const WCHAR;
|
||||
pub type PCWCH = *const WCHAR;
|
||||
pub type NWPSTR = *mut WCHAR;
|
||||
pub type LPWSTR = *mut WCHAR;
|
||||
pub type PWSTR = *mut WCHAR;
|
||||
pub type PZPWSTR = *mut PWSTR;
|
||||
pub type PCZPWSTR = *const PWSTR;
|
||||
pub type LPUWSTR = *mut WCHAR;
|
||||
pub type PUWSTR = *mut WCHAR;
|
||||
pub type LPCWSTR = *const WCHAR;
|
||||
pub type PCWSTR = *const WCHAR;
|
||||
pub type PZPCWSTR = *mut PCWSTR;
|
||||
pub type PCZPCWSTR = *const PCWSTR;
|
||||
pub type LPCUWSTR = *const WCHAR;
|
||||
pub type PCUWSTR = *const WCHAR;
|
||||
pub type PZZWSTR = *mut WCHAR;
|
||||
pub type PCZZWSTR = *const WCHAR;
|
||||
pub type PUZZWSTR = *mut WCHAR;
|
||||
pub type PCUZZWSTR = *const WCHAR;
|
||||
pub type PNZWCH = *mut WCHAR;
|
||||
pub type PCNZWCH = *const WCHAR;
|
||||
pub type PUNZWCH = *mut WCHAR;
|
||||
pub type PCUNZWCH = *const WCHAR;
|
||||
pub type PCHAR = *mut CHAR;
|
||||
pub type LPCH = *mut CHAR;
|
||||
pub type PCH = *mut CHAR;
|
||||
pub type LPCCH = *const CHAR;
|
||||
pub type PCCH = *const CHAR;
|
||||
pub type NPSTR = *mut CHAR;
|
||||
pub type LPSTR = *mut CHAR;
|
||||
pub type PSTR = *mut CHAR;
|
||||
pub type PZPSTR = *mut PSTR;
|
||||
pub type PCZPSTR = *const PSTR;
|
||||
pub type LPCSTR = *const CHAR;
|
||||
pub type PCSTR = *const CHAR;
|
||||
pub type PZPCSTR = *mut PCSTR;
|
||||
pub type PCZPCSTR = *const PCSTR;
|
||||
pub type PZZSTR = *mut CHAR;
|
||||
pub type PCZZSTR = *const CHAR;
|
||||
pub type PNZCH = *mut CHAR;
|
||||
pub type PCNZCH = *const CHAR;
|
||||
pub type TCHAR = ::std::os::raw::c_char;
|
||||
pub type PTCHAR = *mut ::std::os::raw::c_char;
|
||||
pub type TBYTE = ::std::os::raw::c_uchar;
|
||||
pub type PTBYTE = *mut ::std::os::raw::c_uchar;
|
||||
pub type LPTCH = LPCH;
|
||||
pub type PTCH = LPCH;
|
||||
pub type LPCTCH = LPCCH;
|
||||
pub type PCTCH = LPCCH;
|
||||
pub type PTSTR = LPSTR;
|
||||
pub type LPTSTR = LPSTR;
|
||||
pub type PUTSTR = LPSTR;
|
||||
pub type LPUTSTR = LPSTR;
|
||||
pub type PCTSTR = LPCSTR;
|
||||
pub type LPCTSTR = LPCSTR;
|
||||
pub type PCUTSTR = LPCSTR;
|
||||
pub type LPCUTSTR = LPCSTR;
|
||||
pub type PZZTSTR = PZZSTR;
|
||||
pub type PUZZTSTR = PZZSTR;
|
||||
pub type PCZZTSTR = PCZZSTR;
|
||||
pub type PCUZZTSTR = PCZZSTR;
|
||||
pub type PZPTSTR = PZPSTR;
|
||||
pub type PNZTCH = PNZCH;
|
||||
pub type PUNZTCH = PNZCH;
|
||||
pub type PCNZTCH = PCNZCH;
|
||||
pub type PCUNZTCH = PCNZCH;
|
||||
pub type PSHORT = *mut SHORT;
|
||||
pub type PLONG = *mut LONG;
|
||||
pub type ULONG = ::std::os::raw::c_ulong;
|
||||
pub type PULONG = *mut ULONG;
|
||||
pub type USHORT = ::std::os::raw::c_ushort;
|
||||
pub type PUSHORT = *mut USHORT;
|
||||
pub type UCHAR = ::std::os::raw::c_uchar;
|
||||
pub type PUCHAR = *mut UCHAR;
|
||||
pub type PSZ = *mut ::std::os::raw::c_char;
|
||||
pub type DWORD = ::std::os::raw::c_ulong;
|
||||
pub type BOOL = ::std::os::raw::c_int;
|
||||
pub type BYTE = ::std::os::raw::c_uchar;
|
||||
pub type WORD = ::std::os::raw::c_ushort;
|
||||
pub type FLOAT = f32;
|
||||
pub type PFLOAT = *mut FLOAT;
|
||||
pub type PBOOL = *mut BOOL;
|
||||
pub type LPBOOL = *mut BOOL;
|
||||
pub type PBYTE = *mut BYTE;
|
||||
pub type LPBYTE = *mut BYTE;
|
||||
pub type PINT = *mut ::std::os::raw::c_int;
|
||||
pub type LPINT = *mut ::std::os::raw::c_int;
|
||||
pub type PWORD = *mut WORD;
|
||||
pub type LPWORD = *mut WORD;
|
||||
pub type LPLONG = *mut ::std::os::raw::c_long;
|
||||
pub type PDWORD = *mut DWORD;
|
||||
pub type LPDWORD = *mut DWORD;
|
||||
pub type LPVOID = *mut ::std::os::raw::c_void;
|
||||
pub type LPCVOID = *const ::std::os::raw::c_void;
|
||||
pub type INT = ::std::os::raw::c_int;
|
||||
pub type UINT = ::std::os::raw::c_uint;
|
||||
pub type PUINT = *mut ::std::os::raw::c_uint;
|
||||
pub type va_list = *mut ::std::os::raw::c_char;
|
||||
|
||||
pub const TRUE: ::std::os::raw::c_int = 1;
|
||||
pub const FALSE: ::std::os::raw::c_int = 0;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_HEADER {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_HEADER = _CLIPRDR_HEADER;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_CAPABILITY_SET {
|
||||
pub capabilitySetType: UINT16,
|
||||
pub capabilitySetLength: UINT16,
|
||||
}
|
||||
pub type CLIPRDR_CAPABILITY_SET = _CLIPRDR_CAPABILITY_SET;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_GENERAL_CAPABILITY_SET {
|
||||
pub capabilitySetType: UINT16,
|
||||
pub capabilitySetLength: UINT16,
|
||||
pub version: UINT32,
|
||||
pub generalFlags: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_GENERAL_CAPABILITY_SET = _CLIPRDR_GENERAL_CAPABILITY_SET;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_CAPABILITIES {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub cCapabilitiesSets: UINT32,
|
||||
pub capabilitySets: *mut CLIPRDR_CAPABILITY_SET,
|
||||
}
|
||||
pub type CLIPRDR_CAPABILITIES = _CLIPRDR_CAPABILITIES;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_MONITOR_READY {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_MONITOR_READY = _CLIPRDR_MONITOR_READY;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_TEMP_DIRECTORY {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub szTempDir: [::std::os::raw::c_char; 520usize],
|
||||
}
|
||||
pub type CLIPRDR_TEMP_DIRECTORY = _CLIPRDR_TEMP_DIRECTORY;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_FORMAT {
|
||||
pub formatId: UINT32,
|
||||
pub formatName: *mut ::std::os::raw::c_char,
|
||||
}
|
||||
pub type CLIPRDR_FORMAT = _CLIPRDR_FORMAT;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_FORMAT_LIST {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub numFormats: UINT32,
|
||||
pub formats: *mut CLIPRDR_FORMAT,
|
||||
}
|
||||
pub type CLIPRDR_FORMAT_LIST = _CLIPRDR_FORMAT_LIST;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_FORMAT_LIST_RESPONSE {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_FORMAT_LIST_RESPONSE = _CLIPRDR_FORMAT_LIST_RESPONSE;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_LOCK_CLIPBOARD_DATA {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub clipDataId: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_LOCK_CLIPBOARD_DATA = _CLIPRDR_LOCK_CLIPBOARD_DATA;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_UNLOCK_CLIPBOARD_DATA {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub clipDataId: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_UNLOCK_CLIPBOARD_DATA = _CLIPRDR_UNLOCK_CLIPBOARD_DATA;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_FORMAT_DATA_REQUEST {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub requestedFormatId: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_FORMAT_DATA_REQUEST = _CLIPRDR_FORMAT_DATA_REQUEST;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_FORMAT_DATA_RESPONSE {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub requestedFormatData: *const BYTE,
|
||||
}
|
||||
pub type CLIPRDR_FORMAT_DATA_RESPONSE = _CLIPRDR_FORMAT_DATA_RESPONSE;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_FILE_CONTENTS_REQUEST {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub streamId: UINT32,
|
||||
pub listIndex: UINT32,
|
||||
pub dwFlags: UINT32,
|
||||
pub nPositionLow: UINT32,
|
||||
pub nPositionHigh: UINT32,
|
||||
pub cbRequested: UINT32,
|
||||
pub haveClipDataId: BOOL,
|
||||
pub clipDataId: UINT32,
|
||||
}
|
||||
pub type CLIPRDR_FILE_CONTENTS_REQUEST = _CLIPRDR_FILE_CONTENTS_REQUEST;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _CLIPRDR_FILE_CONTENTS_RESPONSE {
|
||||
pub connID: UINT32,
|
||||
pub msgType: UINT16,
|
||||
pub msgFlags: UINT16,
|
||||
pub dataLen: UINT32,
|
||||
pub streamId: UINT32,
|
||||
pub cbRequested: UINT32,
|
||||
pub requestedData: *const BYTE,
|
||||
}
|
||||
pub type CLIPRDR_FILE_CONTENTS_RESPONSE = _CLIPRDR_FILE_CONTENTS_RESPONSE;
|
||||
pub type CliprdrClientContext = _cliprdr_client_context;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct _NOTIFICATION_MESSAGE {
|
||||
pub r#type: UINT32, // 0 - info, 1 - warning, 2 - error
|
||||
pub msg: *const BYTE,
|
||||
pub details: *const BYTE,
|
||||
}
|
||||
pub type NOTIFICATION_MESSAGE = _NOTIFICATION_MESSAGE;
|
||||
pub type pcCliprdrServerCapabilities = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
capabilities: *const CLIPRDR_CAPABILITIES,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientCapabilities = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
capabilities: *const CLIPRDR_CAPABILITIES,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrMonitorReady = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
monitorReady: *const CLIPRDR_MONITOR_READY,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrTempDirectory = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
tempDirectory: *const CLIPRDR_TEMP_DIRECTORY,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcNotifyClipboardMsg =
|
||||
::std::option::Option<unsafe extern "C" fn(connID: UINT32, msg: *const NOTIFICATION_MESSAGE) -> UINT>;
|
||||
pub type pcCliprdrClientFormatList = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatList: *const CLIPRDR_FORMAT_LIST,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerFormatList = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatList: *const CLIPRDR_FORMAT_LIST,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientFormatListResponse = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatListResponse: *const CLIPRDR_FORMAT_LIST_RESPONSE,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerFormatListResponse = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatListResponse: *const CLIPRDR_FORMAT_LIST_RESPONSE,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientLockClipboardData = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
lockClipboardData: *const CLIPRDR_LOCK_CLIPBOARD_DATA,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerLockClipboardData = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
lockClipboardData: *const CLIPRDR_LOCK_CLIPBOARD_DATA,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientUnlockClipboardData = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
unlockClipboardData: *const CLIPRDR_UNLOCK_CLIPBOARD_DATA,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerUnlockClipboardData = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
unlockClipboardData: *const CLIPRDR_UNLOCK_CLIPBOARD_DATA,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientFormatDataRequest = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatDataRequest: *const CLIPRDR_FORMAT_DATA_REQUEST,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerFormatDataRequest = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatDataRequest: *const CLIPRDR_FORMAT_DATA_REQUEST,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientFormatDataResponse = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatDataResponse: *const CLIPRDR_FORMAT_DATA_RESPONSE,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerFormatDataResponse = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
formatDataResponse: *const CLIPRDR_FORMAT_DATA_RESPONSE,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientFileContentsRequest = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
fileContentsRequest: *const CLIPRDR_FILE_CONTENTS_REQUEST,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerFileContentsRequest = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
fileContentsRequest: *const CLIPRDR_FILE_CONTENTS_REQUEST,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientFileContentsResponse = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
fileContentsResponse: *const CLIPRDR_FILE_CONTENTS_RESPONSE,
|
||||
) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrServerFileContentsResponse = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
fileContentsResponse: *const CLIPRDR_FILE_CONTENTS_RESPONSE,
|
||||
) -> UINT,
|
||||
>;
|
||||
|
||||
// TODO: hide more members of clipboard context
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct _cliprdr_client_context {
|
||||
pub Custom: *mut ::std::os::raw::c_void,
|
||||
pub EnableFiles: BOOL,
|
||||
pub EnableOthers: BOOL,
|
||||
pub IsStopped: BOOL,
|
||||
pub ResponseWaitTimeoutSecs: UINT32,
|
||||
pub ServerCapabilities: pcCliprdrServerCapabilities,
|
||||
pub ClientCapabilities: pcCliprdrClientCapabilities,
|
||||
pub MonitorReady: pcCliprdrMonitorReady,
|
||||
pub TempDirectory: pcCliprdrTempDirectory,
|
||||
pub NotifyClipboardMsg: pcNotifyClipboardMsg,
|
||||
pub ClientFormatList: pcCliprdrClientFormatList,
|
||||
pub ServerFormatList: pcCliprdrServerFormatList,
|
||||
pub ClientFormatListResponse: pcCliprdrClientFormatListResponse,
|
||||
pub ServerFormatListResponse: pcCliprdrServerFormatListResponse,
|
||||
pub ClientLockClipboardData: pcCliprdrClientLockClipboardData,
|
||||
pub ServerLockClipboardData: pcCliprdrServerLockClipboardData,
|
||||
pub ClientUnlockClipboardData: pcCliprdrClientUnlockClipboardData,
|
||||
pub ServerUnlockClipboardData: pcCliprdrServerUnlockClipboardData,
|
||||
pub ClientFormatDataRequest: pcCliprdrClientFormatDataRequest,
|
||||
pub ServerFormatDataRequest: pcCliprdrServerFormatDataRequest,
|
||||
pub ClientFormatDataResponse: pcCliprdrClientFormatDataResponse,
|
||||
pub ServerFormatDataResponse: pcCliprdrServerFormatDataResponse,
|
||||
pub ClientFileContentsRequest: pcCliprdrClientFileContentsRequest,
|
||||
pub ServerFileContentsRequest: pcCliprdrServerFileContentsRequest,
|
||||
pub ClientFileContentsResponse: pcCliprdrClientFileContentsResponse,
|
||||
pub ServerFileContentsResponse: pcCliprdrServerFileContentsResponse,
|
||||
pub LastRequestedFormatId: UINT32,
|
||||
}
|
||||
|
||||
// #[link(name = "user32")]
|
||||
// #[link(name = "ole32")]
|
||||
extern "C" {
|
||||
pub(crate) fn init_cliprdr(context: *mut CliprdrClientContext) -> BOOL;
|
||||
pub(crate) fn uninit_cliprdr(context: *mut CliprdrClientContext) -> BOOL;
|
||||
pub(crate) fn empty_cliprdr(context: *mut CliprdrClientContext, connID: UINT32) -> BOOL;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CliprdrError {
|
||||
#[error("invalid cliprdr name")]
|
||||
CliprdrName,
|
||||
#[error("failed to init cliprdr")]
|
||||
CliprdrInit,
|
||||
#[error("unknown cliprdr error")]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl CliprdrClientContext {
|
||||
pub fn create(
|
||||
enable_files: bool,
|
||||
enable_others: bool,
|
||||
response_wait_timeout_secs: u32,
|
||||
notify_callback: pcNotifyClipboardMsg,
|
||||
client_format_list: pcCliprdrClientFormatList,
|
||||
client_format_list_response: pcCliprdrClientFormatListResponse,
|
||||
client_format_data_request: pcCliprdrClientFormatDataRequest,
|
||||
client_format_data_response: pcCliprdrClientFormatDataResponse,
|
||||
client_file_contents_request: pcCliprdrClientFileContentsRequest,
|
||||
client_file_contents_response: pcCliprdrClientFileContentsResponse,
|
||||
) -> Result<Box<Self>, CliprdrError> {
|
||||
let context = CliprdrClientContext {
|
||||
Custom: 0 as *mut _,
|
||||
EnableFiles: if enable_files { TRUE } else { FALSE },
|
||||
EnableOthers: if enable_others { TRUE } else { FALSE },
|
||||
IsStopped: FALSE,
|
||||
ResponseWaitTimeoutSecs: response_wait_timeout_secs,
|
||||
ServerCapabilities: None,
|
||||
ClientCapabilities: None,
|
||||
MonitorReady: None,
|
||||
TempDirectory: None,
|
||||
NotifyClipboardMsg: notify_callback,
|
||||
ClientFormatList: client_format_list,
|
||||
ServerFormatList: None,
|
||||
ClientFormatListResponse: client_format_list_response,
|
||||
ServerFormatListResponse: None,
|
||||
ClientLockClipboardData: None,
|
||||
ServerLockClipboardData: None,
|
||||
ClientUnlockClipboardData: None,
|
||||
ServerUnlockClipboardData: None,
|
||||
ClientFormatDataRequest: client_format_data_request,
|
||||
ServerFormatDataRequest: None,
|
||||
ClientFormatDataResponse: client_format_data_response,
|
||||
ServerFormatDataResponse: None,
|
||||
ClientFileContentsRequest: client_file_contents_request,
|
||||
ServerFileContentsRequest: None,
|
||||
ClientFileContentsResponse: client_file_contents_response,
|
||||
ServerFileContentsResponse: None,
|
||||
LastRequestedFormatId: 0,
|
||||
};
|
||||
let mut context = Box::new(context);
|
||||
unsafe {
|
||||
if FALSE == init_cliprdr(&mut (*context)) {
|
||||
println!("Failed to init cliprdr");
|
||||
Err(CliprdrError::CliprdrInit)
|
||||
} else {
|
||||
Ok(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CliprdrClientContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if FALSE == uninit_cliprdr(&mut *self) {
|
||||
println!("Failed to uninit cliprdr");
|
||||
} else {
|
||||
println!("Succeeded to uninit cliprdr");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,68 +1,72 @@
|
||||
use crate::cliprdr::*;
|
||||
use hbb_common::log;
|
||||
use hbb_common::{log, ResultType};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::CliprdrServiceContext;
|
||||
|
||||
const CLIPBOARD_RESPONSE_WAIT_TIMEOUT_SECS: u32 = 30;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CONTEXT_SEND: ContextSend = ContextSend{addr: Mutex::new(0)};
|
||||
static ref CONTEXT_SEND: ContextSend = ContextSend{addr: Mutex::new(None)};
|
||||
}
|
||||
|
||||
pub struct ContextSend {
|
||||
addr: Mutex<u64>,
|
||||
addr: Mutex<Option<Box<dyn CliprdrServiceContext>>>,
|
||||
}
|
||||
|
||||
impl ContextSend {
|
||||
#[inline]
|
||||
pub fn is_enabled() -> bool {
|
||||
*CONTEXT_SEND.addr.lock().unwrap() != 0
|
||||
CONTEXT_SEND.addr.lock().unwrap().is_some()
|
||||
}
|
||||
|
||||
pub fn set_is_stopped() {
|
||||
let _res = Self::proc(|c| {
|
||||
c.IsStopped = TRUE;
|
||||
0
|
||||
});
|
||||
let _res = Self::proc(|c| c.set_is_stopped().map_err(|e| e.into()));
|
||||
}
|
||||
|
||||
pub fn enable(enabled: bool) {
|
||||
let mut lock = CONTEXT_SEND.addr.lock().unwrap();
|
||||
if enabled {
|
||||
if *lock == 0 {
|
||||
match crate::create_cliprdr_context(
|
||||
true,
|
||||
false,
|
||||
CLIPBOARD_RESPONSE_WAIT_TIMEOUT_SECS,
|
||||
) {
|
||||
Ok(context) => {
|
||||
log::info!("clipboard context for file transfer created.");
|
||||
*lock = Box::into_raw(context) as _;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Create clipboard context for file transfer: {}",
|
||||
err.to_string()
|
||||
);
|
||||
}
|
||||
if lock.is_some() {
|
||||
return;
|
||||
}
|
||||
match crate::create_cliprdr_context(true, false, CLIPBOARD_RESPONSE_WAIT_TIMEOUT_SECS) {
|
||||
Ok(context) => {
|
||||
log::info!("clipboard context for file transfer created.");
|
||||
*lock = Some(context)
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"create clipboard context for file transfer: {}",
|
||||
err.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if *lock != 0 {
|
||||
unsafe {
|
||||
let _ = Box::from_raw(*lock as *mut CliprdrClientContext);
|
||||
}
|
||||
log::info!("clipboard context for file transfer destroyed.");
|
||||
*lock = 0;
|
||||
}
|
||||
} else if let Some(_clp) = lock.take() {
|
||||
*lock = None;
|
||||
log::info!("clipboard context for file transfer destroyed.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proc<F: FnOnce(&mut CliprdrClientContext) -> u32>(f: F) -> u32 {
|
||||
let lock = CONTEXT_SEND.addr.lock().unwrap();
|
||||
if *lock != 0 {
|
||||
unsafe { f(&mut *(*lock as *mut CliprdrClientContext)) }
|
||||
} else {
|
||||
0
|
||||
/// make sure the clipboard context is enabled.
|
||||
pub fn make_sure_enabled() -> ResultType<()> {
|
||||
let mut lock = CONTEXT_SEND.addr.lock().unwrap();
|
||||
if lock.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let ctx = crate::create_cliprdr_context(true, false, CLIPBOARD_RESPONSE_WAIT_TIMEOUT_SECS)?;
|
||||
*lock = Some(ctx);
|
||||
log::info!("clipboard context for file transfer recreated.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn proc<F: FnOnce(&mut Box<dyn CliprdrServiceContext>) -> ResultType<()>>(
|
||||
f: F,
|
||||
) -> ResultType<()> {
|
||||
let mut lock = CONTEXT_SEND.addr.lock().unwrap();
|
||||
match lock.as_mut() {
|
||||
Some(context) => f(context),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
use cliprdr::*;
|
||||
#[allow(dead_code)]
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
use hbb_common::{
|
||||
allow_err, lazy_static, log,
|
||||
tokio::sync::{
|
||||
@ -8,19 +13,59 @@ use hbb_common::{
|
||||
ResultType,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
boxed::Box,
|
||||
ffi::{CStr, CString},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod cliprdr;
|
||||
pub mod context_send;
|
||||
pub mod platform;
|
||||
pub use context_send::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const ERR_CODE_SERVER_FUNCTION_NONE: u32 = 0x00000001;
|
||||
#[cfg(target_os = "windows")]
|
||||
const ERR_CODE_INVALID_PARAMETER: u32 = 0x00000002;
|
||||
|
||||
pub(crate) use platform::create_cliprdr_context;
|
||||
|
||||
/// Ability to handle Clipboard File from remote rustdesk client
|
||||
///
|
||||
/// # Note
|
||||
/// There actually should be 2 parts to implement a useable clipboard file service,
|
||||
/// but this only contains the RPC server part.
|
||||
/// The local listener and transport part is too platform specific to wrap up in typeclasses.
|
||||
pub trait CliprdrServiceContext: Send + Sync {
|
||||
/// set to be stopped
|
||||
fn set_is_stopped(&mut self) -> Result<(), CliprdrError>;
|
||||
/// clear the content on clipboard
|
||||
fn empty_clipboard(&mut self, conn_id: i32) -> Result<bool, CliprdrError>;
|
||||
|
||||
/// run as a server for clipboard RPC
|
||||
fn server_clip_file(&mut self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError>;
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CliprdrError {
|
||||
#[error("invalid cliprdr name")]
|
||||
CliprdrName,
|
||||
#[error("failed to init cliprdr")]
|
||||
CliprdrInit,
|
||||
#[error("cliprdr out of memory")]
|
||||
CliprdrOutOfMemory,
|
||||
#[error("cliprdr internal error")]
|
||||
ClipboardInternalError,
|
||||
#[error("cliprdr occupied")]
|
||||
ClipboardOccupied,
|
||||
#[error("conversion failure")]
|
||||
ConversionFailure,
|
||||
#[error("failure to read clipboard")]
|
||||
OpenClipboard,
|
||||
#[error("failure to read file metadata or content")]
|
||||
FileError { path: PathBuf, err: std::io::Error },
|
||||
#[error("invalid request")]
|
||||
InvalidRequest { description: String },
|
||||
#[error("unknown cliprdr error")]
|
||||
Unknown(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "t", content = "c")]
|
||||
pub enum ClipboardFile {
|
||||
@ -63,6 +108,7 @@ pub enum ClipboardFile {
|
||||
struct MsgChannel {
|
||||
peer_id: String,
|
||||
conn_id: i32,
|
||||
#[allow(dead_code)]
|
||||
sender: UnboundedSender<ClipboardFile>,
|
||||
receiver: Arc<TokioMutex<UnboundedReceiver<ClipboardFile>>>,
|
||||
}
|
||||
@ -74,19 +120,19 @@ lazy_static::lazy_static! {
|
||||
|
||||
impl ClipboardFile {
|
||||
pub fn is_stopping_allowed(&self) -> bool {
|
||||
match self {
|
||||
matches!(
|
||||
self,
|
||||
ClipboardFile::MonitorReady
|
||||
| ClipboardFile::FormatList { .. }
|
||||
| ClipboardFile::FormatDataRequest { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
| ClipboardFile::FormatList { .. }
|
||||
| ClipboardFile::FormatDataRequest { .. }
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_stopping_allowed_from_peer(&self) -> bool {
|
||||
match self {
|
||||
ClipboardFile::MonitorReady | ClipboardFile::FormatList { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(
|
||||
self,
|
||||
ClipboardFile::MonitorReady | ClipboardFile::FormatList { .. }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,8 +194,21 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc<TokioMutex<UnboundedReceiver<C
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))]
|
||||
#[inline]
|
||||
fn send_data(conn_id: i32, data: ClipboardFile) {
|
||||
#[cfg(target_os = "windows")]
|
||||
return send_data_to_channel(conn_id, data);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if conn_id == 0 {
|
||||
send_data_to_all(data);
|
||||
} else {
|
||||
send_data_to_channel(conn_id, data);
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))]
|
||||
#[inline]
|
||||
fn send_data_to_channel(conn_id: i32, data: ClipboardFile) {
|
||||
// no need to handle result here
|
||||
if let Some(msg_channel) = VEC_MSG_CHANNEL
|
||||
.read()
|
||||
@ -161,608 +220,13 @@ fn send_data(conn_id: i32, data: ClipboardFile) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_clipboard(context: &mut CliprdrClientContext, conn_id: i32) -> bool {
|
||||
unsafe { TRUE == cliprdr::empty_cliprdr(context, conn_id as u32) }
|
||||
}
|
||||
|
||||
pub fn server_clip_file(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg: ClipboardFile,
|
||||
) -> u32 {
|
||||
let mut ret = 0;
|
||||
match msg {
|
||||
ClipboardFile::NotifyCallback { .. } => {
|
||||
// unreachable
|
||||
}
|
||||
ClipboardFile::MonitorReady => {
|
||||
log::debug!("server_monitor_ready called");
|
||||
ret = server_monitor_ready(context, conn_id);
|
||||
log::debug!(
|
||||
"server_monitor_ready called, conn_id {}, return {}",
|
||||
conn_id,
|
||||
ret
|
||||
);
|
||||
}
|
||||
ClipboardFile::FormatList { format_list } => {
|
||||
log::debug!(
|
||||
"server_format_list called, conn_id {}, format_list: {:?}",
|
||||
conn_id,
|
||||
&format_list
|
||||
);
|
||||
ret = server_format_list(context, conn_id, format_list);
|
||||
log::debug!(
|
||||
"server_format_list called, conn_id {}, return {}",
|
||||
conn_id,
|
||||
ret
|
||||
);
|
||||
}
|
||||
ClipboardFile::FormatListResponse { msg_flags } => {
|
||||
log::debug!("server_format_list_response called");
|
||||
ret = server_format_list_response(context, conn_id, msg_flags);
|
||||
log::debug!(
|
||||
"server_format_list_response called, conn_id {}, msg_flags {}, return {}",
|
||||
conn_id,
|
||||
msg_flags,
|
||||
ret
|
||||
);
|
||||
}
|
||||
ClipboardFile::FormatDataRequest {
|
||||
requested_format_id,
|
||||
} => {
|
||||
log::debug!("server_format_data_request called");
|
||||
ret = server_format_data_request(context, conn_id, requested_format_id);
|
||||
log::debug!(
|
||||
"server_format_data_request called, conn_id {}, requested_format_id {}, return {}",
|
||||
conn_id,
|
||||
requested_format_id,
|
||||
ret
|
||||
);
|
||||
}
|
||||
ClipboardFile::FormatDataResponse {
|
||||
msg_flags,
|
||||
format_data,
|
||||
} => {
|
||||
log::debug!("server_format_data_response called");
|
||||
ret = server_format_data_response(context, conn_id, msg_flags, format_data);
|
||||
log::debug!(
|
||||
"server_format_data_response called, conn_id {}, msg_flags: {}, return {}",
|
||||
conn_id,
|
||||
msg_flags,
|
||||
ret
|
||||
);
|
||||
}
|
||||
ClipboardFile::FileContentsRequest {
|
||||
stream_id,
|
||||
list_index,
|
||||
dw_flags,
|
||||
n_position_low,
|
||||
n_position_high,
|
||||
cb_requested,
|
||||
have_clip_data_id,
|
||||
clip_data_id,
|
||||
} => {
|
||||
log::debug!("server_file_contents_request called");
|
||||
ret = server_file_contents_request(
|
||||
context,
|
||||
conn_id,
|
||||
stream_id,
|
||||
list_index,
|
||||
dw_flags,
|
||||
n_position_low,
|
||||
n_position_high,
|
||||
cb_requested,
|
||||
have_clip_data_id,
|
||||
clip_data_id,
|
||||
);
|
||||
log::debug!("server_file_contents_request called, conn_id {}, stream_id: {}, list_index {}, dw_flags {}, n_position_low {}, n_position_high {}, cb_requested {}, have_clip_data_id {}, clip_data_id {}, return {}", conn_id,
|
||||
stream_id,
|
||||
list_index,
|
||||
dw_flags,
|
||||
n_position_low,
|
||||
n_position_high,
|
||||
cb_requested,
|
||||
have_clip_data_id,
|
||||
clip_data_id,
|
||||
ret
|
||||
);
|
||||
}
|
||||
ClipboardFile::FileContentsResponse {
|
||||
msg_flags,
|
||||
stream_id,
|
||||
requested_data,
|
||||
} => {
|
||||
log::debug!("server_file_contents_response called");
|
||||
ret = server_file_contents_response(
|
||||
context,
|
||||
conn_id,
|
||||
msg_flags,
|
||||
stream_id,
|
||||
requested_data,
|
||||
);
|
||||
log::debug!("server_file_contents_response called, conn_id {}, msg_flags {}, stream_id {}, return {}",
|
||||
conn_id,
|
||||
msg_flags,
|
||||
stream_id,
|
||||
ret
|
||||
);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn server_monitor_ready(context: &mut CliprdrClientContext, conn_id: i32) -> u32 {
|
||||
unsafe {
|
||||
let monitor_ready = CLIPRDR_MONITOR_READY {
|
||||
connID: conn_id as UINT32,
|
||||
msgType: 0 as UINT16,
|
||||
msgFlags: 0 as UINT16,
|
||||
dataLen: 0 as UINT32,
|
||||
};
|
||||
if let Some(f) = context.MonitorReady {
|
||||
let ret = f(context, &monitor_ready);
|
||||
ret as u32
|
||||
} else {
|
||||
ERR_CODE_SERVER_FUNCTION_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_format_list(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
format_list: Vec<(i32, String)>,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
let num_formats = format_list.len() as UINT32;
|
||||
let mut formats = format_list
|
||||
.into_iter()
|
||||
.map(|format| {
|
||||
if format.1.is_empty() {
|
||||
CLIPRDR_FORMAT {
|
||||
formatId: format.0 as UINT32,
|
||||
formatName: 0 as *mut _,
|
||||
}
|
||||
} else {
|
||||
let n = match CString::new(format.1) {
|
||||
Ok(n) => n,
|
||||
Err(_) => CString::new("").unwrap(),
|
||||
};
|
||||
CLIPRDR_FORMAT {
|
||||
formatId: format.0 as UINT32,
|
||||
formatName: n.into_raw(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<CLIPRDR_FORMAT>>();
|
||||
|
||||
let format_list = CLIPRDR_FORMAT_LIST {
|
||||
connID: conn_id as UINT32,
|
||||
msgType: 0 as UINT16,
|
||||
msgFlags: 0 as UINT16,
|
||||
dataLen: 0 as UINT32,
|
||||
numFormats: num_formats,
|
||||
formats: formats.as_mut_ptr(),
|
||||
};
|
||||
|
||||
let ret = if let Some(f) = context.ServerFormatList {
|
||||
f(context, &format_list)
|
||||
} else {
|
||||
ERR_CODE_SERVER_FUNCTION_NONE
|
||||
};
|
||||
|
||||
for f in formats {
|
||||
if !f.formatName.is_null() {
|
||||
// retake pointer to free memory
|
||||
let _ = CString::from_raw(f.formatName);
|
||||
}
|
||||
}
|
||||
|
||||
ret as u32
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_format_list_response(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg_flags: i32,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
let format_list_response = CLIPRDR_FORMAT_LIST_RESPONSE {
|
||||
connID: conn_id as UINT32,
|
||||
msgType: 0 as UINT16,
|
||||
msgFlags: msg_flags as UINT16,
|
||||
dataLen: 0 as UINT32,
|
||||
};
|
||||
|
||||
if let Some(f) = context.ServerFormatListResponse {
|
||||
f(context, &format_list_response)
|
||||
} else {
|
||||
ERR_CODE_SERVER_FUNCTION_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_format_data_request(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
requested_format_id: i32,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
let format_data_request = CLIPRDR_FORMAT_DATA_REQUEST {
|
||||
connID: conn_id as UINT32,
|
||||
msgType: 0 as UINT16,
|
||||
msgFlags: 0 as UINT16,
|
||||
dataLen: 0 as UINT32,
|
||||
requestedFormatId: requested_format_id as UINT32,
|
||||
};
|
||||
if let Some(f) = context.ServerFormatDataRequest {
|
||||
f(context, &format_data_request)
|
||||
} else {
|
||||
ERR_CODE_SERVER_FUNCTION_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_format_data_response(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg_flags: i32,
|
||||
mut format_data: Vec<u8>,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
let format_data_response = CLIPRDR_FORMAT_DATA_RESPONSE {
|
||||
connID: conn_id as UINT32,
|
||||
msgType: 0 as UINT16,
|
||||
msgFlags: msg_flags as UINT16,
|
||||
dataLen: format_data.len() as UINT32,
|
||||
requestedFormatData: format_data.as_mut_ptr(),
|
||||
};
|
||||
if let Some(f) = context.ServerFormatDataResponse {
|
||||
f(context, &format_data_response)
|
||||
} else {
|
||||
ERR_CODE_SERVER_FUNCTION_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_file_contents_request(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
stream_id: i32,
|
||||
list_index: i32,
|
||||
dw_flags: i32,
|
||||
n_position_low: i32,
|
||||
n_position_high: i32,
|
||||
cb_requested: i32,
|
||||
have_clip_data_id: bool,
|
||||
clip_data_id: i32,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
let file_contents_request = CLIPRDR_FILE_CONTENTS_REQUEST {
|
||||
connID: conn_id as UINT32,
|
||||
msgType: 0 as UINT16,
|
||||
msgFlags: 0 as UINT16,
|
||||
dataLen: 0 as UINT32,
|
||||
streamId: stream_id as UINT32,
|
||||
listIndex: list_index as UINT32,
|
||||
dwFlags: dw_flags as UINT32,
|
||||
nPositionLow: n_position_low as UINT32,
|
||||
nPositionHigh: n_position_high as UINT32,
|
||||
cbRequested: cb_requested as UINT32,
|
||||
haveClipDataId: if have_clip_data_id { TRUE } else { FALSE },
|
||||
clipDataId: clip_data_id as UINT32,
|
||||
};
|
||||
if let Some(f) = context.ServerFileContentsRequest {
|
||||
f(context, &file_contents_request)
|
||||
} else {
|
||||
ERR_CODE_SERVER_FUNCTION_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_file_contents_response(
|
||||
context: &mut CliprdrClientContext,
|
||||
conn_id: i32,
|
||||
msg_flags: i32,
|
||||
stream_id: i32,
|
||||
mut requested_data: Vec<u8>,
|
||||
) -> u32 {
|
||||
unsafe {
|
||||
let file_contents_response = CLIPRDR_FILE_CONTENTS_RESPONSE {
|
||||
connID: conn_id as UINT32,
|
||||
msgType: 0 as UINT16,
|
||||
msgFlags: msg_flags as UINT16,
|
||||
dataLen: 4 + requested_data.len() as UINT32,
|
||||
streamId: stream_id as UINT32,
|
||||
cbRequested: requested_data.len() as UINT32,
|
||||
requestedData: requested_data.as_mut_ptr(),
|
||||
};
|
||||
if let Some(f) = context.ServerFileContentsResponse {
|
||||
f(context, &file_contents_response)
|
||||
} else {
|
||||
ERR_CODE_SERVER_FUNCTION_NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_cliprdr_context(
|
||||
enable_files: bool,
|
||||
enable_others: bool,
|
||||
response_wait_timeout_secs: u32,
|
||||
) -> ResultType<Box<CliprdrClientContext>> {
|
||||
Ok(CliprdrClientContext::create(
|
||||
enable_files,
|
||||
enable_others,
|
||||
response_wait_timeout_secs,
|
||||
Some(notify_callback),
|
||||
Some(client_format_list),
|
||||
Some(client_format_list_response),
|
||||
Some(client_format_data_request),
|
||||
Some(client_format_data_response),
|
||||
Some(client_file_contents_request),
|
||||
Some(client_file_contents_response),
|
||||
)?)
|
||||
}
|
||||
|
||||
extern "C" fn notify_callback(conn_id: UINT32, msg: *const NOTIFICATION_MESSAGE) -> UINT {
|
||||
log::debug!("notify_callback called");
|
||||
let data = unsafe {
|
||||
let msg = &*msg;
|
||||
let details = if msg.details.is_null() {
|
||||
Ok("")
|
||||
} else {
|
||||
CStr::from_ptr(msg.details as _).to_str()
|
||||
};
|
||||
match (CStr::from_ptr(msg.msg as _).to_str(), details) {
|
||||
(Ok(m), Ok(d)) => {
|
||||
let msgtype = format!(
|
||||
"custom-{}-nocancel-nook-hasclose",
|
||||
if msg.r#type == 0 {
|
||||
"info"
|
||||
} else if msg.r#type == 1 {
|
||||
"warn"
|
||||
} else {
|
||||
"error"
|
||||
}
|
||||
);
|
||||
let title = "Clipboard";
|
||||
let text = if d.is_empty() {
|
||||
m.to_string()
|
||||
} else {
|
||||
format!("{} {}", m, d)
|
||||
};
|
||||
ClipboardFile::NotifyCallback {
|
||||
r#type: msgtype,
|
||||
title: title.to_string(),
|
||||
text,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::error!("notify_callback: failed to convert msg");
|
||||
return ERR_CODE_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
#[inline]
|
||||
fn send_data_to_all(data: ClipboardFile) {
|
||||
// no need to handle result here
|
||||
send_data(conn_id as _, data);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn client_format_list(
|
||||
_context: *mut CliprdrClientContext,
|
||||
clip_format_list: *const CLIPRDR_FORMAT_LIST,
|
||||
) -> UINT {
|
||||
let conn_id;
|
||||
let mut format_list: Vec<(i32, String)> = Vec::new();
|
||||
unsafe {
|
||||
let mut i = 0u32;
|
||||
while i < (*clip_format_list).numFormats {
|
||||
let format_data = &(*(*clip_format_list).formats.offset(i as isize));
|
||||
if format_data.formatName.is_null() {
|
||||
format_list.push((format_data.formatId as i32, "".to_owned()));
|
||||
} else {
|
||||
let format_name = CStr::from_ptr(format_data.formatName).to_str();
|
||||
let format_name = match format_name {
|
||||
Ok(n) => n.to_owned(),
|
||||
Err(_) => {
|
||||
log::warn!("failed to get format name");
|
||||
"".to_owned()
|
||||
}
|
||||
};
|
||||
format_list.push((format_data.formatId as i32, format_name));
|
||||
}
|
||||
// log::debug!("format list item {}: format id: {}, format name: {}", i, format_data.formatId, &format_name);
|
||||
i += 1;
|
||||
}
|
||||
conn_id = (*clip_format_list).connID as i32;
|
||||
for msg_channel in VEC_MSG_CHANNEL.read().unwrap().iter() {
|
||||
allow_err!(msg_channel.sender.send(data.clone()));
|
||||
}
|
||||
log::debug!(
|
||||
"client_format_list called, client id: {}, format_list: {:?}",
|
||||
conn_id,
|
||||
&format_list
|
||||
);
|
||||
let data = ClipboardFile::FormatList { format_list };
|
||||
// no need to handle result here
|
||||
if conn_id == 0 {
|
||||
// msg_channel is used for debug, VEC_MSG_CHANNEL cannot be inspected by the debugger.
|
||||
let msg_channel = VEC_MSG_CHANNEL.read().unwrap();
|
||||
msg_channel
|
||||
.iter()
|
||||
.for_each(|msg_channel| allow_err!(msg_channel.sender.send(data.clone())));
|
||||
} else {
|
||||
send_data(conn_id, data);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn client_format_list_response(
|
||||
_context: *mut CliprdrClientContext,
|
||||
format_list_response: *const CLIPRDR_FORMAT_LIST_RESPONSE,
|
||||
) -> UINT {
|
||||
let conn_id;
|
||||
let msg_flags;
|
||||
unsafe {
|
||||
conn_id = (*format_list_response).connID as i32;
|
||||
msg_flags = (*format_list_response).msgFlags as i32;
|
||||
}
|
||||
log::debug!(
|
||||
"client_format_list_response called, client id: {}, msg_flags: {}",
|
||||
conn_id,
|
||||
msg_flags
|
||||
);
|
||||
let data = ClipboardFile::FormatListResponse { msg_flags };
|
||||
send_data(conn_id, data);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn client_format_data_request(
|
||||
_context: *mut CliprdrClientContext,
|
||||
format_data_request: *const CLIPRDR_FORMAT_DATA_REQUEST,
|
||||
) -> UINT {
|
||||
let conn_id;
|
||||
let requested_format_id;
|
||||
unsafe {
|
||||
conn_id = (*format_data_request).connID as i32;
|
||||
requested_format_id = (*format_data_request).requestedFormatId as i32;
|
||||
}
|
||||
let data = ClipboardFile::FormatDataRequest {
|
||||
requested_format_id,
|
||||
};
|
||||
log::debug!(
|
||||
"client_format_data_request called, conn_id: {}, requested_format_id: {}",
|
||||
conn_id,
|
||||
requested_format_id
|
||||
);
|
||||
// no need to handle result here
|
||||
send_data(conn_id, data);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn client_format_data_response(
|
||||
_context: *mut CliprdrClientContext,
|
||||
format_data_response: *const CLIPRDR_FORMAT_DATA_RESPONSE,
|
||||
) -> UINT {
|
||||
let conn_id;
|
||||
let msg_flags;
|
||||
let format_data;
|
||||
unsafe {
|
||||
conn_id = (*format_data_response).connID as i32;
|
||||
msg_flags = (*format_data_response).msgFlags as i32;
|
||||
if (*format_data_response).requestedFormatData.is_null() {
|
||||
format_data = Vec::new();
|
||||
} else {
|
||||
format_data = std::slice::from_raw_parts(
|
||||
(*format_data_response).requestedFormatData,
|
||||
(*format_data_response).dataLen as usize,
|
||||
)
|
||||
.to_vec();
|
||||
}
|
||||
}
|
||||
log::debug!(
|
||||
"client_format_data_response called, client id: {}, msg_flags: {}",
|
||||
conn_id,
|
||||
msg_flags
|
||||
);
|
||||
let data = ClipboardFile::FormatDataResponse {
|
||||
msg_flags,
|
||||
format_data,
|
||||
};
|
||||
send_data(conn_id, data);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn client_file_contents_request(
|
||||
_context: *mut CliprdrClientContext,
|
||||
file_contents_request: *const CLIPRDR_FILE_CONTENTS_REQUEST,
|
||||
) -> UINT {
|
||||
// TODO: support huge file?
|
||||
// if (!cliprdr->hasHugeFileSupport)
|
||||
// {
|
||||
// if (((UINT64)fileContentsRequest->cbRequested + fileContentsRequest->nPositionLow) >
|
||||
// UINT32_MAX)
|
||||
// return ERROR_INVALID_PARAMETER;
|
||||
// if (fileContentsRequest->nPositionHigh != 0)
|
||||
// return ERROR_INVALID_PARAMETER;
|
||||
// }
|
||||
|
||||
let conn_id;
|
||||
let stream_id;
|
||||
let list_index;
|
||||
let dw_flags;
|
||||
let n_position_low;
|
||||
let n_position_high;
|
||||
let cb_requested;
|
||||
let have_clip_data_id;
|
||||
let clip_data_id;
|
||||
unsafe {
|
||||
conn_id = (*file_contents_request).connID as i32;
|
||||
stream_id = (*file_contents_request).streamId as i32;
|
||||
list_index = (*file_contents_request).listIndex as i32;
|
||||
dw_flags = (*file_contents_request).dwFlags as i32;
|
||||
n_position_low = (*file_contents_request).nPositionLow as i32;
|
||||
n_position_high = (*file_contents_request).nPositionHigh as i32;
|
||||
cb_requested = (*file_contents_request).cbRequested as i32;
|
||||
have_clip_data_id = (*file_contents_request).haveClipDataId == TRUE;
|
||||
clip_data_id = (*file_contents_request).clipDataId as i32;
|
||||
}
|
||||
let data = ClipboardFile::FileContentsRequest {
|
||||
stream_id,
|
||||
list_index,
|
||||
dw_flags,
|
||||
n_position_low,
|
||||
n_position_high,
|
||||
cb_requested,
|
||||
have_clip_data_id,
|
||||
clip_data_id,
|
||||
};
|
||||
log::debug!("client_file_contents_request called, data: {:?}", &data);
|
||||
send_data(conn_id, data);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn client_file_contents_response(
|
||||
_context: *mut CliprdrClientContext,
|
||||
file_contents_response: *const CLIPRDR_FILE_CONTENTS_RESPONSE,
|
||||
) -> UINT {
|
||||
let conn_id;
|
||||
let msg_flags;
|
||||
let stream_id;
|
||||
let requested_data;
|
||||
unsafe {
|
||||
conn_id = (*file_contents_response).connID as i32;
|
||||
msg_flags = (*file_contents_response).msgFlags as i32;
|
||||
stream_id = (*file_contents_response).streamId as i32;
|
||||
if (*file_contents_response).requestedData.is_null() {
|
||||
requested_data = Vec::new();
|
||||
} else {
|
||||
requested_data = std::slice::from_raw_parts(
|
||||
(*file_contents_response).requestedData,
|
||||
(*file_contents_response).cbRequested as usize,
|
||||
)
|
||||
.to_vec();
|
||||
}
|
||||
}
|
||||
let data = ClipboardFile::FileContentsResponse {
|
||||
msg_flags,
|
||||
stream_id,
|
||||
requested_data,
|
||||
};
|
||||
log::debug!(
|
||||
"client_file_contents_response called, conn_id: {}, msg_flags: {}, stream_id: {}",
|
||||
conn_id,
|
||||
msg_flags,
|
||||
stream_id
|
||||
);
|
||||
send_data(conn_id, data);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
1168
libs/clipboard/src/platform/fuse.rs
Normal file
1168
libs/clipboard/src/platform/fuse.rs
Normal file
File diff suppressed because it is too large
Load Diff
86
libs/clipboard/src/platform/mod.rs
Normal file
86
libs/clipboard/src/platform/mod.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::{CliprdrError, CliprdrServiceContext};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn create_cliprdr_context(
|
||||
enable_files: bool,
|
||||
enable_others: bool,
|
||||
response_wait_timeout_secs: u32,
|
||||
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
|
||||
let boxed =
|
||||
windows::create_cliprdr_context(enable_files, enable_others, response_wait_timeout_secs)?
|
||||
as Box<_>;
|
||||
Ok(boxed)
|
||||
}
|
||||
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
/// use FUSE for file pasting on these platforms
|
||||
pub mod fuse;
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub mod unix;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn create_cliprdr_context(
|
||||
_enable_files: bool,
|
||||
_enable_others: bool,
|
||||
_response_wait_timeout_secs: u32,
|
||||
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
{
|
||||
use std::{fs::Permissions, os::unix::prelude::PermissionsExt};
|
||||
|
||||
use hbb_common::{config::APP_NAME, log};
|
||||
|
||||
if !_enable_files {
|
||||
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
|
||||
}
|
||||
|
||||
let timeout = std::time::Duration::from_secs(_response_wait_timeout_secs as u64);
|
||||
|
||||
let app_name = APP_NAME.read().unwrap().clone();
|
||||
|
||||
let mnt_path = format!("/tmp/{}/{}", app_name, "cliprdr");
|
||||
|
||||
// this function must be called after the main IPC is up
|
||||
std::fs::create_dir(&mnt_path).ok();
|
||||
std::fs::set_permissions(&mnt_path, Permissions::from_mode(0o777)).ok();
|
||||
|
||||
log::info!("clear previously mounted cliprdr FUSE");
|
||||
if let Err(e) = std::process::Command::new("umount").arg(&mnt_path).status() {
|
||||
log::warn!("umount {:?} may fail: {:?}", mnt_path, e);
|
||||
}
|
||||
|
||||
let unix_ctx = unix::ClipboardContext::new(timeout, mnt_path.parse().unwrap())?;
|
||||
log::debug!("start cliprdr FUSE");
|
||||
unix_ctx.run().expect("failed to start cliprdr FUSE");
|
||||
|
||||
Ok(Box::new(unix_ctx) as Box<_>)
|
||||
}
|
||||
#[cfg(not(feature = "unix-file-copy-paste"))]
|
||||
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
|
||||
}
|
||||
|
||||
struct DummyCliprdrContext {}
|
||||
|
||||
impl CliprdrServiceContext for DummyCliprdrContext {
|
||||
fn set_is_stopped(&mut self) -> Result<(), CliprdrError> {
|
||||
Ok(())
|
||||
}
|
||||
fn empty_clipboard(&mut self, _conn_id: i32) -> Result<bool, CliprdrError> {
|
||||
Ok(true)
|
||||
}
|
||||
fn server_clip_file(
|
||||
&mut self,
|
||||
_conn_id: i32,
|
||||
_msg: crate::ClipboardFile,
|
||||
) -> Result<(), crate::CliprdrError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
// begin of epoch used by microsoft
|
||||
// 1601-01-01 00:00:00 + LDAP_EPOCH_DELTA*(100 ns) = 1970-01-01 00:00:00
|
||||
const LDAP_EPOCH_DELTA: u64 = 116444772610000000;
|
367
libs/clipboard/src/platform/unix/local_file.rs
Normal file
367
libs/clipboard/src/platform/unix/local_file.rs
Normal file
@ -0,0 +1,367 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Read, Seek},
|
||||
os::unix::prelude::PermissionsExt,
|
||||
path::PathBuf,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use hbb_common::{
|
||||
bytes::{BufMut, BytesMut},
|
||||
log,
|
||||
};
|
||||
use utf16string::WString;
|
||||
|
||||
use crate::{
|
||||
platform::{fuse::BLOCK_SIZE, LDAP_EPOCH_DELTA},
|
||||
CliprdrError,
|
||||
};
|
||||
|
||||
/// has valid file attributes
|
||||
const FLAGS_FD_ATTRIBUTES: u32 = 0x04;
|
||||
/// has valid file size
|
||||
const FLAGS_FD_SIZE: u32 = 0x40;
|
||||
/// has valid last write time
|
||||
const FLAGS_FD_LAST_WRITE: u32 = 0x20;
|
||||
/// show progress
|
||||
const FLAGS_FD_PROGRESSUI: u32 = 0x4000;
|
||||
/// transferred from unix, contains file mode
|
||||
/// P.S. this flag is not used in windows
|
||||
const FLAGS_FD_UNIX_MODE: u32 = 0x08;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct LocalFile {
|
||||
pub path: PathBuf,
|
||||
|
||||
pub handle: Option<BufReader<File>>,
|
||||
pub offset: AtomicU64,
|
||||
|
||||
pub name: String,
|
||||
pub size: u64,
|
||||
pub last_write_time: SystemTime,
|
||||
pub is_dir: bool,
|
||||
pub perm: u32,
|
||||
pub read_only: bool,
|
||||
pub hidden: bool,
|
||||
pub system: bool,
|
||||
pub archive: bool,
|
||||
pub normal: bool,
|
||||
}
|
||||
|
||||
impl LocalFile {
|
||||
pub fn try_open(path: &PathBuf) -> Result<Self, CliprdrError> {
|
||||
let mt = std::fs::metadata(path).map_err(|e| CliprdrError::FileError {
|
||||
path: path.clone(),
|
||||
err: e,
|
||||
})?;
|
||||
let size = mt.len() as u64;
|
||||
let is_dir = mt.is_dir();
|
||||
let read_only = mt.permissions().readonly();
|
||||
let system = false;
|
||||
let hidden = path.to_string_lossy().starts_with('.');
|
||||
let archive = false;
|
||||
let normal = !(is_dir || read_only || system || hidden || archive);
|
||||
let last_write_time = mt.modified().unwrap_or(SystemTime::UNIX_EPOCH);
|
||||
|
||||
let perm = mt.permissions().mode();
|
||||
|
||||
let name = path
|
||||
.display()
|
||||
.to_string()
|
||||
.trim_start_matches('/')
|
||||
.replace('/', "\\");
|
||||
|
||||
// NOTE: open files lazily
|
||||
let handle = None;
|
||||
let offset = AtomicU64::new(0);
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
path: path.clone(),
|
||||
handle,
|
||||
offset,
|
||||
size,
|
||||
last_write_time,
|
||||
is_dir,
|
||||
read_only,
|
||||
system,
|
||||
hidden,
|
||||
perm,
|
||||
archive,
|
||||
normal,
|
||||
})
|
||||
}
|
||||
pub fn as_bin(&self) -> Vec<u8> {
|
||||
let mut buf = BytesMut::with_capacity(592);
|
||||
|
||||
let read_only_flag = if self.read_only { 0x1 } else { 0 };
|
||||
let hidden_flag = if self.hidden { 0x2 } else { 0 };
|
||||
let system_flag = if self.system { 0x4 } else { 0 };
|
||||
let directory_flag = if self.is_dir { 0x10 } else { 0 };
|
||||
let archive_flag = if self.archive { 0x20 } else { 0 };
|
||||
let normal_flag = if self.normal { 0x80 } else { 0 };
|
||||
|
||||
let file_attributes: u32 = read_only_flag
|
||||
| hidden_flag
|
||||
| system_flag
|
||||
| directory_flag
|
||||
| archive_flag
|
||||
| normal_flag;
|
||||
|
||||
let win32_time = self
|
||||
.last_write_time
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos() as u64
|
||||
/ 100
|
||||
+ LDAP_EPOCH_DELTA;
|
||||
|
||||
let size_high = (self.size >> 32) as u32;
|
||||
let size_low = (self.size & (u32::MAX as u64)) as u32;
|
||||
|
||||
let path = self.path.to_string_lossy().to_string();
|
||||
|
||||
let wstr: WString<utf16string::LE> = WString::from(&path);
|
||||
let name = wstr.as_bytes();
|
||||
|
||||
log::trace!(
|
||||
"put file to list: name_len {}, name {}",
|
||||
name.len(),
|
||||
&self.name
|
||||
);
|
||||
|
||||
let flags = FLAGS_FD_SIZE
|
||||
| FLAGS_FD_LAST_WRITE
|
||||
| FLAGS_FD_ATTRIBUTES
|
||||
| FLAGS_FD_PROGRESSUI
|
||||
| FLAGS_FD_UNIX_MODE;
|
||||
|
||||
// flags, 4 bytes
|
||||
buf.put_u32_le(flags);
|
||||
// 32 bytes reserved
|
||||
buf.put(&[0u8; 32][..]);
|
||||
// file attributes, 4 bytes
|
||||
buf.put_u32_le(file_attributes);
|
||||
|
||||
// NOTE: this is not used in windows
|
||||
// in the specification, this is 16 bytes reserved
|
||||
// lets use the last 4 bytes to store the file mode
|
||||
//
|
||||
// 12 bytes reserved
|
||||
buf.put(&[0u8; 12][..]);
|
||||
// file permissions, 4 bytes
|
||||
buf.put_u32_le(self.perm);
|
||||
|
||||
// last write time, 8 bytes
|
||||
buf.put_u64_le(win32_time);
|
||||
// file size (high)
|
||||
buf.put_u32_le(size_high);
|
||||
// file size (low)
|
||||
buf.put_u32_le(size_low);
|
||||
// put name and padding to 520 bytes
|
||||
let name_len = name.len();
|
||||
buf.put(name);
|
||||
buf.put(&vec![0u8; 520 - name_len][..]);
|
||||
|
||||
buf.to_vec()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn load_handle(&mut self) -> Result<(), CliprdrError> {
|
||||
if !self.is_dir && self.handle.is_none() {
|
||||
let handle = std::fs::File::open(&self.path).map_err(|e| CliprdrError::FileError {
|
||||
path: self.path.clone(),
|
||||
err: e,
|
||||
})?;
|
||||
let mut reader = BufReader::with_capacity(BLOCK_SIZE as usize * 2, handle);
|
||||
reader.fill_buf().map_err(|e| CliprdrError::FileError {
|
||||
path: self.path.clone(),
|
||||
err: e,
|
||||
})?;
|
||||
self.handle = Some(reader);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_exact_at(&mut self, buf: &mut [u8], offset: u64) -> Result<(), CliprdrError> {
|
||||
self.load_handle()?;
|
||||
|
||||
let handle = self.handle.as_mut().unwrap();
|
||||
|
||||
if offset != self.offset.load(Ordering::Relaxed) {
|
||||
handle
|
||||
.seek(std::io::SeekFrom::Start(offset))
|
||||
.map_err(|e| CliprdrError::FileError {
|
||||
path: self.path.clone(),
|
||||
err: e,
|
||||
})?;
|
||||
}
|
||||
handle
|
||||
.read_exact(buf)
|
||||
.map_err(|e| CliprdrError::FileError {
|
||||
path: self.path.clone(),
|
||||
err: e,
|
||||
})?;
|
||||
let new_offset = offset + (buf.len() as u64);
|
||||
self.offset.store(new_offset, Ordering::Relaxed);
|
||||
|
||||
// gc file handle
|
||||
if new_offset >= self.size {
|
||||
self.offset.store(0, Ordering::Relaxed);
|
||||
self.handle = None;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn construct_file_list(paths: &[PathBuf]) -> Result<Vec<LocalFile>, CliprdrError> {
|
||||
fn constr_file_lst(
|
||||
path: &PathBuf,
|
||||
file_list: &mut Vec<LocalFile>,
|
||||
visited: &mut HashSet<PathBuf>,
|
||||
) -> Result<(), CliprdrError> {
|
||||
// prevent fs loop
|
||||
if visited.contains(path) {
|
||||
return Ok(());
|
||||
}
|
||||
visited.insert(path.clone());
|
||||
|
||||
let local_file = LocalFile::try_open(path)?;
|
||||
file_list.push(local_file);
|
||||
|
||||
let mt = std::fs::metadata(path).map_err(|e| CliprdrError::FileError {
|
||||
path: path.clone(),
|
||||
err: e,
|
||||
})?;
|
||||
|
||||
if mt.is_dir() {
|
||||
let dir = std::fs::read_dir(path).unwrap();
|
||||
for entry in dir {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
constr_file_lst(&path, file_list, visited)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let mut file_list = Vec::new();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
for path in paths {
|
||||
constr_file_lst(path, &mut file_list, &mut visited)?;
|
||||
}
|
||||
Ok(file_list)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod file_list_test {
|
||||
use std::{path::PathBuf, sync::atomic::AtomicU64};
|
||||
|
||||
use hbb_common::bytes::{BufMut, BytesMut};
|
||||
|
||||
use crate::{platform::fuse::FileDescription, CliprdrError};
|
||||
|
||||
use super::LocalFile;
|
||||
|
||||
#[inline]
|
||||
fn generate_tree(prefix: &str) -> Vec<LocalFile> {
|
||||
// generate a tree of local files, no handles
|
||||
// - /
|
||||
// |- a.txt
|
||||
// |- b
|
||||
// |- c.txt
|
||||
#[inline]
|
||||
fn generate_file(path: &str, name: &str, is_dir: bool) -> LocalFile {
|
||||
LocalFile {
|
||||
path: PathBuf::from(path),
|
||||
handle: None,
|
||||
name: name.to_string(),
|
||||
size: 0,
|
||||
offset: AtomicU64::new(0),
|
||||
last_write_time: std::time::SystemTime::UNIX_EPOCH,
|
||||
read_only: false,
|
||||
is_dir,
|
||||
perm: 0o754,
|
||||
hidden: false,
|
||||
system: false,
|
||||
archive: false,
|
||||
normal: false,
|
||||
}
|
||||
}
|
||||
|
||||
let p = prefix;
|
||||
|
||||
let (r_path, a_path, b_path, c_path) = if !prefix.is_empty() {
|
||||
(
|
||||
p.to_string(),
|
||||
format!("{}/a.txt", p),
|
||||
format!("{}/b", p),
|
||||
format!("{}/b/c.txt", p),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
".".to_string(),
|
||||
"a.txt".to_string(),
|
||||
"b".to_string(),
|
||||
"b/c.txt".to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
let root = generate_file(&r_path, ".", true);
|
||||
let a = generate_file(&a_path, "a.txt", false);
|
||||
let b = generate_file(&b_path, "b", true);
|
||||
let c = generate_file(&c_path, "c.txt", false);
|
||||
|
||||
vec![root, a, b, c]
|
||||
}
|
||||
|
||||
fn as_bin_parse_test(prefix: &str) -> Result<(), CliprdrError> {
|
||||
let tree = generate_tree(prefix);
|
||||
let mut pdu = BytesMut::with_capacity(4 + 592 * tree.len());
|
||||
pdu.put_u32_le(tree.len() as u32);
|
||||
for file in tree {
|
||||
pdu.put(file.as_bin().as_slice());
|
||||
}
|
||||
|
||||
let parsed = FileDescription::parse_file_descriptors(pdu.to_vec(), 0)?;
|
||||
assert_eq!(parsed.len(), 4);
|
||||
|
||||
if !prefix.is_empty() {
|
||||
assert_eq!(parsed[0].name.to_str().unwrap(), format!("{}", prefix));
|
||||
assert_eq!(
|
||||
parsed[1].name.to_str().unwrap(),
|
||||
format!("{}/a.txt", prefix)
|
||||
);
|
||||
assert_eq!(parsed[2].name.to_str().unwrap(), format!("{}/b", prefix));
|
||||
assert_eq!(
|
||||
parsed[3].name.to_str().unwrap(),
|
||||
format!("{}/b/c.txt", prefix)
|
||||
);
|
||||
} else {
|
||||
assert_eq!(parsed[0].name.to_str().unwrap(), ".");
|
||||
assert_eq!(parsed[1].name.to_str().unwrap(), "a.txt");
|
||||
assert_eq!(parsed[2].name.to_str().unwrap(), "b");
|
||||
assert_eq!(parsed[3].name.to_str().unwrap(), "b/c.txt");
|
||||
}
|
||||
|
||||
assert!(parsed[0].perm & 0o777 == 0o754);
|
||||
assert!(parsed[1].perm & 0o777 == 0o754);
|
||||
assert!(parsed[2].perm & 0o777 == 0o754);
|
||||
assert!(parsed[3].perm & 0o777 == 0o754);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_file_descriptors() -> Result<(), CliprdrError> {
|
||||
as_bin_parse_test("")?;
|
||||
as_bin_parse_test("/")?;
|
||||
as_bin_parse_test("test")?;
|
||||
as_bin_parse_test("/test")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
593
libs/clipboard/src/platform/unix/mod.rs
Normal file
593
libs/clipboard/src/platform/unix/mod.rs
Normal file
@ -0,0 +1,593 @@
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{mpsc::Sender, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use dashmap::DashMap;
|
||||
use fuser::MountOption;
|
||||
use hbb_common::{
|
||||
bytes::{BufMut, BytesMut},
|
||||
log,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
platform::{fuse::FileDescription, unix::local_file::construct_file_list},
|
||||
send_data, ClipboardFile, CliprdrError, CliprdrServiceContext,
|
||||
};
|
||||
|
||||
use self::local_file::LocalFile;
|
||||
#[cfg(target_os = "linux")]
|
||||
use self::url::{encode_path_to_uri, parse_plain_uri_list};
|
||||
|
||||
use super::fuse::FuseServer;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod x11;
|
||||
|
||||
pub mod local_file;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod url;
|
||||
|
||||
// not actual format id, just a placeholder
|
||||
const FILEDESCRIPTOR_FORMAT_ID: i32 = 49334;
|
||||
const FILEDESCRIPTORW_FORMAT_NAME: &str = "FileGroupDescriptorW";
|
||||
// not actual format id, just a placeholder
|
||||
const FILECONTENTS_FORMAT_ID: i32 = 49267;
|
||||
const FILECONTENTS_FORMAT_NAME: &str = "FileContents";
|
||||
|
||||
lazy_static! {
|
||||
static ref REMOTE_FORMAT_MAP: DashMap<i32, String> = DashMap::from_iter(
|
||||
[
|
||||
(
|
||||
FILEDESCRIPTOR_FORMAT_ID,
|
||||
FILEDESCRIPTORW_FORMAT_NAME.to_string()
|
||||
),
|
||||
(FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME.to_string())
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
);
|
||||
}
|
||||
|
||||
fn get_local_format(remote_id: i32) -> Option<String> {
|
||||
REMOTE_FORMAT_MAP.get(&remote_id).map(|s| s.clone())
|
||||
}
|
||||
|
||||
fn add_remote_format(local_name: &str, remote_id: i32) {
|
||||
REMOTE_FORMAT_MAP.insert(remote_id, local_name.to_string());
|
||||
}
|
||||
|
||||
trait SysClipboard: Send + Sync {
|
||||
fn start(&self);
|
||||
|
||||
fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>;
|
||||
fn get_file_list(&self) -> Vec<PathBuf>;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> {
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
{
|
||||
pub use x11::*;
|
||||
let x11_clip = X11Clipboard::new(ignore_path)?;
|
||||
Ok(Box::new(x11_clip) as Box<_>)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FileContentsRequest {
|
||||
Size {
|
||||
stream_id: i32,
|
||||
file_idx: usize,
|
||||
},
|
||||
|
||||
Range {
|
||||
stream_id: i32,
|
||||
file_idx: usize,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct ClipboardContext {
|
||||
pub fuse_mount_point: PathBuf,
|
||||
/// stores fuse background session handle
|
||||
fuse_handle: Mutex<Option<fuser::BackgroundSession>>,
|
||||
|
||||
/// a sender of clipboard file contents pdu to fuse server
|
||||
fuse_tx: Sender<ClipboardFile>,
|
||||
fuse_server: Arc<Mutex<FuseServer>>,
|
||||
|
||||
clipboard: Arc<dyn SysClipboard>,
|
||||
local_files: Mutex<Vec<LocalFile>>,
|
||||
}
|
||||
|
||||
impl ClipboardContext {
|
||||
pub fn new(timeout: Duration, mount_path: PathBuf) -> Result<Self, CliprdrError> {
|
||||
// assert mount path exists
|
||||
let fuse_mount_point = mount_path.canonicalize().map_err(|e| {
|
||||
log::error!("failed to canonicalize mount path: {:?}", e);
|
||||
CliprdrError::CliprdrInit
|
||||
})?;
|
||||
|
||||
let (fuse_server, fuse_tx) = FuseServer::new(timeout);
|
||||
|
||||
let fuse_server = Arc::new(Mutex::new(fuse_server));
|
||||
|
||||
let clipboard = get_sys_clipboard(&fuse_mount_point)?;
|
||||
let clipboard = Arc::from(clipboard) as Arc<_>;
|
||||
let local_files = Mutex::new(vec![]);
|
||||
|
||||
Ok(Self {
|
||||
fuse_mount_point,
|
||||
fuse_server,
|
||||
fuse_tx,
|
||||
fuse_handle: Mutex::new(None),
|
||||
clipboard,
|
||||
local_files,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<(), CliprdrError> {
|
||||
if !self.is_stopped() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut fuse_handle = self.fuse_handle.lock();
|
||||
|
||||
let mount_path = &self.fuse_mount_point;
|
||||
|
||||
let mnt_opts = [
|
||||
MountOption::FSName("rustdesk-cliprdr-fs".to_string()),
|
||||
MountOption::NoAtime,
|
||||
MountOption::RO,
|
||||
];
|
||||
log::info!(
|
||||
"mounting clipboard FUSE to {}",
|
||||
self.fuse_mount_point.display()
|
||||
);
|
||||
|
||||
let new_handle = fuser::spawn_mount2(
|
||||
FuseServer::client(self.fuse_server.clone()),
|
||||
mount_path,
|
||||
&mnt_opts,
|
||||
)
|
||||
.map_err(|e| {
|
||||
log::error!("failed to mount cliprdr fuse: {:?}", e);
|
||||
CliprdrError::CliprdrInit
|
||||
})?;
|
||||
*fuse_handle = Some(new_handle);
|
||||
|
||||
let clipboard = self.clipboard.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
log::debug!("start listening clipboard");
|
||||
clipboard.start();
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// set clipboard data from file list
|
||||
pub fn set_clipboard(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> {
|
||||
let prefix = self.fuse_mount_point.clone();
|
||||
let paths: Vec<PathBuf> = paths.iter().cloned().map(|p| prefix.join(p)).collect();
|
||||
log::debug!("setting clipboard with paths: {:?}", paths);
|
||||
self.clipboard.set_file_list(&paths)?;
|
||||
log::debug!("clipboard set, paths: {:?}", paths);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serve_file_contents(
|
||||
&self,
|
||||
conn_id: i32,
|
||||
request: FileContentsRequest,
|
||||
) -> Result<(), CliprdrError> {
|
||||
let mut file_list = self.local_files.lock();
|
||||
|
||||
let (file_idx, file_contents_resp) = match request {
|
||||
FileContentsRequest::Size {
|
||||
stream_id,
|
||||
file_idx,
|
||||
} => {
|
||||
log::debug!("file contents (size) requested from conn: {}", conn_id);
|
||||
let Some(file) = file_list.get(file_idx) else {
|
||||
log::error!(
|
||||
"invalid file index {} requested from conn: {}",
|
||||
file_idx,
|
||||
conn_id
|
||||
);
|
||||
resp_file_contents_fail(conn_id, stream_id);
|
||||
|
||||
return Err(CliprdrError::InvalidRequest {
|
||||
description: format!(
|
||||
"invalid file index {} requested from conn: {}",
|
||||
file_idx, conn_id
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"conn {} requested file-{}: {}",
|
||||
conn_id,
|
||||
file_idx,
|
||||
file.name
|
||||
);
|
||||
|
||||
let size = file.size;
|
||||
(
|
||||
file_idx,
|
||||
ClipboardFile::FileContentsResponse {
|
||||
msg_flags: 0x1,
|
||||
stream_id,
|
||||
requested_data: size.to_le_bytes().to_vec(),
|
||||
},
|
||||
)
|
||||
}
|
||||
FileContentsRequest::Range {
|
||||
stream_id,
|
||||
file_idx,
|
||||
offset,
|
||||
length,
|
||||
} => {
|
||||
log::debug!(
|
||||
"file contents (range from {} length {}) request from conn: {}",
|
||||
offset,
|
||||
length,
|
||||
conn_id
|
||||
);
|
||||
let Some(file) = file_list.get_mut(file_idx) else {
|
||||
log::error!(
|
||||
"invalid file index {} requested from conn: {}",
|
||||
file_idx,
|
||||
conn_id
|
||||
);
|
||||
resp_file_contents_fail(conn_id, stream_id);
|
||||
return Err(CliprdrError::InvalidRequest {
|
||||
description: format!(
|
||||
"invalid file index {} requested from conn: {}",
|
||||
file_idx, conn_id
|
||||
),
|
||||
});
|
||||
};
|
||||
log::debug!(
|
||||
"conn {} requested file-{}: {}",
|
||||
conn_id,
|
||||
file_idx,
|
||||
file.name
|
||||
);
|
||||
|
||||
if offset > file.size {
|
||||
log::error!("invalid reading offset requested from conn: {}", conn_id);
|
||||
resp_file_contents_fail(conn_id, stream_id);
|
||||
|
||||
return Err(CliprdrError::InvalidRequest {
|
||||
description: format!(
|
||||
"invalid reading offset requested from conn: {}",
|
||||
conn_id
|
||||
),
|
||||
});
|
||||
}
|
||||
let read_size = if offset + length > file.size {
|
||||
file.size - offset
|
||||
} else {
|
||||
length
|
||||
};
|
||||
|
||||
let mut buf = vec![0u8; read_size as usize];
|
||||
|
||||
file.read_exact_at(&mut buf, offset)?;
|
||||
|
||||
(
|
||||
file_idx,
|
||||
ClipboardFile::FileContentsResponse {
|
||||
msg_flags: 0x1,
|
||||
stream_id,
|
||||
requested_data: buf,
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
send_data(conn_id, file_contents_resp);
|
||||
log::debug!("file contents sent to conn: {}", conn_id);
|
||||
// hot reload next file
|
||||
for next_file in file_list.iter_mut().skip(file_idx + 1) {
|
||||
if !next_file.is_dir {
|
||||
next_file.load_handle()?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn resp_file_contents_fail(conn_id: i32, stream_id: i32) {
|
||||
let resp = ClipboardFile::FileContentsResponse {
|
||||
msg_flags: 0x2,
|
||||
stream_id,
|
||||
requested_data: vec![],
|
||||
};
|
||||
send_data(conn_id, resp)
|
||||
}
|
||||
|
||||
impl ClipboardContext {
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
self.fuse_handle.lock().is_none()
|
||||
}
|
||||
|
||||
pub fn sync_local_files(&self) -> Result<(), CliprdrError> {
|
||||
let mut local_files = self.local_files.lock();
|
||||
let clipboard_files = self.clipboard.get_file_list();
|
||||
let local_file_list: Vec<PathBuf> = local_files.iter().map(|f| f.path.clone()).collect();
|
||||
if local_file_list == clipboard_files {
|
||||
return Ok(());
|
||||
}
|
||||
let new_files = construct_file_list(&clipboard_files)?;
|
||||
*local_files = new_files;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn serve(&self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> {
|
||||
log::debug!("serve clipboard file from conn: {}", conn_id);
|
||||
if self.is_stopped() {
|
||||
log::debug!("cliprdr stopped, restart it");
|
||||
self.run()?;
|
||||
}
|
||||
match msg {
|
||||
ClipboardFile::NotifyCallback { .. } => {
|
||||
unreachable!()
|
||||
}
|
||||
ClipboardFile::MonitorReady => {
|
||||
log::debug!("server_monitor_ready called");
|
||||
|
||||
self.send_file_list(conn_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
ClipboardFile::FormatList { format_list } => {
|
||||
log::debug!("server_format_list called");
|
||||
// filter out "FileGroupDescriptorW" and "FileContents"
|
||||
let fmt_lst: Vec<(i32, String)> = format_list
|
||||
.into_iter()
|
||||
.filter(|(_, name)| {
|
||||
name == FILEDESCRIPTORW_FORMAT_NAME || name == FILECONTENTS_FORMAT_NAME
|
||||
})
|
||||
.collect();
|
||||
if fmt_lst.len() != 2 {
|
||||
log::debug!("no supported formats");
|
||||
return Ok(());
|
||||
}
|
||||
log::debug!("supported formats: {:?}", fmt_lst);
|
||||
let file_contents_id = fmt_lst
|
||||
.iter()
|
||||
.find(|(_, name)| name == FILECONTENTS_FORMAT_NAME)
|
||||
.map(|(id, _)| *id)
|
||||
.unwrap();
|
||||
let file_descriptor_id = fmt_lst
|
||||
.iter()
|
||||
.find(|(_, name)| name == FILEDESCRIPTORW_FORMAT_NAME)
|
||||
.map(|(id, _)| *id)
|
||||
.unwrap();
|
||||
|
||||
add_remote_format(FILECONTENTS_FORMAT_NAME, file_contents_id);
|
||||
add_remote_format(FILEDESCRIPTORW_FORMAT_NAME, file_descriptor_id);
|
||||
|
||||
// sync file system from peer
|
||||
let data = ClipboardFile::FormatDataRequest {
|
||||
requested_format_id: file_descriptor_id,
|
||||
};
|
||||
send_data(conn_id, data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
ClipboardFile::FormatListResponse { msg_flags } => {
|
||||
log::debug!("server_format_list_response called");
|
||||
if msg_flags != 0x1 {
|
||||
send_format_list(conn_id)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
ClipboardFile::FormatDataRequest {
|
||||
requested_format_id,
|
||||
} => {
|
||||
log::debug!("server_format_data_request called");
|
||||
let Some(format) = get_local_format(requested_format_id) else {
|
||||
log::error!(
|
||||
"got unsupported format data request: id={} from conn={}",
|
||||
requested_format_id,
|
||||
conn_id
|
||||
);
|
||||
resp_format_data_failure(conn_id);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if format == FILEDESCRIPTORW_FORMAT_NAME {
|
||||
self.send_file_list(conn_id)?;
|
||||
} else if format == FILECONTENTS_FORMAT_NAME {
|
||||
log::error!(
|
||||
"try to read file contents with FormatDataRequest from conn={}",
|
||||
conn_id
|
||||
);
|
||||
resp_format_data_failure(conn_id);
|
||||
} else {
|
||||
log::error!(
|
||||
"got unsupported format data request: id={} from conn={}",
|
||||
requested_format_id,
|
||||
conn_id
|
||||
);
|
||||
resp_format_data_failure(conn_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ClipboardFile::FormatDataResponse {
|
||||
msg_flags,
|
||||
format_data,
|
||||
} => {
|
||||
log::debug!(
|
||||
"server_format_data_response called, msg_flags={}",
|
||||
msg_flags
|
||||
);
|
||||
|
||||
if msg_flags != 0x1 {
|
||||
resp_format_data_failure(conn_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::debug!("parsing file descriptors");
|
||||
// this must be a file descriptor format data
|
||||
let files = FileDescription::parse_file_descriptors(format_data, conn_id)?;
|
||||
|
||||
let paths = {
|
||||
let mut fuse_guard = self.fuse_server.lock();
|
||||
fuse_guard.load_file_list(files)?;
|
||||
|
||||
fuse_guard.list_root()
|
||||
};
|
||||
|
||||
log::debug!("load file list: {:?}", paths);
|
||||
self.set_clipboard(&paths)?;
|
||||
Ok(())
|
||||
}
|
||||
ClipboardFile::FileContentsResponse { .. } => {
|
||||
log::debug!("server_file_contents_response called");
|
||||
// we don't know its corresponding request, no resend can be performed
|
||||
self.fuse_tx.send(msg).map_err(|e| {
|
||||
log::error!("failed to send file contents response to fuse: {:?}", e);
|
||||
CliprdrError::ClipboardInternalError
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
ClipboardFile::FileContentsRequest {
|
||||
stream_id,
|
||||
list_index,
|
||||
dw_flags,
|
||||
n_position_low,
|
||||
n_position_high,
|
||||
cb_requested,
|
||||
..
|
||||
} => {
|
||||
log::debug!("server_file_contents_request called");
|
||||
let fcr = if dw_flags == 0x1 {
|
||||
FileContentsRequest::Size {
|
||||
stream_id,
|
||||
file_idx: list_index as usize,
|
||||
}
|
||||
} else if dw_flags == 0x2 {
|
||||
let offset = (n_position_high as u64) << 32 | n_position_low as u64;
|
||||
let length = cb_requested as u64;
|
||||
|
||||
FileContentsRequest::Range {
|
||||
stream_id,
|
||||
file_idx: list_index as usize,
|
||||
offset,
|
||||
length,
|
||||
}
|
||||
} else {
|
||||
log::error!("got invalid FileContentsRequest from conn={}", conn_id);
|
||||
resp_file_contents_fail(conn_id, stream_id);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.serve_file_contents(conn_id, fcr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_file_list(&self, conn_id: i32) -> Result<(), CliprdrError> {
|
||||
self.sync_local_files()?;
|
||||
|
||||
let file_list = self.local_files.lock();
|
||||
send_file_list(&*file_list, conn_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl CliprdrServiceContext for ClipboardContext {
|
||||
fn set_is_stopped(&mut self) -> Result<(), CliprdrError> {
|
||||
// unmount the fuse
|
||||
if let Some(fuse_handle) = self.fuse_handle.lock().take() {
|
||||
fuse_handle.join();
|
||||
}
|
||||
// we don't stop the clipboard, keep listening in case of restart
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn empty_clipboard(&mut self, _conn_id: i32) -> Result<bool, CliprdrError> {
|
||||
self.clipboard.set_file_list(&[])?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn server_clip_file(&mut self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> {
|
||||
self.serve(conn_id, msg)
|
||||
}
|
||||
}
|
||||
|
||||
fn resp_format_data_failure(conn_id: i32) {
|
||||
let data = ClipboardFile::FormatDataResponse {
|
||||
msg_flags: 0x2,
|
||||
format_data: vec![],
|
||||
};
|
||||
send_data(conn_id, data)
|
||||
}
|
||||
|
||||
fn send_format_list(conn_id: i32) -> Result<(), CliprdrError> {
|
||||
log::debug!("send format list to remote, conn={}", conn_id);
|
||||
let fd_format_name = get_local_format(FILEDESCRIPTOR_FORMAT_ID)
|
||||
.unwrap_or(FILEDESCRIPTORW_FORMAT_NAME.to_string());
|
||||
let fc_format_name =
|
||||
get_local_format(FILECONTENTS_FORMAT_ID).unwrap_or(FILECONTENTS_FORMAT_NAME.to_string());
|
||||
let format_list = ClipboardFile::FormatList {
|
||||
format_list: vec![
|
||||
(FILEDESCRIPTOR_FORMAT_ID, fd_format_name),
|
||||
(FILECONTENTS_FORMAT_ID, fc_format_name),
|
||||
],
|
||||
};
|
||||
|
||||
send_data(conn_id, format_list);
|
||||
log::debug!("format list to remote dispatched, conn={}", conn_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_file_list_pdu(files: &[LocalFile]) -> Vec<u8> {
|
||||
let mut data = BytesMut::with_capacity(4 + 592 * files.len());
|
||||
data.put_u32_le(files.len() as u32);
|
||||
for file in files.iter() {
|
||||
data.put(file.as_bin().as_slice());
|
||||
}
|
||||
|
||||
data.to_vec()
|
||||
}
|
||||
|
||||
fn send_file_list(files: &[LocalFile], conn_id: i32) -> Result<(), CliprdrError> {
|
||||
log::debug!(
|
||||
"send file list to remote, conn={}, list={:?}",
|
||||
conn_id,
|
||||
files.iter().map(|f| f.path.display()).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let format_data = build_file_list_pdu(files);
|
||||
|
||||
send_data(
|
||||
conn_id,
|
||||
ClipboardFile::FormatDataResponse {
|
||||
msg_flags: 1,
|
||||
format_data,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
75
libs/clipboard/src/platform/unix/url.rs
Normal file
75
libs/clipboard/src/platform/unix/url.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::CliprdrError;
|
||||
|
||||
// on x11, path will be encode as
|
||||
// "/home/rustdesk/pictures/🖼️.png" -> "file:///home/rustdesk/pictures/%F0%9F%96%BC%EF%B8%8F.png"
|
||||
// url encode and decode is needed
|
||||
const ENCODE_SET: percent_encoding::AsciiSet = percent_encoding::CONTROLS.add(b' ').remove(b'/');
|
||||
|
||||
pub(super) fn encode_path_to_uri(path: &PathBuf) -> String {
|
||||
let encoded = percent_encoding::percent_encode(path.to_str().unwrap().as_bytes(), &ENCODE_SET)
|
||||
.to_string();
|
||||
format!("file://{}", encoded)
|
||||
}
|
||||
|
||||
pub(super) fn parse_uri_to_path(encoded_uri: &str) -> Result<PathBuf, CliprdrError> {
|
||||
let encoded_path = encoded_uri.trim_start_matches("file://");
|
||||
let path_str = percent_encoding::percent_decode_str(encoded_path)
|
||||
.decode_utf8()
|
||||
.map_err(|_| CliprdrError::ConversionFailure)?;
|
||||
let path_str = path_str.to_string();
|
||||
|
||||
Ok(Path::new(&path_str).to_path_buf())
|
||||
}
|
||||
|
||||
// helper parse function
|
||||
// convert 'text/uri-list' data to a list of valid Paths
|
||||
// # Note
|
||||
// - none utf8 data will lead to error
|
||||
pub(super) fn parse_plain_uri_list(v: Vec<u8>) -> Result<Vec<PathBuf>, CliprdrError> {
|
||||
let text = String::from_utf8(v).map_err(|_| CliprdrError::ConversionFailure)?;
|
||||
parse_uri_list(&text)
|
||||
}
|
||||
|
||||
// helper parse function
|
||||
// convert 'text/uri-list' data to a list of valid Paths
|
||||
// # Note
|
||||
// - none utf8 data will lead to error
|
||||
pub(super) fn parse_uri_list(text: &str) -> Result<Vec<PathBuf>, CliprdrError> {
|
||||
let mut list = Vec::new();
|
||||
|
||||
for line in text.lines() {
|
||||
if !line.starts_with("file://") {
|
||||
continue;
|
||||
}
|
||||
let decoded = parse_uri_to_path(line)?;
|
||||
list.push(decoded)
|
||||
}
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod uri_test {
|
||||
#[test]
|
||||
fn test_conversion() {
|
||||
let path = std::path::PathBuf::from("/home/rustdesk/pictures/🖼️.png");
|
||||
let uri = super::encode_path_to_uri(&path);
|
||||
assert_eq!(
|
||||
uri,
|
||||
"file:///home/rustdesk/pictures/%F0%9F%96%BC%EF%B8%8F.png"
|
||||
);
|
||||
let convert_back = super::parse_uri_to_path(&uri).unwrap();
|
||||
assert_eq!(path, convert_back);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_list() {
|
||||
let uri_list = r#"file:///home/rustdesk/pictures/%F0%9F%96%BC%EF%B8%8F.png
|
||||
file:///home/rustdesk/pictures/%F0%9F%96%BC%EF%B8%8F.png
|
||||
"#;
|
||||
let list = super::parse_uri_list(uri_list.into()).unwrap();
|
||||
assert!(list.len() == 2);
|
||||
assert_eq!(list[0], list[1]);
|
||||
}
|
||||
}
|
162
libs/clipboard/src/platform/unix/x11.rs
Normal file
162
libs/clipboard/src/platform/unix/x11.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use std::{collections::BTreeSet, path::PathBuf};
|
||||
|
||||
use hbb_common::log;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use x11_clipboard::Clipboard;
|
||||
use x11rb::protocol::xproto::Atom;
|
||||
|
||||
use crate::{platform::unix::send_format_list, CliprdrError};
|
||||
|
||||
use super::{encode_path_to_uri, parse_plain_uri_list, SysClipboard};
|
||||
|
||||
static X11_CLIPBOARD: OnceCell<Clipboard> = OnceCell::new();
|
||||
|
||||
fn get_clip() -> Result<&'static Clipboard, CliprdrError> {
|
||||
X11_CLIPBOARD.get_or_try_init(|| Clipboard::new().map_err(|_| CliprdrError::CliprdrInit))
|
||||
}
|
||||
|
||||
pub struct X11Clipboard {
|
||||
ignore_path: PathBuf,
|
||||
text_uri_list: Atom,
|
||||
gnome_copied_files: Atom,
|
||||
nautilus_clipboard: Atom,
|
||||
|
||||
former_file_list: Mutex<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
impl X11Clipboard {
|
||||
pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
|
||||
let clipboard = get_clip()?;
|
||||
let text_uri_list = clipboard
|
||||
.setter
|
||||
.get_atom("text/uri-list")
|
||||
.map_err(|_| CliprdrError::CliprdrInit)?;
|
||||
let gnome_copied_files = clipboard
|
||||
.setter
|
||||
.get_atom("x-special/gnome-copied-files")
|
||||
.map_err(|_| CliprdrError::CliprdrInit)?;
|
||||
let nautilus_clipboard = clipboard
|
||||
.setter
|
||||
.get_atom("x-special/nautilus-clipboard")
|
||||
.map_err(|_| CliprdrError::CliprdrInit)?;
|
||||
Ok(Self {
|
||||
ignore_path: ignore_path.to_owned(),
|
||||
text_uri_list,
|
||||
gnome_copied_files,
|
||||
nautilus_clipboard,
|
||||
former_file_list: Mutex::new(vec![]),
|
||||
})
|
||||
}
|
||||
|
||||
fn load(&self, target: Atom) -> Result<Vec<u8>, CliprdrError> {
|
||||
let clip = get_clip()?.setter.atoms.clipboard;
|
||||
let prop = get_clip()?.setter.atoms.property;
|
||||
// NOTE:
|
||||
// # why not use `load_wait`
|
||||
// load_wait is likely to wait forever, which is not what we want
|
||||
let res = get_clip()?.load_wait(clip, target, prop);
|
||||
match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err(x11_clipboard::error::Error::UnexpectedType(_)) => Ok(vec![]),
|
||||
Err(x11_clipboard::error::Error::Timeout) => {
|
||||
log::debug!("x11 clipboard get content timeout.");
|
||||
Err(CliprdrError::ClipboardInternalError)
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("x11 clipboard get content fail: {:?}", e);
|
||||
Err(CliprdrError::ClipboardInternalError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn store_batch(&self, batch: Vec<(Atom, Vec<u8>)>) -> Result<(), CliprdrError> {
|
||||
let clip = get_clip()?.setter.atoms.clipboard;
|
||||
log::debug!("try to store clipboard content");
|
||||
get_clip()?
|
||||
.store_batch(clip, batch)
|
||||
.map_err(|_| CliprdrError::ClipboardInternalError)
|
||||
}
|
||||
|
||||
fn wait_file_list(&self) -> Result<Option<Vec<PathBuf>>, CliprdrError> {
|
||||
let v = self.load(self.text_uri_list)?;
|
||||
let p = parse_plain_uri_list(v)?;
|
||||
Ok(Some(p))
|
||||
}
|
||||
}
|
||||
|
||||
impl SysClipboard for X11Clipboard {
|
||||
fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> {
|
||||
*self.former_file_list.lock() = paths.to_vec();
|
||||
|
||||
let uri_list: Vec<String> = paths.iter().map(encode_path_to_uri).collect();
|
||||
let uri_list = uri_list.join("\n");
|
||||
let text_uri_list_data = uri_list.as_bytes().to_vec();
|
||||
let gnome_copied_files_data = ["copy\n".as_bytes(), uri_list.as_bytes()].concat();
|
||||
let batch = vec![
|
||||
(self.text_uri_list, text_uri_list_data),
|
||||
(self.gnome_copied_files, gnome_copied_files_data.clone()),
|
||||
(self.nautilus_clipboard, gnome_copied_files_data),
|
||||
];
|
||||
self.store_batch(batch)
|
||||
.map_err(|_| CliprdrError::ClipboardInternalError)
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
{
|
||||
// clear cached file list
|
||||
*self.former_file_list.lock() = vec![];
|
||||
}
|
||||
loop {
|
||||
let sth = match self.wait_file_list() {
|
||||
Ok(sth) => sth,
|
||||
Err(e) => {
|
||||
log::warn!("failed to get file list from clipboard: {}", e);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(paths) = sth else {
|
||||
// just sleep
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
continue;
|
||||
};
|
||||
|
||||
let filtered = paths
|
||||
.into_iter()
|
||||
.filter(|pb| !pb.starts_with(&self.ignore_path))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if filtered.is_empty() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
let mut former = self.former_file_list.lock();
|
||||
|
||||
let filtered_st: BTreeSet<_> = filtered.iter().collect();
|
||||
let former_st = former.iter().collect::<BTreeSet<_>>();
|
||||
if filtered_st == former_st {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
*former = filtered;
|
||||
}
|
||||
|
||||
if let Err(e) = send_format_list(0) {
|
||||
log::warn!("failed to send format list: {}", e);
|
||||
break;
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
log::debug!("stop listening file related atoms on clipboard");
|
||||
}
|
||||
|
||||
fn get_file_list(&self) -> Vec<PathBuf> {
|
||||
self.former_file_list.lock().clone()
|
||||
}
|
||||
}
|
1219
libs/clipboard/src/platform/windows.rs
Normal file
1219
libs/clipboard/src/platform/windows.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,14 +7,14 @@ use std::{
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, ContextSend};
|
||||
#[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;
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
@ -34,7 +34,7 @@ use hbb_common::{
|
||||
sync::mpsc,
|
||||
time::{self, Duration, Instant, Interval},
|
||||
},
|
||||
Stream,
|
||||
ResultType, Stream,
|
||||
};
|
||||
use scrap::CodecFormat;
|
||||
|
||||
@ -66,7 +66,7 @@ pub struct Remote<T: InvokeUiSession> {
|
||||
last_update_jobs_status: (Instant, HashMap<i32, u64>),
|
||||
is_connected: bool,
|
||||
first_frame: bool,
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
client_conn_id: i32, // used for file clipboard
|
||||
data_count: Arc<AtomicUsize>,
|
||||
frame_count_map: Arc<RwLock<HashMap<usize, usize>>>,
|
||||
@ -101,7 +101,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
last_update_jobs_status: (Instant::now(), Default::default()),
|
||||
is_connected: false,
|
||||
first_frame: false,
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
client_conn_id: 0,
|
||||
data_count: Arc::new(AtomicUsize::new(0)),
|
||||
frame_count_map,
|
||||
@ -146,24 +146,25 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
|
||||
// just build for now
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
|
||||
let (_tx_holder, mut rx_clip_client) = mpsc::unbounded_channel::<i32>();
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
let (_tx_holder, rx) = mpsc::unbounded_channel();
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
let mut rx_clip_client_lock = Arc::new(TokioMutex::new(rx));
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
let is_conn_not_default = self.handler.is_file_transfer()
|
||||
|| self.handler.is_port_forward()
|
||||
|| self.handler.is_rdp();
|
||||
if !is_conn_not_default {
|
||||
log::debug!("get cliprdr client for conn_id {}", self.client_conn_id);
|
||||
(self.client_conn_id, rx_clip_client_lock) =
|
||||
clipboard::get_rx_cliprdr_client(&self.handler.id);
|
||||
};
|
||||
}
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
let mut rx_clip_client = rx_clip_client_lock.lock().await;
|
||||
|
||||
let mut status_timer = time::interval(Duration::new(1, 0));
|
||||
@ -209,8 +210,8 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
_msg = rx_clip_client.recv() => {
|
||||
#[cfg(windows)]
|
||||
self.handle_local_clipboard_msg(&mut peer, _msg).await;
|
||||
#[cfg(any(target_os="windows", target_os="linux", target_os = "macos"))]
|
||||
self.handle_local_clipboard_msg(&mut peer, _msg).await;
|
||||
}
|
||||
_ = self.timer.tick() => {
|
||||
if last_recv_time.elapsed() >= SEC30 {
|
||||
@ -277,17 +278,18 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
Client::try_stop_clipboard(&self.handler.id);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
if _set_disconnected_ok {
|
||||
let conn_id = self.client_conn_id;
|
||||
ContextSend::proc(|context: &mut CliprdrClientContext| -> u32 {
|
||||
empty_clipboard(context, conn_id);
|
||||
0
|
||||
log::debug!("try empty cliprdr for conn_id {}", conn_id);
|
||||
let _ = ContextSend::proc(|context| -> ResultType<()> {
|
||||
context.empty_clipboard(conn_id)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
async fn handle_local_clipboard_msg(
|
||||
&self,
|
||||
peer: &mut crate::client::FramedStream,
|
||||
@ -315,7 +317,12 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if stop {
|
||||
ContextSend::set_is_stopped();
|
||||
} else {
|
||||
allow_err!(peer.send(&crate::clipboard_file::clip_2_msg(clip)).await);
|
||||
if let Err(e) = ContextSend::make_sure_enabled() {
|
||||
log::error!("failed to restart clipboard context: {}", e);
|
||||
};
|
||||
log::debug!("Send system clipboard message to remote");
|
||||
let msg = crate::clipboard_file::clip_2_msg(clip);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1069,7 +1076,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
Some(login_response::Union::PeerInfo(pi)) => {
|
||||
self.handler.handle_peer_info(pi);
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
self.check_clipboard_file_context();
|
||||
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
|
||||
#[cfg(feature = "flutter")]
|
||||
@ -1140,7 +1146,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
Some(message::Union::Cliprdr(clip)) => {
|
||||
self.handle_cliprdr_msg(clip);
|
||||
}
|
||||
@ -1700,9 +1706,14 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
fn check_clipboard_file_context(&self) {
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
feature = "unix-file-copy-paste",
|
||||
any(target_os = "linux", target_os = "macos")
|
||||
)
|
||||
))]
|
||||
{
|
||||
let enabled = *self.handler.server_file_transfer_enabled.read().unwrap()
|
||||
&& self.handler.lc.read().unwrap().enable_file_transfer.v;
|
||||
@ -1710,8 +1721,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
fn handle_cliprdr_msg(&self, clip: hbb_common::message_proto::Cliprdr) {
|
||||
log::debug!("handling cliprdr msg from server peer");
|
||||
#[cfg(feature = "flutter")]
|
||||
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
|
||||
if self.client_conn_id
|
||||
@ -1721,18 +1733,26 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(clip) = crate::clipboard_file::msg_2_clip(clip) {
|
||||
let is_stopping_allowed = clip.is_stopping_allowed_from_peer();
|
||||
let file_transfer_enabled = self.handler.lc.read().unwrap().enable_file_transfer.v;
|
||||
let stop = is_stopping_allowed && !file_transfer_enabled;
|
||||
log::debug!(
|
||||
let Some(clip) = crate::clipboard_file::msg_2_clip(clip) else {
|
||||
log::warn!("failed to decode cliprdr msg from server peer");
|
||||
return;
|
||||
};
|
||||
|
||||
let is_stopping_allowed = clip.is_stopping_allowed_from_peer();
|
||||
let file_transfer_enabled = self.handler.lc.read().unwrap().enable_file_transfer.v;
|
||||
let stop = is_stopping_allowed && !file_transfer_enabled;
|
||||
log::debug!(
|
||||
"Process clipboard message from server peer, stop: {}, is_stopping_allowed: {}, file_transfer_enabled: {}",
|
||||
stop, is_stopping_allowed, file_transfer_enabled);
|
||||
if !stop {
|
||||
ContextSend::proc(|context: &mut CliprdrClientContext| -> u32 {
|
||||
clipboard::server_clip_file(context, self.client_conn_id, clip)
|
||||
});
|
||||
}
|
||||
if !stop {
|
||||
if let Err(e) = ContextSend::make_sure_enabled() {
|
||||
log::error!("failed to restart clipboard context: {}", e);
|
||||
};
|
||||
let _ = ContextSend::proc(|context| -> ResultType<()> {
|
||||
context
|
||||
.server_clip_file(self.client_conn_id, clip)
|
||||
.map_err(|e| e.into())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
src/common.rs
111
src/common.rs
@ -11,9 +11,118 @@ pub enum GrabState {
|
||||
Exit,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
all(target_os = "linux", feature = "unix-file-copy-paste")
|
||||
)))]
|
||||
pub use arboard::Clipboard as ClipboardContext;
|
||||
|
||||
#[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();
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
|
||||
fn get_clipboard() -> Result<&'static x11_clipboard::Clipboard, String> {
|
||||
X11_CLIPBOARD
|
||||
.get_or_try_init(|| x11_clipboard::Clipboard::new())
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
|
||||
pub struct ClipboardContext {
|
||||
string_setter: x11rb::protocol::xproto::Atom,
|
||||
string_getter: x11rb::protocol::xproto::Atom,
|
||||
text_uri_list: x11rb::protocol::xproto::Atom,
|
||||
|
||||
clip: x11rb::protocol::xproto::Atom,
|
||||
prop: x11rb::protocol::xproto::Atom,
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
|
||||
fn parse_plain_uri_list(v: Vec<u8>) -> Result<String, String> {
|
||||
let text = String::from_utf8(v).map_err(|_| "ConversionFailure".to_owned())?;
|
||||
let mut list = String::new();
|
||||
for line in text.lines() {
|
||||
if !line.starts_with("file://") {
|
||||
continue;
|
||||
}
|
||||
let decoded = percent_encoding::percent_decode_str(line)
|
||||
.decode_utf8()
|
||||
.map_err(|_| "ConversionFailure".to_owned())?;
|
||||
list = list + "\n" + decoded.trim_start_matches("file://");
|
||||
}
|
||||
list = list.trim().to_owned();
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
|
||||
impl ClipboardContext {
|
||||
pub fn new() -> Result<Self, String> {
|
||||
let clipboard = get_clipboard()?;
|
||||
let string_getter = clipboard
|
||||
.getter
|
||||
.get_atom("UTF8_STRING")
|
||||
.map_err(|e| e.to_string())?;
|
||||
let string_setter = clipboard
|
||||
.setter
|
||||
.get_atom("UTF8_STRING")
|
||||
.map_err(|e| e.to_string())?;
|
||||
let text_uri_list = clipboard
|
||||
.getter
|
||||
.get_atom("text/uri-list")
|
||||
.map_err(|e| e.to_string())?;
|
||||
let prop = clipboard.getter.atoms.property;
|
||||
let clip = clipboard.getter.atoms.clipboard;
|
||||
Ok(Self {
|
||||
text_uri_list,
|
||||
string_setter,
|
||||
string_getter,
|
||||
clip,
|
||||
prop,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_text(&mut self) -> Result<String, String> {
|
||||
let clip = self.clip;
|
||||
let prop = self.prop;
|
||||
|
||||
const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(120);
|
||||
|
||||
let text_content = get_clipboard()?
|
||||
.load(clip, self.string_getter, prop, TIMEOUT)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let file_urls = get_clipboard()?.load(clip, self.text_uri_list, prop, TIMEOUT);
|
||||
|
||||
if file_urls.is_err() || file_urls.as_ref().unwrap().is_empty() {
|
||||
log::trace!("clipboard get text, no file urls");
|
||||
return String::from_utf8(text_content).map_err(|e| e.to_string());
|
||||
}
|
||||
|
||||
let file_urls = parse_plain_uri_list(file_urls.unwrap())?;
|
||||
|
||||
let text_content = String::from_utf8(text_content).map_err(|e| e.to_string())?;
|
||||
|
||||
if text_content.trim() == file_urls.trim() {
|
||||
log::trace!("clipboard got text but polluted");
|
||||
return Err(String::from("polluted text"));
|
||||
}
|
||||
|
||||
Ok(text_content)
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, content: String) -> Result<(), String> {
|
||||
let clip = self.clip;
|
||||
|
||||
let value = content.clone().into_bytes();
|
||||
get_clipboard()?
|
||||
.store(clip, self.string_setter, value)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::compress::decompress;
|
||||
use hbb_common::{
|
||||
|
@ -1725,6 +1725,17 @@ pub fn main_use_texture_render() -> SyncReturn<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_has_file_clipboard() -> SyncReturn<bool> {
|
||||
let ret = cfg!(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
feature = "unix-file-copy-paste",
|
||||
any(target_os = "linux", target_os = "macos")
|
||||
)
|
||||
));
|
||||
SyncReturn(ret)
|
||||
}
|
||||
|
||||
pub fn cm_init() {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::flutter::connection_manager::cm_init();
|
||||
|
@ -58,7 +58,7 @@ mod ui_session_interface;
|
||||
|
||||
mod hbbs_http;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
pub mod clipboard_file;
|
||||
|
||||
#[cfg(windows)]
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::{input_service::*, *};
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use crate::clipboard_file::*;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::common::update_clipboard;
|
||||
@ -192,7 +192,7 @@ pub struct Connection {
|
||||
// by peer
|
||||
disable_audio: bool,
|
||||
// by peer
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
enable_file_transfer: bool,
|
||||
// by peer
|
||||
audio_sender: Option<MediaSender>,
|
||||
@ -330,7 +330,7 @@ impl Connection {
|
||||
show_remote_cursor: false,
|
||||
ip: "".to_owned(),
|
||||
disable_audio: false,
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
enable_file_transfer: false,
|
||||
disable_clipboard: false,
|
||||
disable_keyboard: false,
|
||||
@ -479,7 +479,7 @@ impl Connection {
|
||||
ipc::Data::RawMessage(bytes) => {
|
||||
allow_err!(conn.stream.send_raw(bytes).await);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os="windows", target_os="linux"))]
|
||||
ipc::Data::ClipboardFile(clip) => {
|
||||
allow_err!(conn.stream.send(&clip_2_msg(clip)).await);
|
||||
}
|
||||
@ -1032,7 +1032,7 @@ impl Connection {
|
||||
pi.hostname = DEVICE_NAME.lock().unwrap().clone();
|
||||
pi.platform = "Android".into();
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
||||
let mut platform_additions = serde_json::Map::new();
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
@ -1062,7 +1062,18 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
any(target_os = "linux", target_os = "macos"),
|
||||
feature = "unix-file-copy-paste"
|
||||
)
|
||||
))]
|
||||
{
|
||||
platform_additions.insert("has_file_clipboard".into(), json!(true));
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
||||
if !platform_additions.is_empty() {
|
||||
pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into());
|
||||
}
|
||||
@ -1236,7 +1247,7 @@ impl Connection {
|
||||
self.audio && !self.disable_audio
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
fn file_transfer_enabled(&self) -> bool {
|
||||
self.file && self.enable_file_transfer
|
||||
}
|
||||
@ -1806,8 +1817,9 @@ impl Connection {
|
||||
}
|
||||
Some(message::Union::Cliprdr(_clip)) =>
|
||||
{
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
if let Some(clip) = msg_2_clip(_clip) {
|
||||
log::debug!("got clipfile from client peer");
|
||||
self.send_to_cm(ipc::Data::ClipboardFile(clip))
|
||||
}
|
||||
}
|
||||
@ -2389,7 +2401,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
if let Ok(q) = o.enable_file_transfer.enum_value() {
|
||||
if q != BoolOption::NotSet {
|
||||
self.enable_file_transfer = q == BoolOption::Yes;
|
||||
|
@ -18,4 +18,4 @@
|
||||
</header>
|
||||
<body #handler>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -196,7 +196,7 @@ class Header: Reactor.Component {
|
||||
{!cursor_embedded && <li #show-remote-cursor .toggle-option><span>{svg_checkmark}</span>{translate('Show remote cursor')}</li>}
|
||||
<li #show-quality-monitor .toggle-option><span>{svg_checkmark}</span>{translate('Show quality monitor')}</li>
|
||||
{audio_enabled ? <li #disable-audio .toggle-option><span>{svg_checkmark}</span>{translate('Mute')}</li> : ""}
|
||||
{is_win && pi.platform == 'Windows' && file_enabled ? <li #enable-file-transfer .toggle-option><span>{svg_checkmark}</span>{translate('Allow file copy and paste')}</li> : ""}
|
||||
{(is_win && pi.platform == "Windows") && file_enabled ? <li #enable-file-transfer .toggle-option><span>{svg_checkmark}</span>{translate('Allow file copy and paste')}</li> : ""}
|
||||
{keyboard_enabled && clipboard_enabled ? <li #disable-clipboard .toggle-option><span>{svg_checkmark}</span>{translate('Disable clipboard')}</li> : ""}
|
||||
{keyboard_enabled ? <li #lock-after-session-end .toggle-option><span>{svg_checkmark}</span>{translate('Lock after session end')}</li> : ""}
|
||||
{keyboard_enabled && pi.platform == "Windows" ? <li #privacy-mode><span>{svg_checkmark}</span>{translate('Privacy mode')}</li> : ""}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
use std::iter::FromIterator;
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -15,11 +15,11 @@ use std::{
|
||||
use crate::ipc::Connection;
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::ipc::{self, Data};
|
||||
#[cfg(windows)]
|
||||
use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, ContextSend};
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use clipboard::ContextSend;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::tokio::sync::mpsc::unbounded_channel;
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
use hbb_common::tokio::sync::Mutex as TokioMutex;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
@ -34,6 +34,7 @@ use hbb_common::{
|
||||
sync::mpsc::{self, UnboundedSender},
|
||||
task::spawn_blocking,
|
||||
},
|
||||
ResultType,
|
||||
};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
@ -69,9 +70,9 @@ struct IpcTaskRunner<T: InvokeUiCM> {
|
||||
close: bool,
|
||||
running: bool,
|
||||
conn_id: i32,
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
file_transfer_enabled: bool,
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
file_transfer_enabled_peer: bool,
|
||||
}
|
||||
|
||||
@ -164,7 +165,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
fn is_authorized(&self, id: i32) -> bool {
|
||||
CLIENTS
|
||||
.read()
|
||||
@ -185,11 +186,11 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
|
||||
.map(|c| c.disconnected = true);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
ContextSend::proc(|context: &mut CliprdrClientContext| -> u32 {
|
||||
empty_clipboard(context, id);
|
||||
0
|
||||
let _ = ContextSend::proc(|context| -> ResultType<()> {
|
||||
context.empty_clipboard(id)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
@ -328,31 +329,35 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
|
||||
// for tmp use, without real conn id
|
||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||
#[cfg(windows)]
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
let is_authorized = self.cm.is_authorized(self.conn_id);
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
let rx_clip1;
|
||||
let mut rx_clip;
|
||||
let _tx_clip;
|
||||
#[cfg(windows)]
|
||||
if is_authorized {
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
if self.conn_id > 0 && is_authorized {
|
||||
log::debug!("Clipboard is enabled from client peer: type 1");
|
||||
rx_clip1 = clipboard::get_rx_cliprdr_server(self.conn_id);
|
||||
rx_clip = rx_clip1.lock().await;
|
||||
} else {
|
||||
log::debug!("Clipboard is enabled from client peer, actually useless: type 2");
|
||||
let rx_clip2;
|
||||
(_tx_clip, rx_clip2) = unbounded_channel::<clipboard::ClipboardFile>();
|
||||
rx_clip1 = Arc::new(TokioMutex::new(rx_clip2));
|
||||
rx_clip = rx_clip1.lock().await;
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
|
||||
{
|
||||
(_tx_clip, rx_clip) = unbounded_channel::<i32>();
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
if ContextSend::is_enabled() {
|
||||
log::debug!("Clipboard is enabled");
|
||||
allow_err!(
|
||||
self.stream
|
||||
.send(&Data::ClipboardFile(clipboard::ClipboardFile::MonitorReady))
|
||||
@ -377,7 +382,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
log::debug!("conn_id: {}", id);
|
||||
self.cm.add_connection(id, is_file_transfer, port_forward, peer_id, name, authorized, keyboard, clipboard, audio, file, restart, recording, from_switch,self.tx.clone());
|
||||
self.conn_id = id;
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
|
||||
{
|
||||
self.file_transfer_enabled = _file_transfer_enabled;
|
||||
}
|
||||
@ -420,7 +425,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Data::ClipboardFile(_clip) => {
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os="linux", target_os = "macos"))]
|
||||
{
|
||||
let is_stopping_allowed = _clip.is_stopping_allowed_from_peer();
|
||||
let is_clipboard_enabled = ContextSend::is_enabled();
|
||||
@ -437,14 +442,15 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
continue;
|
||||
}
|
||||
let conn_id = self.conn_id;
|
||||
ContextSend::proc(|context: &mut CliprdrClientContext| -> u32 {
|
||||
clipboard::server_clip_file(context, conn_id, _clip)
|
||||
let _ = ContextSend::proc(|context| -> ResultType<()> {
|
||||
context.server_clip_file(conn_id, _clip)
|
||||
.map_err(|e| e.into())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Data::ClipboardFileEnabled(_enabled) => {
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os= "windows",target_os ="linux"))]
|
||||
{
|
||||
self.file_transfer_enabled_peer = _enabled;
|
||||
}
|
||||
@ -477,12 +483,13 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
}
|
||||
}
|
||||
Some(data) = self.rx.recv() => {
|
||||
if self.stream.send(&data).await.is_err() {
|
||||
if let Err(e) = self.stream.send(&data).await {
|
||||
log::error!("error encountered in IPC task, quitting: {}", e);
|
||||
break;
|
||||
}
|
||||
match &data {
|
||||
Data::SwitchPermission{name: _name, enabled: _enabled} => {
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os="linux", target_os="windows"))]
|
||||
if _name == "file" {
|
||||
self.file_transfer_enabled = *_enabled;
|
||||
}
|
||||
@ -497,7 +504,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
},
|
||||
clip_file = rx_clip.recv() => match clip_file {
|
||||
Some(_clip) => {
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os ="linux", target_os = "macos"))]
|
||||
{
|
||||
let is_stopping_allowed = _clip.is_stopping_allowed();
|
||||
let is_clipboard_enabled = ContextSend::is_enabled();
|
||||
@ -536,9 +543,9 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
||||
close: true,
|
||||
running: true,
|
||||
conn_id: 0,
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
file_transfer_enabled: false,
|
||||
#[cfg(windows)]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
file_transfer_enabled_peer: false,
|
||||
};
|
||||
|
||||
@ -568,7 +575,13 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
any(target_os = "linux", target_os = "macos"),
|
||||
feature = "unix-file-copy-paste"
|
||||
),
|
||||
))]
|
||||
ContextSend::enable(Config::get_option("enable-file-transfer").is_empty());
|
||||
|
||||
match ipc::new_listener("_cm").await {
|
||||
|
@ -1007,7 +1007,8 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
|
||||
let mut mouse_time = 0;
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
let mut id = "".to_owned();
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
#[allow(unused_mut, dead_code)]
|
||||
let mut enable_file_transfer = "".to_owned();
|
||||
|
||||
loop {
|
||||
@ -1030,7 +1031,13 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
|
||||
*OPTIONS.lock().unwrap() = v;
|
||||
*OPTION_SYNCED.lock().unwrap() = true;
|
||||
|
||||
#[cfg(target_os="windows")]
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
any(target_os="linux", target_os = "macos"),
|
||||
feature = "unix-file-copy-paste"
|
||||
)
|
||||
))]
|
||||
{
|
||||
let b = OPTIONS.lock().unwrap().get("enable-file-transfer").map(|x| x.to_string()).unwrap_or_default();
|
||||
if b != enable_file_transfer {
|
||||
|
@ -1420,7 +1420,13 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>, round: u32) {
|
||||
// It is ok to call this function multiple times.
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
all(
|
||||
any(target_os = "linux", target_os = "macos"),
|
||||
feature = "unix-file-copy-paste"
|
||||
)
|
||||
))]
|
||||
if !handler.is_file_transfer() && !handler.is_port_forward() {
|
||||
clipboard::ContextSend::enable(true);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user