mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-15 07:26:44 +08:00
@@ -212,4 +212,4 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
林枫云主营国内外地域的精品线路业务服务器、高主频游戏服务器和大带宽服务器。
|
林枫云主营国内外地域的精品线路业务服务器、高主频游戏服务器和大带宽服务器。
|
||||||
|
|||||||
@@ -19,8 +19,22 @@ ARCH_MAP=(
|
|||||||
build_arch() {
|
build_arch() {
|
||||||
local rust_target="$1"
|
local rust_target="$1"
|
||||||
|
|
||||||
echo "=== Building: $rust_target (via cross with custom Dockerfile) ==="
|
# Build frontend first
|
||||||
cross build --release --target "$rust_target"
|
if [ ! -d "$PROJECT_DIR/web/dist" ]; then
|
||||||
|
echo "=== Building Frontend ==="
|
||||||
|
cd "$PROJECT_DIR/web" && npm install && npm run build
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local host_arch=$(rustc -vV | grep host | cut -d ' ' -f 2)
|
||||||
|
|
||||||
|
if [ "$rust_target" == "$host_arch" ]; then
|
||||||
|
echo "=== Building: $rust_target (NATIVE build, skipping cross) ==="
|
||||||
|
cargo build --release --target "$rust_target"
|
||||||
|
else
|
||||||
|
echo "=== Building: $rust_target (via cross) ==="
|
||||||
|
cross build --release --target "$rust_target"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
|
|||||||
@@ -362,10 +362,11 @@ mod ffmpeg {
|
|||||||
|
|
||||||
// RKMPP decode only exists on ARM builds where FFmpeg is compiled with RKMPP support.
|
// RKMPP decode only exists on ARM builds where FFmpeg is compiled with RKMPP support.
|
||||||
// Avoid compiling this file on x86/x64 where `AV_HWDEVICE_TYPE_RKMPP` doesn't exist.
|
// Avoid compiling this file on x86/x64 where `AV_HWDEVICE_TYPE_RKMPP` doesn't exist.
|
||||||
|
// Also check if RKMPP is available in the current FFmpeg environment to avoid compilation errors on non-Rockchip ARM (e.g. RPi).
|
||||||
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
||||||
let enable_rkmpp = matches!(target_arch.as_str(), "aarch64" | "arm")
|
let is_arm = matches!(target_arch.as_str(), "aarch64" | "arm");
|
||||||
|| std::env::var_os("CARGO_FEATURE_RKMPP").is_some();
|
|
||||||
if enable_rkmpp {
|
if is_arm {
|
||||||
builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_decode.cpp"));
|
builder.file(ffmpeg_ram_dir.join("ffmpeg_ram_decode.cpp"));
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
|
|||||||
@@ -169,12 +169,12 @@ int linux_support_v4l2m2m() {
|
|||||||
// /dev/video2 - Alternate path
|
// /dev/video2 - Alternate path
|
||||||
// /dev/video32 - Some Allwinner/Rockchip legacy
|
// /dev/video32 - Some Allwinner/Rockchip legacy
|
||||||
const char *m2m_devices[] = {
|
const char *m2m_devices[] = {
|
||||||
"/dev/video10",
|
"/dev/video10",
|
||||||
"/dev/video11",
|
"/dev/video11",
|
||||||
"/dev/video0",
|
"/dev/video0",
|
||||||
"/dev/video1",
|
"/dev/video1",
|
||||||
"/dev/video2",
|
"/dev/video2",
|
||||||
"/dev/video32",
|
"/dev/video32",
|
||||||
};
|
};
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) {
|
for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) {
|
||||||
|
|||||||
@@ -10,11 +10,19 @@ extern "C" {
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#define LOG_MODULE "UTIL"
|
#define LOG_MODULE "UTIL"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
#ifndef FF_PROFILE_H264_BASELINE
|
||||||
|
#define FF_PROFILE_H264_BASELINE 66
|
||||||
|
#endif
|
||||||
|
#ifndef FF_PROFILE_H264_HIGH
|
||||||
|
#define FF_PROFILE_H264_HIGH 100
|
||||||
|
#endif
|
||||||
|
#ifndef FF_PROFILE_HEVC_MAIN
|
||||||
|
#define FF_PROFILE_HEVC_MAIN 1
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Helper function: check if encoder is software H264 (libx264)
|
// Helper function: check if encoder is software H264 (libx264)
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ public:
|
|||||||
callback_ = callback;
|
callback_ = callback;
|
||||||
|
|
||||||
if (name_.find("rkmpp") != std::string::npos) {
|
if (name_.find("rkmpp") != std::string::npos) {
|
||||||
|
#ifdef AV_HWDEVICE_TYPE_RKMPP
|
||||||
hw_device_type_ = AV_HWDEVICE_TYPE_RKMPP;
|
hw_device_type_ = AV_HWDEVICE_TYPE_RKMPP;
|
||||||
|
#else
|
||||||
|
set_last_error("RKMPP support not compiled in FFmpeg");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -354,69 +354,43 @@ impl Encoder {
|
|||||||
let mut last_err: Option<i32> = None;
|
let mut last_err: Option<i32> = None;
|
||||||
let is_v4l2m2m = codec.name.contains("v4l2m2m");
|
let is_v4l2m2m = codec.name.contains("v4l2m2m");
|
||||||
|
|
||||||
let max_attempts = if is_v4l2m2m { 5 } else { 1 };
|
let max_attempts = if codec.name.contains("v4l2m2m") {
|
||||||
|
5
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
for attempt in 0..max_attempts {
|
for attempt in 0..max_attempts {
|
||||||
if is_v4l2m2m {
|
encoder.request_keyframe();
|
||||||
encoder.request_keyframe();
|
|
||||||
}
|
|
||||||
let pts = (attempt as i64) * 33; // 33ms is an approximation for 30 FPS (1000 / 30)
|
let pts = (attempt as i64) * 33; // 33ms is an approximation for 30 FPS (1000 / 30)
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
match encoder.encode(&yuv, pts) {
|
match encoder.encode(&yuv, pts) {
|
||||||
Ok(frames) => {
|
Ok(frames) => {
|
||||||
let elapsed = start.elapsed().as_millis();
|
let elapsed = start.elapsed().as_millis();
|
||||||
|
|
||||||
if is_v4l2m2m {
|
if frames.len() >= 1 && elapsed < TEST_TIMEOUT_MS as _ {
|
||||||
if !frames.is_empty() && elapsed < TEST_TIMEOUT_MS as _ {
|
|
||||||
debug!(
|
|
||||||
"Encoder {} test passed on attempt {} (frames: {})",
|
|
||||||
codec.name,
|
|
||||||
attempt + 1,
|
|
||||||
frames.len()
|
|
||||||
);
|
|
||||||
res.push(codec.clone());
|
|
||||||
passed = true;
|
|
||||||
break;
|
|
||||||
} else if frames.is_empty() {
|
|
||||||
debug!(
|
|
||||||
"Encoder {} test produced no output on attempt {}",
|
|
||||||
codec.name,
|
|
||||||
attempt + 1
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
"Encoder {} test failed on attempt {} - frames: {}, timeout: {}ms",
|
|
||||||
codec.name,
|
|
||||||
attempt + 1,
|
|
||||||
frames.len(),
|
|
||||||
elapsed
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if frames.len() == 1 {
|
|
||||||
if frames[0].key == 1 && elapsed < TEST_TIMEOUT_MS as _ {
|
|
||||||
debug!(
|
|
||||||
"Encoder {} test passed on attempt {}",
|
|
||||||
codec.name,
|
|
||||||
attempt + 1
|
|
||||||
);
|
|
||||||
res.push(codec.clone());
|
|
||||||
passed = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
"Encoder {} test failed on attempt {} - key: {}, timeout: {}ms",
|
|
||||||
codec.name,
|
|
||||||
attempt + 1,
|
|
||||||
frames[0].key,
|
|
||||||
elapsed
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug!(
|
debug!(
|
||||||
"Encoder {} test failed on attempt {} - wrong frame count: {}",
|
"Encoder {} test passed on attempt {} (frames: {})",
|
||||||
codec.name,
|
codec.name,
|
||||||
attempt + 1,
|
attempt + 1,
|
||||||
frames.len()
|
frames.len()
|
||||||
);
|
);
|
||||||
|
res.push(codec.clone());
|
||||||
|
passed = true;
|
||||||
|
break;
|
||||||
|
} else if frames.is_empty() {
|
||||||
|
debug!(
|
||||||
|
"Encoder {} test produced no output on attempt {}",
|
||||||
|
codec.name,
|
||||||
|
attempt + 1
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
"Encoder {} test failed on attempt {} - frames: {}, timeout: {}ms",
|
||||||
|
codec.name,
|
||||||
|
attempt + 1,
|
||||||
|
frames.len(),
|
||||||
|
elapsed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -258,10 +258,7 @@ impl AtxKeyExecutor {
|
|||||||
|
|
||||||
/// Pulse Serial relay
|
/// Pulse Serial relay
|
||||||
async fn pulse_serial(&self, duration: Duration) -> Result<()> {
|
async fn pulse_serial(&self, duration: Duration) -> Result<()> {
|
||||||
info!(
|
info!("Pulse serial relay on {} pin {}", self.config.device, self.config.pin);
|
||||||
"Pulse serial relay on {} pin {}",
|
|
||||||
self.config.device, self.config.pin
|
|
||||||
);
|
|
||||||
// Turn relay on
|
// Turn relay on
|
||||||
self.send_serial_relay_command(true)?;
|
self.send_serial_relay_command(true)?;
|
||||||
|
|
||||||
@@ -285,7 +282,7 @@ impl AtxKeyExecutor {
|
|||||||
// Checksum = A0 + channel + state
|
// Checksum = A0 + channel + state
|
||||||
let state = if on { 1 } else { 0 };
|
let state = if on { 1 } else { 0 };
|
||||||
let checksum = 0xA0u8.wrapping_add(channel).wrapping_add(state);
|
let checksum = 0xA0u8.wrapping_add(channel).wrapping_add(state);
|
||||||
|
|
||||||
// Example for Channel 1:
|
// Example for Channel 1:
|
||||||
// ON: A0 01 01 A2
|
// ON: A0 01 01 A2
|
||||||
// OFF: A0 01 00 A1
|
// OFF: A0 01 00 A1
|
||||||
|
|||||||
@@ -93,7 +93,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_discover_devices() {
|
fn test_discover_devices() {
|
||||||
let _devices = discover_devices();
|
let devices = discover_devices();
|
||||||
|
// Just verify the function runs without error
|
||||||
|
assert!(devices.gpio_chips.len() >= 0);
|
||||||
|
assert!(devices.usb_relays.len() >= 0);
|
||||||
|
assert!(devices.serial_ports.len() >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ pub struct AtxKeyConfig {
|
|||||||
pub pin: u32,
|
pub pin: u32,
|
||||||
/// Active level (only applicable to GPIO, ignored for USB Relay)
|
/// Active level (only applicable to GPIO, ignored for USB Relay)
|
||||||
pub active_level: ActiveLevel,
|
pub active_level: ActiveLevel,
|
||||||
/// Baud rate for serial relay
|
/// Baud rate for serial relay (start with 9600)
|
||||||
pub baud_rate: u32,
|
pub baud_rate: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ pub struct AtxDevices {
|
|||||||
pub gpio_chips: Vec<String>,
|
pub gpio_chips: Vec<String>,
|
||||||
/// Available USB HID relay devices (/dev/hidraw*)
|
/// Available USB HID relay devices (/dev/hidraw*)
|
||||||
pub usb_relays: Vec<String>,
|
pub usb_relays: Vec<String>,
|
||||||
/// Available Serial ports (/dev/ttyUSB* or /dev/ttyACM*)
|
/// Available Serial ports (/dev/ttyUSB*)
|
||||||
pub serial_ports: Vec<String>,
|
pub serial_ports: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3552,6 +3552,7 @@ pub async fn atx_power(
|
|||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Json(req): Json<AtxPowerControlRequest>,
|
Json(req): Json<AtxPowerControlRequest>,
|
||||||
) -> Result<Json<LoginResponse>> {
|
) -> Result<Json<LoginResponse>> {
|
||||||
|
tracing::info!("Received ATX power request: action={}", req.action);
|
||||||
let atx_guard = state.atx.read().await;
|
let atx_guard = state.atx.read().await;
|
||||||
let atx = atx_guard
|
let atx = atx_guard
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ const confirmDescription = computed(() => {
|
|||||||
default: return ''
|
default: return ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// MAC address validation
|
// MAC address validation
|
||||||
const isValidMac = computed(() => {
|
const isValidMac = computed(() => {
|
||||||
const mac = wolMacAddress.value.trim()
|
const mac = wolMacAddress.value.trim()
|
||||||
|
|||||||
@@ -1407,15 +1407,19 @@ function openTerminalInNewTab() {
|
|||||||
|
|
||||||
// ATX actions
|
// ATX actions
|
||||||
async function handlePowerShort() {
|
async function handlePowerShort() {
|
||||||
|
console.log('[ConsoleView] Handling power short press')
|
||||||
try {
|
try {
|
||||||
await atxApi.power('short')
|
const res = await atxApi.power('short')
|
||||||
|
console.log('[ConsoleView] Power short API result:', res)
|
||||||
await systemStore.fetchAtxState()
|
await systemStore.fetchAtxState()
|
||||||
} catch {
|
} catch (e) {
|
||||||
|
console.error('[ConsoleView] Power short API failed:', e)
|
||||||
// ATX action failed
|
// ATX action failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handlePowerLong() {
|
async function handlePowerLong() {
|
||||||
|
console.log('[ConsoleView] Handling power long press')
|
||||||
try {
|
try {
|
||||||
await atxApi.power('long')
|
await atxApi.power('long')
|
||||||
await systemStore.fetchAtxState()
|
await systemStore.fetchAtxState()
|
||||||
|
|||||||
@@ -2044,6 +2044,7 @@ watch(() => config.value.hid_backend, async () => {
|
|||||||
<select id="hid-backend" v-model="config.hid_backend" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
|
<select id="hid-backend" v-model="config.hid_backend" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
|
||||||
<option value="ch9329">CH9329 (Serial)</option>
|
<option value="ch9329">CH9329 (Serial)</option>
|
||||||
<option value="otg">USB OTG</option>
|
<option value="otg">USB OTG</option>
|
||||||
|
<option value="none">{{ t('common.disabled') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
|
<div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
|
||||||
|
|||||||
@@ -372,6 +372,11 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
applyOtgProfileDefault()
|
applyOtgProfileDefault()
|
||||||
|
|
||||||
|
// If no HID devices exist, default to disabled to avoid blocking setup
|
||||||
|
if (result.serial.length === 0 && result.udc.length === 0) {
|
||||||
|
hidBackend.value = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-select audio device if available (and no video device to trigger watch)
|
// Auto-select audio device if available (and no video device to trigger watch)
|
||||||
if (result.audio.length > 0 && !audioDevice.value) {
|
if (result.audio.length > 0 && !audioDevice.value) {
|
||||||
// Prefer HDMI audio device
|
// Prefer HDMI audio device
|
||||||
@@ -387,6 +392,10 @@ onMounted(async () => {
|
|||||||
// Use defaults
|
// Use defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (devices.value.serial.length === 0 && devices.value.udc.length === 0) {
|
||||||
|
hidBackend.value = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
// Load encoder backends
|
// Load encoder backends
|
||||||
try {
|
try {
|
||||||
const codecsResult = await streamApi.getCodecs()
|
const codecsResult = await streamApi.getCodecs()
|
||||||
@@ -896,6 +905,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
CH9329 ({{ t('setup.serialHid') }})
|
CH9329 ({{ t('setup.serialHid') }})
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="otg">USB OTG</SelectItem>
|
<SelectItem value="otg">USB OTG</SelectItem>
|
||||||
|
<SelectItem value="none">{{ t('setup.disableHid') }}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
@@ -1004,6 +1014,10 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p v-if="hidBackend === 'none'" class="text-xs text-muted-foreground">
|
||||||
|
{{ t('setup.hidDisabledHint') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 4: Extensions Settings -->
|
<!-- Step 4: Extensions Settings -->
|
||||||
|
|||||||
Reference in New Issue
Block a user