feat: 新增安卓平台支持

This commit is contained in:
mofeng-git
2026-05-24 08:37:19 +00:00
parent dc6475776e
commit b31aae284d
105 changed files with 7900 additions and 473 deletions

43
src/platform/android.rs Normal file
View File

@@ -0,0 +1,43 @@
//! Android Amlogic platform capabilities.
use super::{FeatureCapability, PlatformCapabilities, PlatformMode};
#[cfg(feature = "android")]
#[allow(dead_code)]
fn _keep_android_bionic_ifaddrs_shim_linked() {
let _ = crate::platform::android_bionic::freeifaddrs
as unsafe extern "C" fn(*mut crate::platform::android_bionic::ifaddrs);
let _ = crate::platform::android_bionic::getifaddrs
as unsafe extern "C" fn(*mut *mut crate::platform::android_bionic::ifaddrs) -> i32;
}
pub fn capabilities() -> PlatformCapabilities {
#[cfg(feature = "android")]
_keep_android_bionic_ifaddrs_shim_linked();
PlatformCapabilities {
mode: PlatformMode::AndroidAmlogic,
mode_label: PlatformMode::AndroidAmlogic.label(),
video_capture: FeatureCapability::available(["v4l2_uvc"])
.with_selected_backend(Some("v4l2_uvc".to_string())),
encoder: FeatureCapability::available(["ffmpeg_mediacodec_h264", "mjpeg"])
.with_selected_backend(Some(
if cfg!(feature = "android-mediacodec") {
"ffmpeg_mediacodec_h264"
} else {
"mjpeg"
}
.to_string(),
)),
hid: FeatureCapability::available(["otg_configfs", "ch9329", "none"]),
atx: FeatureCapability::available(["gpio", "usb_relay", "serial", "wol", "none"]),
msd: FeatureCapability::available(["otg_configfs"]),
otg: FeatureCapability::available(["configfs"]),
audio: FeatureCapability::available(["alsa", "opus"])
.with_selected_backend(Some("alsa".to_string())),
rustdesk: FeatureCapability::available(["builtin"]),
diagnostics: FeatureCapability::available(["android_linux"]),
extensions: FeatureCapability::unsupported("unsupported on Android Amlogic v1"),
service_installation: FeatureCapability::available(["android_foreground_service"]),
}
}

View File

@@ -0,0 +1,175 @@
#![allow(clippy::missing_safety_doc)]
use std::ffi::CString;
use std::mem::{size_of, zeroed};
use std::os::raw::{c_char, c_int, c_uint, c_void};
#[repr(C)]
pub struct ifaddrs {
pub ifa_next: *mut ifaddrs,
pub ifa_name: *mut c_char,
pub ifa_flags: c_uint,
pub ifa_addr: *mut libc::sockaddr,
pub ifa_netmask: *mut libc::sockaddr,
pub ifa_ifu: *mut libc::sockaddr,
pub ifa_data: *mut c_void,
}
#[repr(C)]
struct AddrNode {
ifa: ifaddrs,
name: CString,
addr: libc::sockaddr_in,
next: *mut AddrNode,
}
fn sockaddr_to_ipv4(addr: libc::sockaddr) -> Option<std::net::Ipv4Addr> {
if addr.sa_family as c_int != libc::AF_INET {
return None;
}
unsafe {
let sin = &*(&addr as *const libc::sockaddr as *const libc::sockaddr_in);
Some(std::net::Ipv4Addr::from(u32::from_be(sin.sin_addr.s_addr)))
}
}
fn query_ipv4(iface_name: &str) -> Option<libc::sockaddr_in> {
let name = CString::new(iface_name).ok()?;
if name.as_bytes().len() >= libc::IFNAMSIZ {
return None;
}
unsafe {
let fd = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0);
if fd < 0 {
return None;
}
let mut request: libc::ifreq = zeroed();
std::ptr::copy_nonoverlapping(
name.as_ptr(),
request.ifr_name.as_mut_ptr(),
name.as_bytes_with_nul().len(),
);
let request_code = libc::SIOCGIFADDR.try_into().ok()?;
let rc = libc::ioctl(fd, request_code, &mut request);
libc::close(fd);
if rc < 0 {
return None;
}
let addr = request.ifr_ifru.ifru_addr;
if addr.sa_family as c_int != libc::AF_INET {
return None;
}
let mut sin: libc::sockaddr_in = zeroed();
std::ptr::copy_nonoverlapping(
&addr as *const libc::sockaddr as *const u8,
&mut sin as *mut libc::sockaddr_in as *mut u8,
size_of::<libc::sockaddr_in>(),
);
Some(sin)
}
}
#[no_mangle]
pub unsafe extern "C" fn getifaddrs(addrs: *mut *mut ifaddrs) -> c_int {
if addrs.is_null() {
return -1;
}
*addrs = std::ptr::null_mut();
let net_dir = match std::fs::read_dir("/sys/class/net") {
Ok(dir) => dir,
Err(_) => return -1,
};
let mut head: *mut AddrNode = std::ptr::null_mut();
let mut tail: *mut AddrNode = std::ptr::null_mut();
for entry in net_dir.flatten() {
let iface_name = match entry.file_name().into_string() {
Ok(name) => name,
Err(_) => continue,
};
if iface_name == "lo" {
continue;
}
let operstate_path = entry.path().join("operstate");
let is_up = std::fs::read_to_string(&operstate_path)
.map(|s| s.trim() == "up")
.unwrap_or(false);
if !is_up {
continue;
}
let Some(addr) = query_ipv4(&iface_name) else {
continue;
};
let ip = sockaddr_to_ipv4(unsafe {
std::mem::transmute::<libc::sockaddr_in, libc::sockaddr>(addr)
});
if ip
.map(|ip| ip.is_loopback() || ip.is_unspecified())
.unwrap_or(true)
{
continue;
}
let name = match CString::new(iface_name) {
Ok(name) => name,
Err(_) => continue,
};
let mut node = Box::new(AddrNode {
ifa: ifaddrs {
ifa_next: std::ptr::null_mut(),
ifa_name: std::ptr::null_mut(),
ifa_flags: 0,
ifa_addr: std::ptr::null_mut(),
ifa_netmask: std::ptr::null_mut(),
ifa_ifu: std::ptr::null_mut(),
ifa_data: std::ptr::null_mut(),
},
name,
addr,
next: std::ptr::null_mut(),
});
node.ifa.ifa_name = node.name.as_ptr() as *mut c_char;
node.ifa.ifa_addr = &mut node.addr as *mut libc::sockaddr_in as *mut libc::sockaddr;
node.ifa.ifa_ifu = std::ptr::null_mut();
node.ifa.ifa_netmask = std::ptr::null_mut();
node.ifa.ifa_flags = (libc::IFF_UP | libc::IFF_RUNNING) as c_uint;
let raw = Box::into_raw(node);
if head.is_null() {
head = raw;
} else {
(*tail).next = raw;
(*tail).ifa.ifa_next = raw as *mut ifaddrs;
}
tail = raw;
}
*addrs = if head.is_null() {
std::ptr::null_mut()
} else {
head as *mut ifaddrs
};
0
}
#[no_mangle]
pub unsafe extern "C" fn freeifaddrs(addrs: *mut ifaddrs) {
let mut current = addrs as *mut AddrNode;
while !current.is_null() {
let next = (*current).next;
drop(Box::from_raw(current));
current = next;
}
}

