Compare commits
10 Commits
8952bb14d2
...
5770b7007c
| Author | SHA1 | Date | |
|---|---|---|---|
| 5770b7007c | |||
| 1ded747a0c | |||
| 0cdea53f8d | |||
| c52dd6b336 | |||
| 8d1ebf02e3 | |||
| 98d827f5c3 | |||
| c8dece351c | |||
| fbe064253c | |||
| 2507629b4c | |||
| 96a170f719 |
@@ -4,8 +4,6 @@ use std::collections::{HashMap, HashSet};
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub type Config = AppConfig;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
@@ -72,6 +70,7 @@ pub struct PerspectiveCorrectionConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct ChromaKeyConfig {
|
pub struct ChromaKeyConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@@ -106,6 +105,7 @@ fn default_hsv_max() -> [i32; 3] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct BrightnessAdjustConfig {
|
pub struct BrightnessAdjustConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@@ -272,8 +272,9 @@ pub struct RemoteControlConfig {
|
|||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BLE 配网配置(新增)
|
/// BLE 配网配置
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct BleConfig {
|
pub struct BleConfig {
|
||||||
#[serde(default = "default_ble_enabled")]
|
#[serde(default = "default_ble_enabled")]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@@ -443,14 +444,16 @@ impl PerspectiveCorrectionConfig {
|
|||||||
|
|
||||||
impl ChromaKeyConfig {
|
impl ChromaKeyConfig {
|
||||||
pub fn validate(&self) -> Result<()> {
|
pub fn validate(&self) -> Result<()> {
|
||||||
validate_hsv_component("display.chroma_key.hsv_min[0]", self.hsv_min[0], 0, 180)?;
|
let hsv_ranges = [180, 255, 255]; // H, S, V max values
|
||||||
validate_hsv_component("display.chroma_key.hsv_min[1]", self.hsv_min[1], 0, 255)?;
|
for (index, &max_val) in hsv_ranges.iter().enumerate() {
|
||||||
validate_hsv_component("display.chroma_key.hsv_min[2]", self.hsv_min[2], 0, 255)?;
|
validate_hsv_component(
|
||||||
validate_hsv_component("display.chroma_key.hsv_max[0]", self.hsv_max[0], 0, 180)?;
|
&format!("display.chroma_key.hsv_min[{index}]"),
|
||||||
validate_hsv_component("display.chroma_key.hsv_max[1]", self.hsv_max[1], 0, 255)?;
|
self.hsv_min[index], 0, max_val,
|
||||||
validate_hsv_component("display.chroma_key.hsv_max[2]", self.hsv_max[2], 0, 255)?;
|
)?;
|
||||||
|
validate_hsv_component(
|
||||||
for index in 0..3 {
|
&format!("display.chroma_key.hsv_max[{index}]"),
|
||||||
|
self.hsv_max[index], 0, max_val,
|
||||||
|
)?;
|
||||||
if self.hsv_min[index] > self.hsv_max[index] {
|
if self.hsv_min[index] > self.hsv_max[index] {
|
||||||
bail!("display.chroma_key hsv_min[{index}] 不能大于 hsv_max[{index}]");
|
bail!("display.chroma_key hsv_min[{index}] 不能大于 hsv_max[{index}]");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::core::message::{Destination, Envelope, Message, PlayerCommand, WifiCommand};
|
use crate::core::message::{Destination, Envelope, Message, PlayerCommand, WifiCommand};
|
||||||
|
use crate::core::plugin_ids;
|
||||||
|
|
||||||
/// 命令解析结果
|
/// 命令解析结果
|
||||||
pub struct DispatchResult {
|
pub struct DispatchResult {
|
||||||
@@ -137,7 +138,7 @@ fn ok_video(from: &str, message: Message) -> Result<DispatchResult, String> {
|
|||||||
Ok(DispatchResult {
|
Ok(DispatchResult {
|
||||||
envelope: Envelope {
|
envelope: Envelope {
|
||||||
from: from.to_string(),
|
from: from.to_string(),
|
||||||
to: Destination::Plugin("video".to_string()),
|
to: Destination::Plugin(plugin_ids::VIDEO.to_string()),
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -147,7 +148,7 @@ fn ok_wifi(from: &str, message: Message) -> Result<DispatchResult, String> {
|
|||||||
Ok(DispatchResult {
|
Ok(DispatchResult {
|
||||||
envelope: Envelope {
|
envelope: Envelope {
|
||||||
from: from.to_string(),
|
from: from.to_string(),
|
||||||
to: Destination::Plugin("wifi".to_string()),
|
to: Destination::Plugin(plugin_ids::WIFI.to_string()),
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub struct Envelope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 消息目的地
|
/// 消息目的地
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Destination {
|
pub enum Destination {
|
||||||
/// 点对点发送给指定插件
|
/// 点对点发送给指定插件
|
||||||
Plugin(String),
|
Plugin(String),
|
||||||
@@ -68,7 +68,7 @@ pub enum Message {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PlayerCommand {
|
pub enum PlayerCommand {
|
||||||
Play,
|
Play,
|
||||||
Pause,
|
Pause,
|
||||||
@@ -78,7 +78,7 @@ pub enum PlayerCommand {
|
|||||||
ChangeScene(String),
|
ChangeScene(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct PlayerStatusData {
|
pub struct PlayerStatusData {
|
||||||
pub running: bool,
|
pub running: bool,
|
||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
@@ -88,7 +88,7 @@ pub struct PlayerStatusData {
|
|||||||
pub current_video: Option<String>,
|
pub current_video: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum WifiCommand {
|
pub enum WifiCommand {
|
||||||
Scan,
|
Scan,
|
||||||
Connect { ssid: String, password: String },
|
Connect { ssid: String, password: String },
|
||||||
@@ -100,7 +100,7 @@ pub enum WifiCommand {
|
|||||||
// ── 设备管理 ──
|
// ── 设备管理 ──
|
||||||
|
|
||||||
/// 像素格式
|
/// 像素格式
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum PixelFormat {
|
pub enum PixelFormat {
|
||||||
/// RGBA 8888 格式(每像素 4 字节)
|
/// RGBA 8888 格式(每像素 4 字节)
|
||||||
RGBA8888,
|
RGBA8888,
|
||||||
@@ -111,7 +111,7 @@ pub enum PixelFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 传感器类型
|
/// 传感器类型
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum SensorType {
|
pub enum SensorType {
|
||||||
/// 温度传感器
|
/// 温度传感器
|
||||||
Temperature,
|
Temperature,
|
||||||
@@ -124,7 +124,7 @@ pub enum SensorType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 触摸动作
|
/// 触摸动作
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum TouchAction {
|
pub enum TouchAction {
|
||||||
/// 按下
|
/// 按下
|
||||||
Down,
|
Down,
|
||||||
@@ -135,7 +135,7 @@ pub enum TouchAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 设备能力
|
/// 设备能力
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum DeviceCapability {
|
pub enum DeviceCapability {
|
||||||
/// 显示屏
|
/// 显示屏
|
||||||
Display,
|
Display,
|
||||||
|
|||||||
@@ -9,5 +9,15 @@ pub mod plugin_repo;
|
|||||||
pub mod service_manager;
|
pub mod service_manager;
|
||||||
pub mod version_manager;
|
pub mod version_manager;
|
||||||
|
|
||||||
|
/// 内置插件 ID 常量
|
||||||
|
pub mod plugin_ids {
|
||||||
|
pub const VIDEO: &str = "video";
|
||||||
|
pub const HTTP: &str = "http";
|
||||||
|
pub const WIFI: &str = "wifi";
|
||||||
|
pub const BLE: &str = "ble";
|
||||||
|
pub const DEVICE: &str = "device";
|
||||||
|
pub const SCREEN: &str = "screen";
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::sync::{mpsc, Arc};
|
use std::sync::{mpsc, Arc};
|
||||||
|
|
||||||
/// 单项能力测试结果
|
/// 单项能力测试结果
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct CapabilityTestResult {
|
pub struct CapabilityTestResult {
|
||||||
pub capability: String,
|
pub capability: String,
|
||||||
pub passed: bool,
|
pub passed: bool,
|
||||||
@@ -56,7 +56,7 @@ pub trait Plugin: Send {
|
|||||||
fn stop(&mut self) -> Result<()>;
|
fn stop(&mut self) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct PluginInfo {
|
pub struct PluginInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ fn default_test_timeout() -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 插件错误处理策略
|
/// 插件错误处理策略
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ErrorPolicy {
|
pub enum ErrorPolicy {
|
||||||
/// 自动回退到上一个稳定版本
|
/// 自动回退到上一个稳定版本
|
||||||
|
|||||||
@@ -1137,7 +1137,7 @@ impl ServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 插件状态信息(用于 API 查询)
|
/// 插件状态信息(用于 API 查询)
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PluginStateInfo {
|
pub struct PluginStateInfo {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub info: crate::core::plugin::PluginInfo,
|
pub info: crate::core::plugin::PluginInfo,
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ impl Plugin for HttpPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dependencies(&self) -> Vec<String> {
|
fn dependencies(&self) -> Vec<String> {
|
||||||
vec!["video".to_string()]
|
vec![crate::core::plugin_ids::VIDEO.to_string()]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, ctx: PluginContext) -> Result<()> {
|
fn init(&mut self, ctx: PluginContext) -> Result<()> {
|
||||||
|
|||||||
@@ -1712,11 +1712,17 @@ fn read_attachment_response(path: &Path) -> warp::reply::Response {
|
|||||||
match std::fs::read(path) {
|
match std::fs::read(path) {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
let filename = path.file_name().unwrap_or_default().to_string_lossy();
|
let filename = path.file_name().unwrap_or_default().to_string_lossy();
|
||||||
|
// Sanitize filename for Content-Disposition header to prevent header injection
|
||||||
|
let safe_filename = filename
|
||||||
|
.replace('\\', "_")
|
||||||
|
.replace('"', "_")
|
||||||
|
.replace('\r', "")
|
||||||
|
.replace('\n', "");
|
||||||
match warp::http::Response::builder()
|
match warp::http::Response::builder()
|
||||||
.header("Content-Type", "application/octet-stream")
|
.header("Content-Type", "application/octet-stream")
|
||||||
.header(
|
.header(
|
||||||
"Content-Disposition",
|
"Content-Disposition",
|
||||||
format!("attachment; filename=\"{filename}\""),
|
format!("attachment; filename=\"{safe_filename}\""),
|
||||||
)
|
)
|
||||||
.header("Content-Length", data.len().to_string())
|
.header("Content-Length", data.len().to_string())
|
||||||
.body(data)
|
.body(data)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
use crate::core::{
|
use crate::core::{
|
||||||
message::{Destination, DeviceCommand, Envelope, Message},
|
message::{Destination, DeviceCommand, Envelope, Message},
|
||||||
plugin::*,
|
plugin::*,
|
||||||
|
plugin_ids,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ impl ScreenPlugin {
|
|||||||
if let Some(ctx) = &self.ctx {
|
if let Some(ctx) = &self.ctx {
|
||||||
let envelope = Envelope {
|
let envelope = Envelope {
|
||||||
from: self.id().to_string(),
|
from: self.id().to_string(),
|
||||||
to: Destination::Plugin("device".to_string()),
|
to: Destination::Plugin(plugin_ids::DEVICE.to_string()),
|
||||||
message: Message::DeviceCommand(DeviceCommand::SetSleepInhibit(true)),
|
message: Message::DeviceCommand(DeviceCommand::SetSleepInhibit(true)),
|
||||||
};
|
};
|
||||||
let _ = ctx.tx.send(envelope);
|
let _ = ctx.tx.send(envelope);
|
||||||
@@ -36,7 +37,7 @@ impl ScreenPlugin {
|
|||||||
if let Some(ctx) = &self.ctx {
|
if let Some(ctx) = &self.ctx {
|
||||||
let envelope = Envelope {
|
let envelope = Envelope {
|
||||||
from: self.id().to_string(),
|
from: self.id().to_string(),
|
||||||
to: Destination::Plugin("device".to_string()),
|
to: Destination::Plugin(plugin_ids::DEVICE.to_string()),
|
||||||
message: Message::DeviceCommand(DeviceCommand::SetSleepInhibit(false)),
|
message: Message::DeviceCommand(DeviceCommand::SetSleepInhibit(false)),
|
||||||
};
|
};
|
||||||
let _ = ctx.tx.send(envelope);
|
let _ = ctx.tx.send(envelope);
|
||||||
@@ -47,7 +48,7 @@ impl ScreenPlugin {
|
|||||||
if let Some(ctx) = &self.ctx {
|
if let Some(ctx) = &self.ctx {
|
||||||
let envelope = Envelope {
|
let envelope = Envelope {
|
||||||
from: self.id().to_string(),
|
from: self.id().to_string(),
|
||||||
to: Destination::Plugin("device".to_string()),
|
to: Destination::Plugin(plugin_ids::DEVICE.to_string()),
|
||||||
message: Message::DeviceCommand(DeviceCommand::SetCursorVisible(!hidden)),
|
message: Message::DeviceCommand(DeviceCommand::SetCursorVisible(!hidden)),
|
||||||
};
|
};
|
||||||
let _ = ctx.tx.send(envelope);
|
let _ = ctx.tx.send(envelope);
|
||||||
@@ -76,7 +77,7 @@ impl Plugin for ScreenPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn dependencies(&self) -> Vec<String> {
|
fn dependencies(&self) -> Vec<String> {
|
||||||
vec!["device".to_string()]
|
vec![plugin_ids::DEVICE.to_string()]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self, ctx: PluginContext) -> Result<()> {
|
fn init(&mut self, ctx: PluginContext) -> Result<()> {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub mod state_machine;
|
|||||||
|
|
||||||
use crate::core::message::{Destination, Envelope, Message, PlayerCommand, PlayerStatusData};
|
use crate::core::message::{Destination, Envelope, Message, PlayerCommand, PlayerStatusData};
|
||||||
use crate::core::plugin::{Platform, Plugin, PluginContext, PluginInfo};
|
use crate::core::plugin::{Platform, Plugin, PluginContext, PluginInfo};
|
||||||
|
use crate::core::plugin_ids;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use opencv::highgui;
|
use opencv::highgui;
|
||||||
use processor::VideoProcessor;
|
use processor::VideoProcessor;
|
||||||
@@ -151,7 +152,7 @@ impl Plugin for VideoPlugin {
|
|||||||
if let Some(ctx) = &self.ctx {
|
if let Some(ctx) = &self.ctx {
|
||||||
let _ = ctx.tx.send(Envelope {
|
let _ = ctx.tx.send(Envelope {
|
||||||
from: self.id().to_string(),
|
from: self.id().to_string(),
|
||||||
to: Destination::Plugin("screen".to_string()),
|
to: Destination::Plugin(plugin_ids::SCREEN.to_string()),
|
||||||
message: Message::ScreenLockRequest(true),
|
message: Message::ScreenLockRequest(true),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -162,7 +163,7 @@ impl Plugin for VideoPlugin {
|
|||||||
if let Some(ctx) = &self.ctx {
|
if let Some(ctx) = &self.ctx {
|
||||||
let _ = ctx.tx.send(Envelope {
|
let _ = ctx.tx.send(Envelope {
|
||||||
from: self.id().to_string(),
|
from: self.id().to_string(),
|
||||||
to: Destination::Plugin("screen".to_string()),
|
to: Destination::Plugin(plugin_ids::SCREEN.to_string()),
|
||||||
message: Message::ScreenLockRequest(false),
|
message: Message::ScreenLockRequest(false),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -356,12 +357,7 @@ fn publish_state_changed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn status_changed(old: &PlayerStatusData, new: &PlayerStatusData) -> bool {
|
fn status_changed(old: &PlayerStatusData, new: &PlayerStatusData) -> bool {
|
||||||
old.running != new.running
|
old != new
|
||||||
|| old.paused != new.paused
|
|
||||||
|| old.in_transition != new.in_transition
|
|
||||||
|| old.current_index != new.current_index
|
|
||||||
|| old.playlist_length != new.playlist_length
|
|
||||||
|| old.current_video != new.current_video
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lock_processor(
|
fn lock_processor(
|
||||||
|
|||||||
@@ -13,28 +13,26 @@ use opencv::{
|
|||||||
};
|
};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
type WindowRectKey = (String, i32, i32, i32, i32);
|
type WindowRectKey = (String, i32, i32, i32, i32);
|
||||||
type LoggedSizes = ((i32, i32), (i32, i32), (i32, i32));
|
type LoggedSizes = ((i32, i32), (i32, i32), (i32, i32));
|
||||||
|
|
||||||
fn log_mat_size(label: &str, frame: &Mat) {
|
fn log_mat_size(label: &str, frame: &Mat) {
|
||||||
use std::sync::Mutex;
|
static SEEN: OnceLock<Mutex<HashSet<(String, i32, i32)>>> = OnceLock::new();
|
||||||
|
|
||||||
static SEEN: Mutex<Option<HashSet<(String, i32, i32)>>> = Mutex::new(None);
|
|
||||||
let key = (label.to_string(), frame.cols(), frame.rows());
|
let key = (label.to_string(), frame.cols(), frame.rows());
|
||||||
let mut guard = SEEN.lock().unwrap();
|
let mut guard = SEEN
|
||||||
let set = guard.get_or_insert_with(HashSet::new);
|
.get_or_init(|| Mutex::new(HashSet::new()))
|
||||||
if set.insert(key) {
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
if guard.insert(key) {
|
||||||
println!("{}: cols={} rows={}", label, frame.cols(), frame.rows());
|
println!("{}: cols={} rows={}", label, frame.cols(), frame.rows());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_window_image_rect(window_name: &str) {
|
fn log_window_image_rect(window_name: &str) {
|
||||||
use std::sync::Mutex;
|
static SEEN: OnceLock<Mutex<HashSet<WindowRectKey>>> = OnceLock::new();
|
||||||
|
|
||||||
static SEEN: Mutex<Option<HashSet<WindowRectKey>>> = Mutex::new(None);
|
|
||||||
match highgui::get_window_image_rect(window_name) {
|
match highgui::get_window_image_rect(window_name) {
|
||||||
Ok(rect) => {
|
Ok(rect) => {
|
||||||
let key = (
|
let key = (
|
||||||
@@ -44,9 +42,11 @@ fn log_window_image_rect(window_name: &str) {
|
|||||||
rect.width,
|
rect.width,
|
||||||
rect.height,
|
rect.height,
|
||||||
);
|
);
|
||||||
let mut guard = SEEN.lock().unwrap();
|
let mut guard = SEEN
|
||||||
let set = guard.get_or_insert_with(HashSet::new);
|
.get_or_init(|| Mutex::new(HashSet::new()))
|
||||||
if set.insert(key) {
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
if guard.insert(key) {
|
||||||
println!(
|
println!(
|
||||||
"window.image_rect {}: x={} y={} width={} height={}",
|
"window.image_rect {}: x={} y={} width={} height={}",
|
||||||
window_name, rect.x, rect.y, rect.width, rect.height,
|
window_name, rect.x, rect.y, rect.width, rect.height,
|
||||||
|
|||||||
@@ -36,25 +36,6 @@ impl WifiPlugin {
|
|||||||
Self { ctx: None }
|
Self { ctx: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_connect_args(ssid: &str, password: &str) -> Vec<String> {
|
|
||||||
let mut args: Vec<String> = ["device", "wifi", "connect", ssid]
|
|
||||||
.into_iter()
|
|
||||||
.map(String::from)
|
|
||||||
.collect();
|
|
||||||
if !password.trim().is_empty() {
|
|
||||||
args.push("password".to_string());
|
|
||||||
args.push(password.to_string());
|
|
||||||
}
|
|
||||||
args
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_hotspot_args(ssid: &str, password: &str) -> Vec<String> {
|
|
||||||
["device", "wifi", "hotspot", "ssid", ssid, "password", password]
|
|
||||||
.into_iter()
|
|
||||||
.map(String::from)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_nmcli(args: &[impl AsRef<std::ffi::OsStr> + std::fmt::Debug]) -> Result<String> {
|
fn run_nmcli(args: &[impl AsRef<std::ffi::OsStr> + std::fmt::Debug]) -> Result<String> {
|
||||||
let output = Command::new("nmcli")
|
let output = Command::new("nmcli")
|
||||||
.args(args)
|
.args(args)
|
||||||
@@ -189,7 +170,12 @@ impl WifiPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn connect_network(&self, ssid: &str, password: &str) -> Result<serde_json::Value> {
|
fn connect_network(&self, ssid: &str, password: &str) -> Result<serde_json::Value> {
|
||||||
let output = Self::run_nmcli(&Self::build_connect_args(ssid, password))?;
|
let mut args = vec!["device", "wifi", "connect", ssid];
|
||||||
|
if !password.trim().is_empty() {
|
||||||
|
args.push("password");
|
||||||
|
args.push(password);
|
||||||
|
}
|
||||||
|
let output = Self::run_nmcli(&args)?;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"ok": true,
|
"ok": true,
|
||||||
@@ -260,7 +246,7 @@ impl WifiPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ap_start(&self, ssid: &str, password: &str) -> Result<serde_json::Value> {
|
fn ap_start(&self, ssid: &str, password: &str) -> Result<serde_json::Value> {
|
||||||
let output = Self::run_nmcli(&Self::build_hotspot_args(ssid, password))?;
|
let output = Self::run_nmcli(&["device", "wifi", "hotspot", "ssid", ssid, "password", password])?;
|
||||||
|
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"ok": true,
|
"ok": true,
|
||||||
@@ -280,7 +266,7 @@ impl WifiPlugin {
|
|||||||
"connection",
|
"connection",
|
||||||
"show",
|
"show",
|
||||||
"--active",
|
"--active",
|
||||||
])?;;
|
])?;
|
||||||
let hotspot_name = active
|
let hotspot_name = active
|
||||||
.lines()
|
.lines()
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
@@ -362,40 +348,4 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(fields, vec!["wlan0", "wifi", "connected", "Office:LAN"]);
|
assert_eq!(fields, vec!["wlan0", "wifi", "connected", "Office:LAN"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn connect_args_preserve_special_characters() {
|
|
||||||
let args =
|
|
||||||
WifiPlugin::build_connect_args(r#"ssid \"qa\" demo"#, r#"p@ss\\word with spaces"#);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
args,
|
|
||||||
vec![
|
|
||||||
"device",
|
|
||||||
"wifi",
|
|
||||||
"connect",
|
|
||||||
r#"ssid \"qa\" demo"#,
|
|
||||||
"password",
|
|
||||||
r#"p@ss\\word with spaces"#,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hotspot_args_preserve_special_characters() {
|
|
||||||
let args = WifiPlugin::build_hotspot_args("Showen AP", r#"\\quoted pass\\"#);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
args,
|
|
||||||
vec![
|
|
||||||
"device",
|
|
||||||
"wifi",
|
|
||||||
"hotspot",
|
|
||||||
"ssid",
|
|
||||||
"Showen AP",
|
|
||||||
"password",
|
|
||||||
r#"\\quoted pass\\"#,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user