feat: add OpenHarmony support

This commit is contained in:
Lucas Nogueira
2025-08-19 13:55:53 -03:00
parent 5075b67d36
commit d991c93f44
105 changed files with 2994 additions and 458 deletions

316
Cargo.lock generated
View File

@@ -229,6 +229,8 @@ name = "api"
version = "0.1.0"
dependencies = [
"log",
"napi-derive-ohos",
"napi-ohos",
"serde",
"serde_json",
"tauri",
@@ -1055,13 +1057,13 @@ dependencies = [
[[package]]
name = "cargo-mobile2"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2e0347234eb5a7b47eb66d33dc18560a628f031dffe58c37a7efe44b53e6f9"
version = "0.20.4"
source = "git+https://github.com/tauri-apps/cargo-mobile2?branch=feat/ohos#fa4c50b5ffacc5caf0729512cdb8bc77b71c4412"
dependencies = [
"colored",
"core-foundation 0.10.0",
"deunicode",
"dirs 6.0.0",
"duct",
"dunce",
"embed-resource",
@@ -1393,6 +1395,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "convert_case"
version = "0.8.0"
@@ -1720,8 +1731,18 @@ version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
"darling_core 0.20.10",
"darling_macro 0.20.10",
]
[[package]]
name = "darling"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08440b3dd222c3d0433e63e097463969485f112baff337dfdaca043a0d760570"
dependencies = [
"darling_core 0.21.2",
"darling_macro 0.21.2",
]
[[package]]
@@ -1738,13 +1759,38 @@ dependencies = [
"syn 2.0.95",
]
[[package]]
name = "darling_core"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25b7912bc28a04ab1b7715a68ea03aaa15662b43a1a4b2c480531fd19f8bf7e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.95",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"darling_core 0.20.10",
"quote",
"syn 2.0.95",
]
[[package]]
name = "darling_macro"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce154b9bea7fb0c8e8326e62d00354000c36e79770ff21b8c84e3aa267d9d531"
dependencies = [
"darling_core 0.21.2",
"quote",
"syn 2.0.95",
]
@@ -1831,7 +1877,7 @@ version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"darling 0.20.10",
"proc-macro2",
"quote",
"syn 2.0.95",
@@ -4665,6 +4711,12 @@ version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff539e61c5e3dd4d7d283610662f5d672c2aea0f158df78af694f13dbb3287b"
[[package]]
name = "napi-build-ohos"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "959f833e4ea8bec8f92b23b705b5558d42d8e63672c77b9b281b7228c5df6e88"
[[package]]
name = "napi-derive"
version = "3.0.0"
@@ -4692,6 +4744,48 @@ dependencies = [
"syn 2.0.95",
]
[[package]]
name = "napi-derive-backend-ohos"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b27250baa967a15214e57384dd6228c59afbccb15ab8f97207c9758917544bf5"
dependencies = [
"convert_case 0.8.0",
"proc-macro2",
"quote",
"semver",
"syn 2.0.95",
]
[[package]]
name = "napi-derive-ohos"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c844efa85d53b5adc3b326520f3a108c3a737b7534ee10d406f81884e7e71b3c"
dependencies = [
"convert_case 0.8.0",
"ctor 0.4.2",
"napi-derive-backend-ohos",
"proc-macro2",
"quote",
"syn 2.0.95",
]
[[package]]
name = "napi-ohos"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44cd7f1a2b5b17e763d8fcc33f3a9f426c0303a1fcb9b89d7c553308c3a1db97"
dependencies = [
"bitflags 2.7.0",
"ctor 0.4.2",
"napi-build-ohos",
"napi-sys-ohos",
"nohash-hasher",
"rustc-hash",
"tokio",
]
[[package]]
name = "napi-sys"
version = "3.0.0"
@@ -4701,6 +4795,15 @@ dependencies = [
"libloading 0.8.6",
]
[[package]]
name = "napi-sys-ohos"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff0b7e156bf62a778295ba4f130cde6c2fe07936ebf9448fab6ca0f7c7040682"
dependencies = [
"libloading 0.8.6",
]
[[package]]
name = "native-tls"
version = "0.2.12"
@@ -5281,6 +5384,144 @@ dependencies = [
"subtle",
]
[[package]]
name = "ohos-arkui-binding"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "731d879cf95234bbd12e525bdce7da6475a6786a86dd2362fac0f3cfca760373"
dependencies = [
"bitflags 2.7.0",
"napi-ohos",
"napi-sys-ohos",
"ohos-arkui-sys",
"ohos-xcomponent-binding",
"ohos-xcomponent-sys",
"ohos_enum_macro 0.0.2",
]
[[package]]
name = "ohos-arkui-sys"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a19176486bbe46d33830aca4213b07bb46c745200118e57106639db4962e1c3"
dependencies = [
"napi-sys-ohos",
]
[[package]]
name = "ohos-display-binding"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157fe09232fd0af55ed9207d30b47a5f5ff7f84a0673a3b47f65acb7bbc9b8a9"
dependencies = [
"ohos-display-sys",
"ohos_enum_macro 0.0.1",
]
[[package]]
name = "ohos-display-soloist-binding"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b26e7a334db10af3d3b71cc0cddfd923030421e719031d8d75ca8ffb431614dd"
dependencies = [
"ohos-display-soloist-sys",
]
[[package]]
name = "ohos-display-soloist-sys"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b6f9ced0cbe82574a06d95cf5ffa35eee07b13e0631c8abd421feb03aa16b0a"
[[package]]
name = "ohos-display-sys"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe9e1bba4d94159d0a65f1b4e748c148d4bce7930912c424522693f1837dad77"
[[package]]
name = "ohos-ime-binding"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bed52a087eacd3ea07de5258ca7574276431b17d4c0464ed7f7f57a5fa99bd48"
dependencies = [
"ohos-input-method-sys",
"ohos_enum_macro 0.0.1",
]
[[package]]
name = "ohos-input-method-sys"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e95bb83d997b0d7f1d04e9b2ba8874a1bd8e89dc5e88d0852a4390f8b44b0703"
[[package]]
name = "ohos-web-binding"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cbdbe97bddcb5dcd46d52654799525ef42962cf6f360c57b4bb72597ebd61"
dependencies = [
"bitflags 2.7.0",
"ohos-web-sys",
"ohos_enum_macro 0.0.2",
]
[[package]]
name = "ohos-web-sys"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa646f3223c2bc90a400641c3a5cda4b350387ffda1a904b8bfb3db92b81ecbc"
[[package]]
name = "ohos-xcomponent-binding"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f99c43b17408bb43ae53832a7b929dc6a359685f914624e82fec0a52e9d6ac"
dependencies = [
"napi-derive-ohos",
"napi-ohos",
"napi-sys-ohos",
"ohos-display-binding",
"ohos-xcomponent-sys",
"ohos_enum_macro 0.0.2",
"raw-window-handle",
]
[[package]]
name = "ohos-xcomponent-sys"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5613209329ff61bc8695871ef985d57fc59ea4d55263e2c2395d8810d063575e"
dependencies = [
"ohos-arkui-sys",
]
[[package]]
name = "ohos_enum_macro"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0ee769776099c824423d1686e21b5fd13b464d374d60f7cc25c48c1c836ede1"
dependencies = [
"convert_case 0.6.0",
"proc-macro2",
"quote",
"regex",
"syn 2.0.95",
]
[[package]]
name = "ohos_enum_macro"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4bacd60b52be93024c446f27645d795ab71eb2243bb4c5d87b6238a35341fbd"
dependencies = [
"convert_case 0.6.0",
"proc-macro2",
"quote",
"regex",
"syn 2.0.95",
]
[[package]]
name = "oid-registry"
version = "0.6.1"
@@ -5312,6 +5553,35 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openharmony-ability"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef188801ee7d2356067f4d4d54eb1d1eaab58e9acce9a8f9cc9695f8327bc52"
dependencies = [
"http 1.3.1",
"napi-derive-ohos",
"napi-ohos",
"ohos-arkui-binding",
"ohos-display-binding",
"ohos-display-soloist-binding",
"ohos-ime-binding",
"ohos-web-binding",
"ohos-xcomponent-binding",
]
[[package]]
name = "openharmony-ability-derive"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba6d923f6fc667cb9db25ff570deece3cdffb3fc4b58b8bc04f0419ca5de1d3"
dependencies = [
"darling 0.21.2",
"proc-macro2",
"quote",
"syn 2.0.95",
]
[[package]]
name = "openssl"
version = "0.10.72"
@@ -7689,7 +7959,7 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
dependencies = [
"darling",
"darling 0.20.10",
"proc-macro2",
"quote",
"syn 2.0.95",
@@ -8411,11 +8681,11 @@ dependencies = [
[[package]]
name = "tao"
version = "0.34.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65dc99ae111a3255027d1eca24a3833bb3267d4556a6defddb455f3ca4f5b6c"
version = "0.34.2"
source = "git+https://github.com/richerfu/tao?branch=feat-ohos-webview#efdfe5d002879fa51c0d6508cc2c295c1f3b31b6"
dependencies = [
"bitflags 2.7.0",
"block2 0.6.0",
"core-foundation 0.10.0",
"core-graphics",
"crossbeam-channel",
@@ -8436,10 +8706,12 @@ dependencies = [
"objc2-app-kit",
"objc2-foundation 0.3.0",
"once_cell",
"openharmony-ability",
"openharmony-ability-derive",
"parking_lot",
"raw-window-handle",
"scopeguard",
"tao-macros",
"tao-macros 0.1.3 (git+https://github.com/richerfu/tao?branch=feat-ohos-webview)",
"unicode-segmentation",
"url",
"windows 0.61.1",
@@ -8459,6 +8731,16 @@ dependencies = [
"syn 2.0.95",
]
[[package]]
name = "tao-macros"
version = "0.1.3"
source = "git+https://github.com/richerfu/tao?branch=feat-ohos-webview#efdfe5d002879fa51c0d6508cc2c295c1f3b31b6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.95",
]
[[package]]
name = "tap"
version = "1.0.1"
@@ -8511,6 +8793,8 @@ dependencies = [
"objc2-foundation 0.3.0",
"objc2-ui-kit",
"objc2-web-kit",
"openharmony-ability",
"openharmony-ability-derive",
"percent-encoding",
"plist",
"proptest",
@@ -8859,6 +9143,7 @@ dependencies = [
"objc2 0.6.0",
"objc2-ui-kit",
"objc2-web-kit",
"openharmony-ability",
"raw-window-handle",
"serde",
"serde_json",
@@ -10944,8 +11229,7 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5698e50a589268aec06d2219f48b143222f7b5ad9aa690118b8dce0a8dcac574"
source = "git+https://github.com/richerfu/wry#d5294c487d9c1080aa45473e6b0a4cbfed647612"
dependencies = [
"base64 0.22.1",
"block2 0.6.0",
@@ -10970,11 +11254,13 @@ dependencies = [
"objc2-ui-kit",
"objc2-web-kit",
"once_cell",
"openharmony-ability",
"openharmony-ability-derive",
"percent-encoding",
"raw-window-handle",
"sha2",
"soup3",
"tao-macros",
"tao-macros 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 2.0.12",
"tracing",
"url",

View File

@@ -71,3 +71,6 @@ opt-level = "s"
schemars_derive = { git = 'https://github.com/tauri-apps/schemars.git', branch = 'feat/preserve-description-newlines' }
tauri = { path = "./crates/tauri" }
tauri-plugin = { path = "./crates/tauri-plugin" }
cargo-mobile2 = { git = "https://github.com/tauri-apps/cargo-mobile2", branch = "feat/ohos" }
wry = { git = "https://github.com/richerfu/wry" }
tao = { git = "https://github.com/richerfu/tao", branch = "feat-ohos-webview" }

View File

@@ -462,8 +462,9 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let mobile = target_os == "ios" || target_os == "android" || target_env == "ohos";
cfg_alias("desktop", !mobile);
cfg_alias("mobile", mobile);

View File

@@ -141,13 +141,16 @@ pub fn copy_dir(from: &Path, to: &Path) -> crate::Result<()> {
///
/// Expects a HashMap of PathBuf entries, representing destination and source paths,
/// and also a path of a directory. The files will be stored with respect to this directory.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn copy_custom_files(
files_map: &std::collections::HashMap<std::path::PathBuf, std::path::PathBuf>,
data_dir: &Path,

View File

@@ -1578,6 +1578,13 @@
"enum": [
"iOS"
]
},
{
"description": "OpenHarmony.",
"type": "string",
"enum": [
"openHarmony"
]
}
]
},

View File

@@ -52,7 +52,7 @@ pub struct Options {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -65,7 +65,7 @@ pub struct Options {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -66,7 +66,7 @@ pub struct Options {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -31,7 +31,7 @@ mod plugin;
mod remove;
mod signer;
use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand};
use env_logger::fmt::style::{AnsiColor, Style};
use env_logger::Builder;
use log::Level;
@@ -40,7 +40,6 @@ use std::io::{BufReader, Write};
use std::process::{exit, Command, ExitStatus, Output, Stdio};
use std::{
ffi::OsString,
fmt::Display,
fs::read_to_string,
io::BufRead,
path::PathBuf,
@@ -86,29 +85,6 @@ impl FromStr for ConfigValue {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum RunMode {
Desktop,
#[cfg(target_os = "macos")]
Ios,
Android,
}
impl Display for RunMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Desktop => "desktop",
#[cfg(target_os = "macos")]
Self::Ios => "iOS",
Self::Android => "android",
}
)
}
}
#[derive(Deserialize)]
pub struct VersionMetadata {
tauri: String,
@@ -151,6 +127,8 @@ enum Commands {
Build(build::Options),
Bundle(bundle::Options),
Android(mobile::android::Cli),
#[clap(name("ohos"), alias("oh"), alias("open-harmony"), alias("openharmony"))]
OpenHarmony(mobile::open_harmony::Cli),
#[cfg(target_os = "macos")]
Ios(mobile::ios::Cli),
/// Migrate from v1 to v2
@@ -232,7 +210,7 @@ where
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(cli.verbose);
// set the verbosity level so subsequent CLI calls (xcode-script, android-studio-script) refer to it
// set the verbosity level so subsequent CLI calls (xcode-script, android-studio-script, dev-eco-studio-script) refer to it
std::env::set_var("TAURI_CLI_VERBOSITY", verbosity_number.to_string());
let mut builder = Builder::from_default_env();
@@ -297,6 +275,7 @@ where
Commands::Permission(options) => acl::permission::command(options)?,
Commands::Capability(options) => acl::capability::command(options)?,
Commands::Android(c) => mobile::android::command(c, cli.verbose)?,
Commands::OpenHarmony(c) => mobile::open_harmony::command(c, cli.verbose)?,
#[cfg(target_os = "macos")]
Commands::Ios(c) => mobile::ios::command(c, cli.verbose)?,
Commands::Migrate => migrate::command()?,

View File

@@ -54,7 +54,7 @@ pub struct Options {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -53,7 +53,7 @@ pub struct Options {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -69,7 +69,7 @@ pub struct InitOptions {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -168,6 +168,12 @@ pub fn exec(
)?;
app
}
Target::OpenHarmony => {
let (config, _metadata) =
super::open_harmony::get_config(&app, tauri_config_, None, &Default::default());
super::open_harmony::project::gen(&app, &config, (handlebars, map))?;
app
}
};
Report::victory(

View File

@@ -65,7 +65,7 @@ pub struct Options {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -60,7 +60,7 @@ pub struct Options {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -82,7 +82,7 @@ pub struct InitOptions {
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json)
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,

View File

@@ -51,6 +51,7 @@ pub mod android;
mod init;
#[cfg(target_os = "macos")]
pub mod ios;
pub mod open_harmony;
const MIN_DEVICE_MATCH_SCORE: isize = 0;
@@ -99,6 +100,7 @@ pub enum Target {
Android,
#[cfg(target_os = "macos")]
Ios,
OpenHarmony,
}
impl Target {
@@ -107,6 +109,7 @@ impl Target {
Self::Android => "Android Studio",
#[cfg(target_os = "macos")]
Self::Ios => "Xcode",
Self::OpenHarmony => "Dev-Eco Studio",
}
}
@@ -115,6 +118,7 @@ impl Target {
Self::Android => "android",
#[cfg(target_os = "macos")]
Self::Ios => "ios",
Self::OpenHarmony => "open-harmony",
}
}
@@ -123,6 +127,7 @@ impl Target {
Self::Android => "android-studio-script",
#[cfg(target_os = "macos")]
Self::Ios => "xcode-script",
Self::OpenHarmony => "dev-eco-studio-script",
}
}
@@ -131,6 +136,7 @@ impl Target {
Self::Android => tauri_utils::platform::Target::Android,
#[cfg(target_os = "macos")]
Self::Ios => tauri_utils::platform::Target::Ios,
Self::OpenHarmony => tauri_utils::platform::Target::OpenHarmony,
}
}
}
@@ -437,6 +443,7 @@ pub fn get_app(target: Target, config: &TauriConfig, interface: &AppInterface) -
Target::Android => config.identifier.replace('-', "_"),
#[cfg(target_os = "macos")]
Target::Ios => config.identifier.replace('_', "-"),
Target::OpenHarmony => config.identifier.replace('-', "_"),
};
if identifier.is_empty() {
@@ -526,6 +533,16 @@ fn ensure_init(
.push("you have modified your [lib.name] or [package.name] in the Cargo.toml file");
}
}
Target::OpenHarmony => {
let app_json = json5::from_str::<open_harmony::AppConfig>(
&read_to_string(project_dir.join("AppScope").join("app.json5"))
.context("missing app.json5 file in the OpenHarmony project directory")?,
)?;
if app_json.app.bundle_name != tauri_config_.identifier.replace('-', "_") {
project_outdated_reasons
.push("you have modified your \"identifier\" in the Tauri configuration");
}
}
}
if !project_outdated_reasons.is_empty() {

View File

@@ -0,0 +1,258 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{
delete_codegen_vars, ensure_init, env, get_app, get_config, inject_resources, log_finished,
open_and_wait, MobileTarget, OptionsHandle,
};
use crate::{
build::Options as BuildOptions,
helpers::{
app_paths::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
flock,
},
interface::{AppInterface, Interface, Options as InterfaceOptions},
mobile::{write_options, CliOptions},
ConfigValue, Result,
};
use clap::{ArgAction, Parser};
use anyhow::Context;
use cargo_mobile2::{
open_harmony::{config::Config as OpenHarmonyConfig, env::Env, hap, target::Target},
opts::{NoiseLevel, Profile},
target::TargetTrait,
};
use std::env::set_current_dir;
#[derive(Debug, Clone, Parser)]
#[clap(
about = "Build your app in release mode for OpenHarmony and generate HAPs",
long_about = "Build your app in release mode for OpenHarmony and generate HAPs. It makes use of the `build.frontendDist` property from your `tauri.conf.json` file. It also runs your `build.beforeBuildCommand` which usually builds your frontend into `build.frontendDist`."
)]
pub struct Options {
/// Builds with the debug flag
#[clap(short, long)]
pub debug: bool,
/// Which targets to build (all by default).
#[clap(
short,
long = "target",
action = ArgAction::Append,
num_args(0..),
value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
)]
pub targets: Option<Vec<String>>,
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,
/// Whether to split the HAPs per ABIs.
#[clap(long)]
pub split_per_abi: bool,
/// Open DevEco Studio
#[clap(short, long)]
pub open: bool,
/// Skip prompting for values
#[clap(long, env = "CI")]
pub ci: bool,
/// Command line arguments passed to the runner.
/// Use `--` to explicitly mark the start of the arguments.
/// e.g. `tauri ohos build -- [runnerArgs]`.
#[clap(last(true))]
pub args: Vec<String>,
/// Do not error out if a version mismatch is detected on a Tauri package.
///
/// Only use this when you are sure the mismatch is incorrectly detected as version mismatched Tauri packages can lead to unknown behavior.
#[clap(long)]
pub ignore_version_mismatches: bool,
}
impl From<Options> for BuildOptions {
fn from(options: Options) -> Self {
Self {
runner: None,
debug: options.debug,
target: None,
features: options.features,
bundles: None,
no_bundle: false,
config: options.config,
args: options.args,
ci: options.ci,
skip_stapling: false,
ignore_version_mismatches: options.ignore_version_mismatches,
}
}
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
crate::helpers::app_paths::resolve();
delete_codegen_vars();
let mut build_options: BuildOptions = options.clone().into();
let first_target = Target::all()
.get(
options
.targets
.as_ref()
.and_then(|l| l.first().map(|t| t.as_str()))
.unwrap_or(Target::DEFAULT_KEY),
)
.unwrap();
build_options.target = Some(first_target.triple.into());
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::OpenHarmony,
&options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
)?;
let (interface, config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let interface = AppInterface::new(tauri_config_, build_options.target.clone())?;
interface.build_options(&mut Vec::new(), &mut build_options.features, true);
let app = get_app(MobileTarget::OpenHarmony, tauri_config_, &interface);
let (config, metadata) = get_config(
&app,
tauri_config_,
build_options.features.as_ref(),
&Default::default(),
);
(interface, config, metadata)
};
let profile = if options.debug {
Profile::Debug
} else {
Profile::Release
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
ensure_init(
&tauri_config,
config.app(),
config.project_dir(),
MobileTarget::OpenHarmony,
)?;
let mut env = env()?;
crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
// run an initial build to initialize plugins
first_target.build(&config, &metadata, &env, noise_level, true, profile)?;
let open = options.open;
let _handle = run_build(
interface,
options,
build_options,
tauri_config,
profile,
&config,
&mut env,
noise_level,
)?;
if open {
open_and_wait(&config, &env);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn run_build(
interface: AppInterface,
options: Options,
build_options: BuildOptions,
tauri_config: ConfigHandle,
profile: Profile,
config: &OpenHarmonyConfig,
env: &mut Env,
noise_level: NoiseLevel,
) -> Result<OptionsHandle> {
let interface_options = InterfaceOptions {
debug: build_options.debug,
target: build_options.target.clone(),
args: build_options.args.clone(),
..Default::default()
};
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(&interface_options)?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("ohos"), "OpenHarmony")?;
let cli_options = CliOptions {
dev: false,
features: build_options.features.clone(),
args: build_options.args.clone(),
noise_level,
vars: Default::default(),
config: build_options.config,
target_device: None,
};
let handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?;
let hap_outputs = hap::build(
config,
env,
noise_level,
profile,
get_targets_or_all(options.targets.clone().unwrap_or_default())?,
options.split_per_abi,
)?;
log_finished(hap_outputs, "HAP");
Ok(handle)
}
fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>> {
if targets.is_empty() {
Ok(Target::all().iter().map(|t| t.1).collect())
} else {
let mut outs = Vec::new();
let possible_targets = Target::all()
.keys()
.map(|key| key.to_string())
.collect::<Vec<String>>()
.join(",");
for t in targets {
let target = Target::for_name(&t).ok_or_else(|| {
anyhow::anyhow!(
"Target {} is invalid; the possible targets are {}",
t,
possible_targets
)
})?;
outs.push(target);
}
Ok(outs)
}
}

View File

@@ -0,0 +1,335 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{
delete_codegen_vars, device_prompt, ensure_init, env, get_app, get_config, inject_resources,
open_and_wait, MobileTarget,
};
use crate::{
dev::Options as DevOptions,
helpers::{
app_paths::tauri_dir,
config::{get as get_tauri_config, ConfigHandle},
flock,
},
interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions},
mobile::{
use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevHost, DevProcess,
TargetDevice,
},
ConfigValue, Result,
};
use clap::{ArgAction, Parser};
use anyhow::Context;
use cargo_mobile2::{
open_harmony::{
config::{Config as OpenHarmonyConfig, Metadata as OpenHarmonyMetadata},
device::Device,
env::Env,
target::Target,
},
opts::{NoiseLevel, Profile},
target::TargetTrait,
};
use std::{env::set_current_dir, path::PathBuf};
#[derive(Debug, Clone, Parser)]
#[clap(
about = "Run your app in development mode on OpenHarmony",
long_about = "Run your app in development mode on OpenHarmony with hot-reloading for the Rust code. It makes use of the `build.devUrl` property from your `tauri.conf.json` file. It also runs your `build.beforeDevCommand` which usually starts your frontend devServer."
)]
pub struct Options {
/// List of cargo features to activate
#[clap(short, long, action = ArgAction::Append, num_args(0..))]
pub features: Option<Vec<String>>,
/// Exit on panic
#[clap(short, long)]
exit_on_panic: bool,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,
/// Run the code in release mode
#[clap(long = "release")]
pub release_mode: bool,
/// Skip waiting for the frontend dev server to start before building the tauri application.
#[clap(long, env = "TAURI_CLI_NO_DEV_SERVER_WAIT")]
pub no_dev_server_wait: bool,
/// Disable the file watcher
#[clap(long)]
pub no_watch: bool,
/// Additional paths to watch for changes.
#[clap(long)]
pub additional_watch_folders: Vec<PathBuf>,
/// Open DevEco Studio instead of trying to run on a connected device
#[clap(short, long)]
pub open: bool,
/// Runs on the given device name
pub device: Option<String>,
/// Force prompting for an IP to use to connect to the dev server on mobile.
#[clap(long)]
pub force_ip_prompt: bool,
/// Use the public network address for the development server.
/// If an actual address it provided, it is used instead of prompting to pick one.
///
/// On Windows we use the public network address by default.
///
/// This option is particularly useful along the `--open` flag when you intend on running on a physical device.
///
/// This replaces the devUrl configuration value to match the public network address host,
/// it is your responsibility to set up your development server to listen on this address
/// by using 0.0.0.0 as host for instance.
///
/// When this is set or when running on an iOS device the CLI sets the `TAURI_DEV_HOST`
/// environment variable so you can check this on your framework's configuration to expose the development server
/// on the public network address.
#[clap(long, default_value_t, default_missing_value(""), num_args(0..=1))]
pub host: DevHost,
/// Disable the built-in dev server for static files.
#[clap(long)]
pub no_dev_server: bool,
/// Specify port for the built-in dev server for static files. Defaults to 1430.
#[clap(long, env = "TAURI_CLI_PORT")]
pub port: Option<u16>,
/// Command line arguments passed to the runner.
/// Use `--` to explicitly mark the start of the arguments.
/// e.g. `tauri ohos dev -- [runnerArgs]`.
#[clap(last(true))]
pub args: Vec<String>,
/// Path to the certificate file used by your dev server. Required for mobile dev when using HTTPS.
#[clap(long, env = "TAURI_DEV_ROOT_CERTIFICATE_PATH")]
pub root_certificate_path: Option<PathBuf>,
}
impl From<Options> for DevOptions {
fn from(options: Options) -> Self {
Self {
runner: None,
target: None,
features: options.features,
exit_on_panic: options.exit_on_panic,
config: options.config,
args: options.args,
no_watch: options.no_watch,
additional_watch_folders: options.additional_watch_folders,
no_dev_server_wait: options.no_dev_server_wait,
no_dev_server: options.no_dev_server,
port: options.port,
release_mode: options.release_mode,
host: options.host.0.unwrap_or_default(),
}
}
}
pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
crate::helpers::app_paths::resolve();
let result = run_command(options, noise_level);
if result.is_err() {
crate::dev::kill_before_dev_process();
}
result
}
fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
delete_codegen_vars();
// setup env additions before calling env()
if let Some(root_certificate_path) = &options.root_certificate_path {
std::env::set_var(
"TAURI_DEV_ROOT_CERTIFICATE",
std::fs::read_to_string(root_certificate_path).context("failed to read certificate file")?,
);
}
let tauri_config = get_tauri_config(
tauri_utils::platform::Target::OpenHarmony,
&options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
)?;
let env = env()?;
let device = if options.open {
None
} else {
match device_prompt(&env, options.device.as_deref()) {
Ok(d) => Some(d),
Err(e) => {
log::error!("{e}");
None
}
}
};
let mut dev_options: DevOptions = options.clone().into();
let target_triple = device
.as_ref()
.map(|d| d.target().triple.to_string())
.unwrap_or_else(|| Target::all().values().next().unwrap().triple.into());
dev_options.target = Some(target_triple);
let (interface, config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let interface = AppInterface::new(tauri_config_, dev_options.target.clone())?;
let app = get_app(MobileTarget::OpenHarmony, tauri_config_, &interface);
let (config, metadata) = get_config(
&app,
tauri_config_,
dev_options.features.as_ref(),
&Default::default(),
);
(interface, config, metadata)
};
let tauri_path = tauri_dir();
set_current_dir(tauri_path).with_context(|| "failed to change current working directory")?;
ensure_init(
&tauri_config,
config.app(),
config.project_dir(),
MobileTarget::OpenHarmony,
)?;
run_dev(
interface,
options,
dev_options,
tauri_config,
device,
env,
&config,
&metadata,
noise_level,
)
}
#[allow(clippy::too_many_arguments)]
fn run_dev(
mut interface: AppInterface,
options: Options,
mut dev_options: DevOptions,
tauri_config: ConfigHandle,
device: Option<Device>,
env: Env,
config: &OpenHarmonyConfig,
metadata: &OpenHarmonyMetadata,
noise_level: NoiseLevel,
) -> Result<()> {
// when running on an actual device we must use the network IP
if options.host.0.is_some()
|| device
.as_ref()
.map(|device| !device.model().starts_with("emulator"))
.unwrap_or(false)
{
use_network_address_for_dev_url(&tauri_config, &mut dev_options, options.force_ip_prompt)?;
}
crate::dev::setup(&interface, &mut dev_options, tauri_config.clone())?;
let interface_options = InterfaceOptions {
debug: !dev_options.release_mode,
target: dev_options.target.clone(),
..Default::default()
};
let app_settings = interface.app_settings();
let out_dir = app_settings.out_dir(&interface_options)?;
let _lock = flock::open_rw(out_dir.join("lock").with_extension("ohos"), "OpenHarmony")?;
// run an initial build to initialize plugins
let target_triple = dev_options.target.as_ref().unwrap();
let target = Target::all()
.values()
.find(|t| t.triple == target_triple)
.unwrap_or_else(|| Target::all().values().next().unwrap());
target.build(
config,
metadata,
&env,
noise_level,
true,
if options.release_mode {
Profile::Release
} else {
Profile::Debug
},
)?;
let open = options.open;
interface.mobile_dev(
MobileOptions {
debug: !options.release_mode,
features: options.features,
args: options.args,
config: dev_options.config.clone(),
no_watch: options.no_watch,
additional_watch_folders: options.additional_watch_folders,
},
|options| {
let cli_options = CliOptions {
dev: true,
features: options.features.clone(),
args: options.args.clone(),
noise_level,
vars: Default::default(),
config: dev_options.config.clone(),
target_device: device.as_ref().map(|d| TargetDevice {
id: d.id().to_string(),
name: d.name().to_string(),
}),
};
let _handle = write_options(tauri_config.lock().unwrap().as_ref().unwrap(), cli_options)?;
inject_resources(config, tauri_config.lock().unwrap().as_ref().unwrap())?;
if open {
open_and_wait(config, &env)
} else if let Some(device) = &device {
match run(device, options, config, &env, metadata, noise_level) {
Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess + Send>),
Err(e) => {
crate::dev::kill_before_dev_process();
Err(e)
}
}
} else {
open_and_wait(config, &env)
}
},
)
}
fn run(
device: &Device<'_>,
options: MobileOptions,
config: &OpenHarmonyConfig,
env: &Env,
_metadata: &OpenHarmonyMetadata,
noise_level: NoiseLevel,
) -> crate::Result<DevChild> {
let profile = if options.debug {
Profile::Debug
} else {
Profile::Release
};
device
.run(config, env, noise_level, profile)
.map(DevChild::new)
.map_err(Into::into)
}

