fix: Android, try sync clipboard on connecting (#10218)

* fix: Android, try sync clipboard on connecting

Signed-off-by: fufesou <linlong1266@gmail.com>

* Android, clipboard, more clear skip check

Signed-off-by: fufesou <linlong1266@gmail.com>

* comments

Signed-off-by: fufesou <linlong1266@gmail.com>

* comment todo: Android clipboard listener, callback twice

Signed-off-by: fufesou <linlong1266@gmail.com>

* Android, clipboard, remove listner

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-12-07 15:12:15 +08:00 committed by GitHub
parent 8f44787ba3
commit 3c838e7a92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 41 additions and 118 deletions

View File

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

View File

@ -103,7 +103,6 @@ class MainActivity : FlutterActivity() {
mainService?.let {
unbindService(serviceConnection)
}
rdClipboardManager?.rustEnableServiceClipboard(false)
super.onDestroy()
}
@ -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))
@ -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)
}
}
}

View File

@ -433,6 +433,7 @@ class MainService : Service() {
checkMediaPermission()
_isStart = true
FFI.setFrameRawEnable("video",true)
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
return true
}
@ -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)`.

View File

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

View File

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

View File

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

View File

@ -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
@ -60,8 +59,6 @@ class ServerModel with ChangeNotifier {
bool get fileOk => _fileOk;
bool get clipboardOk => _clipboardOk;
bool get showElevation => _showElevation;
int get connectStatus => _connectStatus;
@ -212,10 +209,6 @@ class ServerModel with ChangeNotifier {
_fileOk = fileOption != 'N';
}
// clipboard
final clipOption = await bind.mainGetOption(key: kOptionEnableClipboard);
_clipboardOk = clipOption != 'N';
notifyListeners();
}
@ -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);

View File

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

View File

@ -831,17 +831,6 @@ 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) {
@ -849,11 +838,6 @@ 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());

View File

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

View File

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

View File

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