mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
100% open source
This commit is contained in:
235
libs/scrap/src/android/ffi.rs
Normal file
235
libs/scrap/src/android/ffi.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
use jni::objects::JByteBuffer;
|
||||
use jni::objects::JString;
|
||||
use jni::objects::JValue;
|
||||
use jni::sys::jboolean;
|
||||
use jni::JNIEnv;
|
||||
use jni::{
|
||||
objects::{GlobalRef, JClass, JObject},
|
||||
JavaVM,
|
||||
};
|
||||
|
||||
use jni::errors::{Error as JniError, Result as JniResult};
|
||||
use lazy_static::lazy_static;
|
||||
use std::ops::Not;
|
||||
use std::sync::atomic::{AtomicPtr, Ordering::SeqCst};
|
||||
use std::sync::{Mutex, RwLock};
|
||||
use std::time::{Duration, Instant};
|
||||
lazy_static! {
|
||||
static ref JVM: RwLock<Option<JavaVM>> = RwLock::new(None);
|
||||
static ref MAIN_SERVICE_CTX: RwLock<Option<GlobalRef>> = RwLock::new(None); // MainService -> video service / audio service / info
|
||||
static ref INPUT_CTX: RwLock<Option<GlobalRef>> = RwLock::new(None);
|
||||
static ref VIDEO_RAW: Mutex<FrameRaw> = Mutex::new(FrameRaw::new("video", MAX_VIDEO_FRAME_TIMEOUT));
|
||||
static ref AUDIO_RAW: Mutex<FrameRaw> = Mutex::new(FrameRaw::new("audio", MAX_AUDIO_FRAME_TIMEOUT));
|
||||
}
|
||||
|
||||
const MAX_VIDEO_FRAME_TIMEOUT: Duration = Duration::from_millis(100);
|
||||
const MAX_AUDIO_FRAME_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
struct FrameRaw {
|
||||
name: &'static str,
|
||||
ptr: AtomicPtr<u8>,
|
||||
len: usize,
|
||||
last_update: Instant,
|
||||
timeout: Duration,
|
||||
enable: bool,
|
||||
}
|
||||
|
||||
impl FrameRaw {
|
||||
fn new(name: &'static str, timeout: Duration) -> Self {
|
||||
FrameRaw {
|
||||
name,
|
||||
ptr: AtomicPtr::default(),
|
||||
len: 0,
|
||||
last_update: Instant::now(),
|
||||
timeout,
|
||||
enable: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_enable(&mut self, value: bool) {
|
||||
self.enable = value;
|
||||
}
|
||||
|
||||
fn update(&mut self, data: &mut [u8]) {
|
||||
if self.enable.not() {
|
||||
return;
|
||||
}
|
||||
self.len = data.len();
|
||||
self.ptr.store(data.as_mut_ptr(), SeqCst);
|
||||
self.last_update = Instant::now();
|
||||
}
|
||||
|
||||
// take inner data as slice
|
||||
// release when success
|
||||
fn take<'a>(&mut self) -> Option<&'a [u8]> {
|
||||
if self.enable.not() {
|
||||
return None;
|
||||
}
|
||||
let ptr = self.ptr.load(SeqCst);
|
||||
if ptr.is_null() || self.len == 0 {
|
||||
None
|
||||
} else {
|
||||
if self.last_update.elapsed() > self.timeout {
|
||||
log::trace!("Failed to take {} raw,timeout!", self.name);
|
||||
return None;
|
||||
}
|
||||
let slice = unsafe { std::slice::from_raw_parts(ptr, self.len) };
|
||||
self.release();
|
||||
Some(slice)
|
||||
}
|
||||
}
|
||||
|
||||
fn release(&mut self) {
|
||||
self.len = 0;
|
||||
self.ptr.store(std::ptr::null_mut(), SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_video_raw<'a>() -> Option<&'a [u8]> {
|
||||
VIDEO_RAW.lock().ok()?.take()
|
||||
}
|
||||
|
||||
pub fn get_audio_raw<'a>() -> Option<&'a [u8]> {
|
||||
AUDIO_RAW.lock().ok()?.take()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onVideoFrameUpdate(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
buffer: JObject,
|
||||
) {
|
||||
let jb = JByteBuffer::from(buffer);
|
||||
let slice = env.get_direct_buffer_address(jb).unwrap();
|
||||
VIDEO_RAW.lock().unwrap().update(slice);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_onAudioFrameUpdate(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
buffer: JObject,
|
||||
) {
|
||||
let jb = JByteBuffer::from(buffer);
|
||||
let slice = env.get_direct_buffer_address(jb).unwrap();
|
||||
AUDIO_RAW.lock().unwrap().update(slice);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_setFrameRawEnable(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
name: JString,
|
||||
value: jboolean,
|
||||
) {
|
||||
if let Ok(name) = env.get_string(name) {
|
||||
let name: String = name.into();
|
||||
let value = value.eq(&1);
|
||||
if name.eq("video") {
|
||||
VIDEO_RAW.lock().unwrap().set_enable(value);
|
||||
} else if name.eq("audio") {
|
||||
AUDIO_RAW.lock().unwrap().set_enable(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
ctx: JObject,
|
||||
) {
|
||||
log::debug!("MainService init from java");
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
|
||||
*JVM.write().unwrap() = Some(jvm);
|
||||
|
||||
let context = env.new_global_ref(ctx).unwrap();
|
||||
*MAIN_SERVICE_CTX.write().unwrap() = Some(context);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_carriez_flutter_1hbb_InputService_init(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
ctx: JObject,
|
||||
) {
|
||||
log::debug!("InputService init from java");
|
||||
let jvm = env.get_java_vm().unwrap();
|
||||
|
||||
*JVM.write().unwrap() = Some(jvm);
|
||||
|
||||
let context = env.new_global_ref(ctx).unwrap();
|
||||
*INPUT_CTX.write().unwrap() = Some(context);
|
||||
}
|
||||
|
||||
pub fn call_input_service_mouse_input(mask: i32, x: i32, y: i32) -> JniResult<()> {
|
||||
if let (Some(jvm), Some(ctx)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
INPUT_CTX.read().unwrap().as_ref(),
|
||||
) {
|
||||
let env = jvm.attach_current_thread_as_daemon()?;
|
||||
env.call_method(
|
||||
ctx,
|
||||
"rustMouseInput",
|
||||
"(III)V",
|
||||
&[JValue::Int(mask), JValue::Int(x), JValue::Int(y)],
|
||||
)?;
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(JniError::ThrowFailed(-1));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_main_service_get_by_name(name: &str) -> JniResult<String> {
|
||||
if let (Some(jvm), Some(ctx)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
||||
) {
|
||||
let env = jvm.attach_current_thread_as_daemon()?;
|
||||
let name = env.new_string(name)?;
|
||||
let res = env
|
||||
.call_method(
|
||||
ctx,
|
||||
"rustGetByName",
|
||||
"(Ljava/lang/String;)Ljava/lang/String;",
|
||||
&[JValue::Object(name.into())],
|
||||
)?
|
||||
.l()?;
|
||||
let res = env.get_string(res.into())?;
|
||||
let res = res.to_string_lossy().to_string();
|
||||
return Ok(res);
|
||||
} else {
|
||||
return Err(JniError::ThrowFailed(-1));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_main_service_set_by_name(
|
||||
name: &str,
|
||||
arg1: Option<&str>,
|
||||
arg2: Option<&str>,
|
||||
) -> JniResult<()> {
|
||||
if let (Some(jvm), Some(ctx)) = (
|
||||
JVM.read().unwrap().as_ref(),
|
||||
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
||||
) {
|
||||
let env = jvm.attach_current_thread_as_daemon()?;
|
||||
let name = env.new_string(name)?;
|
||||
let arg1 = env.new_string(arg1.unwrap_or(""))?;
|
||||
let arg2 = env.new_string(arg2.unwrap_or(""))?;
|
||||
|
||||
env.call_method(
|
||||
ctx,
|
||||
"rustSetByName",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||
&[
|
||||
JValue::Object(name.into()),
|
||||
JValue::Object(arg1.into()),
|
||||
JValue::Object(arg2.into()),
|
||||
],
|
||||
)?;
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(JniError::ThrowFailed(-1));
|
||||
}
|
||||
}
|
||||
6
libs/scrap/src/android/mod.rs
Normal file
6
libs/scrap/src/android/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod ffi;
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub use ffi::*;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
129
libs/scrap/src/common/android.rs
Normal file
129
libs/scrap/src/common/android.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use crate::android::ffi::*;
|
||||
use crate::rgba_to_i420;
|
||||
use lazy_static::lazy_static;
|
||||
use std::io;
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref SCREEN_SIZE: Mutex<(u16, u16)> = Mutex::new((0, 0));
|
||||
}
|
||||
|
||||
pub struct Capturer {
|
||||
display: Display,
|
||||
bgra: Vec<u8>,
|
||||
saved_raw_data: Vec<u128>, // for faster compare and copy
|
||||
}
|
||||
|
||||
impl Capturer {
|
||||
pub fn new(display: Display, _yuv: bool) -> io::Result<Capturer> {
|
||||
Ok(Capturer {
|
||||
display,
|
||||
bgra: Vec::new(),
|
||||
saved_raw_data: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.display.width() as usize
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.display.height() as usize
|
||||
}
|
||||
|
||||
pub fn frame<'a>(&'a mut self, _timeout_ms: u32) -> io::Result<Frame<'a>> {
|
||||
if let Some(buf) = get_video_raw() {
|
||||
crate::would_block_if_equal(&mut self.saved_raw_data, buf)?;
|
||||
rgba_to_i420(self.width(), self.height(), buf, &mut self.bgra);
|
||||
Ok(Frame::RAW(&self.bgra))
|
||||
} else {
|
||||
return Err(io::ErrorKind::WouldBlock.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Frame<'a> {
|
||||
RAW(&'a [u8]),
|
||||
VP9(&'a [u8]),
|
||||
Empty,
|
||||
}
|
||||
|
||||
pub struct Display {
|
||||
default: bool,
|
||||
rect: Rect,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
struct Rect {
|
||||
pub x: i16,
|
||||
pub y: i16,
|
||||
pub w: u16,
|
||||
pub h: u16,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn primary() -> io::Result<Display> {
|
||||
let mut size = SCREEN_SIZE.lock().unwrap();
|
||||
if size.0 == 0 || size.1 == 0 {
|
||||
let (w, h) = get_size().unwrap_or((0, 0));
|
||||
size.0 = w;
|
||||
size.1 = h;
|
||||
}
|
||||
Ok(Display {
|
||||
default: true,
|
||||
rect: Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: size.0,
|
||||
h: size.1,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Display>> {
|
||||
Ok(vec![Display::primary()?])
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.rect.w as usize
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.rect.h as usize
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> (i32, i32) {
|
||||
let r = self.rect;
|
||||
(r.x as _, r.y as _)
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_primary(&self) -> bool {
|
||||
self.default
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
"Android".into()
|
||||
}
|
||||
|
||||
pub fn refresh_size() {
|
||||
let mut size = SCREEN_SIZE.lock().unwrap();
|
||||
let (w, h) = get_size().unwrap_or((0, 0));
|
||||
size.0 = w;
|
||||
size.1 = h;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_size() -> Option<(u16, u16)> {
|
||||
let res = call_main_service_get_by_name("screen_size").ok()?;
|
||||
if res.len() > 0 {
|
||||
let mut sp = res.split(":");
|
||||
let w = sp.next()?.parse::<u16>().ok()?;
|
||||
let h = sp.next()?.parse::<u16>().ok()?;
|
||||
return Some((w, h));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -19,7 +19,10 @@ cfg_if! {
|
||||
} else if #[cfg(dxgi)] {
|
||||
mod dxgi;
|
||||
pub use self::dxgi::*;
|
||||
} else {
|
||||
} else if #[cfg(android)] {
|
||||
mod android;
|
||||
pub use self::android::*;
|
||||
}else {
|
||||
//TODO: Fallback implementation.
|
||||
}
|
||||
}
|
||||
@@ -42,4 +45,4 @@ pub fn would_block_if_equal(old: &mut Vec<u128>, b: &[u8]) -> std::io::Result<()
|
||||
old.resize(b.len(), 0);
|
||||
old.copy_from_slice(b);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -20,4 +20,7 @@ pub mod wayland;
|
||||
#[cfg(dxgi)]
|
||||
pub mod dxgi;
|
||||
|
||||
#[cfg(android)]
|
||||
pub mod android;
|
||||
|
||||
mod common;
|
||||
|
||||
Reference in New Issue
Block a user