Skip to content

Commit

Permalink
Merge pull request #8 from rageagainsthepc/interactive-mode
Browse files Browse the repository at this point in the history
Add interactive mode
  • Loading branch information
rageagainsthepc authored Jan 12, 2022
2 parents bdcd861 + 9afafbe commit 631da88
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 87 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
[package]
name = "ampsim-launcher"
version = "0.2.0"
version = "0.2.1"
edition = "2021"

[dependencies]
stable-eyre = "0.2.2"
mslnk = "0.1.7"
clap = "3.0.1"
path-absolutize = "3.0.5"
sysinfo = "0.22.4"
lazy_static = "1.4.0"
Inflector = "0.11.4"
quoted-string = "0.6.1"

[dependencies.windows]
version = "0.29.0"
Expand Down
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,29 @@ As soon as the application is closed the original power plan will be restored.

## Usage

- Place the binary somewhere on your system (e.g. `C:\Program Files\AmpSimLauncher`).
- Launch a terminal window (e.g. `powershell.exe`) and navigate to the directory where the *AmpSim Launcher* binary is stored. Alternatively, you can type `powershell` into the address bar of your explorer window which will start a `powershell` instance in the current directory.
- Run `ampsim-launcher.exe link '<path\to\your\ampsim.exe>' '<C:\Users\YOUR USER\Desktop\Shortcut Name>'` for each individual amp emulator. This will create a shortcut which launches *AmpSim Launcher* with the correct parameters.
This tool offers an interactive as well as a CLI mode. If you are not familiar using consoles (e.g. `powershell.exe`, `cmd.exe`)
you might be better off using the interactive mode.

You could also launch a program directly by running `ampsim-launcher.exe launch <path\to\your\ampsim.exe>`.
### Interactive

- Place the binary somewhere on your system (e.g. `C:\Program Files\AmpSimLauncher\ampsim-launcher.exe`).
- Start the binary via double click. A console window will open in order to guide you through the process of creating a shortcut.
The shortcut will be configured to execute *AmpSim Launcher* with the correct parameters for your specific target program.
- Repeat the shortcut creation step for each program that should be launched via *AmpSim Launcher*.

#### Tips

- Dragging and dropping binaries into the console window will automatically enter their respective file path.
- If you decide to relocate the *AmpSim Launcher* binary you will have to re-generate all shortcuts.

### CLI

Advanced users may also use this tool from a console window. Currently, there are two subcommands available:

- `launch` for executing a target process
- `link` for creating a shortcut

You can get a more detailed description by running `ampsim-launcher.exe --help`.

## Building

Expand Down
80 changes: 80 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::path::Path;

use crate::errorbox;
use crate::launch::launch;
use crate::link::make_link;
use clap::{arg, App, AppSettings, ArgMatches};
use path_absolutize::Absolutize;
use stable_eyre::eyre::{bail, eyre};
use stable_eyre::Result;

fn run_subcommand(matches: &ArgMatches) -> Result<()> {
match matches.subcommand() {
Some(("launch", sub_matches)) => {
let program = Path::new(
sub_matches
.value_of("PROGRAM")
.ok_or_else(|| eyre!("PRORGAM is required"))?,
)
.absolutize()?;

if !program.exists() {
bail!("Program must exist")
}

launch(&program, matches.is_present("background"))?;
}
Some(("link", sub_matches)) => {
let target = Path::new(
sub_matches
.value_of("TARGET")
.ok_or_else(|| eyre!("TARGET is required"))?,
)
.absolutize()?;
let location = Path::new(
sub_matches
.value_of("LOCATION")
.ok_or_else(|| eyre!("LOCATION is required"))?,
)
.absolutize()?;

if !target.exists() {
bail!("Target must exist")
}

make_link(&target, &location)?;
}
_ => unreachable!(),
}

Ok(())
}

