Skip to content

Commit

Permalink
fix: Android, try sync clipboard on connecting (rustdesk#10218)
Browse files Browse the repository at this point in the history
* fix: Android, try sync clipboard on connecting

Signed-off-by: fufesou <[email protected]>

* Android, clipboard, more clear skip check

Signed-off-by: fufesou <[email protected]>

* comments

Signed-off-by: fufesou <[email protected]>

* comment todo: Android clipboard listener, callback twice

Signed-off-by: fufesou <[email protected]>

* Android, clipboard, remove listner

Signed-off-by: fufesou <[email protected]>

---------

Signed-off-by: fufesou <[email protected]>
  • Loading branch information
fufesou authored Dec 7, 2024
1 parent 8f44787 commit 3c838e7
Show file tree
Hide file tree
Showing 12 changed files with 41 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class FloatingWindowService : Service(), View.OnTouchListener {
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
// For host side, clipboard sync
val idSyncClipboard = 1
val isClipboardListenerEnabled = MainActivity.rdClipboardManager?.isListening ?: false
val isClipboardListenerEnabled = MainActivity.rdClipboardManager?.isCaptureStarted ?: false
if (isClipboardListenerEnabled) {
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ class MainActivity : FlutterActivity() {
mainService?.let {
unbindService(serviceConnection)
}
rdClipboardManager?.rustEnableServiceClipboard(false)
super.onDestroy()
}

Expand Down Expand Up @@ -221,6 +220,10 @@ class MainActivity : FlutterActivity() {
result.success(true)

}
"try_sync_clipboard" -> {
rdClipboardManager?.syncClipboard(true)
result.success(true)
}
GET_START_ON_BOOT_OPT -> {
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
Expand Down Expand Up @@ -402,13 +405,4 @@ 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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ class MainService : Service() {
checkMediaPermission()
_isStart = true
FFI.setFrameRawEnable("video",true)
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
return true
}

Expand All @@ -441,6 +442,7 @@ class MainService : Service() {
Log.d(logTag, "Stop Capture")
FFI.setFrameRawEnable("video",false)
_isStart = false
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
// release video
if (reuseVirtualDisplay) {
// The virtual display video projection can be paused by calling `setSurface(null)`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
// 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
private var _isCaptureStarted = false;

fun checkPrimaryClip(isClient: Boolean, isSync: Boolean) {
val isCaptureStarted: Boolean
get() = _isCaptureStarted

fun checkPrimaryClip(isClient: 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) {
// Ignore the `isClipboardDataEqual()` check if it's a host operation.
// Because it's an action manually triggered by the user.
if (isClient) {
if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) {
Log.d(logTag, "Clipboard data is the same as last update, ignore")
return
Expand Down Expand Up @@ -95,13 +95,6 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
}
}

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)
}
Expand Down Expand Up @@ -136,43 +129,23 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
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
}
}
fun setCaptureStarted(started: Boolean) {
_isCaptureStarted = started
}

@Keep
fun rustEnableClientClipboard(enable: Boolean) {
Log.d(logTag, "rustEnableClientClipboard: enable: $enable")
isClientEnabled = enable
if (enable) {
lastUpdatedClipData = clipboardManager.primaryClip
} else {
lastUpdatedClipData = null
}
lastUpdatedClipData = null
}

fun syncClipboard(isClient: Boolean) {
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled, _isListening: $_isListening")
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled")
if (isClient && !isClientEnabled) {
return
}
if (!isClient && !_isListening) {
return
}
checkPrimaryClip(isClient, true)
checkPrimaryClip(isClient)
}

@Keep
Expand Down
13 changes: 13 additions & 0 deletions flutter/lib/mobile/pages/remote_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
gFFI.chatModel.onVoiceCallClosed("End connetion");
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
trySyncClipboard();
}
}

// For client side
// When swithing from other app to this app, try to sync clipboard.
void trySyncClipboard() {
gFFI.invokeMethod("try_sync_clipboard");
}

@override
void didChangeMetrics() {
// If the soft keyboard is visible and the canvas has been changed(panned or scaled)
Expand Down
2 changes: 0 additions & 2 deletions flutter/lib/mobile/pages/server_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -597,8 +597,6 @@ class _PermissionCheckerState extends State<PermissionChecker> {
style: const TextStyle(color: MyTheme.darkGray),
))
]),
PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk,
serverModel.toggleClipboard),
]));
}
}
Expand Down
15 changes: 0 additions & 15 deletions flutter/lib/models/server_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ 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
Expand Down Expand Up @@ -60,8 +59,6 @@ class ServerModel with ChangeNotifier {

bool get fileOk => _fileOk;

bool get clipboardOk => _clipboardOk;

bool get showElevation => _showElevation;

int get connectStatus => _connectStatus;
Expand Down Expand Up @@ -212,10 +209,6 @@ class ServerModel with ChangeNotifier {
_fileOk = fileOption != 'N';
}

// clipboard
final clipOption = await bind.mainGetOption(key: kOptionEnableClipboard);
_clipboardOk = clipOption != 'N';

notifyListeners();
}

Expand Down Expand Up @@ -322,14 +315,6 @@ 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);
Expand Down
8 changes: 0 additions & 8 deletions libs/scrap/src/android/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,14 +371,6 @@ pub fn call_clipboard_manager_update_clipboard(data: &[u8]) -> JniResult<()> {
}
}

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",
Expand Down
16 changes: 0 additions & 16 deletions src/flutter_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,29 +831,13 @@ 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) {
crate::ui_cm_interface::notify_input_control(config::option2bool(
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());
Expand Down
13 changes: 0 additions & 13 deletions src/server/clipboard_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ use crate::ipc::{self, ClipboardFile, ClipboardNonFile, Data};
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},
Expand Down Expand Up @@ -224,24 +222,13 @@ impl Handler {
}
}

#[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(())
}
10 changes: 8 additions & 2 deletions src/server/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1372,8 +1372,14 @@ impl Connection {
if !self.follow_remote_window {
noperms.push(NAME_WINDOW_FOCUS);
}
if !self.clipboard_enabled()
|| !self.peer_keyboard_enabled()
// Do not consider the clipboard and keyboard permissions on Android.
// Because syncing the clipboard on Android is manually triggered by the user in the floating ball.
#[cfg(target_os = "android")]
let keyboard_clip_noperm = self.disable_keyboard || self.disable_clipboard;
#[cfg(not(target_os = "android"))]
let keyboard_clip_noperm =
!self.clipboard_enabled() || !self.peer_keyboard_enabled();
if keyboard_clip_noperm
|| crate::get_builtin_option(keys::OPTION_ONE_WAY_CLIPBOARD_REDIRECTION) == "Y"
{
noperms.push(super::clipboard_service::NAME);
Expand Down
11 changes: 0 additions & 11 deletions src/ui_cm_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,17 +312,6 @@ 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 {
Expand Down

0 comments on commit 3c838e7

Please sign in to comment.