Skip to content

Commit

Permalink
added KCEF initializer
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed Oct 2, 2023
1 parent dfe05d2 commit 73ffde6
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 23 deletions.
42 changes: 22 additions & 20 deletions example/src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,40 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.window.singleWindowApplication
import dev.datlag.kcef.kcef
import dev.datlag.kcef.KCEF
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.cef.CefApp
import org.cef.browser.CefRendering

fun main() = singleWindowApplication {
var initialized by remember { mutableStateOf(false) }
var download by remember { mutableStateOf(0F) }
var download by remember { mutableStateOf(-1) }

val cefApp by produceState<CefApp?>(null) {
value = withContext(Dispatchers.IO) {
kcef {
addArgs("--no-sandbox")
progress {
onInitialized {
initialized = true
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
KCEF.init(
builder = {
addArgs("--no-sandbox")
progress {
onInitialized {
initialized = true
}
onDownloading {
download = it.toInt()
}
}
onDownloading {
download = it
settings {
noSandbox = true
}
release(true)
}
settings {
noSandbox = true
}
release(true)
}.build()
)
}
}

if (initialized) {
val client = cefApp!!.createClient()
// CEF is definitely initialized here, so we can use the blocking method without produceState
val client = KCEF.newClientBlocking()
val browser = client.createBrowser(
"https://github.com/DATL4G/KCEF",
CefRendering.DEFAULT,
Expand All @@ -49,7 +51,7 @@ fun main() = singleWindowApplication {
modifier = Modifier.fillMaxSize()
)
} else {
if (download > 0F) {
if (download > -1) {
Text("Downloading: $download%")
} else {
Text("Initializing please wait...")
Expand All @@ -58,7 +60,7 @@ fun main() = singleWindowApplication {

DisposableEffect(Unit) {
onDispose {
cefApp?.dispose()
KCEF.disposeBlocking()
}
}
}
9 changes: 8 additions & 1 deletion kcef/src/main/kotlin/dev/datlag/kcef/CefException.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dev.datlag.kcef

sealed class CefException(override val message: String) : Exception(message) {
data object Initialization : CefException("Error while initializing JCef")
data object Startup : CefException("JCef did not initialize correctly!")
data class UnsupportedPlatform(
val os: String?,
Expand All @@ -21,4 +20,12 @@ sealed class CefException(override val message: String) : Exception(message) {
data object Download : CefException("Could not download jcef package")

data object BadArchive : CefException("The provided archive contains a bad (malicious) file")

data object NotInitialized : CefException("Cef was not initialized.")

data object Disposed : CefException("Cef is disposed.")

data object ApplicationRestartRequired : CefException("Application needs to restart.")

data class Error(val exception: Throwable?) : CefException("Got error: ${exception?.message}")
}
176 changes: 176 additions & 0 deletions kcef/src/main/kotlin/dev/datlag/kcef/KCEF.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package dev.datlag.kcef

import dev.datlag.kcef.common.existsSafely
import dev.datlag.kcef.common.suspendCatching
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.cef.CefApp
import org.cef.CefClient
import java.io.File
import kotlin.properties.Delegates

data object KCEF {

private val state: MutableStateFlow<State> = MutableStateFlow(State.New)
private var cefApp by Delegates.notNull<CefApp>()

@JvmOverloads
suspend fun init(
builder: KCEFBuilder.() -> Unit,
onError: (Throwable?) -> Unit = { },
onRestartRequired: () -> Unit = { }
) = init(
builder = KCEFBuilder().apply(builder),
onError = onError,
onRestartRequired = onRestartRequired
)

@JvmOverloads
fun initBlocking(
builder: KCEFBuilder.() -> Unit,
onError: (Throwable?) -> Unit = { },
onRestartRequired: () -> Unit = { }
) = runBlocking {
init(builder, onError, onRestartRequired)
}

@JvmOverloads
suspend fun init(
builder: KCEFBuilder,
onError: (Throwable?) -> Unit = { },
onRestartRequired: () -> Unit = { }
) {
val currentBuilder = when (state.value) {
State.Disposed -> throw CefException.Disposed
State.Initializing, State.Initialized -> null
State.New, is State.Error -> {
state.emit(State.Initializing)
builder
}
} ?: return

val installOk = File(builder.installDir, "install.lock").existsSafely()

if (installOk) {
val result = suspendCatching {
currentBuilder.build()
}
setInitResult(result)
result.exceptionOrNull()?.let(onError)
} else {
val installResult = suspendCatching {
builder.install()
}
installResult.exceptionOrNull()?.let {
setInitResult(Result.failure(it))
onError(it)
}

val result = suspendCatching {
builder.build()
}

setInitResult(result)
if (result.isFailure) {
result.exceptionOrNull()?.let(onError)
setInitResult(Result.failure(CefException.ApplicationRestartRequired))
onRestartRequired.invoke()
}
}
}

@JvmOverloads
fun initBlocking(
builder: KCEFBuilder,
onError: (Throwable?) -> Unit = { },
onRestartRequired: () -> Unit = { }
) = runBlocking {
init(builder, onError, onRestartRequired)
}

suspend fun newClient(): CefClient {
return when (state.value) {
State.New -> throw CefException.NotInitialized
State.Disposed -> throw CefException.Disposed
is State.Error -> throw CefException.Error((state.value as? State.Error)?.exception)
State.Initialized -> cefApp.createClient()
State.Initializing -> {
state.first { it != State.Initializing }

return newClient()
}
}
}

fun newClientBlocking(): CefClient = runBlocking {
newClient()
}

@JvmOverloads
suspend fun newClientOrNull(onError: (Throwable?) -> Unit = { }): CefClient? {
return when (state.value) {
State.New -> {
onError(CefException.NotInitialized)
null
}
State.Disposed -> {
onError(CefException.Disposed)
null
}
is State.Error -> {
onError(CefException.Error((state.value as? State.Error)?.exception))
null
}
State.Initialized -> cefApp.createClient()
State.Initializing -> {
state.first { it != State.Initializing }

return newClientOrNull(onError)
}
}
}

@JvmOverloads
fun newClientOrNullBlocking(onError: (Throwable?) -> Unit = { }): CefClient? = runBlocking {
newClientOrNull(onError)
}

suspend fun dispose() {
when (state.value) {
State.New, State.Disposed, is State.Error -> return
State.Initializing -> {
state.first { it != State.Initializing }

return dispose()
}
State.Initialized -> {
state.emit(State.Disposed)
cefApp.dispose()
}
}
}

fun disposeBlocking() = runBlocking {
dispose()
}

private fun setInitResult(result: Result<CefApp>): Boolean {
val nextState = if (result.isSuccess) {
cefApp = result.getOrThrow()
State.Initialized
} else {
State.Error(result.exceptionOrNull())
}

return state.compareAndSet(State.Initializing, nextState)
}

private sealed class State {
data object New : State()
data object Initializing : State()
data object Initialized : State()
data class Error(val exception: Throwable?) : State()
data object Disposed : State()
}
}
5 changes: 3 additions & 2 deletions kcef/src/main/kotlin/dev/datlag/kcef/KCEFBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import java.io.File

class KCEFBuilder {

private var installDir: File = File("jcef-bundle")
internal var installDir: File = File("jcef-bundle")
private var progress: InitProgress = InitProgress.Builder().build()

private var settings: Settings = scopeCatching {
Expand Down Expand Up @@ -435,7 +435,8 @@ class KCEFBuilder {
uncaughtExceptionStackSize = settings.uncaught_exception_stack_size,
userAgent = settings.user_agent,
userAgentProduct = settings.user_agent_product,
windowlessRenderingEnabled = settings.windowless_rendering_enabled
windowlessRenderingEnabled = settings.windowless_rendering_enabled,
noSandbox = settings.no_sandbox
)
}
}
Expand Down

0 comments on commit 73ffde6

Please sign in to comment.