From 0e925fd8f0ec24155de928628974c50aaf9f8bb7 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Wed, 10 Aug 2022 16:30:07 -0300 Subject: [PATCH] feat(examples): prepare API example for mobile --- examples/api/src-tauri/.gitignore | 4 + examples/api/src-tauri/Cargo.lock | 65 ++++++ examples/api/src-tauri/Cargo.toml | 16 ++ examples/api/src-tauri/mobile.toml | 8 + examples/api/src-tauri/src/desktop.rs | 160 +++++++++++++++ examples/api/src-tauri/src/lib.rs | 159 +++++++++++++++ examples/api/src-tauri/src/main.rs | 273 +------------------------- examples/api/src-tauri/src/mobile.rs | 47 +++++ 8 files changed, 462 insertions(+), 270 deletions(-) create mode 100644 examples/api/src-tauri/mobile.toml create mode 100644 examples/api/src-tauri/src/desktop.rs create mode 100644 examples/api/src-tauri/src/lib.rs create mode 100644 examples/api/src-tauri/src/mobile.rs diff --git a/examples/api/src-tauri/.gitignore b/examples/api/src-tauri/.gitignore index aba21e242..e5f47c4d3 100644 --- a/examples/api/src-tauri/.gitignore +++ b/examples/api/src-tauri/.gitignore @@ -1,3 +1,7 @@ # Generated by Cargo # will have compiled files and executables /target/ + +# cargo-mobile +.cargo/ +/gen diff --git a/examples/api/src-tauri/Cargo.lock b/examples/api/src-tauri/Cargo.lock index 71bf38485..d9916320f 100644 --- a/examples/api/src-tauri/Cargo.lock +++ b/examples/api/src-tauri/Cargo.lock @@ -73,6 +73,24 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android_log-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" + +[[package]] +name = "android_logger" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec2333c185d826313162cee39d3fcc6a84ba08114a839bebf53b961e7e75773" +dependencies = [ + "android_log-sys", + "env_logger 0.7.1", + "lazy_static", + "log", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -92,10 +110,17 @@ checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" name = "api" version = "0.1.0" dependencies = [ + "android_logger", + "env_logger 0.9.0", + "jni 0.19.0", + "log", + "mobile-entry-point", + "paste", "serde", "serde_json", "tauri", "tauri-build", + "tauri-runtime-wry", "tiny_http", "window-shadows", "window-vibrancy", @@ -722,6 +747,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -1307,6 +1355,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.20" @@ -1768,6 +1822,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mobile-entry-point" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bef5a90018326583471cccca10424d7b3e770397b02f03276543cbb9b6a1a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "multipart" version = "0.18.0" diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 07bc72043..baae9446c 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" rust-version = "1.57" license = "Apache-2.0 OR MIT" +[lib] +crate-type = ["staticlib", "cdylib", "rlib"] + [build-dependencies] tauri-build = { path = "../../../core/tauri-build", features = ["isolation", "codegen"] } @@ -35,6 +38,19 @@ features = [ window-vibrancy = "0.2" window-shadows= "0.2" +[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies] +log = "0.4" +tauri-runtime-wry = { path = "../../../core/tauri-runtime-wry/" } + +[target.'cfg(target_os = "android")'.dependencies] +android_logger = "0.9.0" +jni = "0.19.0" +paste = "1.0" + +[target.'cfg(target_os = "ios")'.dependencies] +mobile-entry-point = "0.1.0" +env_logger = "0.9.0" + [features] default = [ "custom-protocol" ] custom-protocol = [ "tauri/custom-protocol" ] diff --git a/examples/api/src-tauri/mobile.toml b/examples/api/src-tauri/mobile.toml new file mode 100644 index 000000000..c74c1c2ac --- /dev/null +++ b/examples/api/src-tauri/mobile.toml @@ -0,0 +1,8 @@ +[app] +name = "api" +stylized-name = "Tauri API" +domain = "com.tauri" +template-pack = "tauri" + +[apple] +development-team = "0" diff --git a/examples/api/src-tauri/src/desktop.rs b/examples/api/src-tauri/src/desktop.rs new file mode 100644 index 000000000..12a6ca7d1 --- /dev/null +++ b/examples/api/src-tauri/src/desktop.rs @@ -0,0 +1,160 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use tauri::{ + api::dialog::ask, CustomMenuItem, GlobalShortcutManager, Manager, RunEvent, SystemTray, + SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowEvent, WindowUrl, +}; + +pub fn main() { + api::AppBuilder::new() + .setup(|app| { + create_tray(app)?; + Ok(()) + }) + .on_event(|app_handle, e| match e { + // Application is ready (triggered only once) + RunEvent::Ready => { + let app_handle = app_handle.clone(); + app_handle + .global_shortcut_manager() + .register("CmdOrCtrl+1", move || { + let app_handle = app_handle.clone(); + let window = app_handle.get_window("main").unwrap(); + window.set_title("New title!").unwrap(); + }) + .unwrap(); + } + + // Triggered when a window is trying to close + RunEvent::WindowEvent { + label, + event: WindowEvent::CloseRequested { api, .. }, + .. + } => { + // for other windows, we handle it in JS + if label == "main" { + let app_handle = app_handle.clone(); + let window = app_handle.get_window(&label).unwrap(); + // use the exposed close api, and prevent the event loop to close + api.prevent_close(); + // ask the user if he wants to quit + ask( + Some(&window), + "Tauri API", + "Are you sure that you want to close this window?", + move |answer| { + if answer { + // .close() cannot be called on the main thread + std::thread::spawn(move || { + app_handle.get_window(&label).unwrap().close().unwrap(); + }); + } + }, + ); + } + } + _ => (), + }) + .run() +} + +fn create_tray(app: &tauri::App) -> tauri::Result<()> { + let tray_menu1 = SystemTrayMenu::new() + .add_item(CustomMenuItem::new("toggle", "Toggle")) + .add_item(CustomMenuItem::new("new", "New window")) + .add_item(CustomMenuItem::new("icon_1", "Tray Icon 1")) + .add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")) + .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) + .add_item(CustomMenuItem::new("exit_app", "Quit")) + .add_item(CustomMenuItem::new("destroy", "Destroy")); + let tray_menu2 = SystemTrayMenu::new() + .add_item(CustomMenuItem::new("toggle", "Toggle")) + .add_item(CustomMenuItem::new("new", "New window")) + .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) + .add_item(CustomMenuItem::new("exit_app", "Quit")) + .add_item(CustomMenuItem::new("destroy", "Destroy")); + let is_menu1 = AtomicBool::new(true); + + let handle = app.handle(); + let tray_id = "my-tray".to_string(); + SystemTray::new() + .with_id(&tray_id) + .with_menu(tray_menu1.clone()) + .on_event(move |event| { + let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap(); + match event { + SystemTrayEvent::LeftClick { + position: _, + size: _, + .. + } => { + let window = handle.get_window("main").unwrap(); + window.show().unwrap(); + window.set_focus().unwrap(); + } + SystemTrayEvent::MenuItemClick { id, .. } => { + let item_handle = tray_handle.get_item(&id); + match id.as_str() { + "exit_app" => { + // exit the app + handle.exit(0); + } + "destroy" => { + tray_handle.destroy().unwrap(); + } + "toggle" => { + let window = handle.get_window("main").unwrap(); + let new_title = if window.is_visible().unwrap() { + window.hide().unwrap(); + "Show" + } else { + window.show().unwrap(); + "Hide" + }; + item_handle.set_title(new_title).unwrap(); + } + "new" => { + WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into())) + .title("Tauri") + .build() + .unwrap(); + } + "icon_1" => { + #[cfg(target_os = "macos")] + tray_handle.set_icon_as_template(true).unwrap(); + + tray_handle + .set_icon(tauri::Icon::Raw( + include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(), + )) + .unwrap(); + } + "icon_2" => { + #[cfg(target_os = "macos")] + tray_handle.set_icon_as_template(true).unwrap(); + + tray_handle + .set_icon(tauri::Icon::Raw( + include_bytes!("../../../.icons/icon.ico").to_vec(), + )) + .unwrap(); + } + "switch_menu" => { + let flag = is_menu1.load(Ordering::Relaxed); + tray_handle + .set_menu(if flag { + tray_menu2.clone() + } else { + tray_menu1.clone() + }) + .unwrap(); + is_menu1.store(!flag, Ordering::Relaxed); + } + _ => {} + } + } + _ => {} + } + }) + .build(app) + .map(|_| ()) +} diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs new file mode 100644 index 000000000..735e97dd2 --- /dev/null +++ b/examples/api/src-tauri/src/lib.rs @@ -0,0 +1,159 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +mod cmd; +#[cfg(mobile)] +mod mobile; +#[cfg(mobile)] +pub use mobile::*; + +use serde::Serialize; +use tauri::{window::WindowBuilder, App, AppHandle, RunEvent, WindowUrl}; + +#[derive(Clone, Serialize)] +struct Reply { + data: String, +} + +pub type SetupHook = Box Result<(), Box> + Send>; +pub type OnEvent = Box; + +#[derive(Default)] +pub struct AppBuilder { + setup: Option, + on_event: Option, +} + +impl AppBuilder { + pub fn new() -> Self { + Self::default() + } + + #[must_use] + pub fn setup(mut self, setup: F) -> Self + where + F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, + { + self.setup.replace(Box::new(setup)); + self + } + + #[must_use] + pub fn on_event(mut self, on_event: F) -> Self + where + F: Fn(&AppHandle, RunEvent) + 'static, + { + self.on_event.replace(Box::new(on_event)); + self + } + + pub fn run(self) { + let setup = self.setup; + let mut on_event = self.on_event; + + #[allow(unused_mut)] + let mut builder = tauri::Builder::default() + .setup(move |app| { + if let Some(setup) = setup { + (setup)(app)?; + } + + #[allow(unused_mut)] + let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()) + .title("Tauri API Validation") + .inner_size(1000., 800.) + .min_inner_size(600., 400.); + + #[cfg(target_os = "windows")] + { + window_builder = window_builder.transparent(true); + window_builder = window_builder.decorations(false); + } + + let window = window_builder.build().unwrap(); + + #[cfg(target_os = "windows")] + { + let _ = window_shadows::set_shadow(&window, true); + let _ = window_vibrancy::apply_blur(&window, Some((0, 0, 0, 0))); + } + + #[cfg(debug_assertions)] + window.open_devtools(); + + std::thread::spawn(|| { + let server = match tiny_http::Server::http("localhost:3003") { + Ok(s) => s, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; + loop { + if let Ok(mut request) = server.recv() { + let mut body = Vec::new(); + let _ = request.as_reader().read_to_end(&mut body); + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + request.headers().to_vec(), + std::io::Cursor::new(body), + request.body_length(), + None, + ); + let _ = request.respond(response); + } + } + }); + + Ok(()) + }) + .on_page_load(|window, _| { + let window_ = window.clone(); + window.listen("js-event", move |event| { + println!("got js-event with message '{:?}'", event.payload()); + let reply = Reply { + data: "something else".to_string(), + }; + + window_ + .emit("rust-event", Some(reply)) + .expect("failed to emit"); + }); + }); + + #[cfg(target_os = "macos")] + { + builder = builder.menu(tauri::Menu::os_default("Tauri API Validation")); + } + + #[allow(unused_mut)] + let mut app = builder + .invoke_handler(tauri::generate_handler![ + cmd::log_operation, + cmd::perform_request, + ]) + .build(tauri::tauri_build_context!()) + .expect("error while building tauri application"); + + #[cfg(target_os = "macos")] + app.set_activation_policy(tauri::ActivationPolicy::Regular); + + #[allow(unused_variables)] + app.run(move |app_handle, e| { + if let RunEvent::ExitRequested { api, .. } = &e { + // Keep the event loop running even if all windows are closed + // This allow us to catch system tray events when there is no window + api.prevent_exit(); + } + if let Some(on_event) = &mut on_event { + (on_event)(app_handle, e); + } + }) + } +} diff --git a/examples/api/src-tauri/src/main.rs b/examples/api/src-tauri/src/main.rs index f14cb16a8..1ade16f98 100644 --- a/examples/api/src-tauri/src/main.rs +++ b/examples/api/src-tauri/src/main.rs @@ -1,274 +1,7 @@ -// Copyright 2019-2021 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] - -mod cmd; - -use serde::Serialize; -use tauri::{window::WindowBuilder, RunEvent, WindowUrl}; - #[cfg(desktop)] -mod desktop { - pub use std::sync::atomic::{AtomicBool, Ordering}; - pub use tauri::{ - api::dialog::ask, CustomMenuItem, GlobalShortcutManager, Manager, SystemTray, SystemTrayEvent, - SystemTrayMenu, WindowEvent, - }; -} -#[cfg(desktop)] -pub use desktop::*; - -#[derive(Clone, Serialize)] -struct Reply { - data: String, -} +mod desktop; fn main() { - #[allow(unused_mut)] - let mut builder = tauri::Builder::default() - .setup(move |app| { - #[cfg(desktop)] - create_tray(app)?; - - #[allow(unused_mut)] - let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()) - .title("Tauri API Validation") - .inner_size(1000., 800.) - .min_inner_size(600., 400.); - - #[cfg(target_os = "windows")] - { - window_builder = window_builder.transparent(true); - window_builder = window_builder.decorations(false); - } - - let window = window_builder.build().unwrap(); - - #[cfg(target_os = "windows")] - { - let _ = window_shadows::set_shadow(&window, true); - let _ = window_vibrancy::apply_blur(&window, Some((0, 0, 0, 0))); - } - - #[cfg(debug_assertions)] - window.open_devtools(); - - std::thread::spawn(|| { - let server = match tiny_http::Server::http("localhost:3003") { - Ok(s) => s, - Err(e) => { - eprintln!("{}", e); - std::process::exit(1); - } - }; - loop { - if let Ok(mut request) = server.recv() { - let mut body = Vec::new(); - let _ = request.as_reader().read_to_end(&mut body); - let response = tiny_http::Response::new( - tiny_http::StatusCode(200), - request.headers().to_vec(), - std::io::Cursor::new(body), - request.body_length(), - None, - ); - let _ = request.respond(response); - } - } - }); - - Ok(()) - }) - .on_page_load(|window, _| { - let window_ = window.clone(); - window.listen("js-event", move |event| { - println!("got js-event with message '{:?}'", event.payload()); - let reply = Reply { - data: "something else".to_string(), - }; - - window_ - .emit("rust-event", Some(reply)) - .expect("failed to emit"); - }); - }); - - #[cfg(target_os = "macos")] - { - builder = builder.menu(tauri::Menu::os_default("Tauri API Validation")); - } - - #[allow(unused_mut)] - let mut app = builder - .invoke_handler(tauri::generate_handler![ - cmd::log_operation, - cmd::perform_request, - ]) - .build(tauri::tauri_build_context!()) - .expect("error while building tauri application"); - - #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Regular); - - #[allow(unused_variables)] - app.run(|app_handle, e| match e { - // Application is ready (triggered only once) - #[cfg(desktop)] - RunEvent::Ready => { - let app_handle = app_handle.clone(); - app_handle - .global_shortcut_manager() - .register("CmdOrCtrl+1", move || { - let app_handle = app_handle.clone(); - let window = app_handle.get_window("main").unwrap(); - window.set_title("New title!").unwrap(); - }) - .unwrap(); - } - - // Triggered when a window is trying to close - #[cfg(desktop)] - RunEvent::WindowEvent { - label, - event: WindowEvent::CloseRequested { api, .. }, - .. - } => { - // for other windows, we handle it in JS - if label == "main" { - let app_handle = app_handle.clone(); - let window = app_handle.get_window(&label).unwrap(); - // use the exposed close api, and prevent the event loop to close - api.prevent_close(); - // ask the user if he wants to quit - ask( - Some(&window), - "Tauri API", - "Are you sure that you want to close this window?", - move |answer| { - if answer { - // .close() cannot be called on the main thread - std::thread::spawn(move || { - app_handle.get_window(&label).unwrap().close().unwrap(); - }); - } - }, - ); - } - } - - // Keep the event loop running even if all windows are closed - // This allow us to catch system tray events when there is no window - RunEvent::ExitRequested { api, .. } => { - api.prevent_exit(); - } - _ => {} - }) -} - -#[cfg(desktop)] -fn create_tray(app: &tauri::App) -> tauri::Result<()> { - let tray_menu1 = SystemTrayMenu::new() - .add_item(CustomMenuItem::new("toggle", "Toggle")) - .add_item(CustomMenuItem::new("new", "New window")) - .add_item(CustomMenuItem::new("icon_1", "Tray Icon 1")) - .add_item(CustomMenuItem::new("icon_2", "Tray Icon 2")) - .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) - .add_item(CustomMenuItem::new("exit_app", "Quit")) - .add_item(CustomMenuItem::new("destroy", "Destroy")); - let tray_menu2 = SystemTrayMenu::new() - .add_item(CustomMenuItem::new("toggle", "Toggle")) - .add_item(CustomMenuItem::new("new", "New window")) - .add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) - .add_item(CustomMenuItem::new("exit_app", "Quit")) - .add_item(CustomMenuItem::new("destroy", "Destroy")); - let is_menu1 = AtomicBool::new(true); - - let handle = app.handle(); - let tray_id = "my-tray".to_string(); - SystemTray::new() - .with_id(&tray_id) - .with_menu(tray_menu1.clone()) - .on_event(move |event| { - let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap(); - match event { - SystemTrayEvent::LeftClick { - position: _, - size: _, - .. - } => { - let window = handle.get_window("main").unwrap(); - window.show().unwrap(); - window.set_focus().unwrap(); - } - SystemTrayEvent::MenuItemClick { id, .. } => { - let item_handle = tray_handle.get_item(&id); - match id.as_str() { - "exit_app" => { - // exit the app - handle.exit(0); - } - "destroy" => { - tray_handle.destroy().unwrap(); - } - "toggle" => { - let window = handle.get_window("main").unwrap(); - let new_title = if window.is_visible().unwrap() { - window.hide().unwrap(); - "Show" - } else { - window.show().unwrap(); - "Hide" - }; - item_handle.set_title(new_title).unwrap(); - } - "new" => { - WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into())) - .title("Tauri") - .build() - .unwrap(); - } - "icon_1" => { - #[cfg(target_os = "macos")] - tray_handle.set_icon_as_template(true).unwrap(); - - tray_handle - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/tray_icon_with_transparency.png").to_vec(), - )) - .unwrap(); - } - "icon_2" => { - #[cfg(target_os = "macos")] - tray_handle.set_icon_as_template(true).unwrap(); - - tray_handle - .set_icon(tauri::Icon::Raw( - include_bytes!("../../../.icons/icon.ico").to_vec(), - )) - .unwrap(); - } - "switch_menu" => { - let flag = is_menu1.load(Ordering::Relaxed); - tray_handle - .set_menu(if flag { - tray_menu2.clone() - } else { - tray_menu1.clone() - }) - .unwrap(); - is_menu1.store(!flag, Ordering::Relaxed); - } - _ => {} - } - } - _ => {} - } - }) - .build(app) - .map(|_| ()) + #[cfg(desktop)] + desktop::main(); } diff --git a/examples/api/src-tauri/src/mobile.rs b/examples/api/src-tauri/src/mobile.rs new file mode 100644 index 000000000..beb20e20c --- /dev/null +++ b/examples/api/src-tauri/src/mobile.rs @@ -0,0 +1,47 @@ +#[cfg(target_os = "android")] +use tauri_runtime_wry::wry::application::{android_fn, platform::android::ndk_glue::*}; + +#[cfg(target_os = "android")] +fn init_logging(app_name: &str) { + android_logger::init_once( + android_logger::Config::default() + .with_min_level(log::Level::Trace) + .with_tag(app_name), + ); +} + +#[cfg(not(target_os = "android"))] +fn init_logging(_app_name: &str) { + env_logger::init(); +} + +fn stop_unwind T, T>(f: F) -> T { + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) { + Ok(t) => t, + Err(err) => { + eprintln!("attempt to unwind out of `rust` with err: {:?}", err); + std::process::abort() + } + } +} + +fn _start_app() { + stop_unwind(main); +} + +#[no_mangle] +#[inline(never)] +pub extern "C" fn start_app() { + #[cfg(target_os = "android")] + android_fn!(com.tauri, api); + _start_app() +} + +fn main() { + super::AppBuilder::new() + .setup(|app| { + init_logging(&app.package_info().name); + Ok(()) + }) + .run(); +}