Skip to content

Commit f21fb3b

Browse files
committed
Better installer WIP #2
1 parent 9ee6982 commit f21fb3b

File tree

4 files changed

+182
-140
lines changed

4 files changed

+182
-140
lines changed

src/cli.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct Cli {
2121
pub update_rate: u64,
2222

2323
/// Path to Wallpaper Engine executable
24-
#[arg(long, default_value = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\wallpaper_engine")]
24+
#[arg(short='w', long, default_value = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\wallpaper_engine")]
2525
pub wallpaper_engine_path: String,
2626

2727
/// Use the 64-bit version of Wallpaper Engine (wallpaper64.exe), otherwise use 32-bit (wallpaper32.exe)
@@ -42,19 +42,18 @@ pub struct Cli {
4242

4343
/// Launch interactive installer (TUI)
4444
#[arg(long)]
45-
pub install: bool,
45+
pub install_tui: bool,
4646

47-
// Hidden/internal fields populated by the TUI
48-
/// Internal: directory chosen by TUI for installation
49-
#[arg(skip)]
47+
/// Install the executable into the specified directory and exit (non-interactive path)
48+
#[arg(long = "install-dir")]
5049
pub install_dir: Option<String>,
5150

52-
/// Internal: install as Windows Service (chosen by TUI)
53-
#[arg(skip)]
51+
/// Add a Windows service to run this program with the specified flags and exit (non-interactive path)
52+
#[arg(long = "add-startup-service")]
5453
pub add_startup_service: bool,
5554

56-
/// Internal: install as Scheduled Task (chosen by TUI)
57-
#[arg(skip)]
55+
/// Add a Windows Scheduled Task to run this program at user logon and exit (non-interactive path)
56+
#[arg(long = "add-startup-task")]
5857
pub add_startup_task: bool,
5958
}
6059

src/install/mod.rs

Lines changed: 5 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -52,79 +52,7 @@ pub fn exit_blocking(code: i32) {
5252
std::process::exit(code);
5353
}
5454

55-
fn kill_other_instances() -> Result<(), Box<dyn std::error::Error>> {
56-
// Determine the image name of the current executable
57-
let this_exe = env::current_exe()?;
58-
let image_name = this_exe.file_name()
59-
.and_then(|s| s.to_str())
60-
.ok_or("Failed to determine current executable name")?
61-
.to_string();
62-
63-
let this_pid = std::process::id();
64-
info!("Attempting to terminate other running instances of {}...", image_name);
65-
66-
// Query tasklist for processes with the same image name, in CSV for easier parsing -- somewhat hacky but works
67-
let output = Command::new("tasklist")
68-
.args(["/FI", &format!("IMAGENAME eq {}", image_name), "/FO", "CSV"])
69-
.output()?;
70-
71-
if !output.status.success() {
72-
let stderr = String::from_utf8_lossy(&output.stderr);
73-
warn!("tasklist failed while searching for other instances: {}", stderr);
74-
return Ok(());
75-
}
76-
77-
let stdout = String::from_utf8_lossy(&output.stdout);
78-
let mut killed_any = false;
79-
80-
for (i, line) in stdout.lines().enumerate() {
81-
if i == 0 { continue; } // skip header
82-
let trimmed = line.trim();
83-
if trimmed.is_empty() { continue; }
84-
// CSV fields quoted, expect: "Image Name","PID","Session Name","Session#","Mem Usage"
85-
// We'll split commas and trim surrounding quotes.
86-
let parts: Vec<String> = trimmed.split(',')
87-
.map(|s| s.trim().trim_matches('"').to_string())
88-
.collect();
89-
if parts.len() < 2 { continue; }
90-
let pid_str = &parts[1];
91-
if let Ok(pid) = pid_str.parse::<u32>() {
92-
if pid == this_pid {
93-
continue; // skip self
94-
}
95-
// Attempt to kill this PID
96-
let kill = Command::new("taskkill").args(["/PID", &pid.to_string(), "/F"]).output();
97-
match kill {
98-
Ok(res) => {
99-
if res.status.success() {
100-
info!("Terminated process PID {} ({})", pid, image_name);
101-
killed_any = true;
102-
} else {
103-
let stderr = String::from_utf8_lossy(&res.stderr);
104-
// If the process exited between list and kill, ignore the error.
105-
warn!("Failed to terminate PID {}: {}", pid, stderr);
106-
}
107-
}
108-
Err(e) => warn!("taskkill failed for PID {}: {}", pid, e),
109-
}
110-
}
111-
}
112-
113-
if killed_any {
114-
// Allow a brief moment for the OS to release file handles
115-
thread::sleep(Duration::from_millis(500));
116-
}
117-
118-
Ok(())
119-
}
120-
121-
12255
pub fn handle_installation(args: &Cli) {
123-
if let Err(e) = kill_other_instances() {
124-
warn!("Failed to terminate other instances automatically: {}", e);
125-
warn!("Continuing with installation; this may fail if files are locked.");
126-
}
127-
12856
let mut install_path = None;
12957
if let Some(path_str) = &args.install_dir {
13058
info!("Starting installation...");
@@ -156,7 +84,7 @@ pub fn handle_installation(args: &Cli) {
15684
},
15785
Err(e) => {
15886
error!("Failed to set up startup service: {:?}", e);
159-
println!("\n\n\tSetup failed! Please try again.\n");
87+
println!("\n\n\t• Setup failed! Please close all OS windows (including Task Manager, and Services) and try again.\n");
16088
exit_blocking(1);
16189
}
16290
}
@@ -165,7 +93,7 @@ pub fn handle_installation(args: &Cli) {
16593
if args.add_startup_task {
16694
let exe_path = resolve_exe_path(install_path);
16795
let mut task_args = filtered_passthrough_args();
168-
// Always add -silent for scheduled task
96+
// Always add `-silent` for scheduled tasks
16997
task_args.push(OsString::from("-silent"));
17098

17199
match setup_startup_scheduled_task(&exe_path, task_args) {
@@ -349,7 +277,7 @@ fn resolve_exe_path(install_path: Option<PathBuf>) -> PathBuf {
349277
fn filtered_passthrough_args() -> Vec<OsString> {
350278
// List of parameters to skip when passing through to the service/task (second arg is whether it takes a value)
351279
let skip = [
352-
(name_of!(install in Cli), false),
280+
(name_of!(install_tui in Cli), false),
353281
(name_of!(install_dir in Cli), true),
354282
(name_of!(add_startup_service in Cli), false),
355283
(name_of!(add_startup_task in Cli), false),
@@ -399,7 +327,8 @@ fn remove_existing_service_if_any(manager: &ServiceManager, name: &str, wait_aft
399327
info!("Service '{}' already exists. Trying to delete it.", name);
400328
let _ = service.stop();
401329
if let Err(e) = service.delete() {
402-
error!("Failed to delete service '{}'. You might need to close Services and Task Manager windows and/or log out from or restart your computer to proceed", name);
330+
error!("Failed to delete service '{}'.", name);
331+
error!("You might need to close Services and Task Manager windows and/or log out from or restart your computer to proceed");
403332
error!("Error: {}", e);
404333
exit_blocking(2);
405334
} else {

src/install/tui.rs

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,61 @@ fn validate_update_rate(s: &str) -> std::result::Result<(), String> {
6565
}
6666
}
6767

68-
fn validate_we_path(s: &str, require_64: bool) -> std::result::Result<(), String> {
69-
let p = Path::new(s.trim());
70-
if !p.exists() || !p.is_dir() { return Err("Path must exist and be a folder".into()); }
71-
let ok = if require_64 {
72-
p.join("wallpaper64.exe").exists()
73-
} else {
74-
p.join("wallpaper32.exe").exists() || p.join("wallpaper64.exe").exists()
75-
};
76-
if !ok {
77-
return Err(if require_64 {
78-
"Could not find wallpaper64.exe in this folder".into()
79-
} else {
80-
"Could not find wallpaper32.exe or wallpaper64.exe in this folder".into()
81-
});
68+
// fn validate_we_path(s: &str, require_64: bool) -> std::result::Result<(), String> {
69+
// let p = Path::new(s.trim());
70+
// if !p.exists() || !p.is_dir() { return Err("Path must exist and be a folder".into()); }
71+
// let ok = if require_64 {
72+
// p.join("wallpaper64.exe").exists()
73+
// } else {
74+
// p.join("wallpaper32.exe").exists() || p.join("wallpaper64.exe").exists()
75+
// };
76+
// if !ok {
77+
// return Err(if require_64 {
78+
// "Could not find wallpaper64.exe in this folder".into()
79+
// } else {
80+
// "Could not find wallpaper32.exe or wallpaper64.exe in this folder".into()
81+
// });
82+
// }
83+
// Ok(())
84+
// }
85+
86+
pub fn run_install_tui_and_relaunch(base: Cli) -> Result<()> {
87+
// Run the wizard to collect all settings
88+
let new_cli = run_install_tui(base)?;
89+
90+
// Build argv and relaunch current executable
91+
let exe = std::env::current_exe()?;
92+
let mut args: Vec<std::ffi::OsString> = Vec::new();
93+
94+
if let Some(dir) = &new_cli.install_dir {
95+
args.push("--install-dir".into());
96+
args.push(dir.clone().into());
8297
}
98+
if new_cli.add_startup_service { args.push("--add-startup-service".into()); }
99+
if new_cli.add_startup_task { args.push("--add-startup-task".into()); }
100+
101+
// runtime flags
102+
args.push("-m".into());
103+
args.push(new_cli.monitors.clone().into());
104+
105+
if let Some(t) = new_cli.threshold { args.push("-t".into());args.push(t.to_string().into()); }
106+
if new_cli.per_monitor { args.push("-p".into()); }
107+
108+
args.push("-u".into());
109+
args.push(new_cli.update_rate.to_string().into());
110+
111+
// TODO: This won't work with wallpaperservice32.exe
112+
// if !new_cli.wallpaper_engine_path.is_empty() {
113+
// args.push("-w".into());
114+
// args.push(format!("'{}'", new_cli.wallpaper_engine_path).into());
115+
// }
116+
117+
if new_cli.bit64 { args.push("--64bit".into()); }
118+
119+
if new_cli.disable_sentry { args.push("--disable-sentry".into()); }
120+
if let Some(dsn) = &new_cli.sentry_dsn { args.push("--sentry-dsn".into()); args.push(dsn.clone().into()); }
121+
std::process::Command::new(exe).args(args).spawn()?;
122+
83123
Ok(())
84124
}
85125

@@ -161,7 +201,7 @@ pub fn run_install_tui(mut base: Cli) -> Result<Cli> {
161201
.interact()?;
162202

163203
if advanced {
164-
println!("\n• Update rate: How often to recalculate visibility (in milliseconds). Lower = more responsive, higher CPU. Suggest 200–5000.");
204+
println!("\n• Update rate: Minimum time between visibility recalculations (in milliseconds).\n Lower = more responsive, higher CPU and more frequent pause/resume for Wallpaper Engine.");
165205
let upd_str: String = Input::with_theme(&theme)
166206
.with_prompt("Update rate in ms (100–60000)")
167207
.default(base.update_rate.to_string())
@@ -174,12 +214,13 @@ pub fn run_install_tui(mut base: Cli) -> Result<Cli> {
174214
.default(base.bit64)
175215
.interact()?;
176216

177-
println!("\n• Wallpaper Engine folder:");
178-
base.wallpaper_engine_path = Input::with_theme(&theme)
179-
.with_prompt("Wallpaper Engine install path")
180-
.default(base.wallpaper_engine_path.clone())
181-
.validate_with(|s: &String| validate_we_path(s, base.bit64))
182-
.interact_text()?;
217+
// TODO: See TODO note in run_install_tui_and_relaunch
218+
// println!("\n• Wallpaper Engine folder:");
219+
// base.wallpaper_engine_path = Input::with_theme(&theme)
220+
// .with_prompt("Wallpaper Engine install path")
221+
// .default(base.wallpaper_engine_path.clone())
222+
// .validate_with(|s: &String| validate_we_path(s, base.bit64))
223+
// .interact_text()?;
183224
}
184225

185226
// Summary & confirmation
@@ -199,7 +240,7 @@ pub fn run_install_tui(mut base: Cli) -> Result<Cli> {
199240
.interact()?;
200241
if !proceed { return Err(anyhow!("User cancelled")); }
201242

202-
// Fill internal fields consumed by installer
243+
// Fill internal fields consumed by the installer
203244
base.install_dir = Some(install_dir);
204245
base.add_startup_service = install_as_service;
205246
base.add_startup_task = install_as_task;

0 commit comments

Comments
 (0)