View File

@@ -0,0 +1,311 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{detect_target_ok, ensure_init, env, get_app, get_config, read_options, MobileTarget};
use crate::{
helpers::config::{get as get_tauri_config, reload as reload_tauri_config},
interface::{AppInterface, Interface},
mobile::CliOptions,
Result,
};
use clap::{ArgAction, Parser};
use anyhow::Context;
use cargo_mobile2::{
open_harmony::{hdc, target::Target},
opts::Profile,
target::{call_for_targets_with_fallback, TargetTrait},
};
use std::path::Path;
#[derive(Debug, Parser)]
pub struct Options {
/// Targets to build.
#[clap(
short,
long = "target",
action = ArgAction::Append,
num_args(0..),
default_value = Target::DEFAULT_KEY,
value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
)]
targets: Option<Vec<String>>,
/// Builds with the release flag
#[clap(short, long)]
release: bool,
}
pub fn command(options: Options) -> Result<()> {
crate::helpers::app_paths::resolve();
let profile = if options.release {
Profile::Release
} else {
Profile::Debug
};
let (tauri_config, cli_options) = {
let tauri_config = get_tauri_config(tauri_utils::platform::Target::OpenHarmony, &[])?;
let cli_options = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
read_options(tauri_config_)
};
let tauri_config = if cli_options.config.is_empty() {
tauri_config
} else {
// reload config with merges from the ohos dev|build script
reload_tauri_config(
&cli_options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
)?
};
(tauri_config, cli_options)
};
let (config, metadata) = {
let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let (config, metadata) = get_config(
&get_app(
MobileTarget::OpenHarmony,
tauri_config_,
&AppInterface::new(tauri_config_, None)?,
),
tauri_config_,
None,
&cli_options,
);
(config, metadata)
};
ensure_init(
&tauri_config,
config.app(),
config.project_dir(),
MobileTarget::OpenHarmony,
)?;
if !cli_options.config.is_empty() {
crate::helpers::config::merge_with(
&cli_options
.config
.iter()
.map(|conf| &conf.0)
.collect::<Vec<_>>(),
)?;
}
let env = env()?;
if cli_options.dev {
let dev_url = tauri_config
.lock()
.unwrap()
.as_ref()
.unwrap()
.build
.dev_url
.clone();
if let Some(url) = dev_url {
let localhost = match url.host() {
Some(url::Host::Domain(d)) => d == "localhost",
Some(url::Host::Ipv4(i)) => i == std::net::Ipv4Addr::LOCALHOST,
_ => false,
};
if localhost {
if let Some(port) = url.port_or_known_default() {
hdc_forward_port(port, &env, &cli_options)?;
}
}
}
}
let mut validated_lib = false;
call_for_targets_with_fallback(
options.targets.unwrap_or_default().iter(),
&detect_target_ok,
&env,
|target: &Target| {
target.build(
&config,
&metadata,
&env,
cli_options.noise_level,
true,
profile,
)?;
if !validated_lib {
validated_lib = true;
let lib_path = config
.app()
.target_dir(target.triple, profile)
.join(config.so_name());
validate_lib(&lib_path)?;
}
Ok(())
},
)
.map_err(|e| anyhow::anyhow!(e.to_string()))?
}
fn validate_lib(path: &Path) -> Result<()> {
let so_bytes = std::fs::read(path)?;
let elf = elf::ElfBytes::<elf::endian::AnyEndian>::minimal_parse(&so_bytes)
.context("failed to parse ELF")?;
let (symbol_table, string_table) = elf
.dynamic_symbol_table()
.context("failed to read dynsym section")?
.context("missing dynsym tables")?;
let mut symbols = Vec::new();
for s in symbol_table.iter() {
if let Ok(symbol) = string_table.get(s.st_name as usize) {
symbols.push(symbol);
}
}
// TODO: implement check
if false {
anyhow::bail!(
"Library from {} does not include required runtime symbols. This means you are likely missing the tauri::mobile_entry_point macro usage, see the documentation for more information: https://v2.tauri.app/start/migrate/from-tauri-1",
path.display()
);
}
Ok(())
}
fn hdc_forward_port(
port: u16,
env: &cargo_mobile2::open_harmony::env::Env,
cli_options: &CliOptions,
) -> Result<()> {
let forward = format!("tcp:{port}");
log::info!("Forwarding port {port} with hdc");
let mut devices = hdc::device_list(env).unwrap_or_default();
// if we could not detect any running device, let's wait a few seconds, it might be booting up
if devices.is_empty() {
log::warn!(
"HDC device list is empty, waiting a few seconds to see if there's any booting device..."
);
let max = 5;
let mut count = 0;
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
devices = hdc::device_list(env).unwrap_or_default();
if !devices.is_empty() {
break;
}
count += 1;
if count == max {
break;
}
}
}
let target_device = if let Some(target_device) = &cli_options.target_device {
Some((target_device.id.clone(), target_device.name.clone()))
} else if devices.len() == 1 {
let device = devices.first().unwrap();
Some((device.id().to_string(), device.name().to_string()))
} else if devices.len() > 1 {
anyhow::bail!("Multiple OpenHarmony devices are connected ({}), please disconnect devices you do not intend to use so Tauri can determine which to use",
devices.iter().map(|d| d.name()).collect::<Vec<_>>().join(", "));
} else {
// when building the app without running to a device, we might have an empty devices list
None
};
if let Some((target_device_serial_no, target_device_name)) = target_device {
let mut already_forwarded = false;
// clear port forwarding for all devices
for device in &devices {
let reverse_list_output = hdc_reverse_list(env, device.id())?;
// check if the device has the port forwarded
if String::from_utf8_lossy(&reverse_list_output.stdout).contains(&forward) {
// device matches our target, we can skip forwarding
if device.id() == target_device_serial_no {
log::debug!(
"device {} already has the forward for {}",
device.name(),
forward
);
already_forwarded = true;
}
break;
}
}
// if there's a known target, we should forward the port to it
if already_forwarded {
log::info!("{forward} already forwarded to {target_device_name}");
} else {
loop {
run_hdc_reverse(env, &target_device_serial_no, &forward, &forward).with_context(|| {
format!("failed to forward port with hdc, is the {target_device_name} device connected?",)
})?;
let reverse_list_output = hdc_reverse_list(env, &target_device_serial_no)?;
// wait and retry until the port has actually been forwarded
if String::from_utf8_lossy(&reverse_list_output.stdout).contains(&forward) {
break;
} else {
log::warn!(
"waiting for the port to be forwarded to {}...",
target_device_name
);
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
}
} else {
log::warn!("no running devices detected with HDC; skipping port forwarding");
}
Ok(())
}
fn run_hdc_reverse(
env: &cargo_mobile2::open_harmony::env::Env,
device_serial_no: &str,
remote: &str,
local: &str,
) -> std::io::Result<std::process::Output> {
hdc::hdc(env, ["-t", device_serial_no, "rport", remote, local])
.stdin_file(os_pipe::dup_stdin().unwrap())
.stdout_file(os_pipe::dup_stdout().unwrap())
.stderr_file(os_pipe::dup_stdout().unwrap())
.run()
}
fn hdc_reverse_list(
env: &cargo_mobile2::open_harmony::env::Env,
device_serial_no: &str,
) -> std::io::Result<std::process::Output> {
hdc::hdc(env, ["-t", device_serial_no, "fport", "ls"])
.stdin_file(os_pipe::dup_stdin().unwrap())
.stdout_capture()
.stderr_capture()
.run()
}

View File

@@ -0,0 +1,335 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use serde::Deserialize;
use cargo_mobile2::{
config::app::{App, DEFAULT_ASSET_DIR},
open_harmony::{
config::{
Config as OpenHarmonyConfig, Metadata as OpenHarmonyMetadata, Raw as RawOpenHarmonyConfig,
},
device::Device,
emulator,
env::Env,
hdc,
target::Target,
},
opts::{FilterLevel, NoiseLevel},
os,
util::prompt,
};
use clap::{Parser, Subcommand};
use std::{
env::set_var,
fs::{create_dir_all, write},
thread::sleep,
time::Duration,
};
use sublime_fuzzy::best_match;
use tauri_utils::resources::ResourcePaths;
use super::{
ensure_init, get_app, init::command as init_command, log_finished, read_options, CliOptions,
OptionsHandle, Target as MobileTarget, MIN_DEVICE_MATCH_SCORE,
};
use crate::{
helpers::config::{BundleResources, Config as TauriConfig},
ConfigValue, Result,
};
mod build;
mod dev;
mod dev_eco_studio_script;
pub(crate) mod project;
#[derive(Deserialize)]
pub struct AppConfig {
pub app: AppConfigObject,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppConfigObject {
pub bundle_name: String,
// TODO: impl versioning
//pub version_code: u32,
//pub version_name: String,
}
#[derive(Parser)]
#[clap(
author,
version,
about = "OpenHarmony commands",
subcommand_required(true),
arg_required_else_help(true)
)]
pub struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Debug, Parser)]
#[clap(about = "Initialize OpenHarmony target in the project")]
pub struct InitOptions {
/// Skip prompting for values
#[clap(long, env = "CI")]
ci: bool,
/// Skips installing rust toolchains via rustup
#[clap(long)]
skip_targets_install: bool,
/// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file
///
/// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts.
///
/// Note that a platform-specific file is looked up and merged with the default file by default
/// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json, tauri.ios.conf.json and tauri.ohos.conf.json)
/// but you can use this for more specific use cases such as different build flavors.
#[clap(short, long)]
pub config: Vec<ConfigValue>,
}
#[derive(Subcommand)]
enum Commands {
Init(InitOptions),
Dev(dev::Options),
Build(build::Options),
#[clap(hide(true))]
DevEcoStudioScript(dev_eco_studio_script::Options),
}
pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
match cli.command {
Commands::Init(options) => {
crate::helpers::app_paths::resolve();
init_command(
MobileTarget::OpenHarmony,
options.ci,
false,
options.skip_targets_install,
options.config,
)?
}
Commands::Dev(options) => dev::command(options, noise_level)?,
Commands::Build(options) => build::command(options, noise_level)?,
Commands::DevEcoStudioScript(options) => dev_eco_studio_script::command(options)?,
}
Ok(())
}
pub fn get_config(
app: &App,
_config: &TauriConfig,
features: Option<&Vec<String>>,
cli_options: &CliOptions,
) -> (OpenHarmonyConfig, OpenHarmonyMetadata) {
let mut open_harmony_options = cli_options.clone();
if let Some(features) = features {
open_harmony_options
.features
.get_or_insert(Vec::new())
.extend_from_slice(features);
}
let raw = RawOpenHarmonyConfig {
features: open_harmony_options.features.clone(),
logcat_filter_specs: vec![
"RustStdoutStderr".into(),
format!(
"*:{}",
match cli_options.noise_level {
NoiseLevel::Polite => FilterLevel::Info,
NoiseLevel::LoudAndProud => FilterLevel::Debug,
NoiseLevel::FranklyQuitePedantic => FilterLevel::Verbose,
}
.logcat()
),
],
..Default::default()
};
let config = OpenHarmonyConfig::from_raw(app.clone(), Some(raw)).unwrap();
let metadata = OpenHarmonyMetadata {
supported: true,
cargo_args: Some(open_harmony_options.args),
features: open_harmony_options.features,
..Default::default()
};
set_var(
"WRY_OHOS_PACKAGE",
app.android_identifier_escape_kotlin_keyword(),
);
set_var("TAURI_OHOS_PACKAGE_UNESCAPED", app.identifier());
set_var("WRY_OHOS_LIBRARY", app.lib_name());
set_var("TAURI_OHOS_PROJECT_PATH", config.project_dir());
(config, metadata)
}
fn env() -> Result<Env> {
let env = super::env()?;
cargo_mobile2::open_harmony::env::Env::from_env(env).map_err(Into::into)
}
fn delete_codegen_vars() {
for (k, _) in std::env::vars() {
if k.starts_with("WRY_") && (k.ends_with("CLASS_EXTENSION") || k.ends_with("CLASS_INIT")) {
std::env::remove_var(k);
}
}
}
fn adb_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
let device_list = hdc::device_list(env)
.map_err(|cause| anyhow::anyhow!("Failed to detect connected OpenHarmony devices: {cause}"))?;
if !device_list.is_empty() {
let device = if let Some(t) = target {
let (device, score) = device_list
.into_iter()
.rev()
.map(|d| {
let score = best_match(t, d.name()).map_or(0, |m| m.score());
(d, score)
})
.max_by_key(|(_, score)| *score)
// we already checked the list is not empty
.unwrap();
if score > MIN_DEVICE_MATCH_SCORE {
device
} else {
anyhow::bail!("Could not find an OpenHarmony device matching {t}")
}
} else if device_list.len() > 1 {
let index = prompt::list(
concat!("Detected ", "OpenHarmony", " devices"),
device_list.iter(),
"device",
None,
"Device",
)
.map_err(|cause| anyhow::anyhow!("Failed to prompt for OpenHarmony device: {cause}"))?;
device_list.into_iter().nth(index).unwrap()
} else {
device_list.into_iter().next().unwrap()
};
log::info!(
"Detected connected device: {} with target {:?}",
device,
device.target().triple,
);
Ok(device)
} else {
Err(anyhow::anyhow!("No connected OpenHarmony devices detected"))
}
}
fn emulator_prompt(_env: &'_ Env, target: Option<&str>) -> Result<emulator::Emulator> {
let emulator_list = emulator::hvd_list().unwrap_or_default();
if !emulator_list.is_empty() {
let emulator = if let Some(t) = target {
let (device, score) = emulator_list
.into_iter()
.rev()
.map(|d| {
let score = best_match(t, d.name()).map_or(0, |m| m.score());
(d, score)
})
.max_by_key(|(_, score)| *score)
// we already checked the list is not empty
.unwrap();
if score > MIN_DEVICE_MATCH_SCORE {
device
} else {
anyhow::bail!("Could not find an OpenHarmony Emulator matching {t}")
}
} else if emulator_list.len() > 1 {
let index = prompt::list(
concat!("Detected ", "OpenHarmony", " emulators"),
emulator_list.iter(),
"emulator",
None,
"Emulator",
)
.map_err(|cause| {
anyhow::anyhow!("Failed to prompt for OpenHarmony Emulator device: {cause}")
})?;
emulator_list.into_iter().nth(index).unwrap()
} else {
emulator_list.into_iter().next().unwrap()
};
Ok(emulator)
} else {
Err(anyhow::anyhow!(
"No available OpenHarmony Emulator detected"
))
}
}
fn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
if let Ok(device) = adb_device_prompt(env, target) {
Ok(device)
} else {
let emulator = emulator_prompt(env, target)?;
log::info!("Starting emulator {}", emulator.name());
emulator.start_detached(env)?;
let mut tries = 0;
loop {
sleep(Duration::from_secs(2));
if let Ok(device) = adb_device_prompt(env, Some(emulator.name())) {
return Ok(device);
}
if tries >= 3 {
log::info!("Waiting for emulator to start... (maybe the emulator is unauthorized or offline, run `adb devices` to check)");
} else {
log::info!("Waiting for emulator to start...");
}
tries += 1;
}
}
}
fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
device_prompt(env, None).map(|device| device.target()).ok()
}
fn open_and_wait(config: &OpenHarmonyConfig, env: &Env) -> ! {
log::info!("Opening DevEco Studio");
if let Err(e) = os::open_file_with("DevEco Studio", config.project_dir(), &env.base) {
log::error!("{e}");
}
loop {
sleep(Duration::from_secs(24 * 60 * 60));
}
}
fn inject_resources(config: &OpenHarmonyConfig, tauri_config: &TauriConfig) -> Result<()> {
let asset_dir = config.project_dir().join(DEFAULT_ASSET_DIR);
create_dir_all(&asset_dir)?;
write(
asset_dir.join("tauri.conf.json"),
serde_json::to_string(&tauri_config)?,
)?;
let resources = match &tauri_config.bundle.resources {
Some(BundleResources::List(paths)) => Some(ResourcePaths::new(paths.as_slice(), true)),
Some(BundleResources::Map(map)) => Some(ResourcePaths::from_map(map, true)),
None => None,
};
if let Some(resources) = resources {
for resource in resources.iter() {
let resource = resource?;
let dest = asset_dir.join(resource.target());
crate::helpers::fs::copy_file(resource.path(), dest)?;
}
}
Ok(())
}