pub(crate) fn run() -> Result<(), stable_eyre::Report> {
let matches = App::new("ampsim_starter")
.about("A tool for launching programs with optimized performance")
.arg(arg!(-b --background "Activate background mode (errors as message boxes, hidden console window)"))
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
App::new("launch")
.about("Launch a process with high performance settings")
.arg(arg!(<PROGRAM> "The program to launch"))
.setting(AppSettings::ArgRequiredElseHelp),
)
.subcommand(
App::new("link")
.about("Create a shortcut for a given program")
.arg(arg!(<TARGET> "Location of the target program"))
.arg(arg!(<LOCATION> "Location of the shortcut"))
.setting(AppSettings::ArgRequiredElseHelp),
)
.get_matches();
let result = run_subcommand(&matches);
if matches.is_present("background") {
match result {
Ok(()) => (),
Err(ref e) => errorbox::show(format!("{:#}", e).as_str()),
}
}
result
}
18 changes: 18 additions & 0 deletions src/errorbox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use inflector::Inflector;
use lazy_static::lazy_static;
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_ICONSTOP};

lazy_static! {
static ref EXE_NAME: String = std::env::current_exe()
.ok()
.and_then(|p| p.file_stem().map(|s| s.to_os_string()))
.and_then(|s| s.into_string().ok())
.unwrap_or_else(|| "AmpSim Launcher".to_string())
.to_title_case();
}

pub(crate) fn show(message: &str) {
unsafe {
MessageBoxW(None, message, EXE_NAME.as_str(), MB_ICONSTOP);
}
}
97 changes: 97 additions & 0 deletions src/interactive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use std::{
env, io,
path::{Path, PathBuf},
};

use path_absolutize::Absolutize;
use stable_eyre::Result;

use crate::{errorbox, link::make_link, pathext::PathExt};

fn get_target_path() -> Result<PathBuf> {
let target_path = loop {
let mut input_buffer = String::new();
std::io::stdin().read_line(&mut input_buffer)?;
let mut line = input_buffer.lines().next().unwrap();
line = quoted_string::strip_dquotes(line).unwrap_or(line);

if line.is_empty() {
println!("Input required. Enter the path of a target executable:");
} else {
match Path::new(line).is_file_ext() {
Ok(is_file) => {
if is_file {
break PathBuf::from(line);
}
println!("Target does not exist or is not a file.");
}
Err(e) => println!("{}", e.to_string()),
}
}
println!("Enter the path of a target executable:");
};
Ok(target_path)
}

fn get_shortcut_path(target_path: &Path) -> Result<PathBuf> {
let default_shortcut_path = Path::new(&env::var("USERPROFILE")?).join("Desktop").join(
Path::new(target_path)
.with_extension("lnk")
.file_name()
.unwrap(),
);
println!(
"Enter the path where the shortcut will be created (default: {}):",
default_shortcut_path.to_string_lossy()
);

let shortcut_path = loop {
let mut input_buffer = String::new();
std::io::stdin().read_line(&mut input_buffer)?;
let mut line = input_buffer.lines().next().unwrap();
line = quoted_string::strip_dquotes(line).unwrap_or(line);

if line.is_empty() {
break None;
} else if let Some(p) = Path::new(line).absolutize()?.parent() {
match p.is_dir_ext() {
Ok(is_dir) => {
if is_dir {
break Some(PathBuf::from(line));
}
println!("Parent directory is not a directory.");
}
Err(e) => println!("{}", e.to_string()),
}
}
println!("Enter a valid shortcut path:");
};

Ok(shortcut_path.unwrap_or(default_shortcut_path))
}

fn create_shortcut() -> Result<()> {
println!("Shortcut Creation Mode");
println!("Enter the path of a target executable:");

let target_path = get_target_path()?;
let shortcut_path = get_shortcut_path(&target_path)?;

make_link(target_path.as_path(), shortcut_path.as_path())?;

println!("Shortcut created sucessfully. Press Enter to terminate...");
let mut finish = String::new();
io::stdin().read_line(&mut finish)?;

Ok(())
}

