From 6038da77baa7040498d82c4e733139ac03f47cc4 Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov <654232+AlexeyTsvetkov@users.noreply.github.com> Date: Mon, 5 Jul 2021 18:41:23 +0300 Subject: [PATCH] Allow adding extra keys to Info.plist (#845) Resolves #795 --- .../application/dsl/PlatformSettings.kt | 9 +++ .../application/internal/InfoPlistBuilder.kt | 4 +- .../internal/configureApplication.kt | 1 + .../application/tasks/AbstractJPackageTask.kt | 7 +- .../macOptions/Expected-Info.plist | 12 +++ .../application/macOptions/build.gradle | 16 ++++ .../README.md | 79 ++++++++++++++++++- 7 files changed, 125 insertions(+), 3 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt index ceb8609fb2..88257b3a75 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/PlatformSettings.kt @@ -41,6 +41,15 @@ open class MacOSPlatformSettings @Inject constructor(objects: ObjectFactory): Pl fun notarization(fn: Action) { fn.execute(notarization) } + + internal val infoPlistSettings = InfoPlistSettings() + fun infoPlist(fn: Action) { + fn.execute(infoPlistSettings) + } +} + +open class InfoPlistSettings { + var extraKeysRawXml: String? = null } open class LinuxPlatformSettings @Inject constructor(objects: ObjectFactory): PlatformSettings(objects) { diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt index 9727216e51..85aad602f8 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/InfoPlistBuilder.kt @@ -5,10 +5,11 @@ package org.jetbrains.compose.desktop.application.internal +import org.jetbrains.compose.desktop.application.dsl.InfoPlistSettings import java.io.File import kotlin.reflect.KProperty -internal class InfoPlistBuilder { +internal class InfoPlistBuilder(private val extraPlistKeysRawXml: String?) { private val values = LinkedHashMap() operator fun get(key: InfoPlistKey): String? = values[key] @@ -27,6 +28,7 @@ internal class InfoPlistBuilder { appendLine(" ${k.name}") appendLine(" $v") } + extraPlistKeysRawXml?.let { appendLine(it) } appendLine(" ") appendLine("") } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt index d269e4aef1..eb512363e2 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt @@ -275,6 +275,7 @@ internal fun AbstractJPackageTask.configurePlatformSettings(app: Application) { provider { mac.dockName } ) nonValidatedMacBundleID.set(provider { mac.bundleID }) + macExtraPlistKeysRawXml.set(provider { mac.infoPlistSettings.extraKeysRawXml }) nonValidatedMacSigningSettings = app.nativeDistributions.macOS.signing iconFile.set(mac.iconFile.orElse(DefaultIcons.forMac(project))) installationPath.set(mac.installationPath) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt index 6def4e1637..bf9c2b002e 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt @@ -15,6 +15,7 @@ import org.gradle.api.tasks.Optional import org.gradle.process.ExecResult import org.gradle.work.ChangeType import org.gradle.work.InputChanges +import org.jetbrains.compose.desktop.application.dsl.InfoPlistSettings import org.jetbrains.compose.desktop.application.dsl.MacOSSigningSettings import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.internal.* @@ -169,6 +170,10 @@ abstract class AbstractJPackageTask @Inject constructor( @get:Optional internal val nonValidatedMacBundleID: Property = objects.nullableProperty() + @get:Input + @get:Optional + internal val macExtraPlistKeysRawXml: Property = objects.nullableProperty() + @get:Optional @get:Nested internal var nonValidatedMacSigningSettings: MacOSSigningSettings? = null @@ -361,7 +366,7 @@ abstract class AbstractJPackageTask @Inject constructor( fileOperations.delete(resourcesDir) fileOperations.mkdir(resourcesDir) if (currentOS == OS.MacOS) { - InfoPlistBuilder() + InfoPlistBuilder(macExtraPlistKeysRawXml.orNull) .also { setInfoPlistValues(it) } .writeToFile(resourcesDir.ioFile.resolve("Info.plist")) } diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist b/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist index 1f914cb8b0..c315c93bbe 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist +++ b/gradle-plugins/compose/src/test/test-projects/application/macOptions/Expected-Info.plist @@ -32,5 +32,17 @@ true NSHighResolutionCapable true + + CFBundleURLTypes + + + CFBundleURLName + Exameple URL + CFBundleURLSchemes + + exampleUrl + + + diff --git a/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle index 0ceff664ba..d1c9437a9d 100644 --- a/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle +++ b/gradle-plugins/compose/src/test/test-projects/application/macOptions/build.gradle @@ -18,6 +18,19 @@ dependencies { implementation compose.desktop.currentOs } +def extraInfoPlistKeys = """ + CFBundleURLTypes + + + CFBundleURLName + Exameple URL + CFBundleURLSchemes + + exampleUrl + + + """ + compose.desktop { application { mainClass = "MainKt" @@ -25,6 +38,9 @@ compose.desktop { packageName = "TestPackage" macOS { dockName = "CustomDockName" + infoPlist { + extraKeysRawXml = extraInfoPlistKeys + } } } } diff --git a/tutorials/Native_distributions_and_local_execution/README.md b/tutorials/Native_distributions_and_local_execution/README.md index f910c194eb..dc4f55863d 100755 --- a/tutorials/Native_distributions_and_local_execution/README.md +++ b/tutorials/Native_distributions_and_local_execution/README.md @@ -370,6 +370,7 @@ The following platform-specific options are available (see the section `Specifying package version` for details); * `pkgPackageVersion = "PKG_VERSION"` — a pkg-specific package version (see the section `Specifying package version` for details); + * `infoPlist` — see the section `Customizing Info.plist on macOS` for details; * Windows: * `console = true` adds a console launcher for the application; * `dirChooser = true` enables customizing the installation path during installation; @@ -383,7 +384,7 @@ The following platform-specific options are available (see the section `Specifying package version` for details); * `exePackageVersion = "EXE_VERSION"` — a pkg-specific package version (see the section `Specifying package version` for details); - + ## App icon The app icon needs to be provided in OS-specific formats: @@ -408,3 +409,79 @@ compose.desktop { } } ``` + +## Customizing Info.plist on macOS + +We aim to support important platform-specific customization use-cases via declarative DSL. +However, the provided DSL is not enough sometimes. If you need to specify `Info.plist` +values, that are not modeled in the DSL, you can work around by specifying a piece +of raw XML, that will be appended to the application's `Info.plist`. + +### Example: deep linking into macOS apps + +1. Specify a custom URL scheme: +``` kotlin +// build.gradle.kts +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg) + packageName = "Deep Linking Example App" + macOS { + bundleID = "org.jetbrains.compose.examples.deeplinking" + infoPlist { + extraKeysRawXml = macExtraPlistKeys + } + } + } + } +} + +val macExtraPlistKeys: String + get() = """ + CFBundleURLTypes + + + CFBundleURLName + Example deep link + CFBundleURLSchemes + + compose + + + + """ +""" +``` + +2. Use `java.awt.Desktop` to set up a URI handler: +``` kotlin +// src/main/main.kt + +import androidx.compose.desktop.Window +import androidx.compose.material.Text +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import java.awt.Desktop + +fun main() { + val text = mutableStateOf("Hello, World!") + + Desktop.getDesktop().setOpenURIHandler { event -> + text.value = "Open URI: " + event.uri + } + + Window { + var text by remember { text } + MaterialTheme { + Text(text) + } + } +} +``` +3. Run `./gradlew runDistributable`. +4. Links like `compose://foo/bar` are now redirected from a browser to your application.