feat(viewer-core,android-ffi): 主控端文本上行与 JNI 入口
Session::send_text 收 &[u16] 而不是 &str:整条链(Java 字符串 → JNI → 线格式 → Windows 注入)的原生表示都是 UTF-16,任何一次 UTF-8 往返都有损,故中间层不做转换。 空输入与超限在本层拦掉,超限走 clamp_text_units 并打日志。 nativeSendText 的参数用 char[](JObject 收再转 JCharArray),经 GetCharArrayRegion 按 u16 直接拷贝。不用 JString + get_string:jni 0.21 那条路走 Modified-UTF-8, IME 提交边界的落单代理项会在往返里丢。JNI 取数失败必须 eprintln,不能静默—— 用户对着远端打字最坏的情况是「看起来发送了」。
This commit is contained in:
parent
5075888861
commit
174a3f8ac0
@ -20,7 +20,7 @@
|
||||
//! 会话结束则整个返回 null)。
|
||||
|
||||
use common::InputEvent;
|
||||
use jni::objects::{JClass, JString};
|
||||
use jni::objects::{JCharArray, JClass, JObject, JString};
|
||||
use jni::sys::{jbyteArray, jint, jlong, jstring};
|
||||
use jni::JNIEnv;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -262,6 +262,45 @@ pub extern "system" fn Java_com_verify_remote_RustBridge_nativeSendKey(
|
||||
});
|
||||
}
|
||||
|
||||
/// 协议 v3:文本上行。`units` 是 Kotlin 侧的 `CharArray`(UTF-16 code unit)。
|
||||
///
|
||||
/// 为什么不用 `JString`:jni 0.21 的 `get_string` 走 Modified-UTF-8 → Rust `String`,
|
||||
/// 落单代理项(IME 提交边界常见)会在这次往返里丢失。`char[]` 经
|
||||
/// `GetCharArrayRegion` 按 u16 直接拷贝,零转换(`jchar = u16`,见 jni-sys-0.3.1/lib.rs:15)。
|
||||
///
|
||||
/// 截断与空输入的判断在 `viewer_core::Session::send_text` 里做,与被控端同一口径。
|
||||
/// 失败必须有可见后果:JNI 取数失败走 eprintln(Android 上落 stderr→logcat),
|
||||
/// 不静默返回成功——用户对着远端打字却没字,最坏的就是「看起来发送了」。
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_verify_remote_RustBridge_nativeSendText<'local>(
|
||||
// 不需要 `mut`:jni 0.21.1 的 `get_array_length` / `get_char_array_region`
|
||||
// 都是 `&self`(jnienv.rs:1652 / :1887),与 `new_string` 之类的 `&mut self` 不同。
|
||||
env: JNIEnv<'local>,
|
||||
_class: JClass<'local>,
|
||||
handle: jlong,
|
||||
units: JObject<'local>,
|
||||
) {
|
||||
if units.as_raw().is_null() {
|
||||
// Kotlin 侧不会传 null,但 JNI 入口的对端数据一律不信任(全局安全规则)
|
||||
return;
|
||||
}
|
||||
let arr = JCharArray::from(units);
|
||||
let len = match env.get_array_length(&arr) {
|
||||
Ok(n) if n > 0 => n as usize,
|
||||
Ok(_) => return,
|
||||
Err(e) => {
|
||||
eprintln!("[android-ffi] get_array_length failed: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut buf = vec![0u16; len];
|
||||
if let Err(e) = env.get_char_array_region(&arr, 0, &mut buf) {
|
||||
eprintln!("[android-ffi] get_char_array_region failed: {e}");
|
||||
return;
|
||||
}
|
||||
with_session(handle, |s| s.send_text(&buf));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_verify_remote_RustBridge_nativeRequestKeyframe(
|
||||
_env: JNIEnv,
|
||||
|
||||
@ -344,6 +344,29 @@ impl Session {
|
||||
let _ = self.tx.send(Command::Input(ev));
|
||||
}
|
||||
|
||||
/// 协议 v3:发送一段文本(UTF-16 code unit)到被控端注入。
|
||||
///
|
||||
/// 为什么参数是 `&[u16]` 而不是 `&str`:被控端的注入通道(Windows
|
||||
/// `KEYEVENTF_UNICODE`)与主控端来源(Java 字符串)都是 UTF-16 code unit,
|
||||
/// 中间任何一次 UTF-8 往返都会把 IME 提交边界的落单代理项变成替换字符。
|
||||
/// 见 `common::InputEvent::Text` 的取舍说明。
|
||||
///
|
||||
/// 超限处理:截断到 [`common::MAX_TEXT_UNITS`] 并打日志——静默丢弃会让用户
|
||||
/// 以为长文本已送达。日志只报长度,不报内容。
|
||||
pub fn send_text(&self, units: &[u16]) {
|
||||
if units.is_empty() {
|
||||
return; // 空提交没有注入语义,也不该占用一条消息
|
||||
}
|
||||
let (units, truncated) = common::clamp_text_units(units.to_vec());
|
||||
if truncated {
|
||||
log(&format!(
|
||||
"文本超过上限,已截断到 {} 个 code unit",
|
||||
common::MAX_TEXT_UNITS
|
||||
));
|
||||
}
|
||||
self.send_input(InputEvent::Text { units });
|
||||
}
|
||||
|
||||
/// 请求被控端强制关键帧(解码器错误/跳帧后的恢复路径)。
|
||||
pub fn request_keyframe(&self) {
|
||||
let _ = self.tx.send(Command::KeyFrameReq);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user