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:
fufesou 2024-11-18 15:43:41 +08:00 committed by GitHub
parent 0707e791e8
commit 8b710f62c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 670 additions and 47 deletions

View File

@ -304,7 +304,13 @@ class FloatingWindowService : Service(), View.OnTouchListener {
val popupMenu = PopupMenu(this, floatingView) val popupMenu = PopupMenu(this, floatingView)
val idShowRustDesk = 0 val idShowRustDesk = 0
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk")) 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.menu.add(0, idStopService, 0, translate("Stop service"))
popupMenu.setOnMenuItemClickListener { menuItem -> popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) { when (menuItem.itemId) {
@ -312,6 +318,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
openMainActivity() openMainActivity()
true true
} }
idSyncClipboard -> {
syncClipboard()
true
}
idStopService -> { idStopService -> {
stopMainService() stopMainService()
true true
@ -340,6 +350,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
} }
} }
private fun syncClipboard() {
MainActivity.rdClipboardManager?.syncClipboard(false)
}
private fun stopMainService() { private fun stopMainService() {
MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null) MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null)
} }

View File

@ -13,6 +13,8 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.ClipboardManager
import android.os.Bundle
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
@ -40,6 +42,9 @@ import kotlin.concurrent.thread
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
companion object { companion object {
var flutterMethodChannel: MethodChannel? = null var flutterMethodChannel: MethodChannel? = null
private var _rdClipboardManager: RdClipboardManager? = null
val rdClipboardManager: RdClipboardManager?
get() = _rdClipboardManager;
} }
private val channelTag = "mChannel" 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() { override fun onDestroy() {
Log.e(logTag, "onDestroy") Log.e(logTag, "onDestroy")
mainService?.let { mainService?.let {
unbindService(serviceConnection) unbindService(serviceConnection)
} }
rdClipboardManager?.rustEnableServiceClipboard(false)
super.onDestroy() super.onDestroy()
} }
@ -393,6 +407,15 @@ class MainActivity : FlutterActivity() {
super.onStart() super.onStart()
stopService(Intent(this, FloatingWindowService::class.java)) 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 // https://cjycode.com/flutter_rust_bridge/guides/how-to/ndk-init

View File

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

View File

@ -5,6 +5,8 @@ package ffi
import android.content.Context import android.content.Context
import java.nio.ByteBuffer import java.nio.ByteBuffer
import com.carriez.flutter_hbb.RdClipboardManager
object FFI { object FFI {
init { init {
System.loadLibrary("rustdesk") System.loadLibrary("rustdesk")
@ -12,6 +14,7 @@ object FFI {
external fun init(ctx: Context) external fun init(ctx: Context)
external fun initContext(ctx: Context) external fun initContext(ctx: Context)
external fun setClipboardManager(clipboardManager: RdClipboardManager)
external fun startServer(app_dir: String, custom_client_config: String) external fun startServer(app_dir: String, custom_client_config: String)
external fun startService() external fun startService()
external fun onVideoFrameUpdate(buf: ByteBuffer) external fun onVideoFrameUpdate(buf: ByteBuffer)
@ -21,4 +24,5 @@ object FFI {
external fun setFrameRawEnable(name: String, value: Boolean) external fun setFrameRawEnable(name: String, value: Boolean)
external fun setCodecInfo(info: String) external fun setCodecInfo(info: String)
external fun getLocalOption(key: String): String external fun getLocalOption(key: String): String
external fun onClipboardUpdate(clips: ByteBuffer)
} }

View File

@ -596,7 +596,9 @@ class _PermissionCheckerState extends State<PermissionChecker> {
translate("android_version_audio_tip"), translate("android_version_audio_tip"),
style: const TextStyle(color: MyTheme.darkGray), style: const TextStyle(color: MyTheme.darkGray),
)) ))
]) ]),
PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk,
serverModel.toggleClipboard),
])); ]));
} }
} }

View File

