Implement a simple graphical app picker

This commit is contained in:
hikari_no_yume
2023-05-05 22:29:32 +02:00
parent c7eab4add8
commit 3d3d8b02e8
7 changed files with 171 additions and 14 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
/target
/vendor/boost
/touchHLE_sandbox
/touchHLE_apps/*.ipa
/touchHLE_apps/*.app
# Xcode (integration testing app)

View File

@@ -29,6 +29,7 @@ Usability:
- touchHLE now supports real accelerometer input on devices with a built-in accelerometer, such as phones and tablets. This is only used if no game controller is connected. (@hikari-no-yume)
- The options help text is now available as a file (`OPTIONS_HELP.txt`), so you don't have to use the command line to get a list of options. (@hikari-no-yume)
- The new `--fullscreen` option lets you display an app in fullscreen rather than in a window. This is independent of the internal resolution/scale hack and supports both upscaling and downscaling. (@hikari-no-yume)
- If you run touchHLE without specifying an app, it will now display a simple graphical app picker. (@hikari-no-yume)
Other:

View File

@@ -54,15 +54,29 @@ First obtain touchHLE, either a [binary release](https://github.com/hikari-no-yu
You'll then need an app that you can run (check [the list of supported apps](APP_SUPPORT.md)). Note that the app binary must be decrypted to be usable.
There's no graphical user interface right now. If you're a Windows user and unfamiliar with the command line, these instructions may be helpful:
There's a few ways you can run an app in touchHLE.
* The easiest and fastest thing to do is to drag and drop the app's `.ipa` file or `.app` folder onto `touchHLE.exe`.
* To configure the options, edit the `touchHLE_options.txt` file. To get a list of options, look in the `OPTIONS_HELP.txt` file.
* You can also run apps directly from the command line:
1. Move the `.ipa` file or `.app` bundle to the same folder as `touchHLE.exe`.
2. Hold the Shift key and right-click on the empty space in the folder window.
3. Click “Open with PowerShell”.
4. Type `.\touchHLE.exe "YourAppNameHere.ipa"` (or `.app` as appropriate) and press Enter. If you want to specify options, add a space after the app name (outside the quotes) and then type the options, separated by spaces.
## Graphical user interface
If you'd prefer not to use the command line:
* You can put an app's `.ipa` file or `.app` bundle in the `touchHLE_apps` directory, then when you run touchHLE (on Windows: double-click on `touchHLE.exe`) you can select the app from the app picker.
* On Windows, you can also directly drag and drop an app's `.ipa` file or `.app` bundle onto `touchHLE.exe`.
To configure the options, you can then edit the `touchHLE_options.txt` file. To get a list of options, look in the `OPTIONS_HELP.txt` file.
## Command-line user interface
You can see the command-line usage by passing the `--help` flag.
If you're a Windows user and unfamiliar with the command line, these instructions may help you get started:
1. Move the `.ipa` file or `.app` bundle to the same folder as `touchHLE.exe`.
2. Hold the Shift key and right-click on the empty space in the folder window.
3. Click “Open with PowerShell”.
4. Type `.\touchHLE.exe "YourAppNameHere.ipa"` (or `.app` as appropriate) and press Enter. If you want to specify options, add a space after the app name (outside the quotes) and then type the options, separated by spaces.
## Other stuff
Currently language detection doesn't work on Windows. To change the language preference reported to the app, you can type `SET LANG=` followed by an ISO 639-1 language code, then press Enter, before running the app in the command line. Some common language codes are: `en` (English), `de` (Deutsch), `es` (español), `fr` (français), `it` (italiano) and `ja` (日本語). Bear in mind that it's the app itself that determines which languages are supported, not the emulator.

View File

@@ -10,6 +10,9 @@ cp -r ../touchHLE_fonts new_release/
pandoc -s new_release/touchHLE_fonts/README.md -o new_release/touchHLE_fonts/README.html
rm new_release/touchHLE_fonts/README.md
mkdir new_release/touchHLE_apps/
cp ../touchHLE_apps/README.txt new_release/touchHLE_apps/
sed -e 's#](APP_SUPPORT.md)#](APP_SUPPORT.html)#g' ../README.md > README-html.md
pandoc -s README-html.md -o new_release/README.html
rm README-html.md

View File

@@ -53,6 +53,7 @@ mod window;
// via re-exports.
use environment::{Environment, ThreadID};
use std::ffi::OsStr;
use std::path::PathBuf;
/// Current version. See `build.rs` for how this is generated.
@@ -62,7 +63,9 @@ const USAGE: &str = "\
Usage:
touchHLE path/to/some.app
General options:
If no app path or special option is specified, a GUI app picker is displayed.
Special options:
--help
Display this help text.
@@ -73,8 +76,99 @@ General options:
Print basic information about the app bundle without running the app.
";
fn app_picker(title: &str) -> Result<PathBuf, String> {
const APPS_DIR: &str = "touchHLE_apps";
fn enumerate_apps() -> Result<Vec<PathBuf>, std::io::Error> {
let mut app_paths = Vec::new();
for app in std::fs::read_dir(APPS_DIR)? {
let app_path = app?.path();
if app_path.extension() != Some(OsStr::new("app"))
&& app_path.extension() != Some(OsStr::new("ipa"))
{
continue;
}
if app_path.to_str().is_none() {
continue;
}
app_paths.push(app_path);
}
Ok(app_paths)
}
let app_paths: Result<Vec<PathBuf>, String> = if !std::path::Path::new(APPS_DIR).is_dir() {
Err(format!("The {} directory couldn't be found. Check you're running touchHLE from the right directory.", APPS_DIR))
} else {
enumerate_apps().map_err(|err| {
format!(
"Couldn't get list of apps in the {} directory: {}.",
APPS_DIR, err
)
})
};
let is_error = app_paths.is_err();
let (app_paths, mut message): (&[PathBuf], String) = match app_paths {
Ok(ref paths) => (
paths,
if !paths.is_empty() {
"Select an app:".to_string()
} else {
format!("No apps were found in the {} directory.", APPS_DIR)
},
),
Err(err) => (&[], err),
};
let mut app_buttons: Vec<(i32, &str)> = app_paths
.iter()
.enumerate()
.map(|(idx, path)| {
let name = path.file_name().unwrap().to_str().unwrap();
(
idx.try_into().unwrap(),
if cfg!(target_os = "windows") {
// On Windows, the buttons are too small to display a full
// app name, so it's more practical to use a short symbol
// and put the full name in the message.
// TODO: hopefully we'll have a better app picker before we
// have more than twenty supported apps? ^^;
let symbols = [
"(1)", "(2)", "(3)", "(4)", "(5)", "(6)", "(7)", "(8)", "(9)", "(10)",
"(11)", "(12)", "(13)", "(14)", "(15)", "(16)", "(17)", "(18)", "(19)",
"(20)",
];
let symbol = symbols[idx % symbols.len()];
use std::fmt::Write;
write!(message, "\n{} {}", symbol, name).unwrap();
symbol
} else {
name
},
)
})
.chain([(-1, "Exit")])
.collect();
// On Windows, the buttons are laid out from right to left, but users
// will presumably expect the opposite. Do in-place reverse so the order
// of lines in the message isn't affected.
if cfg!(target_os = "windows") {
app_buttons.reverse();
}
loop {
match window::show_message_with_options(title, &message, is_error, &app_buttons) {
Some(app_idx @ 0..) => return Ok(app_paths[app_idx as usize].clone()),
None | Some(-1) => return Err("No app was selected".to_string()),
_ => unreachable!(),
}
}
}
pub fn main<T: Iterator<Item = String>>(mut args: T) -> Result<(), String> {
echo!("touchHLE {} — https://touchhle.org/", VERSION);
let long_title = format!("touchHLE {} — https://touchhle.org/", VERSION);
echo!("{}", long_title);
echo!();
let _ = args.next().unwrap(); // skip argv[0]
@@ -107,10 +201,13 @@ pub fn main<T: Iterator<Item = String>>(mut args: T) -> Result<(), String> {
}
}
let Some(bundle_path) = bundle_path else {
echo!("{}", USAGE);
echo!("{}", options::DOCUMENTATION);
return Err("Path to bundle must be specified".to_string());
let bundle_path = if let Some(bundle_path) = bundle_path {
bundle_path
} else {
echo!(
"No app specified, opening app picker. Use the --help flag to see command-line usage."
);
app_picker(&long_title)?
};
// When PowerShell does tab-completion on a directory, for some reason it

View File

@@ -72,6 +72,45 @@ fn surface_from_image(image: &Image) -> Surface {
surface
}
/// Display a message box with custom buttons. Each button has an associated
/// ID, and the ID of the clicked button (if any) will be returned.
pub fn show_message_with_options(
title: &str,
message: &str,
is_error: bool,
options: &[(i32, &str)],
) -> Option<i32> {
use sdl2::messagebox::{
show_message_box, ButtonData, ClickedButton, MessageBoxButtonFlag, MessageBoxFlag,
};
let buttons: Vec<_> = options
.iter()
.map(|&(button_id, text)| ButtonData {
flags: MessageBoxButtonFlag::NOTHING,
button_id,
text,
})
.collect();
let clicked_button = show_message_box(
if is_error {
MessageBoxFlag::ERROR
} else {
MessageBoxFlag::INFORMATION
},
&buttons,
title,
message,
None,
None,
)
.unwrap();
match clicked_button {
ClickedButton::CloseButton => None,
ClickedButton::CustomButton(data) => Some(data.button_id),
}
}
pub struct Window {
_sdl_ctx: sdl2::Sdl,
video_ctx: sdl2::VideoSubsystem,

1
touchHLE_apps/README.txt Normal file
View File

@@ -0,0 +1 @@
If you put your .app bundles or .ipa files in this directory, they will show up in the touchHLE app picker.