Skip to content

Commit 08b6e8b

Browse files
committed
feat(core): Integrate KCEF when packing
1 parent 00f1b00 commit 08b6e8b

File tree

5 files changed

+123
-26
lines changed

5 files changed

+123
-26
lines changed

composeApp/build.gradle.kts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,27 @@ val proxyResourcesDir = layout.buildDirectory.dir("compose/proxy-resources")
3030

3131
val kcefPreparedDir = layout.buildDirectory.dir("kcef/prepared")
3232

33+
val kcefDownloaderClasspath by configurations.creating {
34+
isCanBeConsumed = false
35+
isCanBeResolved = true
36+
}
37+
38+
dependencies {
39+
add(
40+
kcefDownloaderClasspath.name,
41+
if (System.getProperty("os.name").lowercase().contains("win")) {
42+
"dev.datlag:kcef:2024.04.20.4"
43+
} else {
44+
libs.kcef.get().toString()
45+
}
46+
)
47+
add(kcefDownloaderClasspath.name, libs.ktor.client.core.get().toString())
48+
add(kcefDownloaderClasspath.name, libs.ktor.client.okhttp.get().toString())
49+
add(kcefDownloaderClasspath.name, libs.ktor.client.content.negotiation.get().toString())
50+
add(kcefDownloaderClasspath.name, libs.ktor.serialization.kotlinx.json.get().toString())
51+
add(kcefDownloaderClasspath.name, libs.ktor.http.get().toString())
52+
}
53+
3354
val downloadKcefBundle by tasks.registering(JavaExec::class) {
3455
val compileKotlinJvmTask = tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>("compileKotlinJvm")
3556
dependsOn(compileKotlinJvmTask)
@@ -43,7 +64,7 @@ val downloadKcefBundle by tasks.registering(JavaExec::class) {
4364
val installDirFile = installDir.get().asFile
4465

4566
val classesDir = compileKotlinJvmTask.flatMap { it.destinationDirectory }
46-
classpath = files(classesDir, configurations.getByName("jvmRuntimeClasspath"))
67+
classpath = files(classesDir, kcefDownloaderClasspath)
4768

4869
onlyIf {
4970
!installDirFile.exists() || installDirFile.listFiles()?.isEmpty() != false
@@ -141,6 +162,11 @@ afterEvaluate {
141162
"processResources",
142163
"prepareAppResources",
143164
"createDistributable",
165+
"createReleaseDistributable",
166+
"createDebugDistributable",
167+
"runDistributable",
168+
"runReleaseDistributable",
169+
"runDebugDistributable",
144170
"packageRelease",
145171
"packageDebug",
146172
"package"
@@ -285,11 +311,12 @@ kotlin {
285311
implementation(compose.desktop.currentOs)
286312
implementation(libs.kotlinx.coroutinesSwing)
287313
implementation(libs.androidx.runtime.desktop)
288-
if (System.getProperty("os.name").lowercase().contains("win")) {
289-
implementation("dev.datlag:kcef:2024.04.20.4")
290-
} else {
291-
implementation(libs.kcef)
292-
}
314+
// if (System.getProperty("os.name").lowercase().contains("win")) {
315+
// implementation("dev.datlag:kcef:2024.04.20.4")
316+
// } else {
317+
// implementation(libs.kcef)
318+
// }
319+
implementation(libs.kcef)
293320
// implementation(libs.vlcj)
294321
implementation(libs.oshi.core)
295322
implementation(libs.versioncompare)

composeApp/src/jvmMain/kotlin/com/jankinwu/fntv/client/main.kt

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,18 @@ fun main() {
108108

109109
// Cleanup old KCEF directories
110110
val baseDir = kcefBaseDir()
111+
val installDir = File(baseDir, "kcef-bundle-${BuildConfig.VERSION_NAME}")
112+
val cacheDir = File(baseDir, "kcef-cache-${BuildConfig.VERSION_NAME}")
111113
cleanupOldKcefDirs(baseDir)
114+
Logger.withTag("main").i { "KCEF base directory: ${baseDir.absolutePath}" }
115+
Logger.withTag("main").i { "KCEF install directory: ${installDir.absolutePath}" }
116+
Logger.withTag("main").i { "KCEF cache directory: ${cacheDir.absolutePath}" }
112117

113118
application {
114119
LaunchedEffect(Unit) {
115120
WebViewBootstrap.start(
116-
installDir = kcefInstallDir(),
117-
cacheDir = kcefCacheDir(),
121+
installDir = installDir,
122+
cacheDir = cacheDir,
118123
logDir = logDir
119124
)
120125
}
@@ -535,14 +540,8 @@ private fun kcefBaseDir(): File {
535540
)
536541

537542
is Platform.Windows -> {
538-
val localAppData = System.getenv("LOCALAPPDATA")?.takeIf { it.isNotBlank() }
539-
val defaultBase = File(localAppData ?: System.getProperty("user.home"), "FlyNarwhal")
540-
if (defaultBase.absolutePath.any { it.code > 127 }) {
541-
val programData = System.getenv("PROGRAMDATA")?.takeIf { it.isNotBlank() } ?: "C:\\ProgramData"
542-
File(programData, "FlyNarwhal")
543-
} else {
544-
defaultBase
545-
}
543+
val exeDir = ExecutableDirectoryDetector.INSTANCE.getExecutableDirectory()
544+
File(exeDir, "app/resources")
546545
}
547546
}
548547
}

composeApp/src/jvmMain/kotlin/com/jankinwu/fntv/client/utils/DesktopUpdateInstaller.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,8 @@ interface DesktopUpdateInstaller : UpdateInstaller {
9494
is Platform.Linux -> File(System.getProperty("user.home"), ".local/share/fly-narwhal")
9595
is Platform.MacOS -> File(System.getProperty("user.home"), "Library/Application Support/fly-narwhal")
9696
is Platform.Windows -> {
97-
val localAppData = System.getenv("LOCALAPPDATA")?.takeIf { it.isNotBlank() }
98-
val defaultBase = File(localAppData ?: System.getProperty("user.home"), "FlyNarwhal")
99-
if (defaultBase.absolutePath.any { it.code > 127 }) {
100-
val programData = System.getenv("PROGRAMDATA")?.takeIf { it.isNotBlank() } ?: "C:\\ProgramData"
101-
File(programData, "FlyNarwhal")
102-
} else {
103-
defaultBase
104-
}
97+
val exeDir = ExecutableDirectoryDetector.INSTANCE.getExecutableDirectory()
98+
File(exeDir, "app/resources")
10599
}
106100
}
107101

composeApp/src/jvmMain/kotlin/com/jankinwu/fntv/client/utils/WebViewBootstrap.kt

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,19 @@ object WebViewBootstrap {
7171

7272
if (!started.compareAndSet(false, true)) return
7373

74+
runCatching {
75+
installDir.parentFile?.mkdirs()
76+
if (!installDir.exists()) installDir.mkdirs()
77+
if (!cacheDir.exists()) cacheDir.mkdirs()
78+
}.onFailure { t ->
79+
logger.w(t) { "Failed to create KCEF directories: installDir=${installDir.absolutePath}, cacheDir=${cacheDir.absolutePath}" }
80+
}
81+
82+
logger.i {
83+
val resourcesDir = System.getProperty("compose.application.resources.dir") ?: "(null)"
84+
"KCEF bootstrap starting. version=${BuildConfig.VERSION_NAME}, installDir=${installDir.absolutePath}, cacheDir=${cacheDir.absolutePath}, resourcesDir=$resourcesDir"
85+
}
86+
7487
val currentVersion = BuildConfig.VERSION_NAME
7588

7689
var isKcefInitialized = AppSettingsStore.kcefInitialized &&
@@ -183,6 +196,13 @@ object WebViewBootstrap {
183196
initError.value = preflightError
184197
return
185198
}
199+
logger.i("2.KCEF install directory is complete: ${installDir.absolutePath}")
200+
201+
val os = System.getProperty("os.name").lowercase()
202+
if (os.contains("win")) {
203+
val files = installDir.listFiles()?.map { it.name } ?: emptyList()
204+
logger.i("KCEF install directory files: $files")
205+
}
186206

187207
val initFailure = runCatching {
188208
withContext(awtDispatcher) {
@@ -192,6 +212,8 @@ object WebViewBootstrap {
192212
settings {
193213
cachePath = cacheDir.absolutePath
194214
logFile = kcefLog.absolutePath
215+
resourcesDirPath = installDir.absolutePath
216+
localesDirPath = File(installDir, "locales").absolutePath
195217
}
196218
progress {
197219
onInitialized {
@@ -233,10 +255,26 @@ object WebViewBootstrap {
233255
start(lastInstallDir!!, lastCacheDir!!, lastLogDir!!)
234256
}
235257

258+
private fun addLibraryDir(libraryPath: String) {
259+
try {
260+
val field = ClassLoader::class.java.getDeclaredField("usr_paths")
261+
field.isAccessible = true
262+
val paths = field.get(null) as Array<String>
263+
if (paths.contains(libraryPath)) return
264+
val newPaths = paths.copyOf(paths.size + 1)
265+
newPaths[paths.size] = libraryPath
266+
field.set(null, newPaths)
267+
logger.i { "Added to java.library.path: $libraryPath" }
268+
} catch (t: Throwable) {
269+
logger.w(t) { "Failed to add library path: $libraryPath" }
270+
}
271+
}
272+
236273
private fun extractBundledKcef(targetInstallDir: File) {
237274
if (!targetInstallDir.exists()) targetInstallDir.mkdirs()
238275

239276
if (extractBundledKcefFromResourcesDir(targetInstallDir)) return
277+
if (extractBundledKcefFromClasspathDirs(targetInstallDir)) return
240278
if (extractBundledKcefFromClasspathJar(targetInstallDir)) return
241279

242280
logger.w { "Bundled KCEF directory not found in resources dir or classpath, skipping extraction" }
@@ -248,7 +286,35 @@ object WebViewBootstrap {
248286
val bundledDir = File(resourcesDir, "kcef-bundle")
249287
if (!bundledDir.exists() || !bundledDir.isDirectory) return false
250288

251-
logger.i { "Extracting bundled KCEF from resources dir: ${bundledDir.absolutePath} -> ${targetInstallDir.absolutePath}" }
289+
return copyBundledDir(
290+
bundledDir = bundledDir,
291+
targetInstallDir = targetInstallDir,
292+
sourceLabel = "resources dir"
293+
)
294+
}
295+
296+
private fun extractBundledKcefFromClasspathDirs(targetInstallDir: File): Boolean {
297+
val classPath = System.getProperty("java.class.path")?.takeIf { it.isNotBlank() } ?: return false
298+
val entries = classPath.split(File.pathSeparatorChar).map { it.trim() }.filter { it.isNotBlank() }
299+
300+
for (entry in entries) {
301+
val dir = File(entry)
302+
if (!dir.isDirectory) continue
303+
304+
val bundledDir = File(dir, "kcef-bundle")
305+
if (!bundledDir.isDirectory) continue
306+
307+
return copyBundledDir(
308+
bundledDir = bundledDir,
309+
targetInstallDir = targetInstallDir,
310+
sourceLabel = "classpath dir"
311+
)
312+
}
313+
return false
314+
}
315+
316+
private fun copyBundledDir(bundledDir: File, targetInstallDir: File, sourceLabel: String): Boolean {
317+
logger.i { "Extracting bundled KCEF from $sourceLabel: ${bundledDir.absolutePath} -> ${targetInstallDir.absolutePath}" }
252318

253319
val supportsPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix")
254320
var fileCount = 0
@@ -318,6 +384,16 @@ object WebViewBootstrap {
318384
val localesDir = File(installDir, "locales")
319385
val hasLocales = localesDir.isDirectory && localesDir.listFiles()?.any { it.isFile && it.name.endsWith(".pak") } == true
320386
val hasIcu = File(installDir, "icudtl.dat").isFile
387+
388+
// Check for critical binaries on Windows
389+
val os = System.getProperty("os.name").lowercase()
390+
if (os.contains("win")) {
391+
val hasLibCef = File(installDir, "libcef.dll").isFile
392+
val hasJcef = File(installDir, "jcef.dll").isFile
393+
val hasHelper = File(installDir, "jcef_helper.exe").isFile
394+
return hasLocales && hasIcu && hasLibCef && hasJcef && hasHelper
395+
}
396+
321397
return hasLocales && hasIcu
322398
}
323399

settings.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ dependencyResolutionManagement {
3030
mavenCentral()
3131
maven("https://jitpack.io")
3232
maven("https://artifacts.alfresco.com/nexus/content/repositories/public/")
33+
maven("https://maven.scijava.org/content/repositories/public/")
3334
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
3435
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
3536
maven("https://jogamp.org/deployment/maven/")
@@ -41,4 +42,4 @@ plugins {
4142
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
4243
}
4344

44-
include(":composeApp")
45+
include(":composeApp")

0 commit comments

Comments
 (0)