View File

@@ -5,13 +5,16 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PlatformMode {
AndroidAmlogic,
Linux,
Windows,
}
impl PlatformMode {
pub const fn current() -> Self {
if cfg!(windows) {
if cfg!(feature = "android") {
Self::AndroidAmlogic
} else if cfg!(windows) {
Self::Windows
} else {
Self::Linux
@@ -20,6 +23,7 @@ impl PlatformMode {
pub const fn label(self) -> &'static str {
match self {
Self::AndroidAmlogic => "Android Amlogic",
Self::Linux => "Linux",
Self::Windows => "Windows",
}
@@ -81,9 +85,17 @@ pub struct PlatformCapabilities {
impl PlatformCapabilities {
pub fn current() -> Self {
match PlatformMode::current() {
PlatformMode::Linux => crate::platform::linux::capabilities(),
PlatformMode::Windows => crate::platform::windows::capabilities(),
#[cfg(feature = "android")]
{
return crate::platform::android::capabilities();
}
#[cfg(windows)]
{
return crate::platform::windows::capabilities();
}
#[cfg(all(unix, not(feature = "android")))]
{
return crate::platform::linux::capabilities();
}
}
}

View File

@@ -1,11 +1,73 @@
use crate::config::{AppConfig, AtxDriverType, HidBackend};
use crate::config::AppConfig;
#[cfg(windows)]
use crate::config::AtxDriverType;
#[cfg(any(windows, all(unix, feature = "android")))]
use crate::config::HidBackend;
pub fn apply(config: &mut AppConfig) {
if cfg!(windows) {
#[cfg(not(any(windows, all(unix, feature = "android"))))]
{
let _ = config;
}
#[cfg(all(unix, feature = "android"))]
{
apply_android(config);
}
#[cfg(windows)]
{
apply_windows(config);
}
}
#[cfg(all(unix, feature = "android"))]
fn apply_android(config: &mut AppConfig) {
let detected_udc = crate::otg::configfs::find_udc();
if config
.hid
.otg_udc
.as_deref()
.map(str::trim)
.unwrap_or("")
.is_empty()
{
config.hid.otg_udc = detected_udc;
}
let otg_available = config.hid.otg_udc.is_some();
if !config.initialized && otg_available {
config.hid.backend = HidBackend::Otg;
} else if config.hid.backend == HidBackend::Ch9329
&& config.hid.ch9329_port == "/dev/ttyUSB0"
&& !std::path::Path::new(&config.hid.ch9329_port).exists()
&& otg_available
{
config.hid.backend = HidBackend::Otg;
}
if !config.initialized {
config.audio.enabled = false;
config.audio.device.clear();
config.atx.enabled = false;
config.rustdesk.enabled = false;
config.rtsp.enabled = false;
config.redfish.enabled = false;
}
config
.video
.device
.get_or_insert_with(|| "auto".to_string());
config
.video
.format
.get_or_insert_with(|| "MJPEG".to_string());
config.web.bind_address = "0.0.0.0".to_string();
config.web.bind_addresses = vec!["0.0.0.0".to_string()];
}
#[cfg(windows)]
fn apply_windows(config: &mut AppConfig) {
config.msd.enabled = false;
config.hid.otg_udc = None;

View File

@@ -1,10 +1,16 @@
//! Platform selection and capability reporting.
#[cfg(feature = "android")]
pub mod android;
#[cfg(feature = "android")]
pub mod android_bionic;
pub mod capabilities;
pub mod defaults;
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(unix)]
pub mod usb_reset;
#[cfg(windows)]
pub mod windows;
pub use capabilities::{FeatureCapability, PlatformCapabilities, PlatformMode};