View File

@@ -0,0 +1,36 @@
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{helpers::template, Result};
use anyhow::Context;
use cargo_mobile2::{config::app::App, open_harmony::config::Config, os, util};
use handlebars::Handlebars;
use include_dir::{include_dir, Dir};
use std::path::Path;
const TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/mobile/open-harmony");
pub fn gen(
app: &App,
config: &Config,
(handlebars, mut map): (Handlebars, template::JsonMap),
) -> Result<()> {
println!("Generating DevEco Studio project...");
let dest = config.project_dir();
map.insert(
"root-dir-rel",
Path::new(&os::replace_path_separator(
util::relativize_path(app.root_dir(), dest.join(app.name_snake())).into_os_string(),
)),
);
map.insert("root-dir", app.root_dir());
map.insert("windows", cfg!(windows));
template::render(&handlebars, map.inner(), &TEMPLATE_DIR, &dest)
.with_context(|| "failed to process template")?;
Ok(())
}

View File

@@ -0,0 +1,12 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test
/.appanalyzer

View File

@@ -0,0 +1,10 @@
{
"app": {
"bundleName": "{{app.identifier}}",
"vendor": "{{app.publisher}}",
"versionCode": 1,
"versionName": "1.0.0",
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}

View File

@@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "{{app.stylized-name}}"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -0,0 +1,7 @@
{
"layered-image":
{
"background" : "$media:background",
"foreground" : "$media:foreground"
}
}

View File

@@ -0,0 +1,41 @@
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.0.0(12)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
],
"buildModeSet": [
{
"name": "debug",
},
{
"name": "release"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": [
"default"
]
}
]
}
]
}

