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:
parent
3c838e7a92
commit
1c17fddf51
@ -306,8 +306,8 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||||||
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
||||||
// For host side, clipboard sync
|
// For host side, clipboard sync
|
||||||
val idSyncClipboard = 1
|
val idSyncClipboard = 1
|
||||||
val isClipboardListenerEnabled = MainActivity.rdClipboardManager?.isCaptureStarted ?: false
|
val isServiceSyncEnabled = (MainActivity.rdClipboardManager?.isCaptureStarted ?: false) && FFI.isServiceClipboardEnabled()
|
||||||
if (isClipboardListenerEnabled) {
|
if (isServiceSyncEnabled) {
|
||||||
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
|
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
|
||||||
}
|
}
|
||||||
val idStopService = 2
|
val idStopService = 2
|
||||||
|
@ -24,4 +24,5 @@ object FFI {
|
|||||||
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)
|
external fun onClipboardUpdate(clips: ByteBuffer)
|
||||||
|
external fun isServiceClipboardEnabled(): Boolean
|
||||||
}
|
}
|
||||||
|
@ -597,6 +597,8 @@ class _PermissionCheckerState extends State<PermissionChecker> {
|
|||||||
style: const TextStyle(color: MyTheme.darkGray),
|
style: const TextStyle(color: MyTheme.darkGray),
|
||||||
))
|
))
|
||||||
]),
|
]),
|
||||||
|
PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk,
|
||||||
|
serverModel.toggleClipboard),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -834,11 +834,19 @@ pub fn main_show_option(_key: String) -> SyncReturn<bool> {
|
|||||||
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) {
|
||||||
crate::ui_cm_interface::notify_input_control(config::option2bool(
|
crate::ui_cm_interface::switch_permission_all(
|
||||||
config::keys::OPTION_ENABLE_KEYBOARD,
|
"keyboard".to_owned(),
|
||||||
&value,
|
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") {
|
if key.eq("custom-rendezvous-server") {
|
||||||
set_option(key, value.clone());
|
set_option(key, value.clone());
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
@ -2332,7 +2340,7 @@ pub mod server_side {
|
|||||||
use jni::{
|
use jni::{
|
||||||
errors::{Error as JniError, Result as JniResult},
|
errors::{Error as JniError, Result as JniResult},
|
||||||
objects::{JClass, JObject, JString},
|
objects::{JClass, JObject, JString},
|
||||||
sys::jstring,
|
sys::{jboolean, jstring},
|
||||||
JNIEnv,
|
JNIEnv,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2405,4 +2413,12 @@ pub mod server_side {
|
|||||||
};
|
};
|
||||||
return env.new_string(res).unwrap_or_default().into_raw();
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,8 +217,6 @@ pub enum Data {
|
|||||||
MouseMoveTime(i64),
|
MouseMoveTime(i64),
|
||||||
Authorize,
|
Authorize,
|
||||||
Close,
|
Close,
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
InputControl(bool),
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
SAS,
|
SAS,
|
||||||
UserSid(Option<u32>),
|
UserSid(Option<u32>),
|
||||||
|
@ -34,6 +34,8 @@ pub mod audio_service;
|
|||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(not(target_os = "ios"))] {
|
if #[cfg(not(target_os = "ios"))] {
|
||||||
mod clipboard_service;
|
mod clipboard_service;
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub use clipboard_service::is_clipboard_service_ok;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub(crate) mod wayland;
|
pub(crate) mod wayland;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -8,6 +8,8 @@ use crate::ipc::{self, ClipboardFile, ClipboardNonFile, Data};
|
|||||||
use clipboard_master::{CallbackResult, ClipboardHandler};
|
use clipboard_master::{CallbackResult, ClipboardHandler};
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
use hbb_common::config::{keys, option2bool};
|
use hbb_common::config::{keys, option2bool};
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io,
|
||||||
sync::mpsc::{channel, RecvTimeoutError, Sender},
|
sync::mpsc::{channel, RecvTimeoutError, Sender},
|
||||||
@ -16,6 +18,9 @@ use std::{
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
static CLIPBOARD_SERVICE_OK: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
struct Handler {
|
struct Handler {
|
||||||
sp: EmptyExtraFieldService,
|
sp: EmptyExtraFieldService,
|
||||||
@ -27,6 +32,11 @@ struct Handler {
|
|||||||
rt: Option<Runtime>,
|
rt: Option<Runtime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub fn is_clipboard_service_ok() -> bool {
|
||||||
|
CLIPBOARD_SERVICE_OK.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> GenericService {
|
pub fn new() -> GenericService {
|
||||||
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false);
|
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false);
|
||||||
GenericService::run(&svc.clone(), run);
|
GenericService::run(&svc.clone(), run);
|
||||||
@ -224,11 +234,13 @@ impl Handler {
|
|||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||||
|
CLIPBOARD_SERVICE_OK.store(sp.ok(), Ordering::SeqCst);
|
||||||
while sp.ok() {
|
while sp.ok() {
|
||||||
if let Some(msg) = crate::clipboard::get_clipboards_msg(false) {
|
if let Some(msg) = crate::clipboard::get_clipboards_msg(false) {
|
||||||
sp.send(msg);
|
sp.send(msg);
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(INTERVAL));
|
std::thread::sleep(Duration::from_millis(INTERVAL));
|
||||||
}
|
}
|
||||||
|
CLIPBOARD_SERVICE_OK.store(false, Ordering::SeqCst);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -463,11 +463,6 @@ impl Connection {
|
|||||||
conn.on_close("connection manager", true).await;
|
conn.on_close("connection manager", true).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
ipc::Data::InputControl(v) => {
|
|
||||||
conn.keyboard = v;
|
|
||||||
conn.send_permission(Permission::Keyboard, v).await;
|
|
||||||
}
|
|
||||||
ipc::Data::CmErr(e) => {
|
ipc::Data::CmErr(e) => {
|
||||||
if e != "expected" {
|
if e != "expected" {
|
||||||
// cm closed before connection
|
// cm closed before connection
|
||||||
@ -492,6 +487,9 @@ impl Connection {
|
|||||||
conn.keyboard = enabled;
|
conn.keyboard = enabled;
|
||||||
conn.send_permission(Permission::Keyboard, enabled).await;
|
conn.send_permission(Permission::Keyboard, enabled).await;
|
||||||
if let Some(s) = conn.server.upgrade() {
|
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(
|
s.write().unwrap().subscribe(
|
||||||
NAME_CURSOR,
|
NAME_CURSOR,
|
||||||
conn.inner.clone(), enabled || conn.show_remote_cursor);
|
conn.inner.clone(), enabled || conn.show_remote_cursor);
|
||||||
@ -502,7 +500,7 @@ impl Connection {
|
|||||||
if let Some(s) = conn.server.upgrade() {
|
if let Some(s) = conn.server.upgrade() {
|
||||||
s.write().unwrap().subscribe(
|
s.write().unwrap().subscribe(
|
||||||
super::clipboard_service::NAME,
|
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" {
|
} else if &name == "audio" {
|
||||||
conn.audio = enabled;
|
conn.audio = enabled;
|
||||||
@ -1372,16 +1370,7 @@ impl Connection {
|
|||||||
if !self.follow_remote_window {
|
if !self.follow_remote_window {
|
||||||
noperms.push(NAME_WINDOW_FOCUS);
|
noperms.push(NAME_WINDOW_FOCUS);
|
||||||
}
|
}
|
||||||
// Do not consider the clipboard and keyboard permissions on Android.
|
if !self.can_sub_clipboard_service() {
|
||||||
// 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);
|
noperms.push(super::clipboard_service::NAME);
|
||||||
}
|
}
|
||||||
if !self.audio_enabled() {
|
if !self.audio_enabled() {
|
||||||
@ -1453,6 +1442,13 @@ impl Connection {
|
|||||||
self.clipboard && !self.disable_clipboard
|
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 {
|
fn audio_enabled(&self) -> bool {
|
||||||
self.audio && !self.disable_audio
|
self.audio && !self.disable_audio
|
||||||
}
|
}
|
||||||
@ -2930,7 +2926,7 @@ impl Connection {
|
|||||||
s.write().unwrap().subscribe(
|
s.write().unwrap().subscribe(
|
||||||
super::clipboard_service::NAME,
|
super::clipboard_service::NAME,
|
||||||
self.inner.clone(),
|
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(
|
s.write().unwrap().subscribe(
|
||||||
super::clipboard_service::NAME,
|
super::clipboard_service::NAME,
|
||||||
self.inner.clone(),
|
self.inner.clone(),
|
||||||
self.clipboard_enabled() && self.peer_keyboard_enabled(),
|
self.can_sub_clipboard_service(),
|
||||||
);
|
);
|
||||||
s.write().unwrap().subscribe(
|
s.write().unwrap().subscribe(
|
||||||
NAME_CURSOR,
|
NAME_CURSOR,
|
||||||
|
@ -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]
|
#[inline]
|
||||||
pub fn remove(id: i32) {
|
pub fn remove(id: i32) {
|
||||||
CLIENTS.write().unwrap().remove(&id);
|
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"))]
|
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_clients_state() -> String {
|
pub fn get_clients_state() -> String {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user