Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions src-tauri/src/codex_desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use sha2::{Digest, Sha256};
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;

#[cfg(target_os = "windows")]
#[path = "codex_windows_launch.rs"]
pub(crate) mod windows_launch;

pub(crate) const DEFAULT_CODEX_DEBUG_PORT: u16 = 9229;
pub(crate) const CDP_HTTP_TIMEOUT: Duration = Duration::from_secs(2);
const CDP_CONNECT_TIMEOUT: Duration = Duration::from_secs(4);
Expand Down Expand Up @@ -1776,6 +1780,10 @@ pub(crate) fn launch_codex_with_debug_port(
);
}
}
#[cfg(target_os = "windows")]
if let Some(app_id) = windows_launch::resolve_app_id(executable)? {
return launch_windows_app(&app_id, debug_port);
}
let mut command = Command::new(executable);
append_codex_debug_args(&mut command, debug_port);
apply_codex_launch_timezone(&mut command, launch_timezone.as_deref());
Expand All @@ -1802,13 +1810,25 @@ fn apply_codex_launch_timezone(command: &mut Command, timezone: Option<&str>) {
}
}

#[cfg(target_os = "windows")]
pub(crate) fn launch_windows_app(app_id: &str, debug_port: u16) -> Result<(), String> {
windows_launch::activate(app_id, debug_port)?;
// The activation broker does not inherit our per-child environment. Renderer
// timezone emulation is applied separately by install_script over CDP.
crate::codex_egress_timezone::mark_codex_timezone_not_inherited();
Ok(())
}

fn codex_debug_args(debug_port: u16) -> [String; 2] {
[
format!("--remote-debugging-port={debug_port}"),
format!("--remote-allow-origins=http://127.0.0.1:{debug_port}"),
]
}

/// 为 Desktop 启动命令追加 Chromium remote-debugging 参数。
fn append_codex_debug_args(command: &mut Command, debug_port: u16) {
command
.arg(format!("--remote-debugging-port={debug_port}"))
.arg(format!(
"--remote-allow-origins=http://127.0.0.1:{debug_port}"
));
command.args(codex_debug_args(debug_port));
}