View File

@@ -0,0 +1,32 @@
{
"files": [
"**/*.ets"
],
"ignore": [
"**/src/ohosTest/**/*",
"**/src/test/**/*",
"**/src/mock/**/*",
"**/node_modules/**/*",
"**/oh_modules/**/*",
"**/build/**/*",
"**/.preview/**/*"
],
"ruleSet": [
"plugin:@performance/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@security/no-unsafe-aes": "error",
"@security/no-unsafe-hash": "error",
"@security/no-unsafe-mac": "warn",
"@security/no-unsafe-dh": "error",
"@security/no-unsafe-dsa": "error",
"@security/no-unsafe-ecdsa": "error",
"@security/no-unsafe-rsa-encrypt": "error",
"@security/no-unsafe-rsa-sign": "error",
"@security/no-unsafe-rsa-key": "error",
"@security/no-unsafe-dsa-key": "error",
"@security/no-unsafe-dh-key": "error",
"@security/no-unsafe-3des": "error"
}
}

View File

@@ -0,0 +1,7 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
/libs

View File

@@ -0,0 +1,39 @@
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": "",
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
},
"nativeLib": {
"debugSymbol": {
"strip": true,
"exclude": []
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}

View File

@@ -0,0 +1,24 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
import { hvigor, HvigorPlugin, HvigorNode } from '@ohos/hvigor';
import { execSync } from 'child_process';
import { resolve } from 'path';
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[tauriPlugin()] /* Custom plugin to extend the functionality of Hvigor. */
}
function tauriPlugin(): HvigorPlugin {
return {
pluginId: 'tauri',
apply(node: HvigorNode) {
const properties = hvigor.getParameter().getProperties();
const target = properties.target || "aarch64";
execSync(`{{tauri-binary}}`, [`{{quote-and-join tauri-binary-args}}`, "--target", target], {
cwd: resolve(__dirname, "{{root-dir-rel}}"),
stdio: "inherit",
shell: true,
});
}
}
}

