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:
commit
62563ad8a1
15
Cargo.toml
15
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"
|
||||
|
25
build.py
25
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')
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
@ -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}
|
||||
|
@ -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<()> {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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",
|
||||
|
102
res/lang.py
102
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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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> : ""}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user