pub(crate) fn run() -> Result<()> {
let result = create_shortcut();

if let Err(ref e) = result {
errorbox::show(format!("{:#}", e).as_str())
}

result
}
102 changes: 20 additions & 82 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,93 +1,31 @@
mod cli;
mod errorbox;
mod interactive;
mod launch;
mod link;
mod pathext;

use std::path::Path;

use crate::launch::launch;
use crate::link::make_link;
use clap::{arg, App, AppSettings, ArgMatches};
use path_absolutize::Absolutize;
use stable_eyre::eyre::{bail, eyre};
use stable_eyre::Result;
use windows::Win32::UI::WindowsAndMessaging::{MessageBoxW, MB_ICONSTOP};

fn display_error_box(message: &str) {
unsafe {
MessageBoxW(None, message, "AmpSim Launcher", MB_ICONSTOP);
}
}

fn run_subcommand(matches: &ArgMatches) -> Result<()> {
match matches.subcommand() {
Some(("launch", sub_matches)) => {
let program = Path::new(
sub_matches
.value_of("PROGRAM")
.ok_or_else(|| eyre!("PRORGAM is required"))?,
)
.absolutize()?;

if !program.exists() {
bail!("Program must exist")
}

launch(&program, matches.is_present("background"))?;
}
Some(("link", sub_matches)) => {
let target = Path::new(
sub_matches
.value_of("TARGET")
.ok_or_else(|| eyre!("TARGET is required"))?,
)
.absolutize()?;
let location = Path::new(
sub_matches
.value_of("LOCATION")
.ok_or_else(|| eyre!("LOCATION is required"))?,
)
.absolutize()?;

if !target.exists() {
bail!("Target must exist")
}

make_link(&target, &location)?;
}
_ => unreachable!(),
}

Ok(())
use sysinfo::{ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};

fn parent_name() -> String {
let system =
System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new()));
let parent_id = system
.process(std::process::id().try_into().unwrap())
.unwrap()
.parent()
.unwrap();
let parent_name = system.process(parent_id).unwrap().name();
parent_name.to_owned()
}

fn main() -> Result<()> {
stable_eyre::install()?;

let matches = App::new("ampsim_starter")
.about("A tool for launching programs with optimized performance")
.arg(arg!(-b --background "Activate background mode (errors as message boxes, hidden console window)"))
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
App::new("launch")
.about("Launch a process with high performance settings")
.arg(arg!(<PROGRAM> "The program to launch"))
.setting(AppSettings::ArgRequiredElseHelp),
)
.subcommand(
App::new("link")
.about("Create a shortcut for a given program")
.arg(arg!(<TARGET> "Location of the target program"))
.arg(arg!(<LOCATION> "Location of the shortcut"))
.setting(AppSettings::ArgRequiredElseHelp),
)
.get_matches();

let result = run_subcommand(&matches);
if matches.is_present("background") {
match result {
Ok(()) => (),
Err(ref e) => display_error_box(format!("{:#}", e).as_str()),
}
if std::env::args().len() < 2 && parent_name() == "explorer.exe" {
interactive::run()
} else {
cli::run()
}

result
}
20 changes: 20 additions & 0 deletions src/pathext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::{fs, io, path::Path, result::Result};

pub(crate) trait PathExt {
/// Like `std::path::Path::is_dir()` but returns a result object in order to
/// be able to differentiate between an actual result and io errors.
fn is_dir_ext(&self) -> Result<bool, io::Error>;
/// Like `std::path::Path::is_file()` but returns a result object in order to
/// be able to differentiate between an actual result and io errors.
fn is_file_ext(&self) -> Result<bool, io::Error>;
}

impl PathExt for Path {
fn is_dir_ext(&self) -> Result<bool, io::Error> {
fs::metadata(self).map(|m| m.is_dir())
}

fn is_file_ext(&self) -> Result<bool, io::Error> {
fs::metadata(self).map(|m| m.is_file())
}
}

0 comments on commit 631da88

Please sign in to comment.