mirror of
https://github.com/touchHLE/touchHLE.git
synced 2026-01-31 01:25:24 +01:00
Implement a simple graphical app picker
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
/target
|
||||
/vendor/boost
|
||||
/touchHLE_sandbox
|
||||
/touchHLE_apps/*.ipa
|
||||
/touchHLE_apps/*.app
|
||||
|
||||
# Xcode (integration testing app)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
30
README.md
30
README.md
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
109
src/lib.rs
109
src/lib.rs
@@ -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
|
||||
|
||||
@@ -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
1
touchHLE_apps/README.txt
Normal 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.
|
||||
Reference in New Issue
Block a user