@ -30,6 +30,7 @@ class ServerModel with ChangeNotifier {
bool _inputOk = false; bool _inputOk = false;
bool _audioOk = false; bool _audioOk = false;
bool _fileOk = false; bool _fileOk = false;
bool _clipboardOk = false;
bool _showElevation = false; bool _showElevation = false;
bool hideCm = false; bool hideCm = false;
int _connectStatus = 0; // Rendezvous Server status int _connectStatus = 0; // Rendezvous Server status
@ -59,6 +60,8 @@ class ServerModel with ChangeNotifier {
bool get fileOk => _fileOk; bool get fileOk => _fileOk;
bool get clipboardOk => _clipboardOk;
bool get showElevation => _showElevation; bool get showElevation => _showElevation;
int get connectStatus => _connectStatus; int get connectStatus => _connectStatus;
@ -209,6 +212,10 @@ class ServerModel with ChangeNotifier {
_fileOk = fileOption != 'N'; _fileOk = fileOption != 'N';
} }
// clipboard
final clipOption = await bind.mainGetOption(key: kOptionEnableClipboard);
_clipboardOk = clipOption != 'N';
notifyListeners(); notifyListeners();
} }
@ -315,6 +322,14 @@ class ServerModel with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
toggleClipboard() async {
_clipboardOk = !_clipboardOk;
bind.mainSetOption(
key: kOptionEnableClipboard,
value: _clipboardOk ? defaultOptionYes : 'N');
notifyListeners();
}
toggleInput() async { toggleInput() async {
if (clients.isNotEmpty) { if (clients.isNotEmpty) {
await showClientsMayNotBeChangedAlert(parent.target); await showClientsMayNotBeChangedAlert(parent.target);

View File

@ -5,9 +5,11 @@ use jni::sys::jboolean;
use jni::JNIEnv; use jni::JNIEnv;
use jni::{ use jni::{
objects::{GlobalRef, JClass, JObject}, objects::{GlobalRef, JClass, JObject},
strings::JNIString,
JavaVM, JavaVM,
}; };
use hbb_common::{message_proto::MultiClipboards, protobuf::Message};
use jni::errors::{Error as JniError, Result as JniResult}; use jni::errors::{Error as JniError, Result as JniResult};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::Deserialize; use serde::Deserialize;
@ -16,6 +18,7 @@ use std::os::raw::c_void;
use std::sync::atomic::{AtomicPtr, Ordering::SeqCst}; use std::sync::atomic::{AtomicPtr, Ordering::SeqCst};
use std::sync::{Mutex, RwLock}; use std::sync::{Mutex, RwLock};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
lazy_static! { lazy_static! {
static ref JVM: RwLock<Option<JavaVM>> = RwLock::new(None); 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 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 AUDIO_RAW: Mutex<FrameRaw> = Mutex::new(FrameRaw::new("audio", MAX_AUDIO_FRAME_TIMEOUT));
static ref NDK_CONTEXT_INITED: Mutex<bool> = Default::default(); static ref NDK_CONTEXT_INITED: Mutex<bool> = Default::default();
static ref MEDIA_CODEC_INFOS: RwLock<Option<MediaCodecInfos>> = RwLock::new(None); 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); 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) 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] #[no_mangle]
pub extern "system" fn Java_ffi_FFI_onVideoFrameUpdate( pub extern "system" fn Java_ffi_FFI_onVideoFrameUpdate(
env: JNIEnv, 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] #[no_mangle]
pub extern "system" fn Java_ffi_FFI_setFrameRawEnable( pub extern "system" fn Java_ffi_FFI_setFrameRawEnable(
env: JNIEnv, 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"); log::debug!("MainService init from java");
if let Ok(jvm) = env.get_java_vm() { if let Ok(jvm) = env.get_java_vm() {
let java_vm = jvm.get_java_vm_pointer() as *mut c_void; 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) { if let Ok(context) = env.new_global_ref(ctx) {
let context_jobject = context.as_obj().as_raw() as *mut c_void; let context_jobject = context.as_obj().as_raw() as *mut c_void;
*MAIN_SERVICE_CTX.write().unwrap() = Some(context); *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)] #[derive(Debug, Deserialize, Clone)]
pub struct MediaCodecInfo { pub struct MediaCodecInfo {
pub name: String, 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> { pub fn call_main_service_get_by_name(name: &str) -> JniResult<String> {
if let (Some(jvm), Some(ctx)) = ( if let (Some(jvm), Some(ctx)) = (
JVM.read().unwrap().as_ref(), JVM.read().unwrap().as_ref(),

View File

@ -71,8 +71,10 @@ use crate::{
ui_session_interface::{InvokeUiSession, Session}, ui_session_interface::{InvokeUiSession, Session},
}; };
#[cfg(not(target_os = "ios"))]
use crate::clipboard::CLIPBOARD_INTERVAL;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_session_interface::SessionPermissionConfig; use crate::ui_session_interface::SessionPermissionConfig;
@ -131,7 +133,7 @@ pub(crate) struct ClientClipboardContext {
/// Client of the remote desktop. /// Client of the remote desktop.
pub struct Client; pub struct Client;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
struct TextClipboardState { struct TextClipboardState {
is_required: bool, is_required: bool,
running: bool, running: bool,
@ -144,6 +146,10 @@ lazy_static::lazy_static! {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref ENIGO: Arc<Mutex<enigo::Enigo>> = Arc::new(Mutex::new(enigo::Enigo::new())); 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())); static ref TEXT_CLIPBOARD_STATE: Arc<Mutex<TextClipboardState>> = Arc::new(Mutex::new(TextClipboardState::new()));
} }
@ -648,12 +654,12 @@ impl Client {
#[inline] #[inline]
#[cfg(feature = "flutter")] #[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) { pub fn set_is_text_clipboard_required(b: bool) {
TEXT_CLIPBOARD_STATE.lock().unwrap().is_required = b; 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() { fn try_stop_clipboard() {
// There's a bug here. // There's a bug here.
// If session is closed by the peer, `has_sessions_running()` will always return true. // If session is closed by the peer, `has_sessions_running()` will always return true.
@ -748,9 +754,41 @@ impl Client {
Some(rx_started) 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;
}
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(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
impl TextClipboardState { impl TextClipboardState {
fn new() -> Self { fn new() -> Self {
Self { Self {

View File

@ -8,9 +8,9 @@ use std::{
}; };
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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")))] #[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::{ use crate::{
client::{ client::{
self, new_voice_call_request, Client, Data, Interface, MediaData, MediaSender, self, new_voice_call_request, Client, Data, Interface, MediaData, MediaSender,
@ -302,7 +302,7 @@ impl<T: InvokeUiSession> Remote<T> {
.unwrap() .unwrap()
.set_disconnected(round); .set_disconnected(round);
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
if _set_disconnected_ok { if _set_disconnected_ok {
Client::try_stop_clipboard(); Client::try_stop_clipboard();
} }
@ -1177,7 +1177,7 @@ impl<T: InvokeUiSession> Remote<T> {
self.check_clipboard_file_context(); self.check_clipboard_file_context();
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) { if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
let rx = Client::try_start_clipboard(None); let rx = Client::try_start_clipboard(None);
#[cfg(not(feature = "flutter"))] #[cfg(not(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[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. // 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 { if let Some(mut rx) = rx {
timeout(CLIPBOARD_INTERVAL, rx.recv()).await.ok(); 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 // on connection established client
#[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[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 { if !self.handler.lc.read().unwrap().disable_clipboard.v {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
update_clipboard(vec![cb], ClipboardSide::Client); update_clipboard(vec![cb], ClipboardSide::Client);
#[cfg(any(target_os = "android", target_os = "ios"))] #[cfg(target_os = "ios")]
{ {
let content = if cb.compress { let content = if cb.compress {
hbb_common::compress::decompress(&cb.content) hbb_common::compress::decompress(&cb.content)
@ -1251,12 +1256,16 @@ impl<T: InvokeUiSession> Remote<T> {
self.handler.clipboard(content); self.handler.clipboard(content);
} }
} }
#[cfg(target_os = "android")]
crate::clipboard::handle_msg_clipboard(cb);
} }
} }
Some(message::Union::MultiClipboards(_mcb)) => { Some(message::Union::MultiClipboards(_mcb)) => {
if !self.handler.lc.read().unwrap().disable_clipboard.v { if !self.handler.lc.read().unwrap().disable_clipboard.v {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
update_clipboard(_mcb.clipboards, ClipboardSide::Client); 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"))] #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
@ -1421,14 +1430,14 @@ impl<T: InvokeUiSession> Remote<T> {
Ok(Permission::Keyboard) => { Ok(Permission::Keyboard) => {
*self.handler.server_keyboard_enabled.write().unwrap() = p.enabled; *self.handler.server_keyboard_enabled.write().unwrap() = p.enabled;
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
crate::flutter::update_text_clipboard_required(); crate::flutter::update_text_clipboard_required();
self.handler.set_permission("keyboard", p.enabled); self.handler.set_permission("keyboard", p.enabled);
} }
Ok(Permission::Clipboard) => { Ok(Permission::Clipboard) => {
*self.handler.server_clipboard_enabled.write().unwrap() = p.enabled; *self.handler.server_clipboard_enabled.write().unwrap() = p.enabled;
#[cfg(feature = "flutter")] #[cfg(feature = "flutter")]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
crate::flutter::update_text_clipboard_required(); crate::flutter::update_text_clipboard_required();
self.handler.set_permission("clipboard", p.enabled); self.handler.set_permission("clipboard", p.enabled);
} }

View File

@ -1,4 +1,6 @@
#[cfg(not(target_os = "android"))]
use arboard::{ClipboardData, ClipboardFormat}; use arboard::{ClipboardData, ClipboardFormat};
#[cfg(not(target_os = "android"))]
use clipboard_master::{ClipboardHandler, Master, Shutdown}; use clipboard_master::{ClipboardHandler, Master, Shutdown};
use hbb_common::{bail, log, message_proto::*, ResultType}; use hbb_common::{bail, log, message_proto::*, ResultType};
use std::{ use std::{
@ -16,6 +18,7 @@ const RUSTDESK_CLIPBOARD_OWNER_FORMAT: &'static str = "dyn.com.rustdesk.owner";
// Add special format for Excel XML Spreadsheet // Add special format for Excel XML Spreadsheet
const CLIPBOARD_FORMAT_EXCEL_XML_SPREADSHEET: &'static str = "XML Spreadsheet"; const CLIPBOARD_FORMAT_EXCEL_XML_SPREADSHEET: &'static str = "XML Spreadsheet";
#[cfg(not(target_os = "android"))]
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref ARBOARD_MTX: Arc<Mutex<()>> = Arc::new(Mutex::new(())); static ref ARBOARD_MTX: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
// cache the clipboard msg // 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)); 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; const CLIPBOARD_GET_MAX_RETRY: usize = 3;
#[cfg(not(target_os = "android"))]
const CLIPBOARD_GET_RETRY_INTERVAL_DUR: Duration = Duration::from_millis(33); const CLIPBOARD_GET_RETRY_INTERVAL_DUR: Duration = Duration::from_millis(33);
#[cfg(not(target_os = "android"))]
const SUPPORTED_FORMATS: &[ClipboardFormat] = &[ const SUPPORTED_FORMATS: &[ClipboardFormat] = &[
ClipboardFormat::Text, ClipboardFormat::Text,
ClipboardFormat::Html, ClipboardFormat::Html,
@ -146,6 +152,7 @@ impl ClipboardContext {
} }
} }
#[cfg(not(target_os = "android"))]
pub fn check_clipboard( pub fn check_clipboard(
ctx: &mut Option<ClipboardContext>, ctx: &mut Option<ClipboardContext>,
side: ClipboardSide, 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) { fn update_clipboard_(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
let mut to_update_data = proto::from_multi_clipbards(multi_clipboards); let mut to_update_data = proto::from_multi_clipbards(multi_clipboards);
if to_update_data.is_empty() { 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) { pub fn update_clipboard(multi_clipboards: Vec<Clipboard>, side: ClipboardSide) {
std::thread::spawn(move || { std::thread::spawn(move || {
update_clipboard_(multi_clipboards, side); update_clipboard_(multi_clipboards, side);
}); });
} }
#[cfg(not(target_os = "android"))]
#[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))] #[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))]
pub struct ClipboardContext { pub struct ClipboardContext {
inner: arboard::Clipboard, inner: arboard::Clipboard,
} }
#[cfg(not(target_os = "android"))]
#[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))] #[cfg(not(any(all(target_os = "linux", feature = "unix-file-copy-paste"))))]
#[allow(unreachable_code)] #[allow(unreachable_code)]
impl ClipboardContext { impl ClipboardContext {
@ -337,10 +348,20 @@ impl ClipboardContext {
pub fn is_support_multi_clipboard(peer_version: &str, peer_platform: &str) -> bool { pub fn is_support_multi_clipboard(peer_version: &str, peer_platform: &str) -> bool {
use hbb_common::get_version_number; use hbb_common::get_version_number;
get_version_number(peer_version) >= get_version_number("1.3.0") if get_version_number(peer_version) < get_version_number("1.3.0") {
&& !["", "Android", &whoami::Platform::Ios.to_string()].contains(&peer_platform) 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( pub fn get_current_clipboard_msg(
peer_version: &str, peer_version: &str,
peer_platform: &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( pub fn start_clipbard_master_thread(
handler: impl ClipboardHandler + Send + 'static, handler: impl ClipboardHandler + Send + 'static,
tx_start_res: Sender<(Option<Shutdown>, String)>, 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; pub use proto::get_msg_if_not_support_multi_clip;
mod proto { mod proto {
#[cfg(not(target_os = "android"))]
use arboard::ClipboardData; use arboard::ClipboardData;
use hbb_common::{ use hbb_common::{
compress::{compress as compress_func, decompress}, 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 { fn image_to_proto(a: arboard::ImageData) -> Clipboard {
match &a { match &a {
arboard::ImageData::Rgba(rgba) => { arboard::ImageData::Rgba(rgba) => {
@ -519,6 +543,7 @@ mod proto {
} }
} }
#[cfg(not(target_os = "android"))]
fn clipboard_data_to_proto(data: ClipboardData) -> Option<Clipboard> { fn clipboard_data_to_proto(data: ClipboardData) -> Option<Clipboard> {
let d = match data { let d = match data {
ClipboardData::Text(s) => plain_to_proto(s, ClipboardFormat::Text), ClipboardData::Text(s) => plain_to_proto(s, ClipboardFormat::Text),
@ -531,6 +556,7 @@ mod proto {
Some(d) Some(d)
} }
#[cfg(not(target_os = "android"))]
pub fn create_multi_clipboards(vec_data: Vec<ClipboardData>) -> MultiClipboards { pub fn create_multi_clipboards(vec_data: Vec<ClipboardData>) -> MultiClipboards {
MultiClipboards { MultiClipboards {
clipboards: vec_data clipboards: vec_data
@ -541,6 +567,7 @@ mod proto {
} }
} }
#[cfg(not(target_os = "android"))]
fn from_clipboard(clipboard: Clipboard) -> Option<ClipboardData> { fn from_clipboard(clipboard: Clipboard) -> Option<ClipboardData> {
let data = if clipboard.compress { let data = if clipboard.compress {
decompress(&clipboard.content) 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> { pub fn from_multi_clipbards(multi_clipboards: Vec<Clipboard>) -> Vec<ClipboardData> {
multi_clipboards multi_clipboards
.into_iter() .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)
}

View File

@ -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() { pub fn update_text_clipboard_required() {
let is_required = sessions::get_sessions() let is_required = sessions::get_sessions()
.iter() .iter()
.any(|s| s.is_text_clipboard_required()); .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); 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) { pub fn send_text_clipboard_msg(msg: Message) {
for s in sessions::get_sessions() { for s in sessions::get_sessions() {
if s.is_text_clipboard_required() { if s.is_text_clipboard_required() {
@ -2051,7 +2053,7 @@ pub mod sessions {
} }
#[inline] #[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
pub fn has_sessions_running(conn_type: ConnType) -> bool { pub fn has_sessions_running(conn_type: ConnType) -> bool {
SESSIONS.read().unwrap().iter().any(|((_, r#type), s)| { SESSIONS.read().unwrap().iter().any(|((_, r#type), s)| {
*r#type == conn_type && s.session_handlers.read().unwrap().len() != 0 *r#type == conn_type && s.session_handlers.read().unwrap().len() != 0

View File

@ -274,7 +274,7 @@ pub fn session_toggle_option(session_id: SessionID, value: String) {
session.toggle_option(value.clone()); session.toggle_option(value.clone());
try_sync_peer_option(&session, &session_id, &value, None); 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" { if sessions::get_session_by_session_id(&session_id).is_some() && value == "disable-clipboard" {
crate::flutter::update_text_clipboard_required(); crate::flutter::update_text_clipboard_required();
} }
@ -817,6 +817,17 @@ pub fn main_show_option(_key: String) -> SyncReturn<bool> {
SyncReturn(false) 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) { pub fn main_set_option(key: String, value: String) {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
if key.eq(config::keys::OPTION_ENABLE_KEYBOARD) { 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, config::keys::OPTION_ENABLE_KEYBOARD,
&value, &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") { if key.eq("custom-rendezvous-server") {
set_option(key, value.clone()); set_option(key, value.clone());

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "上传文件夹"), ("Upload folder", "上传文件夹"),
("Upload files", "上传文件"), ("Upload files", "上传文件"),
("Clipboard is synchronized", "剪贴板已同步"), ("Clipboard is synchronized", "剪贴板已同步"),
("Update client clipboard", "更新客户端的粘贴板"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Ordner hochladen"), ("Upload folder", "Ordner hochladen"),
("Upload files", "Dateien hochladen"), ("Upload files", "Dateien hochladen"),
("Clipboard is synchronized", "Zwischenablage ist synchronisiert"), ("Clipboard is synchronized", "Zwischenablage ist synchronisiert"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Subir carpeta"), ("Upload folder", "Subir carpeta"),
("Upload files", "Subir archivos"), ("Upload files", "Subir archivos"),
("Clipboard is synchronized", "Portapapeles sincronizado"), ("Clipboard is synchronized", "Portapapeles sincronizado"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Mappa feltöltése"), ("Upload folder", "Mappa feltöltése"),
("Upload files", "Fájlok feltöltése"), ("Upload files", "Fájlok feltöltése"),
("Clipboard is synchronized", "A vágólap szinkronizálva van"), ("Clipboard is synchronized", "A vágólap szinkronizálva van"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Cartella upload"), ("Upload folder", "Cartella upload"),
("Upload files", "File upload"), ("Upload files", "File upload"),
("Clipboard is synchronized", "Gli appunti sono sincronizzati"), ("Clipboard is synchronized", "Gli appunti sono sincronizzati"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "폴더 업로드"), ("Upload folder", "폴더 업로드"),
("Upload files", "파일 업로드"), ("Upload files", "파일 업로드"),
("Clipboard is synchronized", "클립보드가 동기화됨"), ("Clipboard is synchronized", "클립보드가 동기화됨"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Augšupielādēt mapi"), ("Upload folder", "Augšupielādēt mapi"),
("Upload files", "Augšupielādēt failus"), ("Upload files", "Augšupielādēt failus"),
("Clipboard is synchronized", "Starpliktuve ir sinhronizēta"), ("Clipboard is synchronized", "Starpliktuve ir sinhronizēta"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Map uploaden"), ("Upload folder", "Map uploaden"),
("Upload files", "Bestanden uploaden"), ("Upload files", "Bestanden uploaden"),
("Clipboard is synchronized", "Klembord is gesynchroniseerd"), ("Clipboard is synchronized", "Klembord is gesynchroniseerd"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Wyślij folder"), ("Upload folder", "Wyślij folder"),
("Upload files", "Wyślij pliki"), ("Upload files", "Wyślij pliki"),
("Clipboard is synchronized", "Schowek jest zsynchronizowany"), ("Clipboard is synchronized", "Schowek jest zsynchronizowany"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "Загрузить папку"), ("Upload folder", "Загрузить папку"),
("Upload files", "Загрузить файлы"), ("Upload files", "Загрузить файлы"),
("Clipboard is synchronized", "Буфер обмена синхронизирован"), ("Clipboard is synchronized", "Буфер обмена синхронизирован"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", "上傳資料夾"), ("Upload folder", "上傳資料夾"),
("Upload files", "上傳檔案"), ("Upload files", "上傳檔案"),
("Clipboard is synchronized", "剪貼簿已同步"), ("Clipboard is synchronized", "剪貼簿已同步"),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -653,5 +653,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Upload folder", ""), ("Upload folder", ""),
("Upload files", ""), ("Upload files", ""),
("Clipboard is synchronized", ""), ("Clipboard is synchronized", ""),
("Update client clipboard", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -45,7 +45,7 @@ mod custom_server;
mod lang; mod lang;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
mod port_forward; mod port_forward;
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(target_os = "ios"))]
mod clipboard; mod clipboard;
#[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(all(feature = "flutter", feature = "plugin_framework"))]

View File

@ -32,7 +32,7 @@ use crate::ipc::Data;
pub mod audio_service; pub mod audio_service;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(not(any(target_os = "android", target_os = "ios")))] { if #[cfg(not(target_os = "ios"))] {
mod clipboard_service; mod clipboard_service;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub(crate) mod wayland; pub(crate) mod wayland;
@ -42,17 +42,20 @@ pub mod uinput;
pub mod rdp_input; pub mod rdp_input;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub mod dbus; pub mod dbus;
#[cfg(not(target_os = "android"))]
pub mod input_service; pub mod input_service;
} else { } else {
mod clipboard_service { mod clipboard_service {
pub const NAME: &'static str = ""; pub const NAME: &'static str = "";
} }
}
}
#[cfg(any(target_os = "android", target_os = "ios"))]
pub mod input_service { pub mod input_service {
pub const NAME_CURSOR: &'static str = ""; pub const NAME_CURSOR: &'static str = "";
pub const NAME_POS: &'static str = ""; pub const NAME_POS: &'static str = "";
pub const NAME_WINDOW_FOCUS: &'static str = ""; pub const NAME_WINDOW_FOCUS: &'static str = "";
}
}
} }
mod connection; mod connection;
@ -99,10 +102,12 @@ pub fn new() -> ServerPtr {
}; };
server.add_service(Box::new(audio_service::new())); server.add_service(Box::new(audio_service::new()));
#[cfg(not(target_os = "ios"))] #[cfg(not(target_os = "ios"))]
server.add_service(Box::new(display_service::new())); {
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")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
{ {
server.add_service(Box::new(clipboard_service::new()));
if !display_service::capture_cursor_embedded() { if !display_service::capture_cursor_embedded() {
server.add_service(Box::new(input_service::new_cursor())); server.add_service(Box::new(input_service::new_cursor()));
server.add_service(Box::new(input_service::new_pos())); server.add_service(Box::new(input_service::new_pos()));

View File

@ -1,11 +1,15 @@
use super::*; use super::*;
pub use crate::clipboard::{ #[cfg(not(target_os = "android"))]
check_clipboard, ClipboardContext, ClipboardSide, CLIPBOARD_INTERVAL as INTERVAL, pub use crate::clipboard::{check_clipboard, ClipboardContext, ClipboardSide};
CLIPBOARD_NAME as NAME, pub use crate::clipboard::{CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME};
};
#[cfg(windows)] #[cfg(windows)]
use crate::ipc::{self, ClipboardFile, ClipboardNonFile, Data}; use crate::ipc::{self, ClipboardFile, ClipboardNonFile, Data};
#[cfg(not(target_os = "android"))]
use clipboard_master::{CallbackResult, ClipboardHandler}; 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::{ use std::{
io, io,
sync::mpsc::{channel, RecvTimeoutError, Sender}, sync::mpsc::{channel, RecvTimeoutError, Sender},
@ -14,6 +18,7 @@ use std::{
#[cfg(windows)] #[cfg(windows)]
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
#[cfg(not(target_os = "android"))]
struct Handler { struct Handler {
sp: EmptyExtraFieldService, sp: EmptyExtraFieldService,
ctx: Option<ClipboardContext>, ctx: Option<ClipboardContext>,
@ -25,11 +30,12 @@ struct Handler {
} }
pub fn new() -> GenericService { 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); GenericService::run(&svc.clone(), run);
svc.sp svc.sp
} }
#[cfg(not(target_os = "android"))]
fn run(sp: EmptyExtraFieldService) -> ResultType<()> { fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
let (tx_cb_result, rx_cb_result) = channel(); let (tx_cb_result, rx_cb_result) = channel();
let handler = Handler { let handler = Handler {
@ -73,9 +79,9 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
Ok(()) Ok(())
} }
#[cfg(not(target_os = "android"))]
impl ClipboardHandler for Handler { impl ClipboardHandler for Handler {
fn on_clipboard_change(&mut self) -> CallbackResult { fn on_clipboard_change(&mut self) -> CallbackResult {
self.sp.snapshot(|_sps| Ok(())).ok();
if self.sp.ok() { if self.sp.ok() {
if let Some(msg) = self.get_clipboard_msg() { if let Some(msg) = self.get_clipboard_msg() {
self.sp.send(msg); self.sp.send(msg);
@ -92,6 +98,7 @@ impl ClipboardHandler for Handler {
} }
} }
#[cfg(not(target_os = "android"))]
impl Handler { impl Handler {
fn get_clipboard_msg(&mut self) -> Option<Message> { fn get_clipboard_msg(&mut self) -> Option<Message> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -216,3 +223,25 @@ impl Handler {
bail!("failed to get clipboard data from cm"); 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(())
}

View File

@ -690,7 +690,7 @@ impl Connection {
} }
} }
Some(message::Union::MultiClipboards(_multi_clipboards)) => { 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 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 { if let Err(err) = conn.stream.send(&msg_out).await {
conn.on_close(&err.to_string(), false).await; conn.on_close(&err.to_string(), false).await;
@ -2074,7 +2074,9 @@ impl Connection {
if self.clipboard { if self.clipboard {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
update_clipboard(vec![cb], ClipboardSide::Host); 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 { let content = if cb.compress {
hbb_common::compress::decompress(&cb.content) 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")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.clipboard { if self.clipboard {
update_clipboard(_mcb.clipboards, ClipboardSide::Host); update_clipboard(_mcb.clipboards, ClipboardSide::Host);
} }
#[cfg(target_os = "android")]
crate::clipboard::handle_msg_multi_clipboards(_mcb);
} }
Some(message::Union::Cliprdr(_clip)) => Some(message::Union::Cliprdr(_clip)) =>
{ {

View File

@ -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"))] #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
#[inline] #[inline]
pub fn get_clients_state() -> String { pub fn get_clients_state() -> String {

View File

@ -354,7 +354,7 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.read().unwrap().is_privacy_mode_supported() 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 { pub fn is_text_clipboard_required(&self) -> bool {
*self.server_clipboard_enabled.read().unwrap() *self.server_clipboard_enabled.read().unwrap()
&& *self.server_keyboard_enabled.read().unwrap() && *self.server_keyboard_enabled.read().unwrap()
@ -526,10 +526,7 @@ impl<T: InvokeUiSession> Session<T> {
#[cfg(not(feature = "flutter"))] #[cfg(not(feature = "flutter"))]
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn is_xfce(&self) -> bool { pub fn is_xfce(&self) -> bool {
#[cfg(not(any(target_os = "ios")))] crate::platform::is_xfce()
return crate::platform::is_xfce();
#[cfg(any(target_os = "ios"))]
false
} }
pub fn remove_port_forward(&self, port: i32) { pub fn remove_port_forward(&self, port: i32) {