Skip to content
Open
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
16 changes: 16 additions & 0 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2619,7 +2619,23 @@ impl AcceleratorApp {
}

fn ensure_launcher_viewport(&self, ctx: &egui::Context) {
// Re-issuing these commands every frame makes winit emit resize events
// every frame, which marks the UI dirty and keeps egui repainting at
// full speed. Only send them when the window size actually drifted.
let size = launcher_window_size();
let current = ctx.input(|input| {
input
.viewport()
.inner_rect
.map(|rect| rect.size())
});
let drifted = match current {
Some(current) => (current - size).length() > 1.0,
None => true,
};
if !drifted {
return;
}
ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
ctx.send_viewport_cmd(egui::ViewportCommand::MinInnerSize(size));
ctx.send_viewport_cmd(egui::ViewportCommand::MaxInnerSize(size));
Expand Down
64 changes: 46 additions & 18 deletions src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,31 +257,59 @@ pub fn is_process_running(pid: u32) -> bool {

let kill_probe = Command::new("kill").args(["-0", &pid.to_string()]).output();

match kill_probe {
Ok(output) if output.status.success() => {
// Process exists — but check if it's a zombie (already dead, just
// waiting for the parent to reap it). Treat zombies as "not running"
// so that stop/cleanup can proceed without waiting for a kill timeout.
is_unix_zombie(pid)
}
let exists = match kill_probe {
Ok(output) if output.status.success() => true,
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("Operation not permitted") {
// Can't signal the process but it exists; still check zombie state.
return !is_unix_zombie(pid);
// Can't signal the process but it exists.
true
} else {
Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "pid="])
.output()
.ok()
.map(|ps| {
ps.status.success()
&& !String::from_utf8_lossy(&ps.stdout).trim().is_empty()
})
.unwrap_or(false)
}

Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "pid="])
.output()
.ok()
.map(|ps| {
ps.status.success() && !String::from_utf8_lossy(&ps.stdout).trim().is_empty()
})
.unwrap_or(false)
}
Err(_) => false,
};
if !exists {
return false;
}

// Process exists — but check if it's a zombie (already dead, just
// waiting for the parent to reap it). Treat zombies as "not running"
// so that stop/cleanup can proceed without waiting for a kill timeout.
if is_unix_zombie(pid) {
return false;
}

unix_process_is_ours(pid)
}

/// Verify the pid actually belongs to this application. After a reboot a
/// stale pid file often points at a recycled pid owned by an unrelated
/// process (system daemons get the low pids first), which previously made
/// `state::refresh` resurrect a phantom "running" state and the GUI skip
/// the autostart.
#[cfg(unix)]
fn unix_process_is_ours(pid: u32) -> bool {
Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "comm="])
.output()
.ok()
.map(|out| {
// Linux truncates comm to 15 chars ("linuxdo-acceler"); match the
// shared prefix so macOS full paths and Linux comms both pass.
let comm = String::from_utf8_lossy(&out.stdout).to_ascii_lowercase();
comm.contains("linuxdo-acceler")
})
.unwrap_or(false)
}

/// Check if a process is a zombie (defunct) on Unix.
Expand Down
39 changes: 35 additions & 4 deletions src/runtime_log.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::io::{Read, Seek, SeekFrom, Write};
use std::time::{SystemTime, UNIX_EPOCH};

use anyhow::{Context, Result, anyhow};

use crate::paths::AppPaths;
use crate::platform::sync_user_ownership;

/// Rotate operations.log before it grows unbounded (a few months of proxy
/// traffic was observed reaching >100MB, which the UI then re-read on every
/// refresh). Keep one previous generation next to the active file.
const MAX_LOG_SIZE: u64 = 8 * 1024 * 1024;

pub(crate) fn append(paths: &AppPaths, level: &str, action: &str, message: &str) -> Result<()> {
if let Some(parent) = paths.runtime_log_path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("failed to create {}", parent.display()))?;
}

if let Ok(metadata) = fs::metadata(&paths.runtime_log_path) {
if metadata.len() >= MAX_LOG_SIZE {
let rotated = paths.runtime_log_path.with_extension("log.1");
let _ = fs::rename(&paths.runtime_log_path, rotated);
}
}

let mut file = OpenOptions::new()
.create(true)
.append(true)
Expand All @@ -39,10 +51,29 @@ pub(crate) fn read_recent_lines(paths: &AppPaths, max_lines: usize) -> Result<Ve
return Ok(Vec::new());
}

let content = fs::read_to_string(&paths.runtime_log_path)
.with_context(|| format!("failed to read {}", paths.runtime_log_path.display()))?;
let mut lines = content
// operations.log grows without bound while the proxy runs; reading the
// whole file (observed at >100MB) on every UI refresh burns noticeable
// CPU. Seek to the tail and parse only a fixed window instead.
const TAIL_WINDOW: u64 = 64 * 1024;

let mut file = fs::File::open(&paths.runtime_log_path)
.with_context(|| format!("failed to open {}", paths.runtime_log_path.display()))?;
let size = file.metadata()?.len();
let window = size.min(TAIL_WINDOW);
let start = size - window;

file.seek(SeekFrom::Start(start))?;
let mut buf = vec![0u8; window as usize];
file.read_exact(&mut buf)
.with_context(|| format!("failed to read tail of {}", paths.runtime_log_path.display()))?;

let text = String::from_utf8_lossy(&buf);
// When the window starts mid-file the first line is likely a partial
// entry left over at the seek boundary, so skip it.
let skip_first = usize::from(start > 0);
let mut lines = text
.lines()
.skip(skip_first)
.map(str::trim)
.filter(|line| !line.is_empty())
.map(ToOwned::to_owned)
Expand Down
12 changes: 12 additions & 0 deletions src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,18 @@ pub fn run_privileged_helper(config_path: Option<PathBuf>) -> Result<()> {
"特权辅助守护进程启动,开始监听 socket",
);

// After a reboot the previous session's state files survive. This helper
// has not started any proxy yet, so a leftover "running" state is always
// stale; reconcile it before the GUI reads it, otherwise the GUI's
// autostart sees a phantom running state and never starts acceleration.
if let Err(error) = state::refresh(&paths) {
log_error(
&paths,
"privileged-helper",
&format!("清理残留服务状态失败: {error:#}"),
);
}

// Shared state: holds the running proxy's shutdown channel and join handle.
let proxy_state: Arc<Mutex<Option<ProxyHandle>>> = Arc::new(Mutex::new(None));
let socket = helper_ipc::socket_path();
Expand Down