173 lines
4.7 KiB
Rust
173 lines
4.7 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use argh::FromArgs;
|
|
|
|
use game::NonSteamGame;
|
|
use itertools::Itertools;
|
|
use vdf::{Dictionary, VdfValue};
|
|
|
|
mod game;
|
|
mod user;
|
|
mod vdf;
|
|
|
|
#[derive(Debug, FromArgs)]
|
|
/// Manage non-Steam games
|
|
struct Args {
|
|
#[argh(subcommand)]
|
|
cmd: Commands,
|
|
}
|
|
|
|
#[derive(Debug, FromArgs)]
|
|
#[argh(subcommand)]
|
|
enum Commands {
|
|
Add(AddArgs),
|
|
/// list all shortcuts
|
|
List(ListArgs),
|
|
}
|
|
|
|
/// add a new shortcut
|
|
#[derive(Debug, FromArgs)]
|
|
#[argh(subcommand, name = "add")]
|
|
struct AddArgs {
|
|
/// starting directory
|
|
#[argh(option, short = 'd')]
|
|
start_dir: Option<String>,
|
|
/// path to the game icon
|
|
#[argh(option, short = 'i')]
|
|
icon: Option<PathBuf>,
|
|
/// name of the app as it'll show in Steam
|
|
#[argh(option, short = 'n')]
|
|
app_name: String,
|
|
/// what command and args to run to execute the app
|
|
#[argh(positional, greedy)]
|
|
command: Vec<String>,
|
|
}
|
|
|
|
/// list all shortcuts
|
|
#[derive(Debug, FromArgs)]
|
|
#[argh(subcommand, name = "list")]
|
|
struct ListArgs {
|
|
/// print too much info about apps
|
|
#[argh(switch, short = 'v')]
|
|
verbose: bool,
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let args: Args = argh::from_env();
|
|
|
|
let shortcuts_path = {
|
|
let mut dir = user::get_userdir()?;
|
|
dir.push("config/shortcuts.vdf");
|
|
dir
|
|
};
|
|
|
|
match args.cmd {
|
|
Commands::Add(args) => add_shortcut(shortcuts_path, args),
|
|
Commands::List(args) => list_shortcuts(shortcuts_path, args),
|
|
}
|
|
}
|
|
|
|
fn list_shortcuts(
|
|
shortcuts_path: PathBuf,
|
|
args: ListArgs,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
if shortcuts_path.exists() {
|
|
for shortcut in get_shortcuts(shortcuts_path)? {
|
|
if args.verbose {
|
|
println!("App {} (ID {}):", shortcut.app_name, shortcut.app_id);
|
|
println!(" Start from: {}", shortcut.start_dir);
|
|
println!(" Target: {}", shortcut.executable);
|
|
if !shortcut.launch_options.is_empty() {
|
|
println!(" Launch Args: {}", shortcut.launch_options);
|
|
}
|
|
if !shortcut.tags.is_empty() {
|
|
println!(" Tags: {}", shortcut.tags.join(", "));
|
|
}
|
|
if !shortcut.icon.is_empty() {
|
|
println!(" Icon Path: {}", shortcut.icon);
|
|
}
|
|
} else {
|
|
println!("App ID {}: {}", shortcut.app_id, shortcut.app_name);
|
|
}
|
|
}
|
|
} else {
|
|
println!("shortcuts.vdf doesn't exist!");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn add_shortcut(shortcuts_path: PathBuf, args: AddArgs) -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut shortcuts = if shortcuts_path.exists() {
|
|
get_shortcuts(shortcuts_path.clone())?
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
for sc in &shortcuts {
|
|
println!("App {}: {}, {}", sc.app_id, sc.app_name, sc.executable);
|
|
}
|
|
|
|
let executable = prepare_arg(&args.command[0]);
|
|
let exe_opts: Option<String> = if args.command.len() > 1 {
|
|
Some(args.command[1..].to_vec().iter().map(prepare_arg).join(" "))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let mut new_game = NonSteamGame::new(args.app_name, executable, exe_opts, None);
|
|
|
|
if let Some(start_dir) = &args.start_dir {
|
|
new_game.start_dir = format!("\"{}\"", start_dir);
|
|
}
|
|
|
|
if let Some(icon) = &args.icon {
|
|
new_game.icon = icon.display().to_string();
|
|
}
|
|
|
|
shortcuts.push(new_game);
|
|
|
|
let vdf_out: vdf::Dictionary = {
|
|
let mut map = Dictionary::new();
|
|
let arr: vdf::Dictionary = shortcuts
|
|
.into_iter()
|
|
.enumerate()
|
|
.map(|(idx, game)| (idx.to_string(), VdfValue::Dict(game.into())))
|
|
.collect();
|
|
map.insert("shortcuts".to_owned(), VdfValue::Dict(arr));
|
|
map
|
|
};
|
|
|
|
vdf::write_file(vdf_out, shortcuts_path)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_shortcuts(path: PathBuf) -> Result<Vec<NonSteamGame>, String> {
|
|
let vdf_map =
|
|
vdf::read_file(&path).map_err(|x| format!("Error opening {}: {}", &path.display(), x))?;
|
|
let vec: Result<Vec<NonSteamGame>, String> = vdf_map["shortcuts"]
|
|
.clone()
|
|
.into_vec()
|
|
.map_or_else(|| Err("Malformed shortcuts.vdf".to_owned()), Ok)?
|
|
.into_iter()
|
|
.map(|x| {
|
|
if let vdf::VdfValue::Dict(dict) = x {
|
|
Ok(NonSteamGame::try_from(dict).unwrap())
|
|
} else {
|
|
Err("Couldn't parse shortcuts.vdf".to_owned())
|
|
}
|
|
})
|
|
.collect();
|
|
vec
|
|
}
|
|
|
|
fn prepare_arg<S: AsRef<str>>(input: S) -> String {
|
|
let input = input.as_ref();
|
|
if input.contains(char::is_whitespace) {
|
|
format!("\"{}\"", input)
|
|
} else {
|
|
input.to_owned()
|
|
}
|
|
}
|