Skip to content

Commit

Permalink
feat: add file association support, closes #3736 (#4320)
Browse files Browse the repository at this point in the history
Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Co-authored-by: Raphii <iam@raphii.co>
Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
  • Loading branch information
6 people authored Jul 16, 2023
1 parent 84c4159 commit 3b98141
Show file tree
Hide file tree
Showing 43 changed files with 5,566 additions and 163 deletions.
5 changes: 5 additions & 0 deletions .changes/file-associations-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-utils": minor:feat
---

Add a configuration object for file associations under `BundleConfig`.
5 changes: 5 additions & 0 deletions .changes/file-associations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": minor:feat
---

Added support to file associations.
6 changes: 6 additions & 0 deletions .changes/runtime-opened-event.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-runtime": minor:feat
"tauri-runtime-wry": minor:feat
---

Added the `Opened` variant to `RunEvent`.
2 changes: 1 addition & 1 deletion .github/workflows/covector-version-or-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ jobs:
steps.covector.outputs.successfulPublish == 'true' &&
contains(steps.covector.outputs.packagesPublished, '@tauri-apps/cli')
run: |
echo '${{ steps.covector.outputs }}' > output.json
echo '${{ toJSON(steps.covector.outputs) }}' > output.json
id=$(jq '.["-tauri-apps-cli-releaseId"]' < output.json)
rm output.json
echo "cliReleaseId=$id" >> "$GITHUB_OUTPUT"
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ exclude = [
"examples/resources/src-tauri",
"examples/sidecar/src-tauri",
"examples/web/core",
"examples/file-associations/src-tauri",
"examples/workspace",
]

