Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8ece0d5
WIP: determining correct UTI from mime type
davidfokkema May 11, 2025
4264d3b
Set plist default values from UTI
davidfokkema May 13, 2025
4b01f9d
Remove print statements
davidfokkema May 13, 2025
4625cbe
Merge remote-tracking branch 'upstream/main' into file-associations
davidfokkema May 13, 2025
1327b07
Replace match-statement with if-statement
davidfokkema May 13, 2025
f82f484
Added change note
davidfokkema May 13, 2025
5c8faed
Add macOS doctype default variables to test
davidfokkema May 14, 2025
c70ebf6
Added tests and no coverage pragmas
davidfokkema May 14, 2025
03f6b55
Split out extraction of MIME type and rename variable
davidfokkema May 14, 2025
b61fbe6
If plist file is moved in OS update, catch exception
davidfokkema May 14, 2025
904b12b
Merge branch 'file-associations' of https://github.com/davidfokkema/b…
davidfokkema May 14, 2025
b493106
Add descriptions of tests
davidfokkema May 14, 2025
6c4b3c2
Complete code coverage
davidfokkema May 14, 2025
9323eba
Run macOS-specific tests only on macOS
davidfokkema May 14, 2025
a7f5620
Built-in types should have attribute set to True
davidfokkema May 16, 2025
a3c8efd
Renamed builtin_type -> is_core_type
davidfokkema May 18, 2025
3799ca9
Fix inconsistent naming and perform validity check
davidfokkema May 18, 2025
07c1565
Fix failing test
davidfokkema May 18, 2025
41cdc7a
Add tests to complete coverage
davidfokkema May 18, 2025
31f8342
Pass pre-commit, but fail coverage
davidfokkema May 18, 2025
5b28ded
Remove old test code
davidfokkema May 18, 2025
a810a4c
Fix coverage
davidfokkema May 22, 2025
fdbf75d
No-op to satisfy coverage checker
davidfokkema May 22, 2025
7412a90
Update changes/2284.feature.rst
davidfokkema May 23, 2025
2a3b44d
More test cases and improved LSItemContentTypes handling
davidfokkema May 23, 2025
1533af9
Add test to fix coverage
davidfokkema May 23, 2025
46d2cb6
Skip coverage if not macOS
davidfokkema May 23, 2025
fd48660
Reshuffled document type tests
davidfokkema May 25, 2025
06c12d5
Document the new document type support
davidfokkema May 25, 2025
df5909d
Rewrap docs
davidfokkema May 25, 2025
8203397
Trim trailing whitespace
davidfokkema May 25, 2025
7d96377
Simplify documentation to focus on the Briefcase use cases.
freakboy3742 Jun 13, 2025
7131e6e
Small cleanup of logic in macOS doctype processing.
freakboy3742 Jun 13, 2025
bae3210
Cleanup of test cases.
freakboy3742 Jun 13, 2025
d51b53b
Tweak release note.
freakboy3742 Jun 13, 2025
9374c63
Merge branch 'main' into file-associations
freakboy3742 Jun 13, 2025
63f594f
Restore some additional reading links on macOS document types.
freakboy3742 Jun 13, 2025
21ee20d
Correct spelling.
freakboy3742 Jun 13, 2025
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
1 change: 1 addition & 0 deletions changes/2284.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added improved document types (file associations) for Windows and macOS platforms
16 changes: 16 additions & 0 deletions src/briefcase/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ def validate_document_type_config(document_type_id, document_type):
f"The URL associated with document type {document_type_id!r} is invalid: {e}"
)

if sys.platform == "darwin":
from briefcase.platforms.macOS.utils import mime_type_to_UTI

macOS = document_type.setdefault("macOS", {})
if (UTI := mime_type_to_UTI(document_type.get("mime_type", None))) is not None:
macOS.setdefault("LSItemContentType", UTI)
macOS.setdefault("LSHandlerRank", "Alternate")
macOS.setdefault("UTTypeConformsTo", ["public.xyz"])
else:
# LSItemContentType will default to bundle.app_name.document_type_id
# in the Info.plist template if it is not provided.
macOS.setdefault("LSHandlerRank", "Owner")
macOS.setdefault("UTTypeConformsTo", ["public.data", "public.content"])

macOS.setdefault("CFBundleTypeRole", "Viewer")


VALID_BUNDLE_RE = re.compile(r"[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$")

Expand Down
47 changes: 47 additions & 0 deletions src/briefcase/platforms/macOS/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import concurrent
import email
import hashlib
import pathlib
import plistlib
import subprocess
from pathlib import Path

Expand Down Expand Up @@ -272,3 +274,48 @@ def merge_app_packages(
raise future.exception()
else:
self.console.info("No libraries require merging.")


def mime_type_to_UTI(mime_type: str) -> str | None:
"""Convert a MIME type to a Uniform Type Identifier (UTI).

This function reads the system's CoreTypes Info.plist file to determine the
UTI for a given MIME type.

Args:
mime_type: The MIME type to convert.

Returns:
The UTI for the MIME type, or None if the UTI cannot be determined.
"""
plist_data = pathlib.Path(
"/System/Library/CoreServices/CoreTypes.bundle/Contents/Info.plist"
).read_bytes()
plist = plistlib.loads(plist_data)
for type_declaration in (
plist["UTExportedTypeDeclarations"] + plist["UTImportedTypeDeclarations"]
):
# We check both the system built-in types (exported) and the known
# third-party types (imported) to find the UTI for the given MIME type.
# Most type declarations will have a UTTypeTagSpecification dictionary
# with a "public.mime-type" key. That can be either a list of MIME types
# or a single MIME type. We check if the MIME type is in the list or
# matches the single MIME type. If we find a match, we return the UTI
# identifier. If we don't find a match, we return None.

mime_types = type_declaration.get("UTTypeTagSpecification", {}).get(
"public.mime-type", []
)
if isinstance(mime_types, list):
# Most MIME types are declared as a list even if they are a
# single type. Some types define multiple closely-related MIME
# types.
if mime_type in mime_types:
return type_declaration["UTTypeIdentifier"]
else:
# some MIME types are declared as a single type
if mime_types == mime_type:
return type_declaration["UTTypeIdentifier"]

# If no match is found in the entire list, return None
return None
8 changes: 8 additions & 0 deletions tests/config/test_AppConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ def test_extra_attrs():
"extension": "doc",
"description": "A document",
"url": "https://testurl.com",
"macOS": {
"CFBundleTypeRole": "Viewer",
"LSHandlerRank": "Owner",
"UTTypeConformsTo": [
"public.data",
"public.content",
],
},
}
}

Expand Down
Loading