From 8f44787ba356b9f0efbc33bdae88ae306dffb15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?VenusGirl=E2=9D=A4?= Date: Sat, 7 Dec 2024 11:24:32 +0900 Subject: [PATCH 1/3] Update README-KR.md (#10217) --- docs/README-KR.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/README-KR.md b/docs/README-KR.md index af3c665244f3..b0a8b973e2ad 100644 --- a/docs/README-KR.md +++ b/docs/README-KR.md @@ -9,12 +9,12 @@ README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.

-Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) +채팅하기: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) -Rust로 작성되었고, 설정없이 바로 사용할 수 있는 원격 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [스스로 설정](https://rustdesk.com/server)하는 것도, [스스로 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo). +Rust로 작성되었고, 설정없이 바로 사용할 수 있는 원격 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [직접 설정](https://rustdesk.com/server)하거나 [직접 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo). ![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) @@ -43,9 +43,9 @@ RustDesk는 모든 기여를 환영합니다. 기여하고자 한다면 [`docs/C - Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static - Linux/MacOS: vcpkg install libvpx libyuv opus aom -- run `cargo run` +- 실행 `cargo run` -## [Build](https://rustdesk.com/docs/en/dev/build/) +## [빌드](https://rustdesk.com/docs/en/dev/build/) ## Linux에서 빌드 순서 @@ -67,7 +67,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb- sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire ``` -### Install vcpkg +### vcpkg 설치 ```sh git clone https://github.com/microsoft/vcpkg @@ -79,7 +79,7 @@ export VCPKG_ROOT=$HOME/vcpkg vcpkg/vcpkg install libvpx libyuv opus aom ``` -### Fix libvpx (For Fedora) +### libvpx 수정 (For Fedora용) ```sh cd vcpkg/buildtrees/libvpx/src @@ -92,7 +92,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/ cd ``` -### Build +### 빌드 ```sh curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -107,7 +107,7 @@ VCPKG_ROOT=$HOME/vcpkg cargo run ## Docker에 빌드하는 방법 -레포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다. +리포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다. ```sh git clone https://github.com/rustdesk/rustdesk @@ -115,13 +115,13 @@ cd rustdesk docker build -t "rustdesk-builder" . ``` -이후, 애플리케이션을 빌드할 필요가 있을 때마다, 이하의 커맨드를 실행합니다. +이후, 애플리케이션을 빌드할 필요가 있을 때마다, 아래의의 명령을 실행합니다. ```sh docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder ``` -첫 빌드에서는 의존관계가 캐시될 때까지 시간이 걸릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 커맨드에 다른 인수를 지정할 필요가 있다면, 커맨드 끝에 있는 `` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 커맨드 뒤에 `--release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 커맨드로 실행할 수 있습니다. +첫 빌드에서는 의존관계가 캐시될 때까지 시간이 걸릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 명령에 다른 인수를 지정할 필요가 있다면, 명령 끝에 있는 `` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 명령 뒤에 `--release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 명령으로 실행할 수 있습니다. ```sh target/debug/rustdesk @@ -133,9 +133,9 @@ target/debug/rustdesk target/release/rustdesk ``` -커맨드를 RustDesk 리포지토리 루트에서 실행한다는 것을 확인해주세요. 그렇게 하지 않으면 애플리케이션이 필요한 리소스를 발견하지 못 할 가능성이 있습니다. 또한 `install`, `run` 같은 cargo 서브커맨드는 호스트가 아니라 컨테이너 프로그램을 설치, 실행을 위함이므로 현재 이 방법은 지원하지 않다는 점을 유념해주시길 바랍니다. +명령을 RustDesk 리포지토리 루트에서 실행한다는 것을 확인해주세요. 그렇게 하지 않으면 애플리케이션이 필요한 리소스를 발견하지 못 할 가능성이 있습니다. 또한 `install`, `run` 같은 cargo 하위 명령은 호스트가 아니라 컨테이너 프로그램을 설치, 실행을 위함이므로 현재 이 방법은 지원하지 않다는 점을 유념해주시길 바랍니다. -## File Structure +## 파일 구조 - **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 비디오 코덱, 설정, tcp/udp 랩퍼, protobuf, 파일 전송을 위한 fs 함수, 그 외 유틸리티 함수 - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 화면 캡처 @@ -143,12 +143,12 @@ target/release/rustdesk - **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI - **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 오디오, 클립보드, 입력, 비디오 서비스 그리고 네트워크 연결 - **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 피어 접속 시작 -- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)와 통신해서 리모트 다이렉트(TCP hole punching) 혹은 relayed 접속 +- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)와 통신해서 리모트 다이렉트 (TCP hole punching) 혹은 relayed 접속 - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 플랫폼 고유의 코드 -- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile -- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript for Flutter web client +- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 모바일용 Flutter 코드 +- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter 웹 클라이언트용 자바스크립트 -## Snapshot +## 스냅샷 ![image](https://user-images.githubusercontent.com/71636191/113112362-ae4deb80-923b-11eb-957d-ff88daad4f06.png) From 3c838e7a9278df9e3f6293ef6aada14322e3a3c9 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:12:15 +0800 Subject: [PATCH 2/3] fix: Android, try sync clipboard on connecting (#10218) * fix: Android, try sync clipboard on connecting Signed-off-by: fufesou * Android, clipboard, more clear skip check Signed-off-by: fufesou * comments Signed-off-by: fufesou * comment todo: Android clipboard listener, callback twice Signed-off-by: fufesou * Android, clipboard, remove listner Signed-off-by: fufesou --------- Signed-off-by: fufesou --- .../flutter_hbb/FloatingWindowService.kt | 2 +- .../com/carriez/flutter_hbb/MainActivity.kt | 14 ++--- .../com/carriez/flutter_hbb/MainService.kt | 2 + .../carriez/flutter_hbb/RdClipboardManager.kt | 53 +++++-------------- flutter/lib/mobile/pages/remote_page.dart | 13 +++++ flutter/lib/mobile/pages/server_page.dart | 2 - flutter/lib/models/server_model.dart | 15 ------ libs/scrap/src/android/ffi.rs | 8 --- src/flutter_ffi.rs | 16 ------ src/server/clipboard_service.rs | 13 ----- src/server/connection.rs | 10 +++- src/ui_cm_interface.rs | 11 ---- 12 files changed, 41 insertions(+), 118 deletions(-) diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt index c5da81c7c4db..42a1add7bebf 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt @@ -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")) } diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt index 333ea9f14bd2..5c54c18fb821 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt @@ -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) - } - } } diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt index 4c40e3349ed8..e9ec0975d1be 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainService.kt @@ -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)`. diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/RdClipboardManager.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/RdClipboardManager.kt index 0e098cb0831b..8c9d85028402 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/RdClipboardManager.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/RdClipboardManager.kt @@ -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 diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index fa7c35bb64e2..003640e05e1a 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -147,6 +147,19 @@ class _RemotePageState extends State 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) diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index db91e998b6ea..e9382be9f9f4 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -597,8 +597,6 @@ class _PermissionCheckerState extends State { style: const TextStyle(color: MyTheme.darkGray), )) ]), - PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk, - serverModel.toggleClipboard), ])); } } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 7754672c0d0e..1d800ef69678 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -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); diff --git a/libs/scrap/src/android/ffi.rs b/libs/scrap/src/android/ffi.rs index ffa38a965d73..7433e6b090f9 100644 --- a/libs/scrap/src/android/ffi.rs +++ b/libs/scrap/src/android/ffi.rs @@ -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", diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 088149a7e38b..7e3f39c83224 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -831,17 +831,6 @@ pub fn main_show_option(_key: String) -> SyncReturn { 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()); diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index 401bb49336b4..bfba41c92a03 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -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(()) } diff --git a/src/server/connection.rs b/src/server/connection.rs index e1c564ce6b7a..28f653fdec31 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -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); diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index 2ff5e086287d..d2d5b2c83354 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -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 { From 1c17fddf519543d142af47f12f81eac20096a135 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sat, 7 Dec 2024 22:34:54 +0800 Subject: [PATCH 3/3] fix: android clipboard permission (#10223) * fix: android clipboard permission Signed-off-by: fufesou * refact: Android, clipboard, floating ball Call rust to check if clipboard is enabled. Signed-off-by: fufesou --------- Signed-off-by: fufesou --- .../flutter_hbb/FloatingWindowService.kt | 4 +-- flutter/android/app/src/main/kotlin/ffi.kt | 1 + flutter/lib/mobile/pages/server_page.dart | 2 ++ flutter/lib/models/server_model.dart | 15 +++++++++ src/flutter_ffi.rs | 26 ++++++++++++--- src/ipc.rs | 2 -- src/server.rs | 2 ++ src/server/clipboard_service.rs | 12 +++++++ src/server/connection.rs | 32 ++++++++----------- src/ui_cm_interface.rs | 20 ++++++------ 10 files changed, 80 insertions(+), 36 deletions(-) diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt index 42a1add7bebf..696d536c62c3 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/FloatingWindowService.kt @@ -306,8 +306,8 @@ 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?.isCaptureStarted ?: false - if (isClipboardListenerEnabled) { + val isServiceSyncEnabled = (MainActivity.rdClipboardManager?.isCaptureStarted ?: false) && FFI.isServiceClipboardEnabled() + if (isServiceSyncEnabled) { popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard")) } val idStopService = 2 diff --git a/flutter/android/app/src/main/kotlin/ffi.kt b/flutter/android/app/src/main/kotlin/ffi.kt index 69b395ac2e9d..9f0f0216b727 100644 --- a/flutter/android/app/src/main/kotlin/ffi.kt +++ b/flutter/android/app/src/main/kotlin/ffi.kt @@ -24,4 +24,5 @@ object FFI { external fun setCodecInfo(info: String) external fun getLocalOption(key: String): String external fun onClipboardUpdate(clips: ByteBuffer) + external fun isServiceClipboardEnabled(): Boolean } diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index e9382be9f9f4..db91e998b6ea 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -597,6 +597,8 @@ class _PermissionCheckerState extends State { style: const TextStyle(color: MyTheme.darkGray), )) ]), + PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk, + serverModel.toggleClipboard), ])); } } diff --git a/flutter/lib/models/server_model.dart b/flutter/lib/models/server_model.dart index 1d800ef69678..8775764619ee 100644 --- a/flutter/lib/models/server_model.dart +++ b/flutter/lib/models/server_model.dart @@ -30,6 +30,7 @@ class ServerModel with ChangeNotifier { bool _inputOk = false; bool _audioOk = false; bool _fileOk = false; + bool _clipboardOk = false; bool _showElevation = false; bool hideCm = false; int _connectStatus = 0; // Rendezvous Server status @@ -59,6 +60,8 @@ class ServerModel with ChangeNotifier { bool get fileOk => _fileOk; + bool get clipboardOk => _clipboardOk; + bool get showElevation => _showElevation; int get connectStatus => _connectStatus; @@ -209,6 +212,10 @@ class ServerModel with ChangeNotifier { _fileOk = fileOption != 'N'; } + // clipboard + final clipOption = await bind.mainGetOption(key: kOptionEnableClipboard); + _clipboardOk = clipOption != 'N'; + notifyListeners(); } @@ -315,6 +322,14 @@ class ServerModel with ChangeNotifier { notifyListeners(); } + toggleClipboard() async { + _clipboardOk = !clipboardOk; + bind.mainSetOption( + key: kOptionEnableClipboard, + value: clipboardOk ? defaultOptionYes : 'N'); + notifyListeners(); + } + toggleInput() async { if (clients.isNotEmpty) { await showClientsMayNotBeChangedAlert(parent.target); diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 7e3f39c83224..0bb17c9036dc 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -834,11 +834,19 @@ pub fn main_show_option(_key: String) -> SyncReturn { 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, - )); + crate::ui_cm_interface::switch_permission_all( + "keyboard".to_owned(), + config::option2bool(&key, &value), + ); + } + #[cfg(target_os = "android")] + if key.eq(config::keys::OPTION_ENABLE_CLIPBOARD) { + crate::ui_cm_interface::switch_permission_all( + "clipboard".to_owned(), + config::option2bool(&key, &value), + ); } + if key.eq("custom-rendezvous-server") { set_option(key, value.clone()); #[cfg(target_os = "android")] @@ -2332,7 +2340,7 @@ pub mod server_side { use jni::{ errors::{Error as JniError, Result as JniResult}, objects::{JClass, JObject, JString}, - sys::jstring, + sys::{jboolean, jstring}, JNIEnv, }; @@ -2405,4 +2413,12 @@ pub mod server_side { }; return env.new_string(res).unwrap_or_default().into_raw(); } + + #[no_mangle] + pub unsafe extern "system" fn Java_ffi_FFI_isServiceClipboardEnabled( + env: JNIEnv, + _class: JClass, + ) -> jboolean { + jboolean::from(crate::server::is_clipboard_service_ok()) + } } diff --git a/src/ipc.rs b/src/ipc.rs index e3bcfac9a49f..5126aaf4e408 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -217,8 +217,6 @@ pub enum Data { MouseMoveTime(i64), Authorize, Close, - #[cfg(target_os = "android")] - InputControl(bool), #[cfg(windows)] SAS, UserSid(Option), diff --git a/src/server.rs b/src/server.rs index ed2c9f2fd541..ba1682f3d0f5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -34,6 +34,8 @@ pub mod audio_service; cfg_if::cfg_if! { if #[cfg(not(target_os = "ios"))] { mod clipboard_service; +#[cfg(target_os = "android")] +pub use clipboard_service::is_clipboard_service_ok; #[cfg(target_os = "linux")] pub(crate) mod wayland; #[cfg(target_os = "linux")] diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index bfba41c92a03..8ae482500550 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -8,6 +8,8 @@ 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 std::sync::atomic::{AtomicBool, Ordering}; use std::{ io, sync::mpsc::{channel, RecvTimeoutError, Sender}, @@ -16,6 +18,9 @@ use std::{ #[cfg(windows)] use tokio::runtime::Runtime; +#[cfg(target_os = "android")] +static CLIPBOARD_SERVICE_OK: AtomicBool = AtomicBool::new(false); + #[cfg(not(target_os = "android"))] struct Handler { sp: EmptyExtraFieldService, @@ -27,6 +32,11 @@ struct Handler { rt: Option, } +#[cfg(target_os = "android")] +pub fn is_clipboard_service_ok() -> bool { + CLIPBOARD_SERVICE_OK.load(Ordering::SeqCst) +} + pub fn new() -> GenericService { let svc = EmptyExtraFieldService::new(NAME.to_owned(), false); GenericService::run(&svc.clone(), run); @@ -224,11 +234,13 @@ impl Handler { #[cfg(target_os = "android")] fn run(sp: EmptyExtraFieldService) -> ResultType<()> { + CLIPBOARD_SERVICE_OK.store(sp.ok(), Ordering::SeqCst); while sp.ok() { if let Some(msg) = crate::clipboard::get_clipboards_msg(false) { sp.send(msg); } std::thread::sleep(Duration::from_millis(INTERVAL)); } + CLIPBOARD_SERVICE_OK.store(false, Ordering::SeqCst); Ok(()) } diff --git a/src/server/connection.rs b/src/server/connection.rs index 28f653fdec31..153740c28a3d 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -463,11 +463,6 @@ impl Connection { conn.on_close("connection manager", true).await; break; } - #[cfg(target_os = "android")] - ipc::Data::InputControl(v) => { - conn.keyboard = v; - conn.send_permission(Permission::Keyboard, v).await; - } ipc::Data::CmErr(e) => { if e != "expected" { // cm closed before connection @@ -492,6 +487,9 @@ impl Connection { conn.keyboard = enabled; conn.send_permission(Permission::Keyboard, enabled).await; if let Some(s) = conn.server.upgrade() { + s.write().unwrap().subscribe( + super::clipboard_service::NAME, + conn.inner.clone(), conn.can_sub_clipboard_service()); s.write().unwrap().subscribe( NAME_CURSOR, conn.inner.clone(), enabled || conn.show_remote_cursor); @@ -502,7 +500,7 @@ impl Connection { if let Some(s) = conn.server.upgrade() { s.write().unwrap().subscribe( super::clipboard_service::NAME, - conn.inner.clone(), conn.clipboard_enabled() && conn.peer_keyboard_enabled()); + conn.inner.clone(), conn.can_sub_clipboard_service()); } } else if &name == "audio" { conn.audio = enabled; @@ -1372,16 +1370,7 @@ impl Connection { if !self.follow_remote_window { noperms.push(NAME_WINDOW_FOCUS); } - // 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" - { + if !self.can_sub_clipboard_service() { noperms.push(super::clipboard_service::NAME); } if !self.audio_enabled() { @@ -1453,6 +1442,13 @@ impl Connection { self.clipboard && !self.disable_clipboard } + #[inline] + fn can_sub_clipboard_service(&self) -> bool { + self.clipboard_enabled() + && self.peer_keyboard_enabled() + && crate::get_builtin_option(keys::OPTION_ONE_WAY_CLIPBOARD_REDIRECTION) != "Y" + } + fn audio_enabled(&self) -> bool { self.audio && !self.disable_audio } @@ -2930,7 +2926,7 @@ impl Connection { s.write().unwrap().subscribe( super::clipboard_service::NAME, self.inner.clone(), - self.clipboard_enabled() && self.peer_keyboard_enabled(), + self.can_sub_clipboard_service(), ); } } @@ -2942,7 +2938,7 @@ impl Connection { s.write().unwrap().subscribe( super::clipboard_service::NAME, self.inner.clone(), - self.clipboard_enabled() && self.peer_keyboard_enabled(), + self.can_sub_clipboard_service(), ); s.write().unwrap().subscribe( NAME_CURSOR, diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index d2d5b2c83354..a3373f8ccd72 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -280,15 +280,6 @@ pub fn close(id: i32) { }; } -#[inline] -#[cfg(target_os = "android")] -pub fn notify_input_control(v: bool) { - for (_, mut client) in CLIENTS.write().unwrap().iter_mut() { - client.keyboard = v; - allow_err!(client.tx.send(Data::InputControl(v))); - } -} - #[inline] pub fn remove(id: i32) { CLIENTS.write().unwrap().remove(&id); @@ -312,6 +303,17 @@ pub fn switch_permission(id: i32, name: String, enabled: bool) { }; } +#[inline] +#[cfg(target_os = "android")] +pub fn switch_permission_all(name: String, enabled: bool) { + for (_, client) in CLIENTS.read().unwrap().iter() { + allow_err!(client.tx.send(Data::SwitchPermission { + name: name.clone(), + enabled + })); + } +} + #[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))] #[inline] pub fn get_clients_state() -> String {