mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-09 00:35:07 +03:00
fix(keyboard): wayland clipboard input prompt (#14700)
* fix(keyboard): wayland clipboard input prompt Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): Simple refactor Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): clipboard input, remove unused code Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): Simple refactor Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): dialog, better enableAndContinue Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): input dialog consent Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): prompt text Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): text input 1. Use `keysym` for the installed version if possible. 2. Use the clipboard if the string cannot be fully handled by `keysym`. Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): input prompt dialog Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): translations Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): dialog, title type Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): better decode_utf8_prefix() Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): better process_chr() Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): unit tests Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): input prompt dialog, no icon Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): input dialog, Toast show the result Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): input dialog, showToast() on persist failed Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): input prompt, better dialog Signed-off-by: fufesou <linlong1266@gmail.com> * fix(wayland): input prompt dialog, translations Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): better wayland clipboard input prompt Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): wayland clipboard, link external app Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): trivial changes Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): wayland clipboard input, dialog content Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): tranlsations Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): translations Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): translations Signed-off-by: fufesou <linlong1266@gmail.com> * fix(input): translations Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -2,7 +2,7 @@ use super::*;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use crate::clipboard::clipboard_listener;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
pub use crate::clipboard::{check_clipboard, ClipboardContext, ClipboardSide};
|
||||
pub use crate::clipboard::{ClipboardContext, ClipboardSide};
|
||||
pub use crate::clipboard::{CLIPBOARD_INTERVAL as INTERVAL, CLIPBOARD_NAME as NAME};
|
||||
#[cfg(windows)]
|
||||
use crate::ipc::{self, ClipboardFile, ClipboardNonFile, Data};
|
||||
@@ -109,6 +109,62 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const WAYLAND_CLIPBOARD_SKIP_CHECK_MAX_UTF8_BYTES: usize =
|
||||
super::input_service::WAYLAND_CLIPBOARD_INPUT_MAX_TEXT_CHARS * 4;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn decode_utf8_prefix(bytes: &[u8]) -> Option<String> {
|
||||
let end = bytes.len().min(WAYLAND_CLIPBOARD_SKIP_CHECK_MAX_UTF8_BYTES);
|
||||
let slice = &bytes[..end];
|
||||
match std::str::from_utf8(slice) {
|
||||
Ok(text) => Some(text.to_owned()),
|
||||
Err(e) => {
|
||||
if e.error_len().is_some() {
|
||||
return None;
|
||||
}
|
||||
let valid_up_to = e.valid_up_to();
|
||||
std::str::from_utf8(&slice[..valid_up_to])
|
||||
.ok()
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn decode_text_clipboard(clipboard: &Clipboard) -> Option<String> {
|
||||
if clipboard.format.enum_value() != Ok(ClipboardFormat::Text) {
|
||||
return None;
|
||||
}
|
||||
if clipboard.compress {
|
||||
let bytes = hbb_common::compress::decompress(&clipboard.content);
|
||||
return decode_utf8_prefix(&bytes);
|
||||
}
|
||||
decode_utf8_prefix(&clipboard.content)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn should_skip_wayland_clipboard_sync(msg: &Message) -> bool {
|
||||
if crate::platform::linux::is_x11() {
|
||||
return false;
|
||||
}
|
||||
let is_recent_wayland_input = |clipboard: &Clipboard| -> bool {
|
||||
let Some(text) = decode_text_clipboard(clipboard) else {
|
||||
return false;
|
||||
};
|
||||
super::input_service::is_recent_wayland_clipboard_input(&text)
|
||||
};
|
||||
|
||||
match &msg.union {
|
||||
Some(message::Union::Clipboard(clipboard)) => is_recent_wayland_input(clipboard),
|
||||
Some(message::Union::MultiClipboards(multi_clipboards)) => multi_clipboards
|
||||
.clipboards
|
||||
.iter()
|
||||
.any(is_recent_wayland_input),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
impl Handler {
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
@@ -172,7 +228,19 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
check_clipboard(&mut self.ctx, ClipboardSide::Host, false)
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let msg = crate::clipboard::peek_clipboard(&mut self.ctx, ClipboardSide::Host, false)?;
|
||||
if should_skip_wayland_clipboard_sync(&msg) {
|
||||
log::debug!("Skip clipboard sync for recent Wayland keyboard injection");
|
||||
return None;
|
||||
}
|
||||
return Some(msg);
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
crate::clipboard::check_clipboard(&mut self.ctx, ClipboardSide::Host, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Read clipboard data from cm using ipc.
|
||||
@@ -272,3 +340,46 @@ fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
|
||||
CLIPBOARD_SERVICE_OK.store(false, Ordering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_os = "linux")]
|
||||
mod tests {
|
||||
use super::{decode_utf8_prefix, WAYLAND_CLIPBOARD_SKIP_CHECK_MAX_UTF8_BYTES};
|
||||
|
||||
#[test]
|
||||
fn decode_utf8_prefix_returns_text_for_valid_utf8() {
|
||||
let text = "hello-مرحبا";
|
||||
assert_eq!(decode_utf8_prefix(text.as_bytes()), Some(text.to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_utf8_prefix_returns_none_for_invalid_utf8_sequence() {
|
||||
let bytes = b"ab\xffcd";
|
||||
assert_eq!(decode_utf8_prefix(bytes), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_utf8_prefix_trims_incomplete_utf8_suffix() {
|
||||
let bytes = vec![b'a', 0xE4, 0xB8];
|
||||
assert_eq!(decode_utf8_prefix(&bytes), Some("a".to_owned()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_utf8_prefix_applies_max_bytes_limit() {
|
||||
let bytes = vec![b'a'; WAYLAND_CLIPBOARD_SKIP_CHECK_MAX_UTF8_BYTES + 8];
|
||||
let result = decode_utf8_prefix(&bytes).expect("expected decoded prefix");
|
||||
assert_eq!(result.len(), WAYLAND_CLIPBOARD_SKIP_CHECK_MAX_UTF8_BYTES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_utf8_prefix_keeps_utf8_boundary_when_limited() {
|
||||
let mut bytes = vec![b'a'; WAYLAND_CLIPBOARD_SKIP_CHECK_MAX_UTF8_BYTES - 1];
|
||||
bytes.extend_from_slice("ا".as_bytes());
|
||||
let result = decode_utf8_prefix(&bytes).expect("expected decoded prefix");
|
||||
assert_eq!(
|
||||
result.len(),
|
||||
WAYLAND_CLIPBOARD_SKIP_CHECK_MAX_UTF8_BYTES - 1
|
||||
);
|
||||
assert!(result.chars().all(|c| c == 'a'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user