mirror of https://github.com/tauri-apps/tauri
feat: Add support for deep links (#8680)
* initial windows impl * macos * adapt windows impl to config changes for macos * debian * add missing x-scheme-handler prefix * bundle xdg-mime * typo * revert messed up fmt * rm pnpm lock * rm todo * Update core/tauri-utils/src/config.rs Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com> * Update core/tauri-utils/src/config.rs Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com> * &Option<> -> Option<&> * DL0 -> R7 --------- Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
This commit is contained in:
parent
57e3d43d96
commit
38b8e67237
|
@ -1293,7 +1293,7 @@
|
|||
]
|
||||
},
|
||||
"role": {
|
||||
"description": "The app’s role with respect to the type. Maps to `CFBundleTypeRole` on macOS.",
|
||||
"description": "The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.",
|
||||
"default": "Editor",
|
||||
"allOf": [
|
||||
{
|
||||
|
|
|
@ -833,7 +833,7 @@ pub struct FileAssociation {
|
|||
pub name: Option<String>,
|
||||
/// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.
|
||||
pub description: Option<String>,
|
||||
/// The app’s role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
|
||||
/// The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.
|
||||
#[serde(default)]
|
||||
pub role: BundleTypeRole,
|
||||
/// The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.
|
||||
|
@ -841,6 +841,20 @@ pub struct FileAssociation {
|
|||
pub mime_type: Option<String>,
|
||||
}
|
||||
|
||||
/// File association
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(feature = "schema", derive(JsonSchema))]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct DeepLinkProtocol {
|
||||
/// URL schemes to associate with this app without `://`. For example `my-app`
|
||||
pub schemes: Vec<String>,
|
||||
/// The protocol name. **macOS-only** and maps to `CFBundleTypeName`. Defaults to `<bundle-id>.<schemes[0]>`
|
||||
pub name: Option<String>,
|
||||
/// The app's role for these schemes. **macOS-only** and maps to `CFBundleTypeRole`.
|
||||
#[serde(default)]
|
||||
pub role: BundleTypeRole,
|
||||
}
|
||||
|
||||
/// The Updater configuration object.
|
||||
///
|
||||
/// See more: <https://tauri.app/v1/api/config#updaterconfig>
|
||||
|
|
|
@ -125,16 +125,26 @@ pub fn generate_desktop_file(
|
|||
mime_type: Option<String>,
|
||||
}
|
||||
|
||||
let mime_type = if let Some(associations) = settings.file_associations() {
|
||||
let mime_types: Vec<&str> = associations
|
||||
.iter()
|
||||
.filter_map(|association| association.mime_type.as_ref())
|
||||
.map(|s| s.as_str())
|
||||
.collect();
|
||||
Some(mime_types.join(";"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut mime_type: Vec<String> = Vec::new();
|
||||
|
||||
if let Some(associations) = settings.file_associations() {
|
||||
mime_type.extend(
|
||||
associations
|
||||
.iter()
|
||||
.filter_map(|association| association.mime_type.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(protocols) = settings.deep_link_protocols() {
|
||||
mime_type.extend(
|
||||
protocols
|
||||
.iter()
|
||||
.flat_map(|protocol| &protocol.schemes)
|
||||
.map(|s| format!("x-scheme-handler/{s}")),
|
||||
);
|
||||
}
|
||||
|
||||
let mime_type = (!mime_type.is_empty()).then_some(mime_type.join(";"));
|
||||
|
||||
handlebars.render_to_write(
|
||||
"main.desktop",
|
||||
|
|
|
@ -28,6 +28,11 @@ if [[ "$APPIMAGE_BUNDLE_XDG_OPEN" != "0" ]] && [[ -f "/usr/bin/xdg-open" ]]; the
|
|||
cp /usr/bin/xdg-open usr/bin
|
||||
fi
|
||||
|
||||
if [[ "$APPIMAGE_BUNDLE_XDG_MIME" != "0" ]] && [[ -f "/usr/bin/xdg-mime" ]]; then
|
||||
echo "Copying /usr/bin/xdg-mime"
|
||||
cp /usr/bin/xdg-mime usr/bin
|
||||
fi
|
||||
|
||||
if [[ "$TAURI_TRAY_LIBRARY_PATH" != "0" ]]; then
|
||||
echo "Copying appindicator library ${TAURI_TRAY_LIBRARY_PATH}"
|
||||
cp ${TAURI_TRAY_LIBRARY_PATH} usr/lib
|
||||
|
|
|
@ -277,6 +277,44 @@ fn create_info_plist(
|
|||
);
|
||||
}
|
||||
|
||||
if let Some(protocols) = settings.deep_link_protocols() {
|
||||
plist.insert(
|
||||
"CFBundleURLTypes".into(),
|
||||
plist::Value::Array(
|
||||
protocols
|
||||
.iter()
|
||||
.map(|protocol| {
|
||||
let mut dict = plist::Dictionary::new();
|
||||
dict.insert(
|
||||
"CFBundleURLSchemes".into(),
|
||||
plist::Value::Array(
|
||||
protocol
|
||||
.schemes
|
||||
.iter()
|
||||
.map(|s| s.to_string().into())
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
dict.insert(
|
||||
"CFBundleTypeName".into(),
|
||||
protocol
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or(format!(
|
||||
"{} {}",
|
||||
settings.bundle_identifier(),
|
||||
protocol.schemes[0]
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
dict.insert("CFBundleTypeRole".into(), protocol.role.to_string().into());
|
||||
plist::Value::Dictionary(dict)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
plist.insert("LSRequiresCarbon".into(), true.into());
|
||||
plist.insert("NSHighResolutionCapable".into(), true.into());
|
||||
if let Some(copyright) = settings.copyright_string() {
|
||||
|
|
|
@ -7,7 +7,7 @@ use super::category::AppCategory;
|
|||
use crate::bundle::{common, platform::target_triple};
|
||||
pub use tauri_utils::config::WebviewInstallMode;
|
||||
use tauri_utils::{
|
||||
config::{BundleType, FileAssociation, NSISInstallerMode, NsisCompression},
|
||||
config::{BundleType, DeepLinkProtocol, FileAssociation, NSISInstallerMode, NsisCompression},
|
||||
resources::{external_binaries, ResourcePaths},
|
||||
};
|
||||
|
||||
|
@ -478,6 +478,8 @@ pub struct BundleSettings {
|
|||
/// e.g. `sqlite3-universal-apple-darwin`. See
|
||||
/// <https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary>
|
||||
pub external_bin: Option<Vec<String>>,
|
||||
/// Deep-link protocols.
|
||||
pub deep_link_protocols: Option<Vec<DeepLinkProtocol>>,
|
||||
/// Debian-specific settings.
|
||||
pub deb: DebianSettings,
|
||||
/// Rpm-specific settings.
|
||||
|
@ -900,8 +902,14 @@ impl Settings {
|
|||
}
|
||||
|
||||
/// Return file associations.
|
||||
pub fn file_associations(&self) -> &Option<Vec<FileAssociation>> {
|
||||
&self.bundle_settings.file_associations
|
||||
pub fn file_associations(&self) -> Option<&Vec<FileAssociation>> {
|
||||
self.bundle_settings.file_associations.as_ref()
|
||||
}
|
||||
|
||||
/// Return the list of deep link protocols to be registered for
|
||||
/// this bundle.
|
||||
pub fn deep_link_protocols(&self) -> Option<&Vec<DeepLinkProtocol>> {
|
||||
self.bundle_settings.deep_link_protocols.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the app's short description.
|
||||
|
|
|
@ -520,7 +520,7 @@ pub fn build_wix_app_installer(
|
|||
.unwrap_or_default();
|
||||
|
||||
data.insert("product_name", to_json(settings.product_name()));
|
||||
data.insert("version", to_json(&app_version));
|
||||
data.insert("version", to_json(app_version));
|
||||
let bundle_id = settings.bundle_identifier();
|
||||
let manufacturer = settings
|
||||
.publisher()
|
||||
|
@ -570,7 +570,7 @@ pub fn build_wix_app_installer(
|
|||
let merge_modules = get_merge_modules(settings)?;
|
||||
data.insert("merge_modules", to_json(merge_modules));
|
||||
|
||||
data.insert("app_exe_source", to_json(&app_exe_source));
|
||||
data.insert("app_exe_source", to_json(app_exe_source));
|
||||
|
||||
// copy icon from `settings.windows().icon_path` folder to resource folder near msi
|
||||
let icon_path = copy_icon(settings, "icon.ico", &settings.windows().icon_path)?;
|
||||
|
@ -618,10 +618,18 @@ pub fn build_wix_app_installer(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(file_associations) = &settings.file_associations() {
|
||||
if let Some(file_associations) = settings.file_associations() {
|
||||
data.insert("file_associations", to_json(file_associations));
|
||||
}
|
||||
|
||||
if let Some(protocols) = settings.deep_link_protocols() {
|
||||
let schemes = protocols
|
||||
.iter()
|
||||
.flat_map(|p| &p.schemes)
|
||||
.collect::<Vec<_>>();
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
|
||||
if let Some(path) = custom_template_path {
|
||||
handlebars
|
||||
.register_template_string("main.wxs", read_to_string(path)?)
|
||||
|
|
|
@ -323,10 +323,18 @@ fn build_nsis_app_installer(
|
|||
let estimated_size = generate_estimated_size(&main_binary_path, &binaries, &resources)?;
|
||||
data.insert("estimated_size", to_json(estimated_size));
|
||||
|
||||
if let Some(file_associations) = &settings.file_associations() {
|
||||
if let Some(file_associations) = settings.file_associations() {
|
||||
data.insert("file_associations", to_json(file_associations));
|
||||
}
|
||||
|
||||
if let Some(protocols) = settings.deep_link_protocols() {
|
||||
let schemes = protocols
|
||||
.iter()
|
||||
.flat_map(|p| &p.schemes)
|
||||
.collect::<Vec<_>>();
|
||||
data.insert("deep_link_protocols", to_json(schemes));
|
||||
}
|
||||
|
||||
let silent_webview2_install = if let WebviewInstallMode::DownloadBootstrapper { silent }
|
||||
| WebviewInstallMode::EmbedBootstrapper { silent }
|
||||
| WebviewInstallMode::OfflineInstaller { silent } =
|
||||
|
|
|
@ -94,7 +94,7 @@ pub fn verify(path: &Path) -> crate::Result<bool> {
|
|||
// Construct SignTool command
|
||||
let signtool = locate_signtool()?;
|
||||
|
||||
let mut cmd = Command::new(&signtool);
|
||||
let mut cmd = Command::new(signtool);
|
||||
cmd.arg("verify");
|
||||
cmd.arg("/pa");
|
||||
cmd.arg(path);
|
||||
|
|
|
@ -560,6 +560,14 @@ Section Install
|
|||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
; Register deep links
|
||||
{{#each deep_link_protocol as |protocol| ~}}
|
||||
WriteRegStr SHCTX "Software\Classes\{{protocol}}" "URL Protocol" ""
|
||||
WriteRegStr SHCTX "Software\Classes\{{protocol}}" "" "URL:${BUNDLEID} protocol"
|
||||
WriteRegStr SHCTX "Software\Classes\{{protocol}}\DefaultIcon" "" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\",0"
|
||||
WriteRegStr SHCTX "Software\Classes\{{protocol}}\shell\open\command" "" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\" $\"%1$\""
|
||||
{{/each}}
|
||||
|
||||
; Create uninstaller
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
|
@ -652,6 +660,15 @@ Section Uninstall
|
|||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
; Delete deep links
|
||||
{{#each deep_link_protocol as |protocol| ~}}
|
||||
ReadRegStr $R7 SHCTX "Software\Classes\{{protocol}}\shell\open\command" ""
|
||||
!if $R7 == "$\"$INSTDIR\${MAINBINARYNAME}.exe$\" $\"%1$\""
|
||||
DeleteRegKey SHCTX "Software\Classes\{{protocol}}"
|
||||
!endif
|
||||
{{/each}}
|
||||
|
||||
|
||||
; Delete uninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
|
|
|
@ -111,6 +111,19 @@
|
|||
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<!-- Change the Root to HKCU for perUser installations -->
|
||||
{{#each deep_link_protocols as |protocol| ~}}
|
||||
<RegistryKey Root="HKLM" Key="Software\\Classes\\{{protocol}}">
|
||||
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
|
||||
<RegistryValue Type="string" Value="URL:{{bundle_id}} protocol"/>
|
||||
<RegistryKey Key="DefaultIcon">
|
||||
<RegistryValue Type="string" Value=""[!Path]",0" />
|
||||
</RegistryKey>
|
||||
<RegistryKey Key="shell\\open\\command">
|
||||
<RegistryValue Type="string" Value=""[!Path]" "%1"" />
|
||||
</RegistryKey>
|
||||
</RegistryKey>
|
||||
{{/each~}}
|
||||
</Component>
|
||||
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||
|
|
|
@ -1293,7 +1293,7 @@
|
|||
]
|
||||
},
|
||||
"role": {
|
||||
"description": "The app’s role with respect to the type. Maps to `CFBundleTypeRole` on macOS.",
|
||||
"description": "The app's role with respect to the type. Maps to `CFBundleTypeRole` on macOS.",
|
||||
"default": "Editor",
|
||||
"allOf": [
|
||||
{
|
||||
|
|
|
@ -156,6 +156,16 @@ pub fn command(mut options: Options, verbosity: u8) -> Result<()> {
|
|||
if config_.tauri.bundle.appimage.bundle_media_framework {
|
||||
std::env::set_var("APPIMAGE_BUNDLE_GSTREAMER", "1");
|
||||
}
|
||||
|
||||
if let Some(open) = config_.plugins.0.get("shell").and_then(|v| v.get("open")) {
|
||||
if open.as_bool().is_some_and(|x| x) || open.is_string() {
|
||||
std::env::set_var("APPIMAGE_BUNDLE_XDG_OPEN", "1");
|
||||
}
|
||||
}
|
||||
|
||||
if settings.deep_link_protocols().is_some() {
|
||||
std::env::set_var("APPIMAGE_BUNDLE_XDG_MIME", "1");
|
||||
}
|
||||
}
|
||||
|
||||
let bundles = bundle_project(settings)
|
||||
|
|
|
@ -25,7 +25,7 @@ use tauri_bundler::{
|
|||
AppCategory, BundleBinary, BundleSettings, DebianSettings, DmgSettings, MacOsSettings,
|
||||
PackageSettings, Position, RpmSettings, Size, UpdaterSettings, WindowsSettings,
|
||||
};
|
||||
use tauri_utils::config::parse::is_configuration_file;
|
||||
use tauri_utils::config::{parse::is_configuration_file, DeepLinkProtocol};
|
||||
|
||||
use super::{AppSettings, DevProcess, ExitReason, Interface};
|
||||
use crate::helpers::{
|
||||
|
@ -681,6 +681,13 @@ pub struct RustAppSettings {
|
|||
target: Target,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum DesktopDeepLinks {
|
||||
One(DeepLinkProtocol),
|
||||
List(Vec<DeepLinkProtocol>),
|
||||
}
|
||||
|
||||
impl AppSettings for RustAppSettings {
|
||||
fn get_package_settings(&self) -> PackageSettings {
|
||||
self.package_settings.clone()
|
||||
|
@ -694,12 +701,27 @@ impl AppSettings for RustAppSettings {
|
|||
let arch64bits =
|
||||
self.target_triple.starts_with("x86_64") || self.target_triple.starts_with("aarch64");
|
||||
|
||||
tauri_config_to_bundle_settings(
|
||||
let mut settings = tauri_config_to_bundle_settings(
|
||||
&self.manifest,
|
||||
features,
|
||||
config.tauri.bundle.clone(),
|
||||
arch64bits,
|
||||
)
|
||||
)?;
|
||||
|
||||
if let Some(plugin_config) = config
|
||||
.plugins
|
||||
.0
|
||||
.get("deep-link")
|
||||
.and_then(|c| c.get("desktop").cloned())
|
||||
{
|
||||
let protocols: DesktopDeepLinks = serde_json::from_value(plugin_config.clone())?;
|
||||
settings.deep_link_protocols = Some(match protocols {
|
||||
DesktopDeepLinks::One(p) => vec![p],
|
||||
DesktopDeepLinks::List(p) => p,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn app_binary_path(&self, options: &Options) -> crate::Result<PathBuf> {
|
||||
|
|
Loading…
Reference in New Issue