mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-27 14:59:02 +08:00
feat: android clipboard, multi-formats (#9950)
* feat: android clipboard, multi-formats Signed-off-by: fufesou <linlong1266@gmail.com> * Chore Signed-off-by: fufesou <linlong1266@gmail.com> * Remove unused code Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
parent
0707e791e8
commit
8b710f62c8
@ -304,7 +304,13 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
||||
val popupMenu = PopupMenu(this, floatingView)
|
||||
val idShowRustDesk = 0
|
||||
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
||||
val idStopService = 1
|
||||
// For host side, clipboard sync
|
||||
val idSyncClipboard = 1
|
||||
val isClipboardListenerEnabled = MainActivity.rdClipboardManager?.isListening ?: false
|
||||
if (isClipboardListenerEnabled) {
|
||||
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
|
||||
}
|
||||
val idStopService = 2
|
||||
popupMenu.menu.add(0, idStopService, 0, translate("Stop service"))
|
||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
@ -312,6 +318,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
||||
openMainActivity()
|
||||
true
|
||||
}
|
||||
idSyncClipboard -> {
|
||||
syncClipboard()
|
||||
true
|
||||
}
|
||||
idStopService -> {
|
||||
stopMainService()
|
||||
true
|
||||
@ -340,6 +350,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncClipboard() {
|
||||
MainActivity.rdClipboardManager?.syncClipboard(false)
|
||||
}
|
||||
|
||||
private fun stopMainService() {
|
||||
MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null)
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.ClipboardManager
|
||||
import android.os.Bundle
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
@ -40,6 +42,9 @@ import kotlin.concurrent.thread
|
||||
class MainActivity : FlutterActivity() {
|
||||
companion object {
|
||||
var flutterMethodChannel: MethodChannel? = null
|
||||
private var _rdClipboardManager: RdClipboardManager? = null
|
||||
val rdClipboardManager: RdClipboardManager?
|
||||
get() = _rdClipboardManager;
|
||||
}
|
||||
|
||||
private val channelTag = "mChannel"
|
||||
@ -90,11 +95,20 @@ class MainActivity : FlutterActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (_rdClipboardManager == null) {
|
||||
_rdClipboardManager = RdClipboardManager(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
|
||||
FFI.setClipboardManager(_rdClipboardManager!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.e(logTag, "onDestroy")
|
||||
mainService?.let {
|
||||
unbindService(serviceConnection)
|
||||
}
|
||||
rdClipboardManager?.rustEnableServiceClipboard(false)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@ -393,6 +407,15 @@ class MainActivity : FlutterActivity() {
|
||||
super.onStart()
|
||||
stopService(Intent(this, FloatingWindowService::class.java))
|
||||
}
|
||||
|
||||
// For client side
|
||||
// When swithing from other app to this app, try to sync clipboard.
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
rdClipboardManager?.syncClipboard(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://cjycode.com/flutter_rust_bridge/guides/how-to/ndk-init
|
||||
|
@ -0,0 +1,224 @@
|
||||
package com.carriez.flutter_hbb
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipDescription
|
||||
import android.content.ClipboardManager
|
||||
import android.util.Log
|
||||
import androidx.annotation.Keep
|
||||
|
||||
import hbb.MessageOuterClass.ClipboardFormat
|
||||
import hbb.MessageOuterClass.Clipboard
|
||||
import hbb.MessageOuterClass.MultiClipboards
|
||||
|
||||
import ffi.FFI
|
||||
|
||||
class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
||||
private val logTag = "RdClipboardManager"
|
||||
private val supportedMimeTypes = arrayOf(
|
||||
ClipDescription.MIMETYPE_TEXT_PLAIN,
|
||||
ClipDescription.MIMETYPE_TEXT_HTML
|
||||
)
|
||||
|
||||
// 1. Avoid listening to the same clipboard data updated by `rustUpdateClipboard`.
|
||||
// 2. Avoid sending the clipboard data before enabling client clipboard.
|
||||
// 1) Disable clipboard
|
||||
// 2) Copy text "a"
|
||||
// 3) Enable clipboard
|
||||
// 4) Switch to another app
|
||||
// 5) Switch back to the app
|
||||
// 6) "a" should not be sent to the client, because it's copied before enabling clipboard
|
||||
//
|
||||
// It's okay to that `rustEnableClientClipboard(false)` is called after `rustUpdateClipboard`,
|
||||
// though the `lastUpdatedClipData` will be set to null once.
|
||||
private var lastUpdatedClipData: ClipData? = null
|
||||
private var isClientEnabled = true;
|
||||
private var _isListening = false;
|
||||
val isListening: Boolean
|
||||
get() = _isListening
|
||||
|
||||
fun checkPrimaryClip(isClient: Boolean, isSync: Boolean) {
|
||||
val clipData = clipboardManager.primaryClip
|
||||
if (clipData != null && clipData.itemCount > 0) {
|
||||
// Only handle the first item in the clipboard for now.
|
||||
val clip = clipData.getItemAt(0)
|
||||
val isHostSync = !isClient && isSync
|
||||
// Ignore the `isClipboardDataEqual()` check if it's a host sync operation.
|
||||
// Because it's a action manually triggered by the user.
|
||||
if (!isHostSync) {
|
||||
if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) {
|
||||
Log.d(logTag, "Clipboard data is the same as last update, ignore")
|
||||
return
|
||||
}
|
||||
}
|
||||
val mimeTypeCount = clipData.description.getMimeTypeCount()
|
||||
val mimeTypes = mutableListOf<String>()
|
||||
for (i in 0 until mimeTypeCount) {
|
||||
mimeTypes.add(clipData.description.getMimeType(i))
|
||||
}
|
||||
var text: CharSequence? = null;
|
||||
var html: String? = null;
|
||||
if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||
text = clip?.text
|
||||
}
|
||||
if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) {
|
||||
text = clip?.text
|
||||
html = clip?.htmlText
|
||||
}
|
||||
var count = 0
|
||||
val clips = MultiClipboards.newBuilder()
|
||||
if (text != null) {
|
||||
val content = com.google.protobuf.ByteString.copyFromUtf8(text.toString())
|
||||
clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Text).setContent(content).build())
|
||||
count++
|
||||
}
|
||||
if (html != null) {
|
||||
val content = com.google.protobuf.ByteString.copyFromUtf8(html)
|
||||
clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Html).setContent(content).build())
|
||||
count++
|
||||
}
|
||||
if (count > 0) {
|
||||
val clipsBytes = clips.build().toByteArray()
|
||||
val isClientFlag = if (isClient) 1 else 0
|
||||
val clipsBuf = ByteBuffer.allocateDirect(clipsBytes.size + 1).apply {
|
||||
put(isClientFlag.toByte())
|
||||
put(clipsBytes)
|
||||
}
|
||||
clipsBuf.flip()
|
||||
lastUpdatedClipData = clipData
|
||||
Log.d(logTag, "${if (isClient) "client" else "host"}, send clipboard data to the remote")
|
||||
FFI.onClipboardUpdate(clipsBuf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val clipboardListener = object : ClipboardManager.OnPrimaryClipChangedListener {
|
||||
override fun onPrimaryClipChanged() {
|
||||
Log.d(logTag, "onPrimaryClipChanged")
|
||||
checkPrimaryClip(true, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSupportedMimeType(mimeType: String): Boolean {
|
||||
return supportedMimeTypes.contains(mimeType)
|
||||
}
|
||||
|
||||
private fun isClipboardDataEqual(left: ClipData, right: ClipData): Boolean {
|
||||
if (left.description.getMimeTypeCount() != right.description.getMimeTypeCount()) {
|
||||
return false
|
||||
}
|
||||
val mimeTypeCount = left.description.getMimeTypeCount()
|
||||
for (i in 0 until mimeTypeCount) {
|
||||
if (left.description.getMimeType(i) != right.description.getMimeType(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (left.itemCount != right.itemCount) {
|
||||
return false
|
||||
}
|
||||
for (i in 0 until left.itemCount) {
|
||||
val mimeType = left.description.getMimeType(i)
|
||||
if (!isSupportedMimeType(mimeType)) {
|
||||
continue
|
||||
}
|
||||
val leftItem = left.getItemAt(i)
|
||||
val rightItem = right.getItemAt(i)
|
||||
if (mimeType == ClipDescription.MIMETYPE_TEXT_PLAIN || mimeType == ClipDescription.MIMETYPE_TEXT_HTML) {
|
||||
if (leftItem.text != rightItem.text || leftItem.htmlText != rightItem.htmlText) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun rustEnableServiceClipboard(enable: Boolean) {
|
||||
Log.d(logTag, "rustEnableServiceClipboard: enable: $enable, _isListening: $_isListening")
|
||||
if (enable) {
|
||||
if (!_isListening) {
|
||||
clipboardManager.addPrimaryClipChangedListener(clipboardListener)
|
||||
_isListening = true
|
||||
}
|
||||
} else {
|
||||
if (_isListening) {
|
||||
clipboardManager.removePrimaryClipChangedListener(clipboardListener)
|
||||
_isListening = false
|
||||
lastUpdatedClipData = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun rustEnableClientClipboard(enable: Boolean) {
|
||||
Log.d(logTag, "rustEnableClientClipboard: enable: $enable")
|
||||
isClientEnabled = enable
|
||||
if (enable) {
|
||||
lastUpdatedClipData = clipboardManager.primaryClip
|
||||
} else {
|
||||
lastUpdatedClipData = null
|
||||
}
|
||||
}
|
||||
|
||||
fun syncClipboard(isClient: Boolean) {
|
||||
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled, _isListening: $_isListening")
|
||||
if (isClient && !isClientEnabled) {
|
||||
return
|
||||
}
|
||||
if (!isClient && !_isListening) {
|
||||
return
|
||||
}
|
||||
checkPrimaryClip(isClient, true)
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun rustUpdateClipboard(clips: ByteArray) {
|
||||
val clips = MultiClipboards.parseFrom(clips)
|
||||
var mimeTypes = mutableListOf<String>()
|
||||
var text: String? = null
|
||||
var html: String? = null
|
||||
for (clip in clips.getClipboardsList()) {
|
||||
when (clip.format) {
|
||||
ClipboardFormat.Text -> {
|
||||
mimeTypes.add(ClipDescription.MIMETYPE_TEXT_PLAIN)
|
||||
text = String(clip.content.toByteArray(), Charsets.UTF_8)
|
||||
}
|
||||
ClipboardFormat.Html -> {
|
||||
mimeTypes.add(ClipDescription.MIMETYPE_TEXT_HTML)
|
||||
html = String(clip.content.toByteArray(), Charsets.UTF_8)
|
||||
}
|
||||
ClipboardFormat.ImageRgba -> {
|
||||
}
|
||||
ClipboardFormat.ImagePng -> {
|
||||
}
|
||||
else -> {
|
||||
Log.e(logTag, "Unsupported clipboard format: ${clip.format}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val clipDescription = ClipDescription("clipboard", mimeTypes.toTypedArray())
|
||||
var item: ClipData.Item? = null
|
||||
if (text == null) {
|
||||
Log.e(logTag, "No text content in clipboard")
|
||||
return
|
||||
} else {
|
||||
if (html == null) {
|
||||
item = ClipData.Item(text)
|
||||
} else {
|
||||
item = ClipData.Item(text, html)
|
||||
}
|
||||
}
|
||||
if (item == null) {
|
||||
Log.e(logTag, "No item in clipboard")
|
||||
return
|
||||
}
|
||||
val clipData = ClipData(clipDescription, item)
|
||||
lastUpdatedClipData = clipData
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ package ffi
|
||||
import android.content.Context
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
import com.carriez.flutter_hbb.RdClipboardManager
|
||||
|
||||
object FFI {
|
||||
init {
|
||||
System.loadLibrary("rustdesk")
|
||||
@ -12,6 +14,7 @@ object FFI {
|
||||
|
||||
external fun init(ctx: Context)
|
||||
external fun initContext(ctx: Context)
|
||||
external fun setClipboardManager(clipboardManager: RdClipboardManager)
|
||||
external fun startServer(app_dir: String, custom_client_config: String)
|
||||
external fun startService()
|
||||
external fun onVideoFrameUpdate(buf: ByteBuffer)
|
||||
@ -21,4 +24,5 @@ object FFI {
|
||||
external fun setFrameRawEnable(name: String, value: Boolean)
|
||||
external fun setCodecInfo(info: String)
|
||||
external fun getLocalOption(key: String): String
|
||||
external fun onClipboardUpdate(clips: ByteBuffer)
|
||||
}
|
@ -596,7 +596,9 @@ class _PermissionCheckerState extends State<PermissionChecker> {
|
||||
translate("android_version_audio_tip"),
|
||||
style: const TextStyle(color: MyTheme.darkGray),
|
||||
))
|
||||
])
|
||||
]),
|
||||
PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk,
|
||||
serverModel.toggleClipboard),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class ServerModel with ChangeNotifier {
|
||||
bool _inputOk = false;
|
||||
bool _audioOk = false;
|
||||
bool _fileOk = false;
|
||||
bool _clipboardOk = false;
|
||||
bool _showElevation = false;
|
||||
bool hideCm = false;
|
||||
int _connectStatus = 0; // Rendezvous Server status
|
||||
@ -59,6 +60,8 @@ class ServerModel with ChangeNotifier {
|
||||
|
||||
bool get fileOk => _fileOk;
|
||||
|
||||
bool get clipboardOk => _clipboardOk;
|
||||
|
||||
bool get showElevation => _showElevation;
|
||||
|
||||
int get connectStatus => _connectStatus;
|
||||
@ -209,6 +212,10 @@ class ServerModel with ChangeNotifier {
|
||||
_fileOk = fileOption != 'N';
|
||||
}
|
||||
|
||||
// clipboard
|
||||
final clipOption = await bind.mainGetOption(key: kOptionEnableClipboard);
|
||||
_clipboardOk = clipOption != 'N';
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -315,6 +322,14 @@ class ServerModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
toggleClipboard() async {
|
||||
_clipboardOk = !_clipboardOk;
|
||||
bind.mainSetOption(
|
||||
key: kOptionEnableClipboard,
|
||||
value: _clipboardOk ? defaultOptionYes : 'N');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
toggleInput() async {
|
||||
if (clients.isNotEmpty) {
|
||||
await showClientsMayNotBeChangedAlert(parent.target);
|
||||
|
@ -5,9 +5,11 @@ use jni::sys::jboolean;
|
||||
use jni::JNIEnv;
|
||||
use jni::{
|
||||
objects::{GlobalRef, JClass, JObject},
|
||||
strings::JNIString,
|
||||
JavaVM,
|
||||
};
|
||||
|
||||
use hbb_common::{message_proto::MultiClipboards, protobuf::Message};
|
||||
use jni::errors::{Error as JniError, Result as JniResult};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
@ -16,6 +18,7 @@ use std::os::raw::c_void;
|
||||
use std::sync::atomic::{AtomicPtr, Ordering::SeqCst};
|
||||
use std::sync::{Mutex, RwLock};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
lazy_static! {
|
||||
static ref JVM: RwLock<Option<JavaVM>> = RwLock::new(None);
|
||||
static ref MAIN_SERVICE_CTX: RwLock<Option<GlobalRef>> = RwLock::new(None); // MainService -> video service / audio service / info
|
||||
@ -23,6 +26,9 @@ lazy_static! {
|
||||
static ref AUDIO_RAW: Mutex<FrameRaw> = Mutex::new(FrameRaw::new("audio", MAX_AUDIO_FRAME_TIMEOUT));
|
||||
static ref NDK_CONTEXT_INITED: Mutex<bool> = Default::default();
|
||||
static ref MEDIA_CODEC_INFOS: RwLock<Option<MediaCodecInfos>> = RwLock::new(None);
|
||||
static ref CLIPBOARD_MANAGER: RwLock<Option<GlobalRef>> = RwLock::new(None);
|
||||
static ref CLIPBOARDS_HOST: Mutex<Option<MultiClipboards>> = Mutex::new(None);
|
||||
static ref CLIPBOARDS_CLIENT: Mutex<Option<MultiClipboards>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
const MAX_VIDEO_FRAME_TIMEOUT: Duration = Duration::from_millis(100);
|
||||
@ -105,6 +111,14 @@ pub fn get_audio_raw<'a>(dst: &mut Vec<u8>, last: &mut Vec<u8>) -> Option<()> {
|
||||
AUDIO_RAW.lock().ok()?.take(dst, last)
|
||||
}
|
||||
|
||||
pub fn get_clipboards(client: bool) -> Option<MultiClipboards> {
|
||||
if client {
|
||||
CLIPBOARDS_CLIENT.lock().ok()?.take()
|
||||
} else {
|
||||
CLIPBOARDS_HOST.lock().ok()?.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_ffi_FFI_onVideoFrameUpdate(
|
||||
env: JNIEnv,
|
||||
@ -133,6 +147,27 @@ pub extern "system" fn Java_ffi_FFI_onAudioFrameUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_ffi_FFI_onClipboardUpdate(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
buffer: JByteBuffer,
|
||||
) {
|
||||
if let Ok(data) = env.get_direct_buffer_address(&buffer) {
|
||||
if let Ok(len) = env.get_direct_buffer_capacity(&buffer) {
|
||||
let data = unsafe { std::slice::from_raw_parts(data, len) };
|
||||
if let Ok(clips) = MultiClipboards::parse_from_bytes(&data[1..]) {
|
||||
let is_client = data[0] == 1;
|
||||
if is_client {
|
||||
*CLIPBOARDS_CLIENT.lock().unwrap() = Some(clips);
|
||||
} else {
|
||||
*CLIPBOARDS_HOST.lock().unwrap() = Some(clips);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_ffi_FFI_setFrameRawEnable(
|
||||
env: JNIEnv,
|
||||
@ -157,7 +192,11 @@ pub extern "system" fn Java_ffi_FFI_init(env: JNIEnv, _class: JClass, ctx: JObje
|
||||
log::debug!("MainService init from java");
|
||||
if let Ok(jvm) = env.get_java_vm() {
|
||||
let java_vm = jvm.get_java_vm_pointer() as *mut c_void;
|
||||
*JVM.write().unwrap() = Some(jvm);
|
||||
let mut jvm_lock = JVM.write().unwrap();
|
||||
if jvm_lock.is_none() {
|
||||
*jvm_lock = Some(jvm);
|
||||
}
|
||||
drop(jvm_lock);
|
||||
if let Ok(context) = env.new_global_ref(ctx) {
|
||||
let context_jobject = context.as_obj().as_raw() as *mut c_void;
|
||||
*MAIN_SERVICE_CTX.write().unwrap() = Some(context);
|
||||
@ -178,6 +217,26 @@ pub extern "system" fn Java_ffi_FFI_initContext(env: JNIEnv, _class: JClass, ctx
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_ffi_FFI_setClipboardManager(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
clipboard_manager: JObject,
|
||||
) {
|
||||
log::debug!("ClipboardManager init from java");
|
||||
if let Ok(jvm) = env.get_java_vm() {
|
||||
let java_vm = jvm.get_java_vm_pointer() as *mut c_void;
|
||||
let mut jvm_lock = JVM.write().unwrap();
|
||||
if jvm_lock.is_none() {
|
||||
*jvm_lock = Some(jvm);
|
||||
}
|
||||
drop(jvm_lock);
|
||||
if let Ok(manager) = env.new_global_ref(clipboard_manager) {
|
||||
*CLIPBOARD_MANAGER.write().unwrap() = Some(manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct MediaCodecInfo {
|
||||
pub name: String,
|
||||
@ -287,6 +346,59 @@ pub fn call_main_service_key_event(data: &[u8]) -> JniResult<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn _call_clipboard_manager<S, T>(name: S, sig: T, args: &[JValue]) -> JniResult<()>
|
||||
where
|
||||
S: Into<JNIString>,
|
||||
T: Into<JNIString> + AsRef<str>,
|
||||
{
|
||||
if let (Some(jvm), Some(cm)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
CLIPBOARD_MANAGER.read().unwrap().as_ref(),
|
||||
) {
|
||||
let mut env = jvm.attach_current_thread()?;
|
||||
env.call_method(cm, name, sig, args)?;
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(JniError::ThrowFailed(-1));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_clipboard_manager_update_clipboard(data: &[u8]) -> JniResult<()> {
|
||||
if let (Some(jvm), Some(cm)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
CLIPBOARD_MANAGER.read().unwrap().as_ref(),
|
||||
) {
|
||||
let mut env = jvm.attach_current_thread()?;
|
||||
let data = env.byte_array_from_slice(data)?;
|
||||
|
||||
env.call_method(
|
||||
cm,
|
||||
"rustUpdateClipboard",
|
||||
"([B)V",
|
||||
&[JValue::Object(&JObject::from(data))],
|
||||
)?;
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(JniError::ThrowFailed(-1));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_clipboard_manager_enable_service_clipboard(enable: bool) -> JniResult<()> {
|
||||
_call_clipboard_manager(
|
||||
"rustEnableServiceClipboard",
|
||||
"(Z)V",
|
||||
&[JValue::Bool(jboolean::from(enable))],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn call_clipboard_manager_enable_client_clipboard(enable: bool) -> JniResult<()> {
|
||||
_call_clipboard_manager(
|
||||
"rustEnableClientClipboard",
|
||||
"(Z)V",
|
||||
&[JValue::Bool(jboolean::from(enable))],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn call_main_service_get_by_name(name: &str) -> JniResult<String> {
|
||||
if let (Some(jvm), Some(ctx)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
|
@ -71,8 +71,10 @@ use crate::{
|
||||
ui_session_interface::{InvokeUiSession, Session},
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use crate::clipboard::CLIPBOARD_INTERVAL;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::clipboard::{check_clipboard, ClipboardSide, CLIPBOARD_INTERVAL};
|
||||
use crate::clipboard::{check_clipboard, ClipboardSide};
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::ui_session_interface::SessionPermissionConfig;
|
||||
@ -131,7 +133,7 @@ pub(crate) struct ClientClipboardContext {
|
||||
/// Client of the remote desktop.
|
||||
pub struct Client;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
struct TextClipboardState {
|
||||
is_required: bool,
|
||||
running: bool,
|
||||
@ -144,6 +146,10 @@ lazy_static::lazy_static! {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref ENIGO: Arc<Mutex<enigo::Enigo>> = Arc::new(Mutex::new(enigo::Enigo::new()));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref TEXT_CLIPBOARD_STATE: Arc<Mutex<TextClipboardState>> = Arc::new(Mutex::new(TextClipboardState::new()));
|
||||
}
|
||||
|
||||
@ -648,12 +654,12 @@ impl Client {
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
pub fn set_is_text_clipboard_required(b: bool) {
|
||||
TEXT_CLIPBOARD_STATE.lock().unwrap().is_required = b;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
fn try_stop_clipboard() {
|
||||
// There's a bug here.
|
||||
// If session is closed by the peer, `has_sessions_running()` will always return true.
|
||||
@ -748,9 +754,41 @@ impl Client {
|
||||
|
||||
Some(rx_started)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn try_start_clipboard(_p: Option<()>) -> Option<UnboundedReceiver<()>> {
|
||||
let mut clipboard_lock = TEXT_CLIPBOARD_STATE.lock().unwrap();
|
||||
if clipboard_lock.running {
|
||||
return None;
|
||||
}
|
||||
clipboard_lock.running = true;
|
||||
|
||||
log::info!("Start text clipboard loop");
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
if !TEXT_CLIPBOARD_STATE.lock().unwrap().running {
|
||||
break;
|
||||
}
|
||||
if !TEXT_CLIPBOARD_STATE.lock().unwrap().is_required {
|
||||
std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL));
|
||||
continue;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(msg) = crate::clipboard::get_clipboards_msg(true) {
|
||||
crate::flutter::send_text_clipboard_msg(msg);
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(CLIPBOARD_INTERVAL));
|
||||
}
|
||||
log::info!("Stop text clipboard loop");
|
||||
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
impl TextClipboardState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
|
@ -8,9 +8,9 @@ use std::{
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use crate::clipboard::{update_clipboard, ClipboardSide, CLIPBOARD_INTERVAL};
|
||||
use crate::clipboard::{update_clipboard, ClipboardSide};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
use crate::{audio_service, ConnInner, CLIENT_SERVER};
|
||||
use crate::{audio_service, clipboard::CLIPBOARD_INTERVAL, ConnInner, CLIENT_SERVER};
|
||||
use crate::{
|
||||
client::{
|
||||
self, new_voice_call_request, Client, Data, Interface, MediaData, MediaSender,
|
||||
@ -302,7 +302,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
.unwrap()
|
||||
.set_disconnected(round);
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if _set_disconnected_ok {
|
||||
Client::try_stop_clipboard();
|
||||
}
|
||||
@ -1177,7 +1177,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.check_clipboard_file_context();
|
||||
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
let rx = Client::try_start_clipboard(None);
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@ -1188,7 +1188,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
},
|
||||
));
|
||||
// To make sure current text clipboard data is updated.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Some(mut rx) = rx {
|
||||
timeout(CLIPBOARD_INTERVAL, rx.recv()).await.ok();
|
||||
}
|
||||
@ -1209,6 +1209,11 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
});
|
||||
}
|
||||
}
|
||||
// to-do: Android, is `sync_init_clipboard` really needed?
|
||||
// https://github.com/rustdesk/rustdesk/discussions/9010
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
crate::flutter::update_text_clipboard_required();
|
||||
|
||||
// on connection established client
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
@ -1240,7 +1245,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(vec![cb], ClipboardSide::Client);
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
let content = if cb.compress {
|
||||
hbb_common::compress::decompress(&cb.content)
|
||||
@ -1251,12 +1256,16 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.handler.clipboard(content);
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
crate::clipboard::handle_msg_clipboard(cb);
|
||||
}
|
||||
}
|
||||
Some(message::Union::MultiClipboards(_mcb)) => {
|
||||
if !self.handler.lc.read().unwrap().disable_clipboard.v {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(_mcb.clipboards, ClipboardSide::Client);
|
||||
#[cfg(target_os = "android")]
|
||||
crate::clipboard::handle_msg_multi_clipboards(_mcb);
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
@ -1421,14 +1430,14 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
Ok(Permission::Keyboard) => {
|
||||
*self.handler.server_keyboard_enabled.write().unwrap() = p.enabled;
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
crate::flutter::update_text_clipboard_required();
|
||||
self.handler.set_permission("keyboard", p.enabled);
|
||||
}
|
||||
Ok(Permission::Clipboard) => {
|
||||
*self.handler.server_clipboard_enabled.write().unwrap() = p.enabled;
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
crate::flutter::update_text_clipboard_required();
|
||||
self.handler.set_permission("clipboard", p.enabled);
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use arboard::{ClipboardData, ClipboardFormat};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use clipboard_master::{ClipboardHandler, Master, Shutdown};
|
||||
use hbb_common::{bail, log, message_proto::*, ResultType};
|
||||
use std::{
|
||||
@ -16,6 +18,7 @@ const RUSTDESK_CLIPBOARD_OWNER_FORMAT: &'static str = "dyn.com.rustdesk.owner";
|
||||
// Add special format for Excel XML Spreadsheet
|
||||
const CLIPBOARD_FORMAT_EXCEL_XML_SPREADSHEET: &'static str = "XML Spreadsheet";
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
lazy_static::lazy_static! {
|
||||
static ref ARBOARD_MTX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
|
||||
// cache the clipboard msg
|
||||
@ -27,9 +30,12 @@ lazy_static::lazy_static! {
|
||||
static ref CLIPBOARD_CTX: Arc<Mutex<Option<ClipboardContext>>> = Arc::new(Mutex::new(None));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
const CLIPBOARD_GET_MAX_RETRY: usize = 3;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
const CLIPBOARD_GET_RETRY_INTERVAL_DUR: Duration = Duration::from_millis(33);
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
const SUPPORTED_FORMATS: &[ClipboardFormat] = &[
|
||||
ClipboardFormat::Text,
|
||||
ClipboardFormat::Html,
|
||||
@ -146,6 +152,7 @@ impl ClipboardContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub fn check_clipboard(
|
||||
ctx: &mut Option<ClipboardContext>,
|
||||
side: ClipboardSide,
|
||||
@ -194,6 +201,7 @@ pub fn check_clipboard_cm() -> ResultType<MultiClipboards> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn update_clipboard_(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
|
||||
let mut to_update_data = proto::from_multi_clipbards(multi_clipboards);
|
||||
if to_update_data.is_empty() {
|
||||
@ -224,17 +232,20 @@ fn update_clipboard_(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub fn update_clipboard(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
|
||||
std::thread::spawn(move || {
|
||||
update_clipboard_(multi_clipboards, side);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))]
|
||||
pub struct ClipboardContext {
|
||||
inner: arboard::Clipboard,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))]
|
||||
#[allow(unreachable_code)]
|
||||
impl ClipboardContext {
|
||||
@ -337,10 +348,20 @@ impl ClipboardContext {
|
||||
|
||||
pub fn is_support_multi_clipboard(peer_version: &str, peer_platform: &str) -> bool {
|
||||
use hbb_common::get_version_number;
|
||||
get_version_number(peer_version) >= get_version_number("1.3.0")
|
||||
&& !["", "Android", &whoami::Platform::Ios.to_string()].contains(&peer_platform)
|
||||
if get_version_number(peer_version) < get_version_number("1.3.0") {
|
||||
return false;
|
||||
}
|
||||
if ["", &whoami::Platform::Ios.to_string()].contains(&peer_platform) {
|
||||
return false;
|
||||
}
|
||||
if "Android" == peer_platform && get_version_number(peer_version) < get_version_number("1.3.3")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub fn get_current_clipboard_msg(
|
||||
peer_version: &str,
|
||||
peer_platform: &str,
|
||||
@ -406,6 +427,7 @@ impl std::fmt::Display for ClipboardSide {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub fn start_clipbard_master_thread(
|
||||
handler: impl ClipboardHandler + Send + 'static,
|
||||
tx_start_res: Sender<(Option<Shutdown>, String)>,
|
||||
@ -437,6 +459,7 @@ pub fn start_clipbard_master_thread(
|
||||
|
||||
pub use proto::get_msg_if_not_support_multi_clip;
|
||||
mod proto {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use arboard::ClipboardData;
|
||||
use hbb_common::{
|
||||
compress::{compress as compress_func, decompress},
|
||||
@ -459,6 +482,7 @@ mod proto {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn image_to_proto(a: arboard::ImageData) -> Clipboard {
|
||||
match &a {
|
||||
arboard::ImageData::Rgba(rgba) => {
|
||||
@ -519,6 +543,7 @@ mod proto {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn clipboard_data_to_proto(data: ClipboardData) -> Option<Clipboard> {
|
||||
let d = match data {
|
||||
ClipboardData::Text(s) => plain_to_proto(s, ClipboardFormat::Text),
|
||||
@ -531,6 +556,7 @@ mod proto {
|
||||
Some(d)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub fn create_multi_clipboards(vec_data: Vec<ClipboardData>) -> MultiClipboards {
|
||||
MultiClipboards {
|
||||
clipboards: vec_data
|
||||
@ -541,6 +567,7 @@ mod proto {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn from_clipboard(clipboard: Clipboard) -> Option<ClipboardData> {
|
||||
let data = if clipboard.compress {
|
||||
decompress(&clipboard.content)
|
||||
@ -569,6 +596,7 @@ mod proto {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub fn from_multi_clipbards(multi_clipboards: Vec<Clipboard>) -> Vec<ClipboardData> {
|
||||
multi_clipboards
|
||||
.into_iter()
|
||||
@ -597,3 +625,49 @@ mod proto {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn handle_msg_clipboard(mut cb: Clipboard) {
|
||||
use hbb_common::protobuf::Message;
|
||||
|
||||
if cb.compress {
|
||||
cb.content = bytes::Bytes::from(hbb_common::compress::decompress(&cb.content));
|
||||
}
|
||||
let multi_clips = MultiClipboards {
|
||||
clipboards: vec![cb],
|
||||
..Default::default()
|
||||
};
|
||||
if let Ok(bytes) = multi_clips.write_to_bytes() {
|
||||
let _ = scrap::android::ffi::call_clipboard_manager_update_clipboard(&bytes);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn handle_msg_multi_clipboards(mut mcb: MultiClipboards) {
|
||||
use hbb_common::protobuf::Message;
|
||||
|
||||
for cb in mcb.clipboards.iter_mut() {
|
||||
if cb.compress {
|
||||
cb.content = bytes::Bytes::from(hbb_common::compress::decompress(&cb.content));
|
||||
}
|
||||
}
|
||||
if let Ok(bytes) = mcb.write_to_bytes() {
|
||||
let _ = scrap::android::ffi::call_clipboard_manager_update_clipboard(&bytes);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn get_clipboards_msg(client: bool) -> Option<Message> {
|
||||
let mut clipboards = scrap::android::ffi::get_clipboards(client)?;
|
||||
let mut msg = Message::new();
|
||||
for c in &mut clipboards.clipboards {
|
||||
let compressed = hbb_common::compress::compress(&c.content);
|
||||
let compress = compressed.len() < c.content.len();
|
||||
if compress {
|
||||
c.content = compressed.into();
|
||||
}
|
||||
c.compress = compress;
|
||||
}
|
||||
msg.set_multi_clipboards(clipboards);
|
||||
Some(msg)
|
||||
}
|
||||
|
@ -1250,15 +1250,17 @@ fn try_send_close_event(event_stream: &Option<StreamSink<EventToUI>>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
pub fn update_text_clipboard_required() {
|
||||
let is_required = sessions::get_sessions()
|
||||
.iter()
|
||||
.any(|s| s.is_text_clipboard_required());
|
||||
#[cfg(target_os = "android")]
|
||||
let _ = scrap::android::ffi::call_clipboard_manager_enable_client_clipboard(is_required);
|
||||
Client::set_is_text_clipboard_required(is_required);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
pub fn send_text_clipboard_msg(msg: Message) {
|
||||
for s in sessions::get_sessions() {
|
||||
if s.is_text_clipboard_required() {
|
||||
@ -2051,7 +2053,7 @@ pub mod sessions {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
pub fn has_sessions_running(conn_type: ConnType) -> bool {
|
||||
SESSIONS.read().unwrap().iter().any(|((_, r#type), s)| {
|
||||
*r#type == conn_type && s.session_handlers.read().unwrap().len() != 0
|
||||
|
@ -274,7 +274,7 @@ pub fn session_toggle_option(session_id: SessionID, value: String) {
|
||||
session.toggle_option(value.clone());
|
||||
try_sync_peer_option(&session, &session_id, &value, None);
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if sessions::get_session_by_session_id(&session_id).is_some() && value == "disable-clipboard" {
|
||||
crate::flutter::update_text_clipboard_required();
|
||||
}
|
||||
@ -817,6 +817,17 @@ pub fn main_show_option(_key: String) -> SyncReturn<bool> {
|
||||
SyncReturn(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "android")]
|
||||
fn enable_server_clipboard(keyboard_enabled: &str, clip_enabled: &str) {
|
||||
use scrap::android::ffi::call_clipboard_manager_enable_service_clipboard;
|
||||
let keyboard_enabled =
|
||||
config::option2bool(config::keys::OPTION_ENABLE_KEYBOARD, &keyboard_enabled);
|
||||
let clip_enabled = config::option2bool(config::keys::OPTION_ENABLE_CLIPBOARD, &clip_enabled);
|
||||
crate::ui_cm_interface::switch_permission_all("clipboard".to_owned(), clip_enabled);
|
||||
let _ = call_clipboard_manager_enable_service_clipboard(keyboard_enabled && clip_enabled);
|
||||
}
|
||||
|
||||
pub fn main_set_option(key: String, value: String) {
|
||||
#[cfg(target_os = "android")]
|
||||
if key.eq(config::keys::OPTION_ENABLE_KEYBOARD) {
|
||||
@ -824,6 +835,11 @@ pub fn main_set_option(key: String, value: String) {
|
||||
config::keys::OPTION_ENABLE_KEYBOARD,
|
||||
&value,
|
||||
));
|
||||
enable_server_clipboard(&value, &get_option(config::keys::OPTION_ENABLE_CLIPBOARD));
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
if key.eq(config::keys::OPTION_ENABLE_CLIPBOARD) {
|
||||
enable_server_clipboard(&get_option(config::keys::OPTION_ENABLE_KEYBOARD), &value);
|
||||
}
|
||||
if key.eq("custom-rendezvous-server") {
|
||||
set_option(key, value.clone());
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "上传文件夹"),
|
||||
("Upload files", "上传文件"),
|
||||
("Clipboard is synchronized", "剪贴板已同步"),
|
||||
("Update client clipboard", "更新客户端的粘贴板"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Ordner hochladen"),
|
||||
("Upload files", "Dateien hochladen"),
|
||||
("Clipboard is synchronized", "Zwischenablage ist synchronisiert"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Subir carpeta"),
|
||||
("Upload files", "Subir archivos"),
|
||||
("Clipboard is synchronized", "Portapapeles sincronizado"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Mappa feltöltése"),
|
||||
("Upload files", "Fájlok feltöltése"),
|
||||
("Clipboard is synchronized", "A vágólap szinkronizálva van"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Cartella upload"),
|
||||
("Upload files", "File upload"),
|
||||
("Clipboard is synchronized", "Gli appunti sono sincronizzati"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "폴더 업로드"),
|
||||
("Upload files", "파일 업로드"),
|
||||
("Clipboard is synchronized", "클립보드가 동기화됨"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Augšupielādēt mapi"),
|
||||
("Upload files", "Augšupielādēt failus"),
|
||||
("Clipboard is synchronized", "Starpliktuve ir sinhronizēta"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Map uploaden"),
|
||||
("Upload files", "Bestanden uploaden"),
|
||||
("Clipboard is synchronized", "Klembord is gesynchroniseerd"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Wyślij folder"),
|
||||
("Upload files", "Wyślij pliki"),
|
||||
("Clipboard is synchronized", "Schowek jest zsynchronizowany"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "Загрузить папку"),
|
||||
("Upload files", "Загрузить файлы"),
|
||||
("Clipboard is synchronized", "Буфер обмена синхронизирован"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", "上傳資料夾"),
|
||||
("Upload files", "上傳檔案"),
|
||||
("Clipboard is synchronized", "剪貼簿已同步"),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Upload folder", ""),
|
||||
("Upload files", ""),
|
||||
("Clipboard is synchronized", ""),
|
||||
("Update client clipboard", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ mod custom_server;
|
||||
mod lang;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod port_forward;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
mod clipboard;
|
||||
|
||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||
|
@ -32,7 +32,7 @@ use crate::ipc::Data;
|
||||
|
||||
pub mod audio_service;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(any(target_os = "android", target_os = "ios")))] {
|
||||
if #[cfg(not(target_os = "ios"))] {
|
||||
mod clipboard_service;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) mod wayland;
|
||||
@ -42,18 +42,21 @@ pub mod uinput;
|
||||
pub mod rdp_input;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod dbus;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub mod input_service;
|
||||
} else {
|
||||
mod clipboard_service {
|
||||
pub const NAME: &'static str = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub mod input_service {
|
||||
pub const NAME_CURSOR: &'static str = "";
|
||||
pub const NAME_POS: &'static str = "";
|
||||
pub const NAME_WINDOW_FOCUS: &'static str = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod connection;
|
||||
pub mod display_service;
|
||||
@ -99,10 +102,12 @@ pub fn new() -> ServerPtr {
|
||||
};
|
||||
server.add_service(Box::new(audio_service::new()));
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
{
|
||||
server.add_service(Box::new(display_service::new()));
|
||||
server.add_service(Box::new(clipboard_service::new()));
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
server.add_service(Box::new(clipboard_service::new()));
|
||||
if !display_service::capture_cursor_embedded() {
|
||||
server.add_service(Box::new(input_service::new_cursor()));
|
||||
server.add_service(Box::new(input_service::new_pos()));
|
||||
|
@ -1,11 +1,15 @@
|
||||
use super::*;
|
||||
pub use crate::clipboard::{
|
||||
check_clipboard, ClipboardContext, ClipboardSide, CLIPBOARD_INTERVAL as INTERVAL,
|
||||
CLIPBOARD_NAME as NAME,
|
||||
};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub use crate::clipboard::{check_clipboard, ClipboardContext, ClipboardSide};
|
||||
pub use crate::clipboard::{CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME};
|
||||
#[cfg(windows)]
|
||||
use crate::ipc::{self, ClipboardFile, ClipboardNonFile, Data};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use clipboard_master::{CallbackResult, ClipboardHandler};
|
||||
#[cfg(target_os = "android")]
|
||||
use hbb_common::config::{keys, option2bool};
|
||||
#[cfg(target_os = "android")]
|
||||
use scrap::android::ffi::call_clipboard_manager_enable_service_clipboard;
|
||||
use std::{
|
||||
io,
|
||||
sync::mpsc::{channel, RecvTimeoutError, Sender},
|
||||
@ -14,6 +18,7 @@ use std::{
|
||||
#[cfg(windows)]
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
struct Handler {
|
||||
sp: EmptyExtraFieldService,
|
||||
ctx: Option<ClipboardContext>,
|
||||
@ -25,11 +30,12 @@ struct Handler {
|
||||
}
|
||||
|
||||
pub fn new() -> GenericService {
|
||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
|
||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false);
|
||||
GenericService::run(&svc.clone(), run);
|
||||
svc.sp
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
let (tx_cb_result, rx_cb_result) = channel();
|
||||
let handler = Handler {
|
||||
@ -73,9 +79,9 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
impl ClipboardHandler for Handler {
|
||||
fn on_clipboard_change(&mut self) -> CallbackResult {
|
||||
self.sp.snapshot(|_sps| Ok(())).ok();
|
||||
if self.sp.ok() {
|
||||
if let Some(msg) = self.get_clipboard_msg() {
|
||||
self.sp.send(msg);
|
||||
@ -92,6 +98,7 @@ impl ClipboardHandler for Handler {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
impl Handler {
|
||||
fn get_clipboard_msg(&mut self) -> Option<Message> {
|
||||
#[cfg(target_os = "windows")]
|
||||
@ -216,3 +223,25 @@ impl Handler {
|
||||
bail!("failed to get clipboard data from cm");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn is_clipboard_enabled() -> bool {
|
||||
let keyboard_enabled = crate::ui_interface::get_option(keys::OPTION_ENABLE_KEYBOARD);
|
||||
let keyboard_enabled = option2bool(keys::OPTION_ENABLE_KEYBOARD, &keyboard_enabled);
|
||||
let clip_enabled = crate::ui_interface::get_option(keys::OPTION_ENABLE_CLIPBOARD);
|
||||
let clip_enabled = option2bool(keys::OPTION_ENABLE_CLIPBOARD, &clip_enabled);
|
||||
keyboard_enabled && clip_enabled
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
let _res = call_clipboard_manager_enable_service_clipboard(is_clipboard_enabled());
|
||||
while sp.ok() {
|
||||
if let Some(msg) = crate::clipboard::get_clipboards_msg(false) {
|
||||
sp.send(msg);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(INTERVAL));
|
||||
}
|
||||
let _res = call_clipboard_manager_enable_service_clipboard(false);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -690,7 +690,7 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
Some(message::Union::MultiClipboards(_multi_clipboards)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
if let Some(msg_out) = crate::clipboard::get_msg_if_not_support_multi_clip(&conn.lr.version, &conn.lr.my_platform, _multi_clipboards) {
|
||||
if let Err(err) = conn.stream.send(&msg_out).await {
|
||||
conn.on_close(&err.to_string(), false).await;
|
||||
@ -2074,7 +2074,9 @@ impl Connection {
|
||||
if self.clipboard {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
update_clipboard(vec![cb], ClipboardSide::Host);
|
||||
#[cfg(all(feature = "flutter", target_os = "android"))]
|
||||
// ios as the controlled side is actually not supported for now.
|
||||
// The following code is only used to preserve the logic of handling text clipboard on mobile.
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
let content = if cb.compress {
|
||||
hbb_common::compress::decompress(&cb.content)
|
||||
@ -2092,14 +2094,17 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
crate::clipboard::handle_msg_clipboard(cb);
|
||||
}
|
||||
}
|
||||
Some(message::Union::MultiClipboards(_mcb)) =>
|
||||
{
|
||||
Some(message::Union::MultiClipboards(_mcb)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if self.clipboard {
|
||||
update_clipboard(_mcb.clipboards, ClipboardSide::Host);
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
crate::clipboard::handle_msg_multi_clipboards(_mcb);
|
||||
}
|
||||
Some(message::Union::Cliprdr(_clip)) =>
|
||||
{
|
||||
|
@ -312,6 +312,17 @@ pub fn switch_permission(id: i32, name: String, enabled: bool) {
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "android")]
|
||||
pub fn switch_permission_all(name: String, enabled: bool) {
|
||||
for (_, client) in CLIENTS.read().unwrap().iter() {
|
||||
allow_err!(client.tx.send(Data::SwitchPermission {
|
||||
name: name.clone(),
|
||||
enabled
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||
#[inline]
|
||||
pub fn get_clients_state() -> String {
|
||||
|
@ -354,7 +354,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
self.lc.read().unwrap().is_privacy_mode_supported()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
pub fn is_text_clipboard_required(&self) -> bool {
|
||||
*self.server_clipboard_enabled.read().unwrap()
|
||||
&& *self.server_keyboard_enabled.read().unwrap()
|
||||
@ -526,10 +526,7 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
#[cfg(not(feature = "flutter"))]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn is_xfce(&self) -> bool {
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
return crate::platform::is_xfce();
|
||||
#[cfg(any(target_os = "ios"))]
|
||||
false
|
||||
crate::platform::is_xfce()
|
||||
}
|
||||
|
||||
pub fn remove_port_forward(&self, port: i32) {
|
||||
|
Loading…
Reference in New Issue
Block a user