diff --git a/broadcast-client/src-tauri/Cargo.lock b/broadcast-client/src-tauri/Cargo.lock index 782d7aa..c4f82e1 100644 --- a/broadcast-client/src-tauri/Cargo.lock +++ b/broadcast-client/src-tauri/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler2" @@ -306,13 +306,12 @@ dependencies = [ name = "broadcast-client" version = "0.1.1" dependencies = [ - "arboard", + "chrono", "fs2", "serde", "serde_json", "tauri", "tauri-build", - "time", ] [[package]] @@ -472,8 +471,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link 0.2.1", ] diff --git a/broadcast-client/src-tauri/src/lib.rs b/broadcast-client/src-tauri/src/lib.rs index 6cb8328..2fcc0b2 100644 --- a/broadcast-client/src-tauri/src/lib.rs +++ b/broadcast-client/src-tauri/src/lib.rs @@ -173,6 +173,7 @@ pub fn run() { stop_socket_service, get_socket_service_status, check_apt_update, + setup_zyyun_apt_source, debug_log, quit_app ]) @@ -462,6 +463,110 @@ fn check_apt_update( } } +#[cfg(target_os = "linux")] +const ZYYUN_APT_SETUP_PROGRESS_EVENT: &str = "zyyun-apt-setup-progress"; + +#[cfg(target_os = "linux")] +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +struct AptSetupProgressPayload { + percent: u8, + message: String, +} + +#[cfg(target_os = "linux")] +fn bash_single_quoted_apt_line(content: &str) -> String { + format!("'{}'", content.replace('\'', "'\\''")) +} + +#[cfg(target_os = "linux")] +fn emit_apt_setup_progress_broadcast(app: &tauri::AppHandle, percent: u8, message: impl Into) { + let msg = message.into(); + let _ = app.emit_all( + ZYYUN_APT_SETUP_PROGRESS_EVENT, + AptSetupProgressPayload { + percent, + message: msg.clone(), + }, + ); + append_socket_log( + app, + "INFO", + format!("[apt-setup] {percent}% {msg}"), + ); +} + +#[cfg(target_os = "linux")] +fn run_privileged_apt_setup_broadcast(deb_line: &str) -> Result<(), String> { + let quoted = bash_single_quoted_apt_line(deb_line.trim()); + let inner = format!( + "set -e; install -d /etc/apt/sources.list.d; printf '%s\\n' {quoted} > /etc/apt/sources.list.d/zyyun.list; apt-get update -y" + ); + + let output = Command::new("pkexec") + .args(["bash", "-c", &inner]) + .output() + .map_err(|e| format!("无法启动 pkexec(请确认已安装 policykit-1):{e}"))?; + + if output.status.success() { + return Ok(()); + } + + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + Err(format!( + "配置更新源失败(退出码 {:?})。stderr={} stdout={}", + output.status.code(), + if stderr.is_empty() { "(空)" } else { &stderr }, + if stdout.is_empty() { "(空)" } else { &stdout } + )) +} + +/// 首次配置:写入紫云 apt 源并执行 `apt-get update`(需 `pkexec` 授权)。 +#[tauri::command] +fn setup_zyyun_apt_source(app: tauri::AppHandle, deb_line: String) -> Result<(), String> { + #[cfg(not(target_os = "linux"))] + { + let _ = (&app, deb_line); + return Err("当前系统不支持自动配置 apt 源。".to_string()); + } + + #[cfg(target_os = "linux")] + { + let line = deb_line.trim(); + if line.is_empty() { + return Err("deb 源行不能为空".to_string()); + } + + emit_apt_setup_progress_broadcast(&app, 5, "准备配置紫云 apt 软件源…"); + emit_apt_setup_progress_broadcast( + &app, + 15, + "即将弹出权限确认,请在对话框中输入管理员密码…", + ); + + let result = run_privileged_apt_setup_broadcast(line); + + match &result { + Ok(()) => { + emit_apt_setup_progress_broadcast(&app, 92, "正在完成索引刷新…"); + emit_apt_setup_progress_broadcast(&app, 100, "配置完成,可继续检查更新。"); + append_socket_log(&app, "INFO", "[apt-setup] 紫云 apt 源配置成功"); + } + Err(err) => { + emit_apt_setup_progress_broadcast(&app, 100, format!("配置失败:{err}")); + append_socket_log( + &app, + "ERROR", + format!("[apt-setup] 紫云 apt 源配置失败: {err}"), + ); + } + } + + result + } +} + fn emit_socket_status(app: &tauri::AppHandle, running: bool) { let _ = app.emit_all( SOCKET_STATUS_EVENT, diff --git a/broadcast-client/src/models/config.ts b/broadcast-client/src/models/config.ts index 0362cb1..ab77ed3 100644 --- a/broadcast-client/src/models/config.ts +++ b/broadcast-client/src/models/config.ts @@ -93,6 +93,8 @@ export interface BroadcastConfig { segments: SegmentConfigItem[]; windowAreas: ChildWindowAreaConfig[]; subtitleAreas: SubtitleAreaConfig[]; + /** 是否已在本机自动配置过紫云 apt 源(用于检查更新时跳过重复配置) */ + zyyunAptSourceConfigured: boolean; } // 应用启动时使用的默认配置。 @@ -137,4 +139,5 @@ export const DEFAULT_BROADCAST_CONFIG: BroadcastConfig = { segments: [], windowAreas: [], subtitleAreas: [], + zyyunAptSourceConfigured: false, }; diff --git a/broadcast-client/src/services/configStore.ts b/broadcast-client/src/services/configStore.ts index 0c84055..2a9d3d7 100644 --- a/broadcast-client/src/services/configStore.ts +++ b/broadcast-client/src/services/configStore.ts @@ -269,6 +269,10 @@ function normalizeConfig(raw: unknown): BroadcastConfig { ), windowAreas: windowAreasRaw.map((item, index) => normalizeWindowArea(item, index)), subtitleAreas: subtitleAreasRaw.map((item, index) => normalizeSubtitleArea(item, index)), + zyyunAptSourceConfigured: + typeof source.zyyunAptSourceConfigured === "boolean" + ? source.zyyunAptSourceConfigured + : DEFAULT_BROADCAST_CONFIG.zyyunAptSourceConfigured, }; } diff --git a/broadcast-client/src/views/ConfigView.vue b/broadcast-client/src/views/ConfigView.vue index 50f4cf8..9ba6867 100644 --- a/broadcast-client/src/views/ConfigView.vue +++ b/broadcast-client/src/views/ConfigView.vue @@ -465,15 +465,32 @@ 退出程序 {{ saveMessage }} + + + +

{{ aptSetupHint }}

+