/// Windows 下查找 Codex App 主进程的脚本。
Expand Down Expand Up @@ -2665,6 +2685,21 @@ fn version_tuple_from_package_name(name: &str) -> Vec<u32> {
mod tests {
use super::*;

#[test]
fn desktop_launch_transports_share_the_requested_debug_port() {
let mut command = Command::new("codex-desktop-placeholder");
append_codex_debug_args(&mut command, 9231);
let arguments: Vec<_> = command
.get_args()
.map(|arg| arg.to_string_lossy())
.collect();
assert_eq!(arguments, codex_debug_args(9231));
assert_eq!(
codex_debug_args(9231).join(" "),
"--remote-debugging-port=9231 --remote-allow-origins=http://127.0.0.1:9231"
);
}

/// 返回当前测试平台的 Desktop 主程序文件名。
fn desktop_test_executable_name() -> &'static str {
if cfg!(target_os = "windows") {
Expand Down
67 changes: 55 additions & 12 deletions src-tauri/src/codex_egress_timezone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub enum CodexEgressMonitorState {
Checking,
Ready,
RestartRequired,
RendererOnly,
Error,
}

Expand All @@ -67,6 +68,7 @@ pub(crate) struct CodexEgressMonitorRuntime {
pub last_trigger: Option<String>,
pub last_error: Option<String>,
pub restart_required: bool,
pub process_timezone_unavailable: bool,
pub consecutive_failures: u32,
}

Expand Down Expand Up @@ -98,11 +100,16 @@ pub(crate) fn monitor_status_from_parts(
runtime: &CodexEgressMonitorRuntime,
_now: i64,
) -> CodexEgressMonitorStatus {
let state = if settings.mode != CodexEgressTimezoneMode::Auto {
let process_timezone_unavailable =
runtime.process_timezone_unavailable && settings.mode != CodexEgressTimezoneMode::Off;
let restart_required = runtime.restart_required && !process_timezone_unavailable;
let state = if process_timezone_unavailable {
CodexEgressMonitorState::RendererOnly
} else if settings.mode != CodexEgressTimezoneMode::Auto {
CodexEgressMonitorState::Disabled
} else if runtime.running {
CodexEgressMonitorState::Checking
} else if runtime.restart_required {
} else if restart_required {
CodexEgressMonitorState::RestartRequired
} else if runtime.last_error.is_some() {
CodexEgressMonitorState::Error
Expand Down Expand Up @@ -133,7 +140,7 @@ pub(crate) fn monitor_status_from_parts(
.map(|at| at.saturating_add(i64::from(interval) * 60))
},
monitor_interval_minutes: interval,
restart_required: runtime.restart_required,
restart_required,
}
}

Expand Down Expand Up @@ -816,6 +823,22 @@ pub(crate) fn notify_proxy_failure(app_type: &str, error: &ProxyError) {

pub(crate) fn mark_codex_timezone_applied() {
let applied_timezone = resolve_launch_timezone(&crate::settings::get_settings());
record_codex_launch_timezone(applied_timezone, false);
}

#[cfg(target_os = "windows")]
pub(crate) fn mark_codex_timezone_not_inherited() {
let configured = resolve_launch_timezone(&crate::settings::get_settings());
if configured.is_some() {
log::warn!("Codex MSIX activation cannot inherit TZ; process timezone remains unapplied. Renderer timezone emulation will be attempted over CDP.");
}
record_codex_launch_timezone(None, true);
}

fn record_codex_launch_timezone(
applied_timezone: Option<String>,
process_timezone_unavailable: bool,
) {
if let Err(error) = crate::settings::mutate_codex_egress_timezone(|settings| {
settings.last_applied_timezone = applied_timezone;
settings.last_applied_at = Some(Utc::now().timestamp());
Expand All @@ -826,6 +849,7 @@ pub(crate) fn mark_codex_timezone_applied() {
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
runtime.restart_required = false;
runtime.process_timezone_unavailable = process_timezone_unavailable;
drop(runtime);
emit_monitor_status(None);
}
Expand All @@ -836,16 +860,21 @@ pub(crate) fn start_automatic_monitor(app_handle: AppHandle) {
return;
}
let settings = crate::settings::get_settings().codex_egress_timezone;
if settings.mode == CodexEgressTimezoneMode::Auto
&& settings.detected_timezone.is_some()
&& settings.detected_timezone != settings.last_applied_timezone
&& crate::codex_desktop::is_codex_desktop_running()
{
monitor_runtime()
let running = crate::codex_desktop::detect_running_codex_main_process();
#[cfg(target_os = "windows")]
let packaged = running
.as_deref()
.is_some_and(crate::codex_desktop::windows_launch::is_packaged_codex);
#[cfg(not(target_os = "windows"))]
let packaged = false;
initialize_monitor_launch_state(
&mut monitor_runtime()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.restart_required = true;
}
.unwrap_or_else(|poisoned| poisoned.into_inner()),
&settings,
running.is_some(),
packaged,
);
tauri::async_runtime::spawn(async move {
let mut last_tick = Instant::now();
loop {
Expand All @@ -864,6 +893,20 @@ pub(crate) fn start_automatic_monitor(app_handle: AppHandle) {
});
}

pub(crate) fn initialize_monitor_launch_state(
runtime: &mut CodexEgressMonitorRuntime,
settings: &CodexEgressTimezoneSettings,
codex_running: bool,
packaged: bool,
) {
runtime.process_timezone_unavailable = codex_running && packaged;
runtime.restart_required = !runtime.process_timezone_unavailable
&& codex_running
&& settings.mode == CodexEgressTimezoneMode::Auto
&& settings.detected_timezone.is_some()
&& settings.detected_timezone != settings.last_applied_timezone;
}

#[tauri::command]
pub fn get_codex_egress_timezone_monitor_status() -> CodexEgressMonitorStatus {
current_monitor_status()
Expand Down
52 changes: 52 additions & 0 deletions src-tauri/src/codex_egress_timezone_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,58 @@ fn monitor_status_exposes_failure_backoff_as_the_next_check() {
assert_eq!(status.state, CodexEgressMonitorState::Error);
assert_eq!(status.next_check_at, Some(2_180));
}

#[test]
fn package_activation_reports_renderer_only_without_a_refresh_loop() {
let runtime = CodexEgressMonitorRuntime {
process_timezone_unavailable: true,
restart_required: true,
..CodexEgressMonitorRuntime::default()
};
for mode in [
CodexEgressTimezoneMode::Auto,
CodexEgressTimezoneMode::Manual,
] {
let settings = CodexEgressTimezoneSettings {
mode,
..CodexEgressTimezoneSettings::default()
};
let status = monitor_status_from_parts(&settings, &runtime, 2_010);
assert_eq!(status.state, CodexEgressMonitorState::RendererOnly);
assert!(!status.restart_required);
}
let status =
monitor_status_from_parts(&CodexEgressTimezoneSettings::default(), &runtime, 2_010);
assert_eq!(status.state, CodexEgressMonitorState::Disabled);
}

#[test]
fn restarting_ccsm_preserves_the_running_package_timezone_limitation() {
let settings = CodexEgressTimezoneSettings {
mode: CodexEgressTimezoneMode::Auto,
detected_timezone: Some("Asia/Taipei".into()),
..CodexEgressTimezoneSettings::default()
};
let mut runtime = CodexEgressMonitorRuntime::default();
super::codex_egress_timezone::initialize_monitor_launch_state(
&mut runtime,
&settings,
true,
true,
);
assert_eq!(
monitor_status_from_parts(&settings, &runtime, 0).state,
CodexEgressMonitorState::RendererOnly
);
assert!(!runtime.restart_required);
super::codex_egress_timezone::initialize_monitor_launch_state(
&mut runtime,
&settings,
true,
false,
);
assert!(runtime.restart_required);
}
#[tokio::test]
#[ignore = "Explicit network diagnostic; never run as a regular test gate"]
async fn live_trace_transport_diagnostic() {
Expand Down
Loading
Loading