Merge branch 'feat/x11/clipboard-file/init' into feat/osx/clipboard-file

Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
ClSlaid 2023-10-30 15:26:03 +08:00
commit 62563ad8a1
No known key found for this signature in database
GPG Key ID: E0A5F564C51C056E
25 changed files with 271 additions and 191 deletions

View File

@ -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"

View File

@ -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')

View File

@ -436,7 +436,9 @@ Future<List<TToggleMenu>> 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);

View File

@ -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";

View File

@ -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}

View File

@ -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<F: FnOnce(&mut Box<dyn CliprdrServiceContext>) -> ResultType<()>>(
f: F,
) -> ResultType<()> {

View File

@ -108,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>>>,
}
@ -193,6 +194,7 @@ 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")]
@ -204,7 +206,7 @@ fn send_data(conn_id: i32, data: ClipboardFile) {
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
@ -218,6 +220,7 @@ fn send_data_to_channel(conn_id: i32, data: ClipboardFile) {
}
}
#[cfg(feature = "unix-file-copy-paste")]
#[inline]
fn send_data_to_all(data: ClipboardFile) {
// no need to handle result here

View File

@ -14,45 +14,53 @@ pub fn create_cliprdr_context(
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_files: bool,
_enable_others: bool,
response_wait_timeout_secs: u32,
_response_wait_timeout_secs: u32,
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
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;

View File

@ -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<PathBuf>;
@ -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(())
}

View File

@ -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<Vec<PathBuf>>,
@ -37,16 +32,10 @@ pub struct NsPasteboard {
impl NsPasteboard {
pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
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<PathBuf> {
self.former_file_list.lock().clone()
}

View File

@ -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<Clipboard> = 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<Option<Vec<PathBuf>>, 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) => {

View File

@ -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",

View File

@ -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()

View File

@ -317,6 +317,9 @@ impl<T: InvokeUiSession> Remote<T> {
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<T: InvokeUiSession> Remote<T> {
}
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<T: InvokeUiSession> Remote<T> {
"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)

View File

@ -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<x11_clipboard::Clipboard> =
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<u8>) -> Result<String, String> {
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<u8>) -> Result<String, String> {
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<Self, String> {
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)

View File

@ -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();

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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());
}

View File

@ -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")||(is_linux && pi.platform == "Linux"))||(is_osx && pi.platform == "Mac OS") && 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> : ""}

View File

@ -575,12 +575,13 @@ pub async fn start_ipc<T: InvokeUiCM>(cm: ConnectionManager<T>) {
}
});
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 {

View File

@ -1008,6 +1008,7 @@ async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc:
#[cfg(not(feature = "flutter"))]
let mut id = "".to_owned();
#[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(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"
)
))]
{
let b = OPTIONS.lock().unwrap().get("enable-file-transfer").map(|x| x.to_string()).unwrap_or_default();
if b != enable_file_transfer {

View File

@ -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(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);
}