View File

@@ -0,0 +1,23 @@
# Define project specific obfuscation rules here.
# You can include the obfuscation configuration files in the current module's build-profile.json5.
#
# For more details, see
# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
# Obfuscation options:
# -disable-obfuscation: disable all obfuscations
# -enable-property-obfuscation: obfuscate the property names
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
# -compact: remove unnecessary blank spaces and all line feeds
# -remove-log: remove all console.* statements
# -print-namecache: print the name cache that contains the mapping from the old names to new names
# -apply-namecache: reuse the given cache file
# Keep options:
# -keep-property-name: specifies property names that you want to keep
# -keep-global-name: specifies names that you want to keep in the global scope
-enable-property-obfuscation
-enable-toplevel-obfuscation
-enable-filename-obfuscation
-enable-export-obfuscation

View File

@@ -0,0 +1,27 @@
{
"meta": {
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@ohos-rs/ability@0.2.1": "@ohos-rs/ability@0.2.1",
"libentry.so@src/main/cpp/types/libentry": "libentry.so@src/main/cpp/types/libentry"
},
"packages": {
"@ohos-rs/ability@0.2.1": {
"name": "@ohos-rs/ability",
"version": "0.2.1",
"integrity": "sha512-/IYR/8+TgAn5ij3OT/gRk4e71eQ/+WBFQuADqMRyIUhIEVg/9MhDyWZJmITvm50ay5cMbGtry14b+TN0vwlOBg==",
"resolved": "https://repo.harmonyos.com/ohpm/@ohos-rs/ability/-/ability-0.2.1.har",
"registryType": "ohpm"
},
"libentry.so@src/main/cpp/types/libentry": {
"name": "libentry.so",
"version": "1.0.0",
"resolved": "src/main/cpp/types/libentry",
"registryType": "local"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"libentry.so": "file:./src/main/cpp/types/libentry",
"@ohos-rs/ability": "0.2.1"
}
}

View File

@@ -0,0 +1,15 @@
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(webview_example)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)

View File

@@ -0,0 +1,53 @@
#include "napi/native_api.h"
static napi_value Add(napi_env env, napi_callback_info info)
{
size_t argc = 2;
napi_value args[2] = {nullptr};
napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1);
double value0;
napi_get_value_double(env, args[0], &value0);
double value1;
napi_get_value_double(env, args[1], &value1);
napi_value sum;
napi_create_double(env, value0 + value1, &sum);
return sum;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
{ "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "entry",
.nm_priv = ((void*)0),
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
napi_module_register(&demoModule);
}

View File

@@ -0,0 +1 @@
export const add: (a: number, b: number) => number;

View File

@@ -0,0 +1,6 @@
{
"name": "libentry.so",
"types": "./Index.d.ts",
"version": "1.0.0",
"description": "Please describe the basic information."
}

View File

@@ -0,0 +1,21 @@
import { RustAbility } from '@ohos-rs/ability'
import Want from '@ohos.app.ability.Want'
import { AbilityConstant } from '@kit.AbilityKit';
import window from '@ohos.window';
export default class EntryAbility extends RustAbility {
// change to dynamic library name
public moduleName: string = "{{app.name}}"
public defaultPage: boolean = true;
public mode: 'xcomponent' | 'webview' = 'webview'
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
super.onCreate(want, launchParam);
}
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
const window = windowStage.getMainWindowSync();
await window.setWindowLayoutFullScreen(false);
super.onWindowStageCreate(windowStage);
}
}

View File

@@ -0,0 +1,16 @@
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
const DOMAIN = 0x0000;
export default class EntryBackupAbility extends BackupExtensionAbility {
async onBackup() {
hilog.info(DOMAIN, 'testTag', 'onBackup ok');
await Promise.resolve();
}
async onRestore(bundleVersion: BundleVersion) {
hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
await Promise.resolve();
}
}

View File

@@ -0,0 +1,26 @@
import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so';
const DOMAIN = 0x0000;
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.message = 'Welcome';
hilog.info(DOMAIN, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
})
}
.width('100%')
}
.height('100%')
}
}

View File

@@ -0,0 +1,57 @@
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}

View File

@@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"float": [
{
"name": "page_text_font_size",
"value": "50fp"
}
]
}

View File

@@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -0,0 +1,7 @@
{
"layered-image":
{
"background" : "$media:background",
"foreground" : "$media:foreground"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,3 @@
{
"allowToBackupRestore": true
}

View File

@@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#000000"
}
]
}

View File

@@ -0,0 +1,7 @@
const NativeMock: Record<string, Object> = {
'add': (a: number, b: number) => {
return a + b;
},
};
export default NativeMock;

View File

@@ -0,0 +1,5 @@
{
"libentry.so": {
"source": "src/mock/Libentry.mock.ets"
}
}

View File

