diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index 0ec8f6836e..61485c6e68 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -91,6 +91,9 @@ compose.desktop { jvmArgs += "-Xmx2g" + // VLC plugin path fallback — used if JNA setenv and bundled discovery both fail + jvmArgs += "-Dvlc.plugin.path=\$APPDIR/resources/vlc/plugins" + // Forward platform-preview overrides from the gradle invocation to the // launched app's JVM so `./gradlew :desktopApp:run -Damethyst.platform=GNOME` // works in addition to the env-var form (`AMETHYST_PLATFORM=GNOME`). @@ -101,7 +104,15 @@ compose.desktop { nativeDistributions { appResourcesRootDir.set(project.layout.projectDirectory.dir("src/jvmMain/appResources")) targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.Rpm) - modules("java.management") // Required by kmp-tor TorRuntime + // Output of ./gradlew suggestRuntimeModules (+ java.management already present) + modules( + "java.instrument", // Runtime instrumentation (agent/profiler hooks) + "java.management", // Required by kmp-tor TorRuntime + "java.prefs", // java.util.prefs (desktop persistence) + "java.sql", // JDBC metadata (Jackson, SQLite driver) + "jdk.security.auth", // JAAS authentication callbacks + "jdk.unsupported", // sun.misc.Unsafe (VLCJ ByteBufferFactory) + ) packageName = "Amethyst" packageVersion = appVersion @@ -143,6 +154,7 @@ compose.desktop { // whose declared return type the JVM verifier rejects (R8 doesn't hit // this — it generates bridges differently from ProGuard). buildTypes.release.proguard { + version.set("7.9.1") // Kotlin 2.3 metadata support configurationFiles.from(project.file("compose-rules.pro")) } } diff --git a/desktopApp/compose-rules.pro b/desktopApp/compose-rules.pro index bb824643d1..8241422ed6 100644 --- a/desktopApp/compose-rules.pro +++ b/desktopApp/compose-rules.pro @@ -96,6 +96,16 @@ native ; } +# kmp-tor — loads native Tor daemon via JNI reflection +-keep class io.matthewnelson.** { *; } + +# Coil image loader — uses ServiceLoader for decoder/fetcher registration +-keep class coil3.** { *; } + +# OkHttp/Okio — platform detection and I/O via reflection +-keep class okhttp3.** { *; } +-keep class okio.** { *; } + # ============================================================================ # Optimize sub-pass — disable the one that produces invalid okio bytecode # ============================================================================ @@ -185,3 +195,9 @@ # to detect logging. We ship slf4j-nop; keep it intact so detection succeeds. -keep class org.slf4j.** { *; } -dontwarn org.slf4j.** + +# ============================================================================ +# Kotlin 2.3 stdlib stubs — compile-time classes with no JVM runtime class +# ============================================================================ +-dontwarn kotlin.concurrent.atomics.** +-dontwarn kotlin.jvm.internal.EnhancedNullability diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/service/media/MacOsVlcDiscoverer.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/service/media/MacOsVlcDiscoverer.kt index d7f9c02f2f..58d7f9fa54 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/service/media/MacOsVlcDiscoverer.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/service/media/MacOsVlcDiscoverer.kt @@ -20,8 +20,8 @@ */ package com.vitorpamplona.amethyst.desktop.service.media +import com.sun.jna.Function import com.sun.jna.NativeLibrary -import uk.co.caprica.vlcj.binding.lib.LibC import uk.co.caprica.vlcj.binding.support.runtime.RuntimeUtil import uk.co.caprica.vlcj.factory.discovery.strategy.BaseNativeDiscoveryStrategy @@ -36,6 +36,14 @@ class MacOsVlcDiscoverer : arrayOf("libvlc\\.dylib", "libvlccore\\.dylib"), arrayOf("%s/plugins"), ) { + /** Plugin path discovered during [setPluginPath], available after discovery. */ + var discoveredPluginPath: String? = null + private set + + /** Whether [setPluginPath] successfully set the process env var. */ + var envVarSet: Boolean = false + private set + override fun supported(): Boolean { val os = System.getProperty("os.name").lowercase() return "mac" in os @@ -52,5 +60,22 @@ class MacOsVlcDiscoverer : return true } - override fun setPluginPath(pluginPath: String?): Boolean = LibC.INSTANCE.setenv(PLUGIN_ENV_NAME, pluginPath, 1) == 0 + override fun setPluginPath(pluginPath: String?): Boolean { + if (pluginPath == null) return false + discoveredPluginPath = pluginPath + return try { + // Call setenv directly via JNA Function API. This bypasses vlcj's + // LibC interface binding which fails on macOS 13+ because dlsym + // can't resolve the versioned symbol `setenv$3b99ba0d`. + val setenv = Function.getFunction("c", "setenv") + val result = setenv.invokeInt(arrayOf(PLUGIN_ENV_NAME, pluginPath, 1)) == 0 + envVarSet = result + result + } catch (_: Throwable) { + // JNA Function call also failed — VlcjPlayerPool will use + // --plugin-path factory arg as fallback. + envVarSet = false + false + } + } }