From e852439b6a8d168655837fcd90cfbe9b073126ad Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Tue, 19 May 2026 21:56:36 +0200 Subject: [PATCH] Fix open on Android (#921) * fix open * more * more * mroe --- .github/workflows/build.yml | 4 +- Cargo.lock | 120 ++++++++---------------- Cargo.toml | 16 +--- src/lib.rs | 4 +- src/ui.rs | 2 +- src/ui/{android_input.rs => android.rs} | 75 ++++++++++++++- src/ui/gui.rs | 30 +++--- src/ui/gui/appwindow.slint | 1 + src/ui/gui/localgame_page.slint | 3 +- src/ui/input.rs | 4 +- src/ui/retroachievements.rs | 2 +- 11 files changed, 143 insertions(+), 118 deletions(-) rename src/ui/{android_input.rs => android.rs} (59%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 151c0f4..cea23e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -267,9 +267,9 @@ jobs: cache: ${{ github.ref_type != 'tag' }} - name: Build run: | - cargo install cargo-apk2 cargo-ndk + cargo install cargo-apk cargo-ndk source <(cargo ndk-env --target ${{ matrix.target }}) - cargo apk2 build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} --lib + cargo apk build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} --lib collect-artifacts: name: Collect release artifacts diff --git a/Cargo.lock b/Cargo.lock index 69247f2..b7c2e23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -735,25 +735,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "block-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" -dependencies = [ - "objc-sys", -] - -[[package]] -name = "block2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f" -dependencies = [ - "block-sys", - "objc2 0.5.2", -] - [[package]] name = "block2" version = "0.5.1" @@ -2268,12 +2249,12 @@ dependencies = [ "futures", "jni 0.22.4", "mimalloc", + "open", "postcard", "rand 0.10.1", "regex", "reqwest", "rfd", - "robius-open", "sdl3-image-sys", "sdl3-sys", "sdl3-ttf-sys", @@ -2894,16 +2875,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icrate" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642" -dependencies = [ - "block2 0.4.0", - "objc2 0.5.2", -] - [[package]] name = "icu_collections" version = "2.2.0" @@ -3182,6 +3153,25 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -4326,6 +4316,17 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "open" +version = "5.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "openssl-probe" version = "0.2.1" @@ -4450,6 +4451,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pbkdf2" version = "0.13.0" @@ -5163,30 +5170,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "robius-android-env" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "087fcb3061ccc432658a605cb868edd44e0efb08e7a159b486f02804a7616bef" -dependencies = [ - "jni 0.21.1", - "ndk-context", -] - -[[package]] -name = "robius-open" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243e2abbc8c1ca8ddc283056d4675b67e452fd527c3741c5318642da37840ff3" -dependencies = [ - "cfg-if", - "icrate", - "jni 0.21.1", - "objc2 0.5.2", - "robius-android-env", - "windows 0.54.0", -] - [[package]] name = "rowan" version = "0.16.1" @@ -7324,16 +7307,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "windows" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" -dependencies = [ - "windows-core 0.54.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -7377,16 +7350,6 @@ dependencies = [ "windows-core 0.62.2", ] -[[package]] -name = "windows-core" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" -dependencies = [ - "windows-result 0.1.2", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -7500,15 +7463,6 @@ dependencies = [ "windows-strings 0.5.1", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index d91ff64..35f9ce3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ rust-version = "1.95.0" [features] default = ["gui"] -gui = ["dep:rfd", "dep:slint", "dep:robius-open", "dep:slint-build"] +gui = ["dep:rfd", "dep:slint", "dep:open", "dep:slint-build"] [lib] crate-type = ["cdylib", "rlib"] @@ -19,7 +19,6 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde-big-array = "0.5" sha2 = "0.11" -robius-open = { version = "0.1", default-features = false, optional = true } sdl3-sys = { version = "0.6", features = ["build-from-source-static"] } sdl3-ttf-sys = { version = "0.6", features = ["build-static-vendored", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] } sdl3-image-sys = { version = "0.6", features = ["build-static-vendored"] } @@ -43,6 +42,7 @@ discord-rich-presence = "1.1" slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "unstable-winit-030", "backend-winit", "renderer-femtovg-wgpu", "accessibility"], optional = true } rfd = {version = "0.17", optional = true } dirs = "6.0" +open = { version = "5.3", optional = true } [target.'cfg(target_os = "android")'.dependencies] slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-android-activity-06", "accessibility"], optional = true } @@ -74,22 +74,10 @@ osx_frameworks = ["/opt/homebrew/opt/molten-vk/lib/libMoltenVK.dylib"] osx_minimum_system_version = "15.0" [package.metadata.android] -use_aapt2 = true package = "io.github.gopher64.gopher64" build_targets = ["aarch64-linux-android"] resources = "data/android/res" -[[package.metadata.android.application.activity]] -name = "android.app.NativeActivity" - -[[package.metadata.android.application.activity.intent_filter]] -actions = ["android.intent.action.MAIN"] -categories = ["android.intent.category.LAUNCHER"] - -[[package.metadata.android.application.activity.meta_data]] -name = "android.app.lib_name" -value = "gopher64" - [package.metadata.android.application] icon = "@mipmap/ic_launcher" label = "Gopher64" diff --git a/src/lib.rs b/src/lib.rs index b25dfc8..8b0794f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -293,7 +293,7 @@ pub async fn run() -> std::io::Result<()> { #[cfg(feature = "gui")] { retroachievements::init_client(false, false, false); - ui::gui::app_window(); + ui::gui::app_window(false); retroachievements::shutdown_client(); } } @@ -317,5 +317,5 @@ async fn android_main(app: slint::android::AndroidApp) { std::fs::create_dir_all(dirs.data_dir.join("saves")).unwrap(); std::fs::create_dir_all(dirs.data_dir.join("states")).unwrap(); - ui::gui::app_window(); + ui::gui::app_window(true); } diff --git a/src/ui.rs b/src/ui.rs index b924542..2631d4a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,5 @@ #[cfg(target_os = "android")] -pub mod android_input; +pub mod android; pub mod audio; #[cfg(feature = "gui")] pub mod cheats; diff --git a/src/ui/android_input.rs b/src/ui/android.rs similarity index 59% rename from src/ui/android_input.rs rename to src/ui/android.rs index 44abac8..88d292e 100644 --- a/src/ui/android_input.rs +++ b/src/ui/android.rs @@ -1,5 +1,43 @@ +use jni::objects::JString; +use jni::refs::Global; use jni::{Env, JavaVM, bind_java_type}; +use crate::ui; + +bind_java_type! { + AndroidActivity => "android.app.Activity", + type_map = { + AndroidIntent => "android.content.Intent", + }, + methods { + fn start_activity(intent: AndroidIntent) -> (), + }, +} + +bind_java_type! { + AndroidIntent => "android.content.Intent", + type_map = { + AndroidUri => "android.net.Uri", + }, + fields { + #[allow(non_snake_case)] + static ACTION_VIEW: JString, + }, + constructors { + fn new(action: JString), + }, + methods { + fn set_data(uri: AndroidUri) -> AndroidIntent, + }, +} + +bind_java_type! { + AndroidUri => "android.net.Uri", + methods { + static fn parse(uri_string: JString) -> AndroidUri, + }, +} + bind_java_type! { pub AndroidInputDevice => "android.view.InputDevice", fields { @@ -30,7 +68,7 @@ pub struct ControllerInfo { /// Lists connected gamepads and joysticks using the Android framework. pub fn list_controllers() -> Vec { - let Some(app) = crate::ui::ANDROID_APP.get() else { + let Some(app) = ui::ANDROID_APP.get() else { eprintln!("Android app not initialized; cannot list controllers"); return Vec::new(); }; @@ -104,3 +142,38 @@ fn list_controllers_on_jvm(env: &mut Env<'_>) -> jni::errors::Result, + app: &slint::android::AndroidApp, + path: &str, +) -> jni::errors::Result<()> { + let activity_ptr = app.activity_as_ptr().cast(); + let activity = unsafe { env.as_cast_raw::>(&activity_ptr)? }; + + let uri_string = JString::from_str(env, path.to_string())?; + let uri = AndroidUri::parse(env, &uri_string)?; + + let action_view = AndroidIntent::ACTION_VIEW(env)?; + let intent = AndroidIntent::new(env, &action_view)?; + intent.set_data(env, &uri)?; + + activity.as_ref().start_activity(env, &intent)?; + Ok(()) +} diff --git a/src/ui/gui.rs b/src/ui/gui.rs index f521507..ed209cc 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -46,12 +46,18 @@ fn check_latest_version(weak: slint::Weak) { }); } -pub fn open_uri(path: &str) { - if let Err(e) = robius_open::Uri::new(path).open() { - eprintln!("Error opening path: {:?}", e); +#[cfg(not(target_os = "android"))] +pub fn open_uri(path: impl AsRef) { + if let Err(e) = open::that_detached(path) { + eprintln!("Error opening path: {}", e); } } +#[cfg(target_os = "android")] +pub fn open_uri(path: impl AsRef) { + ui::android::open_uri(path.as_ref().to_str().unwrap()) +} + fn run_with_path(weak: slint::Weak, path: std::path::PathBuf) { let weak2 = weak.clone(); weak.upgrade_in_event_loop(move |handle| { @@ -88,8 +94,6 @@ fn file_dropped(app: &AppWindow) { } fn local_game_window(app: &AppWindow, config: &ui::config::Config) { - let dirs = ui::get_dirs(); - app.set_recent_roms(slint::ModelRc::from(std::rc::Rc::new( slint::VecModel::from( config @@ -131,12 +135,15 @@ fn local_game_window(app: &AppWindow, config: &ui::config::Config) { .unwrap(); }); - let saves_path = dirs.data_dir.join("saves"); - app.on_saves_folder_button_clicked(move || { - open_uri(saves_path.to_str().unwrap()); - }); #[cfg(not(target_os = "android"))] - file_dropped(app); + { + let saves_path = ui::get_dirs().data_dir.join("saves"); + app.on_saves_folder_button_clicked(move || { + open_uri(&saves_path); + }); + + file_dropped(app); + } } fn input_profiles(config: &ui::config::Config) -> Vec { @@ -501,8 +508,9 @@ fn about_window(app: &AppWindow) { } } -pub fn app_window() { +pub fn app_window(is_android: bool) { let app = AppWindow::new().unwrap(); + app.set_is_android(is_android); about_window(&app); ui::retroachievements::ra_window(&app); { diff --git a/src/ui/gui/appwindow.slint b/src/ui/gui/appwindow.slint index 0b27831..3dfc7fe 100644 --- a/src/ui/gui/appwindow.slint +++ b/src/ui/gui/appwindow.slint @@ -79,6 +79,7 @@ export component AppWindow inherits Window { in-out property controller_paths <=> ControllerData.controller_paths; in-out property selected_controller <=> ControllerData.selected_controller; in-out property game_running <=> State.game_running; + in-out property is_android <=> State.is_android; in-out property recent_roms <=> State.recent_roms; in-out property cheat_game_name <=> CheatsData.cheat_game_name; in-out property cheat_game_crc <=> CheatsData.cheat_game_crc; diff --git a/src/ui/gui/localgame_page.slint b/src/ui/gui/localgame_page.slint index 4ade531..fb7e9c5 100644 --- a/src/ui/gui/localgame_page.slint +++ b/src/ui/gui/localgame_page.slint @@ -7,6 +7,7 @@ import { AboutData, NewVersionButton } from "about_page.slint"; export global State { in-out property game_running; + in-out property is_android; in-out property <[{a:string, b:string}]> recent_roms; } @@ -30,7 +31,7 @@ export component LocalGame inherits Page { } } - HorizontalBox { + if !State.is_android:HorizontalBox { alignment: center; Button { text: @tr("Open Saves Folder"); diff --git a/src/ui/input.rs b/src/ui/input.rs index bd890f1..42563af 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -362,7 +362,7 @@ pub fn get_controller_names() -> Vec { #[cfg(target_os = "android")] pub fn get_controller_names() -> Vec { - let mut controllers: Vec = ui::android_input::list_controllers() + let mut controllers: Vec = ui::android::list_controllers() .into_iter() .map(|c| c.name) .collect(); @@ -391,7 +391,7 @@ pub fn get_controller_paths() -> Vec { #[cfg(target_os = "android")] #[cfg(feature = "gui")] pub fn get_controller_paths() -> Vec { - let mut controller_paths: Vec = ui::android_input::list_controllers() + let mut controller_paths: Vec = ui::android::list_controllers() .into_iter() .map(|c| c.descriptor) .collect(); diff --git a/src/ui/retroachievements.rs b/src/ui/retroachievements.rs index a410a56..d383553 100644 --- a/src/ui/retroachievements.rs +++ b/src/ui/retroachievements.rs @@ -100,7 +100,7 @@ pub fn ra_window(app: &ui::gui::AppWindow) { }); app.on_ra_show_profile_clicked(move || { - ui::gui::open_uri(&format!( + ui::gui::open_uri(format!( "https://retroachievements.org/user/{}", retroachievements::get_username().unwrap_or_default() ));