Expand Down
101 changes: 101 additions & 0 deletions core/tauri-config-schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,16 @@
"null"
]
},
"fileAssociations": {
"description": "File associations to application.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/FileAssociation"
}
},
"shortDescription": {
"description": "A short description of your application.",
"type": [
Expand Down Expand Up @@ -1122,6 +1132,97 @@
}
]
},
"FileAssociation": {
"description": "File association",
"type": "object",
"required": [
"ext"
],
"properties": {
"ext": {
"description": "File extensions to associate with this app. e.g. 'png'",
"type": "array",
"items": {
"$ref": "#/definitions/AssociationExt"
}
},
"name": {
"description": "The name. Maps to `CFBundleTypeName` on macOS. Default to ext[0]",
"type": [
"string",
"null"
]
},
"description": {
"description": "The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.",
"type": [
"string",
"null"
]
},
"role": {
"description": "The app’s role with respect to the type. Maps to `CFBundleTypeRole` on macOS.",
"default": "Editor",
"allOf": [
{
"$ref": "#/definitions/BundleTypeRole"
}
]
},
"mimeType": {
"description": "The mime-type e.g. 'image/png' or 'text/plain'. Linux-only.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"AssociationExt": {
"description": "An extension for a [`FileAssociation`].\n\nA leading `.` is automatically stripped.",
"type": "string"
},
"BundleTypeRole": {
"description": "macOS-only. Corresponds to CFBundleTypeRole",
"oneOf": [
{
"description": "CFBundleTypeRole.Editor. Files can be read and edited.",
"type": "string",
"enum": [
"Editor"
]
},
{
"description": "CFBundleTypeRole.Viewer. Files can be read.",
"type": "string",
"enum": [
"Viewer"
]
},
{
"description": "CFBundleTypeRole.Shell",
"type": "string",
"enum": [
"Shell"
]
},
{
"description": "CFBundleTypeRole.QLGenerator",
"type": "string",
"enum": [
"QLGenerator"
]
},
{
"description": "CFBundleTypeRole.None",
"type": "string",
"enum": [
"None"
]
}
]
},
"AppImageConfig": {
"description": "Configuration for AppImage bundles.\n\nSee more: https://tauri.app/v1/api/config#appimageconfig",
"type": "object",
Expand Down
10 changes: 5 additions & 5 deletions core/tauri-runtime-wry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ edition = { workspace = true }
rust-version = { workspace = true }

[dependencies]
wry = { version = "0.28.3", default-features = false, features = [ "file-drop", "protocol" ] }
wry = { version = "0.30", default-features = false, features = [ "file-drop", "protocol" ] }
tauri-runtime = { version = "0.13.0-alpha.6", path = "../tauri-runtime" }
tauri-utils = { version = "2.0.0-alpha.6", path = "../tauri-utils" }
uuid = { version = "1", features = [ "v4" ] }
rand = "0.8"
raw-window-handle = "0.5"

[target."cfg(windows)".dependencies]
webview2-com = "0.22"
webview2-com = "0.25"

[target."cfg(windows)".dependencies.windows]
version = "0.44"
version = "0.48"
features = [ "Win32_Foundation" ]

[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
gtk = { version = "0.16", features = [ "v3_24" ] }
webkit2gtk = { version = "0.19.1", features = [ "v2_38" ] }
webkit2gtk = { version = "1.1", features = [ "v2_38" ] }
percent-encoding = "2.1"

[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
Expand All @@ -48,4 +48,4 @@ macos-private-api = [
"tauri-runtime/macos-private-api"
]
objc-exception = [ "wry/objc-exception" ]
linux-headers = [ "wry/linux-headers", "webkit2gtk/v2_36" ]
linux-headers = [ ]
4 changes: 4 additions & 0 deletions core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2952,6 +2952,10 @@ fn handle_event_loop<T: UserEvent>(
);
}
},
#[cfg(target_os = "macos")]
Event::Opened { urls } => {
callback(RunEvent::Opened { urls });
}
_ => (),
}

Expand Down
5 changes: 4 additions & 1 deletion core/tauri-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ rand = "0.8"
url = { version = "2" }

[target."cfg(windows)".dependencies.windows]
version = "0.44"
version = "0.48"
features = [ "Win32_Foundation" ]

[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
Expand All @@ -44,6 +44,9 @@ gtk = { version = "0.16", features = [ "v3_24" ] }
[target."cfg(target_os = \"android\")".dependencies]
jni = "0.20"

[target."cfg(target_os = \"macos\")".dependencies]
url = "2"

[features]
devtools = [ ]
system-tray = [ ]
Expand Down
4 changes: 4 additions & 0 deletions core/tauri-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ pub trait UserEvent: Debug + Clone + Send + 'static {}
impl<T: Debug + Clone + Send + 'static> UserEvent for T {}

/// Event triggered on the event loop run.
#[derive(Debug)]
#[non_exhaustive]
pub enum RunEvent<T: UserEvent> {
/// Event loop is exiting.
Expand All @@ -313,6 +314,9 @@ pub enum RunEvent<T: UserEvent> {
///
/// This event is useful as a place to put your code that should be run after all state-changing events have been handled and you want to do stuff (updating state, performing calculations, etc) that happens as the “main body” of your event loop.
MainEventsCleared,
/// Emitted when the user wants to open the specified resource with the app.
#[cfg(target_os = "macos")]
Opened { urls: Vec<url::Url> },
/// A custom event defined by the user.
UserEvent(T),
}
Expand Down
2 changes: 1 addition & 1 deletion core/tauri-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ dunce = "1"
heck = "0.4"

[target."cfg(windows)".dependencies.windows]
version = "0.44.0"
version = "0.48.0"
features = [
"implement",
"Win32_Foundation",
Expand Down
79 changes: 78 additions & 1 deletion core/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,78 @@ impl Default for WindowsConfig {
}
}

/// macOS-only. Corresponds to CFBundleTypeRole
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum BundleTypeRole {
/// CFBundleTypeRole.Editor. Files can be read and edited.
#[default]
Editor,
/// CFBundleTypeRole.Viewer. Files can be read.
Viewer,
/// CFBundleTypeRole.Shell
Shell,
/// CFBundleTypeRole.QLGenerator
QLGenerator,
/// CFBundleTypeRole.None
None,
}

impl Display for BundleTypeRole {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Editor => write!(f, "Editor"),
Self::Viewer => write!(f, "Viewer"),
Self::Shell => write!(f, "Shell"),
Self::QLGenerator => write!(f, "QLGenerator"),
Self::None => write!(f, "None"),
}
}
}

/// An extension for a [`FileAssociation`].
///
/// A leading `.` is automatically stripped.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct AssociationExt(pub String);

impl fmt::Display for AssociationExt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

impl<'d> serde::Deserialize<'d> for AssociationExt {
fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
let ext = String::deserialize(deserializer)?;
if let Some(ext) = ext.strip_prefix('.') {
Ok(AssociationExt(ext.into()))
} else {
Ok(AssociationExt(ext))
}
}
}

/// File association
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FileAssociation {
/// File extensions to associate with this app. e.g. 'png'
pub ext: Vec<AssociationExt>,
/// 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.
pub description: Option<String>,
/// 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.
#[serde(alias = "mime-type")]
pub mime_type: Option<String>,
}

/// The Updater configuration object.
///
/// See more: https://tauri.app/v1/api/config#updaterconfig
Expand Down Expand Up @@ -720,6 +792,8 @@ pub struct BundleConfig {
/// Should be one of the following:
/// Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.
pub category: Option<String>,
/// File associations to application.
pub file_associations: Option<Vec<FileAssociation>>,
/// A short description of your application.
#[serde(alias = "short-description")]
pub short_description: Option<String>,
Expand Down Expand Up @@ -1668,7 +1742,7 @@ fn default_dist_dir() -> AppUrl {
struct PackageVersion(String);

impl<'d> serde::Deserialize<'d> for PackageVersion {
fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<PackageVersion, D::Error> {
fn deserialize<D: Deserializer<'d>>(deserializer: D) -> Result<Self, D::Error> {
struct PackageVersionVisitor;

impl<'d> Visitor<'d> for PackageVersionVisitor {
Expand Down Expand Up @@ -2291,6 +2365,7 @@ mod build {
let resources = quote!(None);
let copyright = quote!(None);
let category = quote!(None);
let file_associations = quote!(None);
let short_description = quote!(None);
let long_description = quote!(None);
let appimage = quote!(Default::default());
Expand All @@ -2313,6 +2388,7 @@ mod build {
resources,
copyright,
category,
file_associations,
short_description,
long_description,
appimage,
Expand Down Expand Up @@ -2616,6 +2692,7 @@ mod test {
resources: None,
copyright: None,
category: None,
file_associations: None,
short_description: None,
long_description: None,
appimage: Default::default(),
Expand Down
6 changes: 3 additions & 3 deletions core/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,18 @@ ico = { version = "0.2.0", optional = true }
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
gtk = { version = "0.16", features = [ "v3_24" ] }
glib = "0.16"
webkit2gtk = { version = "0.19.1", features = [ "v2_38" ] }
webkit2gtk = { version = "1.1", features = [ "v2_38" ] }

[target."cfg(target_os = \"macos\")".dependencies]
embed_plist = "1.2"
cocoa = "0.24"
objc = "0.2"

[target."cfg(windows)".dependencies]
webview2-com = "0.22"
webview2-com = "0.25"

[target."cfg(windows)".dependencies.windows]
version = "0.44"
version = "0.48"
features = [ "Win32_Foundation" ]

[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
Expand Down
Loading

0 comments on commit 3b98141

Please sign in to comment.