fix: android clipboard permission (#10223)

* fix: android clipboard permission

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

* refact: Android, clipboard, floating ball

Call rust to check if clipboard is enabled.

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

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou 2024-12-07 22:34:54 +08:00 committed by GitHub
parent 3c838e7a92
commit 1c17fddf51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 80 additions and 36 deletions

View File

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

View File

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

View File

@ -597,6 +597,8 @@ class _PermissionCheckerState extends State<PermissionChecker> {
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 _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);

View File

@ -834,11 +834,19 @@ pub fn main_show_option(_key: String) -> SyncReturn<bool> {
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())
}
}

View File

@ -217,8 +217,6 @@ pub enum Data {
MouseMoveTime(i64),
Authorize,
Close,
#[cfg(target_os = "android")]
InputControl(bool),
#[cfg(windows)]
SAS,
UserSid(Option<u32>),

View File

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

View File

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

View File

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

View File

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