Skip to content

Commit

Permalink
fixup! Add file associations support
Browse files Browse the repository at this point in the history
[Desktop: Add support for file associations](#773)
  • Loading branch information
zhelenskiy committed Jun 21, 2024
1 parent 8e6c08e commit 100aeef
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.jetbrains.compose.desktop.application.dsl

import java.io.File
import java.io.Serializable

data class FileAssociation(
val mimeType: String,
val extension: String,
val description: String,
val iconFile: File?,
) : Serializable
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.jetbrains.compose.desktop.application.dsl

import org.gradle.api.Action
import java.io.File

internal val DEFAULT_RUNTIME_MODULES = arrayOf(
"java.base", "java.desktop", "java.logging", "jdk.crypto.ec"
Expand Down Expand Up @@ -33,8 +34,12 @@ abstract class JvmApplicationDistributions : AbstractDistributions() {
fn.execute(windows)
}

internal val fileAssociations: MutableSet<FileAssociation> = mutableSetOf()
fun fileAssociation(mimeType: String, extension: String, description: String) {
fileAssociations.add(FileAssociation(mimeType, extension, description))
fun fileAssociation(
mimeType: String, extension: String, description: String,
linuxIcon: File? = null, windowsIcon: File? = null, macOSIcon: File? = null,
) {
linux.fileAssociation(mimeType, extension, description, linuxIcon)
windows.fileAssociation(mimeType, extension, description, windowsIcon)
macOS.fileAssociation(mimeType, extension, description, macOSIcon)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package org.jetbrains.compose.desktop.application.dsl
import org.gradle.api.Action
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import java.io.File
import javax.inject.Inject

abstract class AbstractPlatformSettings {
Expand All @@ -17,6 +18,11 @@ abstract class AbstractPlatformSettings {
val iconFile: RegularFileProperty = objects.fileProperty()
var packageVersion: String? = null
var installationPath: String? = null

internal val fileAssociations: MutableSet<FileAssociation> = mutableSetOf()
fun fileAssociation(mimeType: String, extension: String, description: String, iconFile: File? = null) {
fileAssociations.add(FileAssociation(mimeType, extension, description, iconFile))
}
}

abstract class AbstractMacOSPlatformSettings : AbstractPlatformSettings() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,6 @@ private fun JvmApplicationContext.configurePackageTask(
packageTask.packageVendor.set(packageTask.provider { executables.vendor })
packageTask.packageVersion.set(packageVersionFor(packageTask.targetFormat))
packageTask.licenseFile.set(executables.licenseFile)
packageTask.fileAssociations.set(executables.fileAssociations)
}

packageTask.destinationDir.set(app.nativeDistributions.outputBaseDir.map {
Expand Down Expand Up @@ -376,6 +375,7 @@ internal fun JvmApplicationContext.configurePlatformSettings(
packageTask.linuxRpmLicenseType.set(provider { linux.rpmLicenseType })
packageTask.iconFile.set(linux.iconFile.orElse(defaultResources.get { linuxIcon }))
packageTask.installationPath.set(linux.installationPath)
packageTask.fileAssociations.set(linux.fileAssociations)
}
}
OS.Windows -> {
Expand All @@ -389,6 +389,7 @@ internal fun JvmApplicationContext.configurePlatformSettings(
packageTask.winUpgradeUuid.set(provider { win.upgradeUuid })
packageTask.iconFile.set(win.iconFile.orElse(defaultResources.get { windowsIcon }))
packageTask.installationPath.set(win.installationPath)
packageTask.fileAssociations.set(win.fileAssociations)
}
}
OS.MacOS -> {
Expand All @@ -415,6 +416,7 @@ internal fun JvmApplicationContext.configurePlatformSettings(
packageTask.nonValidatedMacSigningSettings = app.nativeDistributions.macOS.signing
packageTask.iconFile.set(mac.iconFile.orElse(defaultResources.get { macIcon }))
packageTask.installationPath.set(mac.installationPath)
packageTask.fileAssociations.set(mac.fileAssociations)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.jetbrains.compose.desktop.application.internal.files.MacJarSignFileCo
import org.jetbrains.compose.desktop.application.internal.JvmRuntimeProperties
import org.jetbrains.compose.desktop.application.internal.validation.validate
import org.jetbrains.compose.internal.utils.*
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import java.io.*
import java.nio.file.LinkOption
import java.util.*
Expand Down Expand Up @@ -250,6 +251,22 @@ abstract class AbstractJPackageTask @Inject constructor(
@get:Input
@get:Optional
val fileAssociations: SetProperty<FileAssociation> = objects.setProperty(FileAssociation::class.java)

private val iconMapping by lazy {
val icons = fileAssociations.orNull.orEmpty().mapNotNull { it.iconFile }
if (icons.isEmpty()) return@lazy emptyMap()
val iconTempNames = generateSequence {
icons.mapTo(mutableSetOf()) { String(CharArray(10) { ('a'..'z').random() }) }
}.first { it.size == icons.size }
val appDir = destinationDir.ioFile.resolve("${packageName.get()}.app")
val iconsDir = appDir.resolve("Contents").resolve("Resources")
if (iconsDir.exists()) {
iconsDir.deleteRecursively()
}
icons.zip(iconTempNames) { icon, newName ->
icon to iconsDir.resolve(newName + icon.name.drop(icon.nameWithoutExtension.length))
}.toMap()
}

private lateinit var jvmRuntimeInfo: JvmRuntimeProperties

Expand Down Expand Up @@ -387,12 +404,14 @@ abstract class AbstractJPackageTask @Inject constructor(
associations.mapIndexed { index, association ->
propertyFilesDirJava.resolve("FA${extension}${if (index > 0) index.toString() else ""}.properties")
.apply {
val withoutIcon = """
mime-type=${association.mimeType}
extension=${association.extension}
description=${association.description}
""".trimIndent()
writeText(
"""
mime-type=${association.mimeType}
extension=${association.extension}
description=${association.description}
""".trimIndent()
if (association.iconFile == null) withoutIcon
else "${withoutIcon}\nicon=${association.iconFile.normalizedPath()}"
)
}
}
Expand Down Expand Up @@ -604,6 +623,15 @@ abstract class AbstractJPackageTask @Inject constructor(

macSigner.sign(runtimeDir, runtimeEntitlementsFile, forceEntitlements = true)
macSigner.sign(appDir, appEntitlementsFile, forceEntitlements = true)

if (iconMapping.isNotEmpty()) {
for ((originalIcon, newIcon) in iconMapping) {
if (originalIcon.exists()) {
newIcon.ensureParentDirsCreated()
originalIcon.copyTo(newIcon)
}
}
}
}

override fun initState() {
Expand Down Expand Up @@ -661,10 +689,11 @@ abstract class AbstractJPackageTask @Inject constructor(
.groupBy { it.mimeType to it.description }
.map { (key, extensions) ->
val (mimeType, description) = key
val iconPath = extensions.firstNotNullOfOrNull { it.iconFile }?.let { iconMapping[it]?.name }
InfoPlistMapValue(
PlistKeys.CFBundleTypeRole to InfoPlistStringValue("Editor"),
PlistKeys.CFBundleTypeExtensions to InfoPlistListValue(extensions.map { InfoPlistStringValue(it.extension) }),
PlistKeys.CFBundleTypeIconFile to InfoPlistStringValue("$packageName.icns"),
PlistKeys.CFBundleTypeIconFile to InfoPlistStringValue(iconPath ?: "$packageName.icns"),
PlistKeys.CFBundleTypeMIMETypes to InfoPlistStringValue(mimeType),
PlistKeys.CFBundleTypeName to InfoPlistStringValue(description),
PlistKeys.CFBundleTypeOSTypes to InfoPlistListValue(InfoPlistStringValue("****")),
Expand Down

0 comments on commit 100aeef

Please sign in to comment.