瀏覽代碼

feat(examples): prepare API example for mobile

Lucas Nogueira 3 年之前
父節點
當前提交
0e925fd8f0

+ 4 - 0
examples/api/src-tauri/.gitignore

@@ -1,3 +1,7 @@
 # Generated by Cargo
 # will have compiled files and executables
 /target/
+
+# cargo-mobile
+.cargo/
+/gen

+ 65 - 0
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"

+ 16 - 0
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" ]

+ 8 - 0
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"

+ 160 - 0
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(|_| ())
+}

+ 159 - 0
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<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
+pub type OnEvent = Box<dyn FnMut(&AppHandle, RunEvent)>;
+
+#[derive(Default)]
+pub struct AppBuilder {
+  setup: Option<SetupHook>,
+  on_event: Option<OnEvent>,
+}
+
+impl AppBuilder {
+  pub fn new() -> Self {
+    Self::default()
+  }
+
+  #[must_use]
+  pub fn setup<F>(mut self, setup: F) -> Self
+  where
+    F: FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send + 'static,
+  {
+    self.setup.replace(Box::new(setup));
+    self
+  }
+
+  #[must_use]
+  pub fn on_event<F>(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);
+      }
+    })
+  }
+}

+ 3 - 270
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();
 }

+ 47 - 0
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<F: FnOnce() -> 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();
+}