Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/file-association-content-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-cli": minor:feat
"@tauri-apps/cli": minor:feat
---

Added support to defining the content type of the declared file association on macOS (maps to LSItemContentTypes property).
6 changes: 6 additions & 0 deletions .changes/file-association-exported-type-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-cli": minor:feat
"@tauri-apps/cli": minor:feat
---

Added support to defining the metadata for custom types declared in `tauri.conf.json > bundle > fileAssociations > exportedType` via the `UTExportedTypeDeclarations` Info.plist property.
5 changes: 5 additions & 0 deletions .changes/file-association-exported-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": minor:feat
---

Added `FileAssociation::exported_type` and `FileAssociation::content_types` for better support to defining custom types on macOS.
80 changes: 70 additions & 10 deletions crates/tauri-bundler/src/bundle/macos/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,23 +268,83 @@ fn create_info_plist(
}

if let Some(associations) = settings.file_associations() {
let exported_associations = associations
.iter()
.filter_map(|association| {
association.exported_type.as_ref().map(|exported_type| {
let mut dict = plist::Dictionary::new();

dict.insert(
"UTTypeIdentifier".into(),
exported_type.identifier.clone().into(),
);
if let Some(description) = &association.description {
dict.insert("UTTypeDescription".into(), description.clone().into());
}
if let Some(conforms_to) = &exported_type.conforms_to {
dict.insert(
"UTTypeConformsTo".into(),
plist::Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
);
}

let mut specification = plist::Dictionary::new();
specification.insert(
"public.filename-extension".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|s| s.to_string().into())
.collect(),
),
);
if let Some(mime_type) = &association.mime_type {
specification.insert("public.mime-type".into(), mime_type.clone().into());
}

dict.insert("UTTypeTagSpecification".into(), specification.into());

plist::Value::Dictionary(dict)
})
})
.collect::<Vec<_>>();

if !exported_associations.is_empty() {
plist.insert(
"UTExportedTypeDeclarations".into(),
plist::Value::Array(exported_associations),
);
}

plist.insert(
"CFBundleDocumentTypes".into(),
plist::Value::Array(
associations
.iter()
.map(|association| {
let mut dict = plist::Dictionary::new();
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);

if !association.ext.is_empty() {
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);
}

if let Some(content_types) = &association.content_types {
dict.insert(
"LSItemContentTypes".into(),
plist::Value::Array(content_types.iter().map(|s| s.to_string().into()).collect()),
);
}

dict.insert(
"CFBundleTypeName".into(),
association
Expand Down
45 changes: 45 additions & 0 deletions crates/tauri-cli/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,16 @@
"$ref": "#/definitions/AssociationExt"
}
},
"contentTypes": {
"description": "Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\n\n This allows supporting any file format declared by another application that conforms to this type.\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"name": {
"description": "The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`",
"type": [
Expand Down Expand Up @@ -2471,6 +2481,17 @@
"$ref": "#/definitions/HandlerRank"
}
]
},
"exportedType": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\n\n You should define this if the associated file is a custom file type defined by your application.",
"anyOf": [
{
"$ref": "#/definitions/ExportedFileAssociation"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
Expand Down Expand Up @@ -2552,6 +2573,30 @@
}
]
},
"ExportedFileAssociation": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.",
"type": "object",
"required": [
"identifier"
],
"properties": {
"identifier": {
"description": "The unique identifier for the exported type. Maps to `UTTypeIdentifier`.",
"type": "string"
},
"conformsTo": {
"description": "The types that this type conforms to. Maps to `UTTypeConformsTo`.\n\n Examples are `public.data`, `public.image`, `public.image`, `public.database`.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"WindowsConfig": {
"description": "Windows bundler configuration.\n\n See more: <https://v2.tauri.app/reference/config/#windowsconfig>",
"type": "object",
Expand Down
45 changes: 45 additions & 0 deletions crates/tauri-schema-generator/schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,16 @@
"$ref": "#/definitions/AssociationExt"
}
},
"contentTypes": {
"description": "Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\n\n This allows supporting any file format declared by another application that conforms to this type.\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"name": {
"description": "The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`",
"type": [
Expand Down Expand Up @@ -2471,6 +2481,17 @@
"$ref": "#/definitions/HandlerRank"
}
]
},
"exportedType": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\n\n You should define this if the associated file is a custom file type defined by your application.",
"anyOf": [
{
"$ref": "#/definitions/ExportedFileAssociation"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
Expand Down Expand Up @@ -2552,6 +2573,30 @@
}
]
},
"ExportedFileAssociation": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.",
"type": "object",
"required": [
"identifier"
],
"properties": {
"identifier": {
"description": "The unique identifier for the exported type. Maps to `UTTypeIdentifier`.",
"type": "string"
},
"conformsTo": {
"description": "The types that this type conforms to. Maps to `UTTypeConformsTo`.\n\n Examples are `public.data`, `public.image`, `public.image`, `public.database`.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"WindowsConfig": {
"description": "Windows bundler configuration.\n\n See more: <https://v2.tauri.app/reference/config/#windowsconfig>",
"type": "object",
Expand Down
23 changes: 23 additions & 0 deletions crates/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,11 @@ impl<'d> serde::Deserialize<'d> for AssociationExt {
pub struct FileAssociation {
/// File extensions to associate with this app. e.g. 'png'
pub ext: Vec<AssociationExt>,
/// Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.
///
/// This allows supporting any file format declared by another application that conforms to this type.
/// Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].
pub content_types: Option<Vec<String>>,
/// The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`
pub name: Option<String>,
/// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.
Expand All @@ -1190,6 +1195,24 @@ pub struct FileAssociation {
/// The ranking of this app among apps that declare themselves as editors or viewers of the given file type. Maps to `LSHandlerRank` on macOS.
#[serde(default)]
pub rank: HandlerRank,
/// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.
///
/// You should define this if the associated file is a custom file type defined by your application.
pub exported_type: Option<ExportedFileAssociation>,
}

/// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ExportedFileAssociation {
/// The unique identifier for the exported type. Maps to `UTTypeIdentifier`.
pub identifier: String,
/// The types that this type conforms to. Maps to `UTTypeConformsTo`.
///
/// Examples are `public.data`, `public.image`, `public.image`, `public.database`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate public.image

#[serde(alias = "conforms-to")]
pub conforms_to: Option<Vec<String>>,
}

/// Deep link protocol configuration.
Expand Down
Loading