mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-09-20 06:34:00 +00:00
fix(webview): linux webview crashing the app
This commit is contained in:
parent
e0b9ab530d
commit
e826052691
@ -19,6 +19,7 @@ package dev.krtirtho.spotube
|
||||
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -41,6 +42,7 @@ import dev.krtirtho.spotube.core.ui.theming.SpotubeTheme
|
||||
import dev.krtirtho.spotube.modules.settings.SettingsRepository
|
||||
import dev.krtirtho.spotube.modules.settings.UserSettings
|
||||
import dev.krtirtho.spotube.modules.shell.AppShell
|
||||
import dev.krtirtho.spotube.modules.webview.WebViewScreen
|
||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxHome
|
||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxHomeBroken
|
||||
@ -89,7 +91,11 @@ val tabs = listOf(
|
||||
)
|
||||
)
|
||||
|
||||
@OptIn(KoinExperimentalAPI::class, ExperimentalCoroutinesApi::class, ExperimentalSharedTransitionApi::class)
|
||||
@OptIn(
|
||||
KoinExperimentalAPI::class,
|
||||
ExperimentalCoroutinesApi::class,
|
||||
ExperimentalSharedTransitionApi::class
|
||||
)
|
||||
@Composable
|
||||
fun App(
|
||||
content: @Composable () -> Unit = {}
|
||||
@ -108,19 +114,29 @@ fun App(
|
||||
SpotubeTheme(settings = userSettings) {
|
||||
val baseUITheme = rememberBaseUITheme()
|
||||
CompositionLocalProvider(LocalBaseUITheme provides baseUITheme) {
|
||||
AppShell(navigator, navigationState) {
|
||||
SharedTransitionLayout {
|
||||
CompositionLocalProvider(
|
||||
LocalSharedTransitionScope provides this@SharedTransitionLayout,
|
||||
) {
|
||||
Column {
|
||||
NavDisplay(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onBack = navigator::pop,
|
||||
entries = navigationState.toEntries(koinEntryProvider())
|
||||
)
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
val currentRoute = navigationState.backStacks[navigationState.topLevelRoute]?.last()
|
||||
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AppShell(navigator, navigationState) {
|
||||
SharedTransitionLayout {
|
||||
CompositionLocalProvider(
|
||||
LocalSharedTransitionScope provides this@SharedTransitionLayout,
|
||||
) {
|
||||
Column {
|
||||
NavDisplay(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onBack = navigator::pop,
|
||||
entries = navigationState.toEntries(koinEntryProvider())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
when (currentRoute) {
|
||||
Routes.WebView -> WebViewScreen(koinInject())
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
content()
|
||||
|
||||
@ -18,9 +18,8 @@
|
||||
package dev.krtirtho.spotube.core.navigation
|
||||
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import dev.krtirtho.spotube.modules.artist.ArtistScreen
|
||||
import dev.krtirtho.spotube.modules.album.AlbumScreen
|
||||
import dev.krtirtho.spotube.modules.artist.ArtistViewModel
|
||||
import dev.krtirtho.spotube.modules.artist.ArtistScreen
|
||||
import dev.krtirtho.spotube.modules.blacklist.BlacklistScreen
|
||||
import dev.krtirtho.spotube.modules.home.HomeScreen
|
||||
import dev.krtirtho.spotube.modules.library.LibraryScreen
|
||||
@ -31,7 +30,6 @@ import dev.krtirtho.spotube.modules.saved_tracks.SAVED_TRACKS_COLLECTION_ID
|
||||
import dev.krtirtho.spotube.modules.saved_tracks.SavedTracksScreen
|
||||
import dev.krtirtho.spotube.modules.search.SearchScreen
|
||||
import dev.krtirtho.spotube.modules.settings.SettingsScreen
|
||||
import dev.krtirtho.spotube.modules.webview.WebViewScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.annotation.KoinExperimentalAPI
|
||||
@ -97,9 +95,7 @@ val navigationModule = module {
|
||||
navigation<Routes.Plugins> {
|
||||
PluginScreen(pluginManager = get())
|
||||
}
|
||||
navigation<Routes.WebView> {
|
||||
WebViewScreen(get())
|
||||
}
|
||||
navigation<Routes.WebView> {}
|
||||
navigation<Routes.Playlist> {
|
||||
PlaylistScreen(
|
||||
playlistId = it.playlistId,
|
||||
|
||||
@ -19,6 +19,7 @@ package dev.krtirtho.spotube.core.webview
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@ -51,6 +52,7 @@ import compose.icons.feathericons.ChevronLeft
|
||||
import compose.icons.feathericons.ChevronRight
|
||||
import compose.icons.feathericons.X
|
||||
import dev.krtirtho.spotube.core.tools.user_agents.UserAgents
|
||||
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
||||
import dev.nucleusframework.webview.jsbridge.IJsMessageHandler
|
||||
import dev.nucleusframework.webview.jsbridge.JsMessage
|
||||
import dev.nucleusframework.webview.jsbridge.rememberWebViewJsBridge
|
||||
@ -149,64 +151,63 @@ fun PlatformWebViewScreen(webViewController: WebViewController) {
|
||||
Scaffold(
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().statusBarsPadding().height(56.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
modifier = Modifier.height(56.dp)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
navigator.navigateBack()
|
||||
}, enabled = navigator.canGoBack
|
||||
ApplicationMainBar(
|
||||
title = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().statusBarsPadding().height(56.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
FeatherIcons.ChevronLeft,
|
||||
contentDescription = "Go back to browser history"
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
navigator.navigateForward()
|
||||
}, enabled = navigator.canGoForward
|
||||
) {
|
||||
Icon(
|
||||
FeatherIcons.ChevronRight,
|
||||
contentDescription = "Go forward to browser history"
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
modifier = Modifier.height(56.dp)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
navigator.navigateBack()
|
||||
}, enabled = navigator.canGoBack
|
||||
) {
|
||||
Icon(
|
||||
FeatherIcons.ChevronLeft,
|
||||
contentDescription = "Go back to browser history"
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
navigator.navigateForward()
|
||||
}, enabled = navigator.canGoForward
|
||||
) {
|
||||
Icon(
|
||||
FeatherIcons.ChevronRight,
|
||||
contentDescription = "Go forward to browser history"
|
||||
)
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier.weight(1f).height(36.dp).padding(horizontal = 4.dp),
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
|
||||
border = BorderStroke(1.dp, Color.Gray.copy(alpha = 0.5f))
|
||||
) {
|
||||
BasicTextField(
|
||||
value = state.lastLoadedUrl ?: "",
|
||||
onValueChange = {}, // Read-only
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
textStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Start
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp)
|
||||
.wrapContentHeight(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier.weight(1f).height(36.dp).padding(horizontal = 4.dp),
|
||||
shape = RoundedCornerShape(18.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
|
||||
border = BorderStroke(1.dp, Color.Gray.copy(alpha = 0.5f))
|
||||
) {
|
||||
BasicTextField(
|
||||
value = state.lastLoadedUrl ?: "",
|
||||
onValueChange = {}, // Read-only
|
||||
readOnly = true,
|
||||
singleLine = true,
|
||||
textStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Start
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp)
|
||||
.wrapContentHeight(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
webViewController.closeWebview()
|
||||
}) {
|
||||
Icon(FeatherIcons.X, contentDescription = "Close WebView")
|
||||
}
|
||||
}
|
||||
}) { innerPadding ->
|
||||
)
|
||||
})
|
||||
{ innerPadding ->
|
||||
WebView(
|
||||
state = state,
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize(),
|
||||
|
||||
@ -26,6 +26,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Native
|
||||
import dev.krtirtho.spotube.core.di.initKoin
|
||||
import dev.krtirtho.spotube.core.newpipe.NewPipeDownloader
|
||||
import dev.krtirtho.spotube.core.paths.Paths
|
||||
@ -55,8 +57,33 @@ private object KoinServicesProvider : KoinComponent {
|
||||
val settingsProvider: SettingsProvider get() = get()
|
||||
}
|
||||
|
||||
private interface LibC : Library {
|
||||
fun setenv(name: String, value: String, overwrite: Int): Int
|
||||
|
||||
companion object {
|
||||
val INSTANCE: LibC = Native.load("c", LibC::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebKitGTK is only used on Linux (Windows = WebView2, macOS = WKWebView).
|
||||
* The webview is created *after* the window is already mapped and the Tao
|
||||
* render/swap loop is running. WebKitGTK's accelerated-compositing path then
|
||||
* initialises its own GL context in-process, racing Tao's swap thread on the
|
||||
* same Mesa display, which deterministically segfaults `libgallium` on the
|
||||
* next Compose flush. Disabling WebKit's hardware-accelerated compositing
|
||||
* (and its DMABUF renderer) removes that GL context entirely — login pages
|
||||
* render fine in software. Must run before libwebkit2gtk is loaded.
|
||||
*/
|
||||
private fun disableWebKitGpuCompositing() {
|
||||
if (!System.getProperty("os.name").lowercase().contains("linux")) return
|
||||
LibC.INSTANCE.setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", 1)
|
||||
LibC.INSTANCE.setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", 1)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun main() {
|
||||
disableWebKitGpuCompositing()
|
||||
FileKit.init(appId = "dev.krtirtho.spotube")
|
||||
initKoin()
|
||||
NewPipeDownloader.init(KoinPathsProvider.paths)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user