@@ -0,0 +1,35 @@
import { hilog } from '@kit.PerformanceAnalysisKit';
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
})
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
})
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
})
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
})
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
})
})
}

View File

@@ -0,0 +1,5 @@
import abilityTest from './Ability.test';
export default function testsuite() {
abilityTest();
}

View File

@@ -0,0 +1,13 @@
{
"module": {
"name": "entry_test",
"type": "feature",
"deviceTypes": [
"phone",
"tablet",
"2in1"
],
"deliveryWithInstall": true,
"installationFree": false
}
}

View File

@@ -0,0 +1,5 @@
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}

View File

@@ -0,0 +1,33 @@
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
export default function localUnitTest() {
describe('localUnitTest', () => {
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
beforeAll(() => {
// Presets an action, which is performed only once before all test cases of the test suite start.
// This API supports only one parameter: preset action function.
});
beforeEach(() => {
// Presets an action, which is performed before each unit test case starts.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: preset action function.
});
afterEach(() => {
// Presets a clear action, which is performed after each unit test case ends.
// The number of execution times is the same as the number of test cases defined by **it**.
// This API supports only one parameter: clear action function.
});
afterAll(() => {
// Presets a clear action, which is performed after all test cases of the test suite end.
// This API supports only one parameter: clear action function.
});
it('assertContain', 0, () => {
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
let a = 'abc';
let b = 'b';
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}

View File

@@ -0,0 +1,22 @@
{
"modelVersion": "5.0.3",
"dependencies": {
},
"execution": {
// "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
// "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
// "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
// "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
// "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
},
"logging": {
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
},
"debugging": {
// "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
},
"nodeOptions": {
// "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
// "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
}
}

View File

@@ -0,0 +1,6 @@
import { appTasks } from '@ohos/hvigor-ohos-plugin';
export default {
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
}

View File

@@ -0,0 +1,28 @@
{
"meta": {
"stableOrder": true,
"enableUnifiedLockfile": false
},
"lockfileVersion": 3,
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
"specifiers": {
"@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0",
"@ohos/hypium@1.0.21": "@ohos/hypium@1.0.21"
},
"packages": {
"@ohos/hamock@1.0.0": {
"name": "@ohos/hamock",
"version": "1.0.0",
"integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==",
"resolved": "https://repo.harmonyos.com/ohpm/@ohos/hamock/-/hamock-1.0.0.har",
"registryType": "ohpm"
},
"@ohos/hypium@1.0.21": {
"name": "@ohos/hypium",
"version": "1.0.21",
"integrity": "sha512-iyKGMXxE+9PpCkqEwu0VykN/7hNpb+QOeIuHwkmZnxOpI+dFZt6yhPB7k89EgV1MiSK/ieV/hMjr5Z2mWwRfMQ==",
"resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.21.har",
"registryType": "ohpm"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"modelVersion": "5.0.3",
"description": "Please describe the basic information.",
"dependencies": {
},
"devDependencies": {
"@ohos/hypium": "1.0.21",
"@ohos/hamock": "1.0.0"
}
}

View File

@@ -89,6 +89,16 @@ pub fn entry_point(_attributes: TokenStream, item: TokenStream) -> TokenStream {
pub extern "C" fn start_app() {
_start_app()
}
#[cfg(target_env = "ohos")]
use ::tauri::ohos::*;
#[cfg(target_env = "ohos")]
#[::tauri::ohos::openharmony_ability_derive::ability(webview)]
pub fn openharmony(app: ::tauri::ohos::openharmony_ability::OpenHarmonyApp) {
::tauri::ohos::APP.lock().unwrap().replace(app);
_start_app()
}
)
.into()
}

View File

@@ -64,10 +64,11 @@ pub(crate) fn setup(
android_path: Option<PathBuf>,
#[allow(unused_variables)] ios_path: Option<PathBuf>,
) -> Result<()> {
let target_os = build_var("CARGO_CFG_TARGET_OS")?;
let mobile = target_os == "android" || target_os == "ios";
cfg_alias("mobile", mobile);
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let mobile = target_os == "ios" || target_os == "android" || target_env == "ohos";
cfg_alias("desktop", !mobile);
cfg_alias("mobile", mobile);
match target_os.as_str() {
"android" => {

View File

@@ -41,7 +41,7 @@ once_cell = "1.20"
version = "0.61"
features = ["Win32_Foundation", "Win32_Graphics_Dwm"]
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
[target.'cfg(all(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"), not(target_env = "ohos")))'.dependencies]
gtk = { version = "0.18", features = ["v3_24"] }
webkit2gtk = { version = "=2.0", features = ["v2_40"] }
percent-encoding = "2"

View File

@@ -13,7 +13,8 @@ fn alias(alias: &str, has_feature: bool) {
fn main() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let mobile = target_os == "ios" || target_os == "android" || target_env == "ohos";
alias("desktop", !mobile);
alias("mobile", mobile);
}

View File

@@ -35,7 +35,7 @@ use tauri_runtime::{
use objc2::rc::Retained;
#[cfg(target_os = "macos")]
use tao::platform::macos::{EventLoopWindowTargetExtMacOS, WindowBuilderExtMacOS};
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
#[cfg(windows)]
use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
@@ -100,7 +100,8 @@ use wry::{
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
target_os = "android",
target_env = "ohos",
)))]
use wry::{WebViewBuilderExtUnix, WebViewExtUnix};
@@ -110,6 +111,8 @@ pub use tao::platform::ios::WindowExtIOS;
pub use tao::platform::macos::{
ActivationPolicy as TaoActivationPolicy, EventLoopExtMacOS, WindowExtMacOS,
};
#[cfg(target_env = "ohos")]
pub use tao::platform::ohos::EventLoopBuilderExtOpenHarmony;
#[cfg(target_os = "macos")]
use tauri_runtime::ActivationPolicy;
@@ -858,7 +861,7 @@ impl WindowBuilder for WindowBuilderWrapper {
");
}
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
// Mouse event is disabled on Linux since sudden event bursts could block event loop.
window.inner = window.inner.with_cursor_moved_event(false);
@@ -1114,12 +1117,15 @@ impl WindowBuilder for WindowBuilderWrapper {
self
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
fn transient_for(mut self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {
self.inner = self.inner.with_transient_for(parent);
@@ -1190,13 +1196,18 @@ impl WindowBuilder for WindowBuilderWrapper {
self
}
#[cfg(any(windows, target_os = "linux"))]
#[cfg(all(any(windows, target_os = "linux"), not(target_env = "ohos")))]
fn skip_taskbar(mut self, skip: bool) -> Self {
self.inner = self.inner.with_skip_taskbar(skip);
self
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "android",
target_env = "ohos"
))]
fn skip_taskbar(self, _skip: bool) -> Self {
self
}
@@ -1236,38 +1247,50 @@ impl WindowBuilder for WindowBuilderWrapper {
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub struct GtkWindow(pub gtk::ApplicationWindow);
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for GtkWindow {}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub struct GtkBox(pub gtk::Box);
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for GtkBox {}
@@ -1309,20 +1332,26 @@ pub enum WindowMessage {
PrimaryMonitor(Sender<Option<MonitorHandle>>),
MonitorFromPoint(Sender<Option<MonitorHandle>>, (f64, f64)),
AvailableMonitors(Sender<Vec<MonitorHandle>>),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
GtkWindow(Sender<GtkWindow>),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
GtkBox(Sender<GtkBox>),
RawWindowHandle(Sender<std::result::Result<SendRawWindowHandle, raw_window_handle::HandleError>>),
@@ -1964,23 +1993,29 @@ impl<T: UserEvent> WindowDispatch<T> for WryWindowDispatcher<T> {
window_getter!(self, WindowMessage::IsAlwaysOnTop)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
fn gtk_window(&self) -> Result<gtk::ApplicationWindow> {
window_getter!(self, WindowMessage::GtkWindow).map(|w| w.0)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
fn default_vbox(&self) -> Result<gtk::Box> {
window_getter!(self, WindowMessage::GtkBox).map(|w| w.0)
@@ -2742,12 +2777,20 @@ impl<T: UserEvent> Wry<T> {
event_loop_builder.with_msg_hook(hook);
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(target_env = "ohos")]
{
event_loop_builder.with_openharmony_app(args.app);
}
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
if let Some(app_id) = args.app_id {
use tao::platform::unix::EventLoopBuilderExtUnix;
@@ -2799,12 +2842,15 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
fn new(args: RuntimeInitArgs) -> Result<Self> {
Self::init_with_builder(EventLoopBuilder::<Message<T>>::with_user_event(), args)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
fn new_any_thread(args: RuntimeInitArgs) -> Result<Self> {
use tao::platform::unix::EventLoopBuilderExtUnix;
@@ -2821,6 +2867,11 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
Self::init_with_builder(event_loop_builder, args)
}
#[cfg(target_env = "ohos")]
fn new_any_thread(args: RuntimeInitArgs) -> Result<Self> {
unimplemented!()
}
fn create_proxy(&self) -> EventProxy<T> {
EventProxy(self.event_loop.create_proxy())
}
@@ -3264,20 +3315,26 @@ fn handle_user_message<T: UserEvent>(
WindowMessage::AvailableMonitors(tx) => {
tx.send(window.available_monitors().collect()).unwrap()
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
WindowMessage::GtkWindow(tx) => tx.send(GtkWindow(window.gtk_window().clone())).unwrap(),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
WindowMessage::GtkBox(tx) => tx
.send(GtkBox(window.default_vbox().unwrap().clone()))
@@ -3401,7 +3458,7 @@ fn handle_user_message<T: UserEvent>(
}
#[allow(unused_variables)]
WindowMessage::SetSkipTaskbar(skip) => {
#[cfg(any(windows, target_os = "linux"))]
#[cfg(all(any(windows, target_os = "linux"), not(target_env = "ohos")))]
let _ = window.set_skip_taskbar(skip);
}
WindowMessage::SetCursorGrab(grab) => {
@@ -3446,12 +3503,15 @@ fn handle_user_message<T: UserEvent>(
#[cfg(target_os = "macos")]
window.set_badge_label(_count.map(|x| x.to_string()));
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
window.set_badge_count(_count, _desktop_filename);
}
@@ -3504,14 +3564,17 @@ fn handle_user_message<T: UserEvent>(
}
}
Message::Webview(window_id, webview_id, webview_message) => {
#[cfg(any(
target_os = "macos",
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "macos",
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
if let WebviewMessage::Reparent(new_parent_window_id, tx) = webview_message {
let webview_handle = windows.0.borrow_mut().get_mut(&window_id).and_then(|w| {
@@ -3536,12 +3599,15 @@ fn handle_user_message<T: UserEvent>(
#[cfg(windows)]
let reparent_result = { webview.inner.reparent(new_parent_window.hwnd()) };
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
let reparent_result = {
if let Some(container) = new_parent_window.default_vbox() {
@@ -3804,12 +3870,15 @@ fn handle_user_message<T: UserEvent>(
}
},
WebviewMessage::WithWebview(f) => {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
{
f(webview.webview());
@@ -4361,6 +4430,7 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
}
}
#[cfg(not(target_env = "ohos"))]
if let Some(margin) = window_builder.prevent_overflow {
let work_area = monitor.work_area();
let margin = margin.to_physical::<u32>(scale_factor);
@@ -4420,20 +4490,26 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(
let raw = RawWindow {
#[cfg(windows)]
hwnd: window.hwnd(),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
gtk_window: window.gtk_window(),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
default_vbox: window.default_vbox(),
_marker: &std::marker::PhantomData,
@@ -4708,12 +4784,15 @@ You may have it installed on another user account, but it is not available for t
wry::NewWindowResponse::Create {
#[cfg(target_os = "macos")]
webview: wry::WebViewExtMacOS::webview(&*webview).as_super().into(),
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
webview: webview.webview(),
#[cfg(windows)]
@@ -4838,13 +4917,16 @@ You may have it installed on another user account, but it is not available for t
.with_browser_extensions_enabled(webview_attributes.browser_extensions_enabled);
}
#[cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
{
if let Some(path) = &webview_attributes.extensions_path {
@@ -4852,12 +4934,15 @@ You may have it installed on another user account, but it is not available for t
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
{
if let Some(related_view) = webview_attributes.related_view {
@@ -4907,12 +4992,15 @@ You may have it installed on another user account, but it is not available for t
for (scheme, protocol) in uri_scheme_protocols {
// on Linux the custom protocols are associated with the web context
// and you cannot register a scheme more than once
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
{
if web_context.registered_custom_protocols.contains(&scheme) {
@@ -4959,7 +5047,8 @@ You may have it installed on another user account, but it is not available for t
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
target_os = "android",
target_env = "ohos"
)))]
WebviewKind::WindowChild => {
// only way to account for menu bar height, and also works for multiwebviews :)
@@ -4970,7 +5059,8 @@ You may have it installed on another user account, but it is not available for t
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
target_os = "android",
target_env = "ohos"
))]
WebviewKind::WindowChild => webview_builder.build_as_child(&window),
WebviewKind::WindowContent => {
@@ -4978,14 +5068,16 @@ You may have it installed on another user account, but it is not available for t
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
target_os = "android",
target_env = "ohos"
))]
let builder = webview_builder.build(&window);
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
target_os = "android",
target_env = "ohos"
)))]
let builder = {
let vbox = window.default_vbox().unwrap();
@@ -4997,12 +5089,15 @@ You may have it installed on another user account, but it is not available for t
.map_err(|e| Error::CreateWebview(Box::new(e)))?;
if kind == WebviewKind::WindowContent {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
undecorated_resizing::attach_resize_handler(&webview);
#[cfg(windows)]

View File

@@ -4,12 +4,15 @@
use tauri_runtime::dpi::PhysicalRect;
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
mod linux;
#[cfg(target_os = "macos")]

View File

@@ -2,13 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![cfg(any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#![cfg(all(
any(
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
const CLIENT: isize = 0b0000;

View File

@@ -2,17 +2,25 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
mod imp {
pub type Webview = webkit2gtk::WebView;
}
#[cfg(target_env = "ohos")]
mod imp {
pub type Webview = ();
}
#[cfg(target_vendor = "apple")]
mod imp {
use std::ffi::c_void;

View File

@@ -3,12 +3,15 @@
// SPDX-License-Identifier: MIT
use gtk::prelude::*;
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
use tao::platform::unix::WindowExtUnix;

View File

@@ -2,12 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
mod linux;
#[cfg(target_os = "macos")]

View File

@@ -44,13 +44,16 @@ features = ["Win32_Foundation", "Win32_System_WinRT"]
[target."cfg(windows)".dependencies]
webview2-com = "0.38"
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
[target.'cfg(all(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"), not(target_env = "ohos")))'.dependencies]
gtk = { version = "0.18", features = ["v3_24"] }
webkit2gtk = { version = "=2.0", features = ["v2_40"] }
[target."cfg(target_os = \"android\")".dependencies]
jni = "0.21"
[target.'cfg(target_env = "ohos")'.dependencies]
openharmony-ability = { version = "0.2" }
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2 = "0.6"
objc2-ui-kit = { version = "0.3.0", default-features = false, features = [

View File

@@ -13,7 +13,8 @@ fn alias(alias: &str, has_feature: bool) {
fn main() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let mobile = target_os == "ios" || target_os == "android" || target_env == "ohos";
alias("desktop", !mobile);
alias("mobile", mobile);
}

View File

@@ -375,18 +375,22 @@ pub trait EventLoopProxy<T: UserEvent>: Debug + Clone + Send + Sync {
fn send_event(&self, event: T) -> Result<()>;
}
#[derive(Default)]
pub struct RuntimeInitArgs {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub app_id: Option<String>,
#[cfg(windows)]
pub msg_hook: Option<Box<dyn FnMut(*const std::ffi::c_void) -> bool + 'static>>,
#[cfg(target_env = "ohos")]
pub app: openharmony_ability::OpenHarmonyApp,
}
/// The webview runtime interface.
@@ -716,22 +720,28 @@ pub trait WindowDispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 's
fn available_monitors(&self) -> Result<Vec<Monitor>>;
/// Returns the `ApplicationWindow` from gtk crate that is used by this window.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
fn gtk_window(&self) -> Result<gtk::ApplicationWindow>;
/// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
fn default_vbox(&self) -> Result<gtk::Box>;

View File

@@ -4,7 +4,7 @@
//! A layer between raw [`Runtime`] webviews and Tauri.
//!
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(not(any(target_os = "android", target_os = "ios", target_env = "ohos")))]
use crate::window::WindowId;
use crate::{window::is_label_valid, Rect, Runtime, UserEvent};
@@ -90,12 +90,15 @@ pub struct NewWindowOpener {
/// The instance of the webview that initiated the new window request.
///
/// This must be set as the related view of the new webview. See [`WebviewAttributes::related_view`].
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub webview: webkit2gtk::WebView,
/// The instance of the webview that initiated the new window request.
@@ -164,7 +167,7 @@ pub enum NewWindowResponse {
///
/// **Linux**: The webview must be related to the caller webview. See [`WebviewAttributes::related_view`].
/// **Windows**: The webview must use the same environment as the caller webview. See [`WebviewAttributes::environment`].
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(not(any(target_os = "android", target_os = "ios", target_env = "ohos")))]
Create { window_id: WindowId },
/// Deny the window from being opened.
Deny,
@@ -361,12 +364,15 @@ pub struct WebviewAttributes {
/// Creates a new webview sharing the same web process with the provided webview.
/// Useful if you need to link a webview to another, for instance when using the [`PendingWebview::new_window_handler`].
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub related_view: Option<webkit2gtk::WebView>,
@@ -482,12 +488,15 @@ impl WebviewAttributes {
input_accessory_view_builder: None,
#[cfg(windows)]
environment: None,
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
related_view: None,
#[cfg(target_os = "macos")]

View File

@@ -425,12 +425,15 @@ pub trait WindowBuilder: WindowBuilderBase {
/// Sets the window to be created transient for parent.
///
/// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self;
@@ -601,20 +604,26 @@ impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
pub struct RawWindow<'a> {
#[cfg(windows)]
pub hwnd: isize,
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
pub gtk_window: &'a gtk::ApplicationWindow,
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
pub default_vbox: Option<&'a gtk::Box>,
pub _marker: &'a PhantomData<()>,

View File

@@ -227,6 +227,13 @@
"enum": [
"iOS"
]
},
{
"description": "OpenHarmony.",
"type": "string",
"enum": [
"openHarmony"
]
}
]
}

View File

@@ -1578,6 +1578,13 @@
"enum": [
"iOS"
]
},
{
"description": "OpenHarmony.",
"type": "string",
"enum": [
"openHarmony"
]
}
]
},

View File

@@ -198,6 +198,13 @@
"enum": [
"iOS"
]
},
{
"description": "OpenHarmony.",
"type": "string",
"enum": [
"openHarmony"
]
}
]
}

View File

@@ -56,6 +56,7 @@ impl ConfigFormat {
Target::Linux => "tauri.linux.conf.json",
Target::Android => "tauri.android.conf.json",
Target::Ios => "tauri.ios.conf.json",
Target::OpenHarmony => "tauri.ohos.conf.json",
},
Self::Json5 => match target {
Target::MacOS => "tauri.macos.conf.json5",
@@ -63,6 +64,7 @@ impl ConfigFormat {
Target::Linux => "tauri.linux.conf.json5",
Target::Android => "tauri.android.conf.json5",
Target::Ios => "tauri.ios.conf.json5",
Target::OpenHarmony => "tauri.ohos.conf.json5",
},
Self::Toml => match target {
Target::MacOS => "Tauri.macos.toml",
@@ -70,6 +72,7 @@ impl ConfigFormat {
Target::Linux => "Tauri.linux.toml",
Target::Android => "Tauri.android.toml",
Target::Ios => "Tauri.ios.toml",
Target::OpenHarmony => "Tauri.ohos.toml",
},
}
}
@@ -172,6 +175,7 @@ pub fn is_configuration_file(target: Target, path: &Path) -> bool {
/// - `tauri.windows.conf.json[5]` or `Tauri.windows.toml` on Windows
/// - `tauri.android.conf.json[5]` or `Tauri.android.toml` on Android
/// - `tauri.ios.conf.json[5]` or `Tauri.ios.toml` on iOS
/// - `tauri.ohos.conf.json[5]` or `Tauri.ohos.toml` on OpenHarmony
/// Merging the configurations using [JSON Merge Patch (RFC 7396)].
///
/// Returns the raw configuration and used config paths.

View File

@@ -270,10 +270,10 @@ impl Display for Theme {
#[non_exhaustive]
pub struct Env {
/// The APPIMAGE environment variable.
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
pub appimage: Option<std::ffi::OsString>,
/// The APPDIR environment variable.
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
pub appdir: Option<std::ffi::OsString>,
/// The command line arguments of the current process.
pub args_os: Vec<OsString>,
@@ -283,12 +283,12 @@ pub struct Env {
impl Default for Env {
fn default() -> Self {
let args_os = std::env::args_os().collect();
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
let env = Self {
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
appimage: std::env::var_os("APPIMAGE"),
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
appdir: std::env::var_os("APPDIR"),
args_os,
};
@@ -311,7 +311,7 @@ impl Default for Env {
}
env
}
#[cfg(not(target_os = "linux"))]
#[cfg(any(target_env = "ohos", not(target_os = "linux")))]
{
Self { args_os }
}

View File

@@ -37,6 +37,8 @@ pub enum Target {
/// iOS.
#[serde(rename = "iOS")]
Ios,
/// OpenHarmony.
OpenHarmony,
}
impl Display for Target {
@@ -50,6 +52,7 @@ impl Display for Target {
Self::Linux => "linux",
Self::Android => "android",
Self::Ios => "iOS",
Self::OpenHarmony => "open-harmony",
}
)
}
@@ -308,7 +311,7 @@ fn resource_dir_from<P: AsRef<std::path::Path>>(
#[allow(unused_mut, unused_assignments)]
let mut res = Err(crate::Error::UnsupportedPlatform);
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
// (canonicalize checks for existence, so there's no need for an extra check)
res = if let Ok(bundle_dir) = exe_dir
@@ -393,6 +396,7 @@ mod build {
Self::Windows => quote! { #prefix::Windows },
Self::Android => quote! { #prefix::Android },
Self::Ios => quote! { #prefix::Ios },
Self::OpenHarmony => quote! { #prefix::OpenHarmony },
});
}
}
@@ -432,7 +436,7 @@ mod tests {
let resource_dir = super::resource_dir_from(&path, &package_info, &env);
#[cfg(target_os = "macos")]
assert!(resource_dir.is_err());
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
assert_eq!(resource_dir.unwrap(), PathBuf::from("/usr/lib/MyApp"));
#[cfg(windows)]
assert_eq!(resource_dir.unwrap(), path.parent().unwrap());

View File

@@ -87,7 +87,7 @@ specta = { version = "^2.0.0-rc.16", optional = true, default-features = false,
cookie = "0.18"
# desktop
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows", target_os = "macos"))'.dependencies]
[target.'cfg(all(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows", target_os = "macos"), not(target_env = "ohos")))'.dependencies]
muda = { version = "0.17", default-features = false, features = [
"serde",
"gtk",
@@ -97,7 +97,7 @@ tray-icon = { version = "0.21", default-features = false, features = [
], optional = true }
# linux
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
[target.'cfg(all(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"), not(target_env = "ohos")))'.dependencies]
gtk = { version = "0.18", features = ["v3_24"] }
webkit2gtk = { version = "=2.0.1", features = ["v2_40"], optional = true }
@@ -141,7 +141,7 @@ windows = { version = "0.61", features = [
] }
# mobile
[target.'cfg(any(target_os = "android", all(target_vendor = "apple", not(target_os = "macos"))))'.dependencies]
[target.'cfg(any(target_os = "android", target_env = "ohos", all(target_vendor = "apple", not(target_os = "macos"))))'.dependencies]
bytes = { version = "1", features = ["serde"] }
reqwest = { version = "0.12", default-features = false, features = [
"json",
@@ -149,6 +149,11 @@ reqwest = { version = "0.12", default-features = false, features = [
] }
[target.'cfg(target_env = "ohos")'.dependencies]
openharmony-ability = { version = "0.2" }
openharmony-ability-derive = { version = "0.2" }
# android
[target.'cfg(target_os = "android")'.dependencies]
jni = "0.21"

View File

@@ -255,7 +255,8 @@ fn main() {
println!("cargo:dev={dev}");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let mobile = target_os == "ios" || target_os == "android";
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let mobile = target_os == "ios" || target_os == "android" || target_env == "ohos";
alias("desktop", !mobile);
alias("mobile", mobile);

View File

@@ -2121,12 +2121,15 @@ tauri::Builder::default()
self.invoke_key,
));
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
let app_id = if manager.config.app.enable_gtk_app_id {
Some(manager.config.identifier.clone())
@@ -2135,12 +2138,15 @@ tauri::Builder::default()
};
let runtime_args = RuntimeInitArgs {
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
app_id,
@@ -2163,6 +2169,13 @@ tauri::Builder::default()
}
}))
},
#[cfg(target_env = "ohos")]
app: crate::ohos::APP
.lock()
.unwrap()
.take()
.expect("OpenHarmony app instance not initialized"),
};
#[cfg(any(windows, target_os = "linux"))]

View File

@@ -83,6 +83,9 @@ pub use url::Url;
pub(crate) mod app;
pub mod async_runtime;
#[doc(hidden)]
#[cfg(target_env = "ohos")]
pub mod ohos;
mod error;
mod event;
pub mod ipc;

View File

@@ -56,12 +56,15 @@ impl<R: Runtime> ContextMenuBase for Menu<R> {
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
if let Ok(w) = window.gtk_window() {
self_

View File

@@ -49,12 +49,15 @@ impl<R: Runtime> ContextMenuBase for Submenu<R> {
}
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
if let Ok(w) = window.gtk_window() {
self_

6
crates/tauri/src/ohos.rs Normal file
View File

@@ -0,0 +1,6 @@
use std::sync::Mutex;
pub use openharmony_ability;
pub use openharmony_ability_derive;
pub static APP: Mutex<Option<openharmony_ability::OpenHarmonyApp>> = Mutex::new(None);

View File

@@ -342,6 +342,17 @@ impl<R: Runtime> PluginHandle<R> {
}
}
#[cfg(target_env = "ohos")]
pub(crate) fn run_command<R: Runtime, C: AsRef<str>, F: FnOnce(PluginResponse) + Send + 'static>(
_name: &str,
_handle: &AppHandle<R>,
_command: C,
_payload: serde_json::Value,
_handler: F,
) -> Result<(), PluginInvokeError> {
unimplemented!()
}
#[cfg(target_os = "ios")]
pub(crate) fn run_command<R: Runtime, C: AsRef<str>, F: FnOnce(PluginResponse) + Send + 'static>(
name: &str,

View File

@@ -47,7 +47,7 @@ use std::path::PathBuf;
/// [AppImage]: https://appimage.org/
pub fn current_binary(_env: &Env) -> std::io::Result<PathBuf> {
// if we are running from an AppImage, we ONLY want the set AppImage path
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
if let Some(app_image_path) = &_env.appimage {
return Ok(PathBuf::from(app_image_path));
}

View File

@@ -220,7 +220,7 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
fn display_handle(
&self,
) -> std::result::Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
return Ok(unsafe {
raw_window_handle::DisplayHandle::borrow_raw(raw_window_handle::RawDisplayHandle::Xlib(
raw_window_handle::XlibDisplayHandle::new(None, 0),
@@ -483,12 +483,15 @@ impl WindowBuilder for MockWindowBuilder {
self
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {
self
@@ -774,23 +777,29 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
Ok(Theme::Light)
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
fn gtk_window(&self) -> Result<gtk::ApplicationWindow> {
unimplemented!()
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
fn default_vbox(&self) -> Result<gtk::Box> {
unimplemented!()
@@ -799,7 +808,7 @@ impl<T: UserEvent> WindowDispatch<T> for MockWindowDispatcher {
fn window_handle(
&self,
) -> std::result::Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
return unsafe {
Ok(raw_window_handle::WindowHandle::borrow_raw(
raw_window_handle::RawWindowHandle::Xlib(raw_window_handle::XlibWindowHandle::new(0)),

View File

@@ -562,7 +562,7 @@ impl<R: Runtime> TrayIcon<R> {
pub fn set_temp_dir_path<P: AsRef<Path>>(&self, path: Option<P>) -> crate::Result<()> {
#[allow(unused)]
let p = path.map(|p| p.as_ref().to_path_buf());
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
run_item_main_thread!(self, |self_: Self| self_.inner.set_temp_dir_path(p))?;
Ok(())
}

View File

@@ -153,12 +153,15 @@ pub struct PlatformWebview(tauri_runtime_wry::Webview);
#[cfg(feature = "wry")]
impl PlatformWebview {
/// Returns [`webkit2gtk::WebView`] handle.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
#[cfg_attr(
docsrs,
@@ -1195,7 +1198,8 @@ fn main() {
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
)
),
not(target_env = "ohos")
))]
pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {
self.webview_attributes.related_view.replace(related_view);
@@ -1548,7 +1552,7 @@ tauri::Builder::default()
.setup(|app| {
let main_webview = app.get_webview("main").unwrap();
main_webview.with_webview(|webview| {
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(target_env = "ohos")))]
{
// see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>
// and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>

View File

@@ -788,12 +788,15 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
/// Sets the window to be created transient for parent.
///
/// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn transient_for(mut self, parent: &WebviewWindow<R>) -> crate::Result<Self> {
self.window_builder = self.window_builder.transient_for(&parent.window)?;
@@ -803,12 +806,15 @@ impl<'a, R: Runtime, M: Manager<R>> WebviewWindowBuilder<'a, R, M> {
/// Sets the window to be created transient for parent.
///
/// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
#[must_use]
pub fn transient_for_raw(mut self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {
@@ -1284,7 +1290,8 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
)
),
not(target_env = "ohos")
))]
pub fn with_related_view(mut self, related_view: webkit2gtk::WebView) -> Self {
self.webview_builder = self.webview_builder.with_related_view(related_view);
@@ -1306,14 +1313,17 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
/// Set the window features.
/// Useful if you need to share the same window features, for instance when using the [`Self::on_new_window`].
#[cfg(any(
target_os = "macos",
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "macos",
windows,
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn window_features(mut self, features: NewWindowFeatures) -> Self {
if let Some(position) = features.position() {
@@ -1346,7 +1356,8 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
)
),
not(target_env = "ohos")
))]
{
self.webview_builder = self
@@ -1761,12 +1772,15 @@ impl<R: Runtime> WebviewWindow<R> {
/// Returns the `ApplicationWindow` from gtk crate that is used by this window.
///
/// Note that this type can only be used on the main thread.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn gtk_window(&self) -> crate::Result<gtk::ApplicationWindow> {
self.window.gtk_window()
@@ -1775,12 +1789,15 @@ impl<R: Runtime> WebviewWindow<R> {
/// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.
///
/// Note that this type can only be used on the main thread.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn default_vbox(&self) -> crate::Result<gtk::Box> {
self.window.default_vbox()
@@ -2214,7 +2231,7 @@ impl<R: Runtime> WebviewWindow<R> {
/// .setup(|app| {
/// let main_webview = app.get_webview_window("main").unwrap();
/// main_webview.with_webview(|webview| {
/// #[cfg(target_os = "linux")]
/// #[cfg(all(target_os = "linux", not(target_env = "ohos")))]
/// {
/// // see <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/struct.WebView.html>
/// // and <https://docs.rs/webkit2gtk/2.0.0/webkit2gtk/trait.WebViewExt.html>

View File

@@ -730,12 +730,15 @@ impl<'a, R: Runtime, M: Manager<R>> WindowBuilder<'a, R, M> {
self.window_builder = self.window_builder.owner(parent.hwnd()?);
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
{
self.window_builder = self.window_builder.transient_for(&parent.gtk_window()?);
@@ -811,12 +814,15 @@ impl<'a, R: Runtime, M: Manager<R>> WindowBuilder<'a, R, M> {
/// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
///
/// **Note:** This is a low level API. See [`Self::parent`] for a higher level wrapper for Tauri windows.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn transient_for(mut self, parent: &Window<R>) -> crate::Result<Self> {
self.window_builder = self.window_builder.transient_for(&parent.gtk_window()?);
@@ -828,12 +834,15 @@ impl<'a, R: Runtime, M: Manager<R>> WindowBuilder<'a, R, M> {
/// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
///
/// **Note:** This is a low level API. See [`Self::parent`] and [`Self::transient_for`] for higher level wrappers for Tauri windows.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
#[must_use]
pub fn transient_for_raw(mut self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self {
@@ -1213,12 +1222,15 @@ tauri::Builder::default()
let _ = unsafe { menu_.inner().init_for_hwnd_with_theme(hwnd.0 as _, theme) };
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
),
not(target_env = "ohos")
))]
if let (Ok(gtk_window), Ok(gtk_box)) = (window.gtk_window(), window.default_vbox()) {
let _ = menu_
@@ -1594,12 +1606,15 @@ impl<R: Runtime> Window<R> {
/// Returns the `ApplicationWindow` from gtk crate that is used by this window.
///
/// Note that this type can only be used on the main thread.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn gtk_window(&self) -> crate::Result<gtk::ApplicationWindow> {
self.window.dispatcher.gtk_window().map_err(Into::into)
@@ -1608,12 +1623,15 @@ impl<R: Runtime> Window<R> {
/// Returns the vertical [`gtk::Box`] that is added by default as the sole child of this window.
///
/// Note that this type can only be used on the main thread.
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
#[cfg(all(
any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
),
not(target_env = "ohos")
))]
pub fn default_vbox(&self) -> crate::Result<gtk::Box> {
self.window.dispatcher.default_vbox().map_err(Into::into)

Some files were not shown because too many files have changed in this diff Show More