diff --git a/Cargo.toml b/Cargo.toml index 00ce102cf..52d77211f 100644 --- a/Cargo.toml +++ b/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,10 +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"} -x11rb = {version = "0.12", features = ["all-extensions"]} -percent-encoding = "2.3" -once_cell = "1.18" +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" diff --git a/build.py b/build.py index 42dc8d6de..8cff351e2 100755 --- a/build.py +++ b/build.py @@ -24,18 +24,21 @@ else: flutter_build_dir_2 = f'flutter/{flutter_build_dir}' skip_cargo = False + def get_arch() -> str: custom_arch = os.environ.get("ARCH") if custom_arch is None: return "amd64" return custom_arch + def system2(cmd): err = os.system(cmd) if err != 0: print(f"Error occurred when executing: {cmd}. Exiting.") sys.exit(-1) + def get_version(): with open("Cargo.toml", encoding="utf-8") as fh: for line in fh: @@ -123,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', @@ -185,6 +193,7 @@ def download_extract_features(features, res_dir): import re proxy = '' + def req(url): if not proxy: return url @@ -196,9 +205,9 @@ def download_extract_features(features, res_dir): for (feat, feat_info) in features.items(): includes = feat_info['include'] if 'include' in feat_info and feat_info['include'] else [] - includes = [ re.compile(p) for p in includes ] + includes = [re.compile(p) for p in includes] excludes = feat_info['exclude'] if 'exclude' in feat_info and feat_info['exclude'] else [] - excludes = [ re.compile(p) for p in excludes ] + excludes = [re.compile(p) for p in excludes] print(f'{feat} download begin') download_filename = feat_info['zip_url'].split('/')[-1] @@ -272,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 @@ -350,6 +361,7 @@ def build_flutter_deb(version, features): os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version) os.chdir("..") + def build_deb_from_folder(version, binary_folder): os.chdir('flutter') system2('mkdir -p tmpdeb/usr/bin/') @@ -388,10 +400,12 @@ def build_deb_from_folder(version, binary_folder): os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version) os.chdir("..") + 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") @@ -557,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: @@ -577,7 +592,7 @@ def main(): else: print('Not signed') else: - # buid deb package + # build deb package system2( 'mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb') system2('dpkg-deb -R rustdesk.deb tmpdeb') diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 17c8883a5..0bead716f 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -436,7 +436,9 @@ Future> toolbarDisplayToggle( child: Text(translate('Mute')))); } // file copy and paste - if (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); diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 073edbfec..460894c31 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -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"; diff --git a/libs/clipboard/Cargo.toml b/libs/clipboard/Cargo.toml index a05eab3fc..36f783fc1 100644 --- a/libs/clipboard/Cargo.toml +++ b/libs/clipboard/Cargo.toml @@ -9,6 +9,21 @@ build = "build.rs" [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", +"dep:cacao" +] + [dependencies] thiserror = "1.0" lazy_static = "1.4" @@ -18,17 +33,18 @@ hbb_common = { path = "../hbb_common" } parking_lot = {version = "0.12"} [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] -rand = {version = "0.8"} -fuser = {version = "0.13"} -libc = {version = "0.2"} -dashmap = "5.5" -percent-encoding = "2.3" -utf16string = "0.2" -once_cell = "1.18" +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} +utf16string = {version = "0.2", optional = true} +once_cell = {version = "1.18", optional = true} [target.'cfg(target_os = "linux")'.dependencies] -x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch"} -x11rb = {version = "0.12", features = ["all-extensions"]} +percent-encoding = {version ="2.3", optional = true} +x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true} +x11rb = {version = "0.12", features = ["all-extensions"], optional = true} [target.'cfg(target_os = "macos")'.dependencies] -cacao = {git="https://github.com/clslaid/cacao", branch = "feat/set-file-urls"} +cacao = {git="https://github.com/clslaid/cacao", branch = "feat/set-file-urls", optional = true} diff --git a/libs/clipboard/src/context_send.rs b/libs/clipboard/src/context_send.rs index 64ab85983..f3606509f 100644 --- a/libs/clipboard/src/context_send.rs +++ b/libs/clipboard/src/context_send.rs @@ -47,6 +47,19 @@ impl ContextSend { } } + /// 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) -> ResultType<()>>( f: F, ) -> ResultType<()> { diff --git a/libs/clipboard/src/lib.rs b/libs/clipboard/src/lib.rs index 822d83a10..0e80cda2d 100644 --- a/libs/clipboard/src/lib.rs +++ b/libs/clipboard/src/lib.rs @@ -108,6 +108,7 @@ pub enum ClipboardFile { struct MsgChannel { peer_id: String, conn_id: i32, + #[allow(dead_code)] sender: UnboundedSender, receiver: Arc>>, } @@ -193,6 +194,7 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc crate::ResultType> { - use std::{fs::Permissions, os::unix::prelude::PermissionsExt}; + #[cfg(feature = "unix-file-copy-paste")] + { + use std::{fs::Permissions, os::unix::prelude::PermissionsExt}; - use hbb_common::{config::APP_NAME, log}; + use hbb_common::{config::APP_NAME, log}; - if !enable_files { - return Ok(Box::new(DummyCliprdrContext {}) as Box<_>); + 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<_>) } - 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 {} @@ -73,7 +81,8 @@ impl CliprdrServiceContext for DummyCliprdrContext { } } +#[cfg(feature = "unix-file-copy-paste")] +#[cfg(any(target_os = "linux", target_os = "macos"))] // begin of epoch used by microsoft // 1601-01-01 00:00:00 + LDAP_EPOCH_DELTA*(100 ns) = 1970-01-01 00:00:00 -#[cfg(any(target_os = "linux", target_os = "macos"))] const LDAP_EPOCH_DELTA: u64 = 116444772610000000; diff --git a/libs/clipboard/src/platform/unix/mod.rs b/libs/clipboard/src/platform/unix/mod.rs index e8665eb91..a58361f82 100644 --- a/libs/clipboard/src/platform/unix/mod.rs +++ b/libs/clipboard/src/platform/unix/mod.rs @@ -24,7 +24,6 @@ use self::url::{encode_path_to_uri, parse_plain_uri_list}; use super::fuse::FuseServer; -#[cfg(not(feature = "wayland"))] #[cfg(target_os = "linux")] /// clipboard implementation of x11 pub mod x11; @@ -34,6 +33,7 @@ pub mod x11; pub mod ns_clipboard; pub mod local_file; + #[cfg(target_os = "linux")] pub mod url; @@ -68,7 +68,6 @@ fn add_remote_format(local_name: &str, remote_id: i32) { trait SysClipboard: Send + Sync { fn start(&self); - fn stop(&self); fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>; fn get_file_list(&self) -> Vec; @@ -531,7 +530,7 @@ impl CliprdrServiceContext for ClipboardContext { if let Some(fuse_handle) = self.fuse_handle.lock().take() { fuse_handle.join(); } - self.clipboard.stop(); + // we don't stop the clipboard, keep listening in case of restart Ok(()) } diff --git a/libs/clipboard/src/platform/unix/ns_clipboard.rs b/libs/clipboard/src/platform/unix/ns_clipboard.rs index 19dc3641b..32c60a464 100644 --- a/libs/clipboard/src/platform/unix/ns_clipboard.rs +++ b/libs/clipboard/src/platform/unix/ns_clipboard.rs @@ -1,8 +1,4 @@ -use std::{ - collections::BTreeSet, - path::PathBuf, - sync::atomic::{AtomicBool, Ordering}, -}; +use std::{collections::BTreeSet, path::PathBuf}; use cacao::pasteboard::{Pasteboard, PasteboardName}; use hbb_common::log; @@ -28,7 +24,6 @@ fn set_file_list(file_list: &[PathBuf]) -> Result<(), CliprdrError> { } pub struct NsPasteboard { - stopped: AtomicBool, ignore_path: PathBuf, former_file_list: Mutex>, @@ -37,16 +32,10 @@ pub struct NsPasteboard { impl NsPasteboard { pub fn new(ignore_path: &PathBuf) -> Result { Ok(Self { - stopped: AtomicBool::new(false), ignore_path: ignore_path.to_owned(), former_file_list: Mutex::new(vec![]), }) } - - #[inline] - fn is_stopped(&self) -> bool { - self.stopped.load(Ordering::Relaxed) - } } impl SysClipboard for NsPasteboard { @@ -56,13 +45,11 @@ impl SysClipboard for NsPasteboard { } fn start(&self) { - self.stopped.store(false, Ordering::Relaxed); + { + *self.former_file_list.lock() = vec![]; + } loop { - if self.is_stopped() { - std::thread::sleep(std::time::Duration::from_millis(100)); - continue; - } let file_list = match wait_file_list() { Some(v) => v, None => { @@ -104,10 +91,6 @@ impl SysClipboard for NsPasteboard { log::debug!("stop listening file related atoms on clipboard"); } - fn stop(&self) { - self.stopped.store(true, Ordering::Relaxed); - } - fn get_file_list(&self) -> Vec { self.former_file_list.lock().clone() } diff --git a/libs/clipboard/src/platform/unix/x11.rs b/libs/clipboard/src/platform/unix/x11.rs index 20ca02049..4ca3a2c0b 100644 --- a/libs/clipboard/src/platform/unix/x11.rs +++ b/libs/clipboard/src/platform/unix/x11.rs @@ -1,8 +1,4 @@ -use std::{ - collections::BTreeSet, - path::PathBuf, - sync::atomic::{AtomicBool, Ordering}, -}; +use std::{collections::BTreeSet, path::PathBuf}; use hbb_common::log; use once_cell::sync::OnceCell; @@ -16,15 +12,11 @@ use super::{encode_path_to_uri, parse_plain_uri_list, SysClipboard}; static X11_CLIPBOARD: OnceCell = OnceCell::new(); -// this is tested on an Arch Linux with X11 -const X11_CLIPBOARD_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(70); - fn get_clip() -> Result<&'static Clipboard, CliprdrError> { X11_CLIPBOARD.get_or_try_init(|| Clipboard::new().map_err(|_| CliprdrError::CliprdrInit)) } pub struct X11Clipboard { - stop: AtomicBool, ignore_path: PathBuf, text_uri_list: Atom, gnome_copied_files: Atom, @@ -50,7 +42,6 @@ impl X11Clipboard { .map_err(|_| CliprdrError::CliprdrInit)?; Ok(Self { ignore_path: ignore_path.to_owned(), - stop: AtomicBool::new(false), text_uri_list, gnome_copied_files, nautilus_clipboard, @@ -64,11 +55,18 @@ impl X11Clipboard { // NOTE: // # why not use `load_wait` // load_wait is likely to wait forever, which is not what we want - let res = get_clip()?.load(clip, target, prop, X11_CLIPBOARD_TIMEOUT); + let res = get_clip()?.load_wait(clip, target, prop); match res { Ok(res) => Ok(res), Err(x11_clipboard::error::Error::UnexpectedType(_)) => Ok(vec![]), - Err(_) => Err(CliprdrError::ClipboardInternalError), + 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) + } } } @@ -81,22 +79,12 @@ impl X11Clipboard { } fn wait_file_list(&self) -> Result>, CliprdrError> { - if self.stop.load(Ordering::Relaxed) { - return Ok(None); - } let v = self.load(self.text_uri_list)?; let p = parse_plain_uri_list(v)?; Ok(Some(p)) } } -impl X11Clipboard { - #[inline] - fn is_stopped(&self) -> bool { - self.stop.load(Ordering::Relaxed) - } -} - impl SysClipboard for X11Clipboard { fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> { *self.former_file_list.lock() = paths.to_vec(); @@ -114,19 +102,12 @@ impl SysClipboard for X11Clipboard { .map_err(|_| CliprdrError::ClipboardInternalError) } - fn stop(&self) { - self.stop.store(true, Ordering::Relaxed); - } - fn start(&self) { - self.stop.store(false, Ordering::Relaxed); - + { + // clear cached file list + *self.former_file_list.lock() = vec![]; + } loop { - if self.is_stopped() { - std::thread::sleep(std::time::Duration::from_millis(100)); - continue; - } - let sth = match self.wait_file_list() { Ok(sth) => sth, Err(e) => { diff --git a/libs/portable/generate.py b/libs/portable/generate.py index 640f2ae6a..61d8c78f7 100644 --- a/libs/portable/generate.py +++ b/libs/portable/generate.py @@ -1,4 +1,3 @@ -from ast import parse import os import optparse from hashlib import md5 @@ -47,7 +46,7 @@ def write_metadata(md5_table: dict, output_folder: str, exe: str): f.write((len(path)).to_bytes(length=length_count, byteorder='big')) f.write(path) # data length & compressed data - f.write((data_length).to_bytes( + f.write(data_length.to_bytes( length=length_count, byteorder='big')) f.write(compressed_data) # md5 code @@ -65,6 +64,8 @@ def build_portable(output_folder: str): # Linux: python3 generate.py -f ../rustdesk-portable-packer/test -o . -e ./test/main.py # Windows: python3 .\generate.py -f ..\rustdesk\flutter\build\windows\runner\Debug\ -o . -e ..\rustdesk\flutter\build\windows\runner\Debug\rustdesk.exe + + if __name__ == '__main__': parser = optparse.OptionParser() parser.add_option("-f", "--folder", dest="folder", diff --git a/res/lang.py b/res/lang.py index 74492818a..4655d2cbe 100644 --- a/res/lang.py +++ b/res/lang.py @@ -5,20 +5,22 @@ import glob import sys import csv + def get_lang(lang): - out = {} - for ln in open('./src/lang/%s.rs'%lang, encoding='utf8'): - ln = ln.strip() - if ln.startswith('("'): - k, v = line_split(ln) - out[k] = v - return out + out = {} + for ln in open('./src/lang/%s.rs' % lang, encoding='utf8'): + ln = ln.strip() + if ln.startswith('("'): + k, v = line_split(ln) + out[k] = v + return out + def line_split(line): toks = line.split('", "') if len(toks) != 2: print(line) - assert(0) + assert 0 # Replace fixed position. # Because toks[1] may be v") or v"), k = toks[0][toks[0].find('"') + 1:] @@ -27,62 +29,62 @@ def line_split(line): def main(): - if len(sys.argv) == 1: - expand() - elif sys.argv[1] == '1': - to_csv() - else: - to_rs(sys.argv[1]) + if len(sys.argv) == 1: + expand() + elif sys.argv[1] == '1': + to_csv() + else: + to_rs(sys.argv[1]) def expand(): - for fn in glob.glob('./src/lang/*.rs'): - lang = os.path.basename(fn)[:-3] - if lang in ['en','template']: continue - print(lang) - dict = get_lang(lang) - fw = open("./src/lang/%s.rs"%lang, "wt", encoding='utf8') - for line in open('./src/lang/template.rs', encoding='utf8'): - line_strip = line.strip() - if line_strip.startswith('("'): - k, v = line_split(line_strip) - if k in dict: - # embraced with " to avoid empty v - line = line.replace('"%s"'%v, '"%s"'%dict[k]) - else: - line = line.replace(v, "") - fw.write(line) - else: - fw.write(line) - fw.close() + for fn in glob.glob('./src/lang/*.rs'): + lang = os.path.basename(fn)[:-3] + if lang in ['en', 'template']: continue + print(lang) + dict = get_lang(lang) + fw = open("./src/lang/%s.rs" % lang, "wt", encoding='utf8') + for line in open('./src/lang/template.rs', encoding='utf8'): + line_strip = line.strip() + if line_strip.startswith('("'): + k, v = line_split(line_strip) + if k in dict: + # embraced with " to avoid empty v + line = line.replace('"%s"' % v, '"%s"' % dict[k]) + else: + line = line.replace(v, "") + fw.write(line) + else: + fw.write(line) + fw.close() def to_csv(): - for fn in glob.glob('./src/lang/*.rs'): - lang = os.path.basename(fn)[:-3] - csvfile = open('./src/lang/%s.csv'%lang, "wt", encoding='utf8') - csvwriter = csv.writer(csvfile) - for line in open(fn, encoding='utf8'): - line_strip = line.strip() - if line_strip.startswith('("'): - k, v = line_split(line_strip) - csvwriter.writerow([k, v]) - csvfile.close() + for fn in glob.glob('./src/lang/*.rs'): + lang = os.path.basename(fn)[:-3] + csvfile = open('./src/lang/%s.csv' % lang, "wt", encoding='utf8') + csvwriter = csv.writer(csvfile) + for line in open(fn, encoding='utf8'): + line_strip = line.strip() + if line_strip.startswith('("'): + k, v = line_split(line_strip) + csvwriter.writerow([k, v]) + csvfile.close() def to_rs(lang): - csvfile = open('%s.csv'%lang, "rt", encoding='utf8') - fw = open("./src/lang/%s.rs"%lang, "wt", encoding='utf8') - fw.write('''lazy_static::lazy_static! { + csvfile = open('%s.csv' % lang, "rt", encoding='utf8') + fw = open("./src/lang/%s.rs" % lang, "wt", encoding='utf8') + fw.write('''lazy_static::lazy_static! { pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ''') - for row in csv.reader(csvfile): - fw.write(' ("%s", "%s"),\n'%(row[0].replace('"', '\"'), row[1].replace('"', '\"'))) - fw.write(''' ].iter().cloned().collect(); + for row in csv.reader(csvfile): + fw.write(' ("%s", "%s"),\n' % (row[0].replace('"', '\"'), row[1].replace('"', '\"'))) + fw.write(''' ].iter().cloned().collect(); } ''') - fw.close() + fw.close() main() diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 597314bc8..ca5ffee3b 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -317,6 +317,9 @@ impl Remote { if stop { ContextSend::set_is_stopped(); } else { + 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); @@ -1704,7 +1707,13 @@ impl Remote { } fn check_clipboard_file_context(&self) { - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + #[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; @@ -1736,6 +1745,9 @@ impl Remote { "Process clipboard message from server peer, stop: {}, is_stopping_allowed: {}, file_transfer_enabled: {}", stop, is_stopping_allowed, file_transfer_enabled); 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) diff --git a/src/common.rs b/src/common.rs index 7435f4c6e..27e32c98a 100644 --- a/src/common.rs +++ b/src/common.rs @@ -14,22 +14,22 @@ pub enum GrabState { #[cfg(not(any( target_os = "android", target_os = "ios", - all(target_os = "linux", not(feature = "wayland")) + all(target_os = "linux", feature = "unix-file-copy-paste") )))] pub use arboard::Clipboard as ClipboardContext; -#[cfg(all(target_os = "linux", not(feature = "wayland")))] +#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))] static X11_CLIPBOARD: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); -#[cfg(all(target_os = "linux", not(feature = "wayland")))] +#[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", not(feature = "wayland")))] +#[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, @@ -39,7 +39,7 @@ pub struct ClipboardContext { prop: x11rb::protocol::xproto::Atom, } -#[cfg(all(target_os = "linux", not(feature = "wayland")))] +#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))] fn parse_plain_uri_list(v: Vec) -> Result { let text = String::from_utf8(v).map_err(|_| "ConversionFailure".to_owned())?; let mut list = String::new(); @@ -56,7 +56,7 @@ fn parse_plain_uri_list(v: Vec) -> Result { Ok(list) } -#[cfg(all(target_os = "linux", not(feature = "wayland")))] +#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))] impl ClipboardContext { pub fn new() -> Result { let clipboard = get_clipboard()?; @@ -87,7 +87,7 @@ impl ClipboardContext { let clip = self.clip; let prop = self.prop; - const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100); + const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(120); let text_content = get_clipboard()? .load(clip, self.string_getter, prop, TIMEOUT) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 872d782d8..dc4a904d4 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1725,6 +1725,17 @@ pub fn main_use_texture_render() -> SyncReturn { } } +pub fn main_has_file_clipboard() -> SyncReturn { + 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(); diff --git a/src/lang/de.rs b/src/lang/de.rs index fde9d132c..3fcd2fada 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -570,7 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Big tiles", "Große Kacheln"), ("Small tiles", "Kleine Kacheln"), ("List", "Liste"), - ("Virtual display", ""), - ("Plug out all", ""), + ("Virtual display", "Virtueller Bildschirm"), + ("Plug out all", "Alle ausschalten"), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 87964f39a..b0a5218aa 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -570,7 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Big tiles", "Icone grandi"), ("Small tiles", "Icone piccole"), ("List", "Elenco"), - ("Virtual display", ""), - ("Plug out all", ""), + ("Virtual display", "Scehrmo virtuale"), + ("Plug out all", "Scollega tutto"), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 9c4101dfa..fbb04387d 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -274,7 +274,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Chat", "Czat"), ("Total", "Łącznie"), ("items", "elementów"), - ("Selected", "Zaznaczonych"), + ("Selected", "zaznaczonych"), ("Screen Capture", "Przechwytywanie ekranu"), ("Input Control", "Kontrola wejścia"), ("Audio Capture", "Przechwytywanie dźwięku"), @@ -564,13 +564,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("elevated_switch_display_msg", "Przełącz się na ekran główny, ponieważ wyświetlanie kilku ekranów nie jest obsługiwane przy podniesionych uprawnieniach."), ("Open in new window", "Otwórz w nowym oknie"), ("Show displays as individual windows", "Pokaż ekrany w osobnych oknach"), - ("Use all my displays for the remote session", ""), - ("selinux_tip", ""), - ("Change view", ""), - ("Big tiles", ""), - ("Small tiles", ""), - ("List", ""), - ("Virtual display", ""), - ("Plug out all", ""), + ("Use all my displays for the remote session", "Użyj wszystkich moich ekranów do zdalnej sesji"), + ("selinux_tip", "SELinux jest włączony na Twoim urządzeniu, co może przeszkodzić w uruchomieniu RustDesk po stronie kontrolowanej."), + ("Change view", "Zmień widok"), + ("Big tiles", "Duże kafelki"), + ("Small tiles", "Małe kafelki"), + ("List", "Lista"), + ("Virtual display", "Witualne ekrany"), + ("Plug out all", "Odłącz wszystko"), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index f43df683c..1ea714bf4 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -570,7 +570,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Big tiles", "Большие значки"), ("Small tiles", "Маленькие значки"), ("List", "Список"), - ("Virtual display", ""), - ("Plug out all", ""), + ("Virtual display", "Виртуальный дисплей"), + ("Plug out all", "Отключить все"), ].iter().cloned().collect(); } diff --git a/src/server/connection.rs b/src/server/connection.rs index fb12b8c0e..be7a10aa2 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -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()); } diff --git a/src/ui/header.tis b/src/ui/header.tis index d4b826728..b0b21bc5e 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -196,7 +196,7 @@ class Header: Reactor.Component { {!cursor_embedded &&
  • {svg_checkmark}{translate('Show remote cursor')}
  • }
  • {svg_checkmark}{translate('Show quality monitor')}
  • {audio_enabled ?
  • {svg_checkmark}{translate('Mute')}
  • : ""} - {((is_win && pi.platform == "Windows")||(is_linux && pi.platform == "Linux"))||(is_osx && pi.platform == "Mac OS") && file_enabled ?
  • {svg_checkmark}{translate('Allow file copy and paste')}
  • : ""} + {(is_win && pi.platform == "Windows") && file_enabled ?
  • {svg_checkmark}{translate('Allow file copy and paste')}
  • : ""} {keyboard_enabled && clipboard_enabled ?
  • {svg_checkmark}{translate('Disable clipboard')}
  • : ""} {keyboard_enabled ?
  • {svg_checkmark}{translate('Lock after session end')}
  • : ""} {keyboard_enabled && pi.platform == "Windows" ?
  • {svg_checkmark}{translate('Privacy mode')}
  • : ""} diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 177488e6c..7380b7e63 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -575,12 +575,13 @@ pub async fn start_ipc(cm: ConnectionManager) { } }); - log::debug!( - "start_ipc enable context_send: {}", - Config::get_option("enable-file-transfer").is_empty() - ); - - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + #[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 { diff --git a/src/ui_interface.rs b/src/ui_interface.rs index de0baec0b..7cabdf790 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1008,6 +1008,7 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver Session { #[tokio::main(flavor = "current_thread")] pub async fn io_loop(handler: Session, round: u32) { // It is ok to call this function multiple times. - #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] + #[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); }