From ad5d40756d24e7288ed0dc624d8755f2ba334e23 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Wed, 14 May 2025 11:39:31 -0500 Subject: [PATCH 1/9] Fix danish string for dialog_message_exit_game --- app/src/main/res/values-da/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 744f69e9..af1a61d3 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -156,7 +156,7 @@ Er du sikker på, at du vil slette denne app?\n\n\tStørrelse på disk: %1$s Den app, der installeres, har følgende pladskrav. Vil du fortsætte?\nDownloadstørrelse: %1$s\nStørrelse på disk: %2$s\nTilgængelig plads: %3$s Ubuntu Isoen skal downloades og installeres, før du kan redigere konfigurationen. Denne handling kan tage et par minutter. Vil du fortsætte? - "Er du sikker på, at du vil lukke spillet " + "Er du sikker på, at du vil lukke spillet %1$s?" Er du sikker på, at du vil blokere %1$s? Er du sikker på, at du vil sætte %1$s som favorit? Ingen tidligere aliaser fundet From dd7937ad4f786a1ebf856daf344bac6ca25c487a Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Wed, 14 May 2025 12:10:43 -0500 Subject: [PATCH 2/9] Add TODOs about keyboard. --- .../com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt | 7 ++++++- .../OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt index 6750971f..ec2fb30a 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.viewinterop.AndroidView import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -51,6 +52,7 @@ fun XServerScreen( ) { val context = LocalContext.current val scope = rememberCoroutineScope() + val keyboard = LocalSoftwareKeyboardController.current val xServerState by viewModel.state.collectAsStateWithLifecycle() @@ -59,6 +61,7 @@ fun XServerScreen( when (event) { XServerViewModel.XServerUiEvent.OnExit -> onExit() XServerViewModel.XServerUiEvent.OnNavigateBack -> navigateBack() + XServerViewModel.XServerUiEvent.OnKeyboard -> keyboard?.show() } } } @@ -70,8 +73,8 @@ fun XServerScreen( onConfirmClick = { scope.launch { viewModel.exit() + exitDialogVisible = false } - exitDialogVisible = false }, onDismissClick = { exitDialogVisible = false }, confirmBtnText = R.string.close, @@ -96,6 +99,8 @@ fun XServerScreen( } } + // TODO IME onKeyEvent or onPreviewKeyEvent ? + // TODO Hamburger menu or option to invoke keyboard in "quit" dialog. AndroidView( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt index fb6f3802..6f8222ef 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt @@ -99,6 +99,7 @@ class XServerViewModel @Inject constructor( sealed class XServerUiEvent { data object OnExit : XServerUiEvent() + data object OnKeyboard : XServerUiEvent() data object OnNavigateBack : XServerUiEvent() } From b3017301ada3889d1d05b1ebf8d7ebc5eeb49bb8 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Wed, 14 May 2025 21:38:04 -0500 Subject: [PATCH 3/9] Add Navigation Drawer. Get the keyboard to finally show, still not consuming inputs currently. --- .../Pluvia/ui/screen/xserver/XServerScreen.kt | 318 +++++++++++++----- .../Pluvia/ui/screen/xserver/XServerState.kt | 44 +-- .../ui/screen/xserver/XServerViewModel.kt | 13 +- app/src/main/res/values/strings.xml | 1 + 4 files changed, 266 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt index ec2fb30a..1cf15ffe 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt @@ -1,29 +1,58 @@ package com.OxGames.Pluvia.ui.screen.xserver +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import android.text.format.DateUtils import android.view.MotionEvent +import android.view.inputmethod.InputMethodManager +import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ExitToApp +import androidx.compose.material.icons.filled.Keyboard +import androidx.compose.material3.DrawerValue +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.OxGames.Pluvia.R import com.OxGames.Pluvia.ui.component.dialog.MessageDialog +import com.OxGames.Pluvia.ui.theme.PluviaTheme import com.OxGames.Pluvia.utils.ContainerUtils import com.winlator.container.ContainerManager import com.winlator.inputcontrols.TouchMouse @@ -52,31 +81,48 @@ fun XServerScreen( ) { val context = LocalContext.current val scope = rememberCoroutineScope() - val keyboard = LocalSoftwareKeyboardController.current + var isExitDialogOpen by remember { mutableStateOf(false) } val xServerState by viewModel.state.collectAsStateWithLifecycle() + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + + // imm.showSoftInput() doesn't work + // Compose LocalSoftwareKeyboardController doesn't work. + @Suppress("DEPRECATION") + val showKeyboard = { + // Old school way just like in Winlator, with extra Compose hackery. + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { + (context as ComponentActivity).window.decorView.postDelayed( + { + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) + }, + 500L, + ) + } else { + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) + } + } LaunchedEffect(Unit) { viewModel.uiEvent.collect { event -> when (event) { XServerViewModel.XServerUiEvent.OnExit -> onExit() XServerViewModel.XServerUiEvent.OnNavigateBack -> navigateBack() - XServerViewModel.XServerUiEvent.OnKeyboard -> keyboard?.show() } } } - var exitDialogVisible by rememberSaveable { mutableStateOf(false) } MessageDialog( - visible = exitDialogVisible, - onDismissRequest = { exitDialogVisible = false }, + visible = isExitDialogOpen, + onDismissRequest = { isExitDialogOpen = false }, onConfirmClick = { scope.launch { + isExitDialogOpen = false viewModel.exit() - exitDialogVisible = false } }, - onDismissClick = { exitDialogVisible = false }, + onDismissClick = { isExitDialogOpen = false }, confirmBtnText = R.string.close, dismissBtnText = R.string.cancel, icon = Icons.AutoMirrored.Filled.ExitToApp, @@ -88,7 +134,15 @@ fun XServerScreen( ) BackHandler { - exitDialogVisible = true + scope.launch { + if (drawerState.isClosed) { + drawerState.open() + } else { + drawerState.close() + } + + Timber.i("Drawer state is now: ${drawerState.currentValue}") + } } // Verify @@ -99,92 +153,180 @@ fun XServerScreen( } } - // TODO IME onKeyEvent or onPreviewKeyEvent ? - // TODO Hamburger menu or option to invoke keyboard in "quit" dialog. - AndroidView( - modifier = Modifier - .fillMaxSize() - .pointerHoverIcon(PointerIcon(0)) - .pointerInteropFilter(onTouchEvent = touchHandler), - factory = { - Timber.i("Creating XServerView and XServer") - - viewModel.init(appId) - - XServerView(context, XServer(ScreenInfo(xServerState.screenSize))).apply { - viewModel.xServerView = this - - renderer.isCursorVisible = false - - xServer.renderer = renderer - xServer.winHandler = WinHandler(xServer, this) - - viewModel.touchMouse = TouchMouse(xServer) - viewModel.keyboard = Keyboard(xServer) - - if (!bootToContainer) { - renderer.setUnviewableWMClasses("explorer.exe") - // TODO: make 'force fullscreen' be an option of the app being launched - viewModel.appLaunchInfo?.let { renderer.forceFullscreenWMClass = Paths.get(it.executable).name } - } - - xServer.windowManager.addOnWindowModificationListener( - object : WindowManager.OnWindowModificationListener { - override fun onUpdateWindowContent(window: Window) { - if (!xServerState.winStarted && window.isApplicationWindow()) { - renderer.setCursorVisible(true) - viewModel.winStarted(true) - } - } + ModalNavigationDrawer( + drawerState = drawerState, + gesturesEnabled = false, + drawerContent = { + XServerDrawer( + state = xServerState, + onKeyboard = { + scope.launch { + Timber.i("Show keyboard called") + drawerState.close() + showKeyboard() + } + }, + onExit = { + scope.launch { + Timber.i("Exit Dialog called") + drawerState.close() + isExitDialogOpen = true + } + }, + ) + }, + content = { + AndroidView( + modifier = Modifier + .fillMaxSize() + .pointerHoverIcon(PointerIcon(0)) + .pointerInteropFilter(onTouchEvent = touchHandler), + factory = { + Timber.i("Creating XServerView and XServer") - override fun onModifyWindowProperty(window: Window, property: Property) { - } + viewModel.init(appId) - override fun onMapWindow(window: Window) { - Timber.i( - "onMapWindow:" + - "\n\twindowName: ${window.name}" + - "\n\twindowClassName: ${window.className}" + - "\n\tprocessId: ${window.processId}" + - "\n\thasParent: ${window.parent != null}" + - "\n\tchildrenSize: ${window.children.size}", - ) + XServerView(context, XServer(ScreenInfo(xServerState.screenSize))).apply { + viewModel.xServerView = this - viewModel.assignTaskAffinity(window, xServer.winHandler) + renderer.isCursorVisible = false - onWindowMapped?.invoke(window) - } + xServer.renderer = renderer + xServer.winHandler = WinHandler(xServer, this) - override fun onUnmapWindow(window: Window) { - Timber.i( - "onUnmapWindow:" + - "\n\twindowName: ${window.name}" + - "\n\twindowClassName: ${window.className}" + - "\n\tprocessId: ${window.processId}" + - "\n\thasParent: ${window.parent != null}" + - "\n\tchildrenSize: ${window.children.size}", - ) - - // changeFrameRatingVisibility(window, null) - onWindowUnmapped?.invoke(window) + viewModel.touchMouse = TouchMouse(xServer) + viewModel.keyboard = Keyboard(xServer) + + if (!bootToContainer) { + renderer.setUnviewableWMClasses("explorer.exe") + // TODO: make 'force fullscreen' be an option of the app being launched + viewModel.appLaunchInfo?.let { renderer.forceFullscreenWMClass = Paths.get(it.executable).name } } - }, - ) - if (viewModel.xEnvironment != null) { - viewModel.xEnvironment = viewModel.shiftXEnvironmentToContext(xServer = xServer) - } else { - val containerManager = ContainerManager(context) - val container = ContainerUtils.getContainer(context, appId) - containerManager.activateContainer(container) + xServer.windowManager.addOnWindowModificationListener( + object : WindowManager.OnWindowModificationListener { + override fun onUpdateWindowContent(window: Window) { + if (!xServerState.winStarted && window.isApplicationWindow()) { + renderer.setCursorVisible(true) + viewModel.winStarted(true) + } + } - viewModel.setBootConfig(container, containerManager, bootToContainer) - } - } - }, - update = {}, - onRelease = { - Timber.w("AndroidView Release") + override fun onModifyWindowProperty(window: Window, property: Property) { + } + + override fun onMapWindow(window: Window) { + Timber.i( + "onMapWindow:" + + "\n\twindowName: ${window.name}" + + "\n\twindowClassName: ${window.className}" + + "\n\tprocessId: ${window.processId}" + + "\n\thasParent: ${window.parent != null}" + + "\n\tchildrenSize: ${window.children.size}", + ) + + viewModel.assignTaskAffinity(window, xServer.winHandler) + + onWindowMapped?.invoke(window) + } + + override fun onUnmapWindow(window: Window) { + Timber.i( + "onUnmapWindow:" + + "\n\twindowName: ${window.name}" + + "\n\twindowClassName: ${window.className}" + + "\n\tprocessId: ${window.processId}" + + "\n\thasParent: ${window.parent != null}" + + "\n\tchildrenSize: ${window.children.size}", + ) + + // changeFrameRatingVisibility(window, null) + onWindowUnmapped?.invoke(window) + } + }, + ) + + if (viewModel.xEnvironment != null) { + viewModel.xEnvironment = viewModel.shiftXEnvironmentToContext(xServer = xServer) + } else { + val containerManager = ContainerManager(context) + val container = ContainerUtils.getContainer(context, appId) + containerManager.activateContainer(container) + + viewModel.setBootConfig(container, containerManager, bootToContainer) + } + } + }, + update = { }, + onRelease = { + Timber.w("AndroidView Release") + }, + ) }, ) } + +@Composable +private fun XServerDrawer( + state: XServerState, + onKeyboard: () -> Unit, + onExit: () -> Unit, +) { + ModalDrawerSheet { + // Header + Row( + modifier = Modifier + .background(MaterialTheme.colorScheme.surfaceDim) + .padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + modifier = Modifier.size(96.dp), + painter = painterResource(id = R.drawable.ic_logo_color), + contentDescription = null, + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.Center, + ) { + Text( + text = stringResource(id = R.string.app_name), + fontSize = 32.sp, + ) + Text( + text = DateUtils.formatDateTime(LocalContext.current, state.currentTime, DateUtils.FORMAT_SHOW_TIME), + fontSize = 16.sp, + ) + } + } + // Items + NavigationDrawerItem( + label = { Text(text = stringResource(R.string.xserver_drawer_show_keyboard)) }, + icon = { Icon(imageVector = Icons.Default.Keyboard, contentDescription = null) }, + selected = false, + onClick = onKeyboard, + ) + NavigationDrawerItem( + label = { Text(text = stringResource(R.string.exit)) }, + icon = { Icon(imageVector = Icons.AutoMirrored.Filled.ExitToApp, contentDescription = null) }, + selected = false, + onClick = onExit, + ) + } +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL) +@Preview +@Composable +private fun Preview_XServerDrawer() { + PluviaTheme { + Surface { + XServerDrawer( + state = XServerState(), + onKeyboard = {}, + onExit = {}, + ) + } + } +} diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerState.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerState.kt index b0c064ab..4b2cb262 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerState.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerState.kt @@ -1,21 +1,23 @@ -package com.OxGames.Pluvia.ui.screen.xserver - -import com.winlator.container.Container -import com.winlator.core.KeyValueSet -import com.winlator.core.WineInfo - -data class XServerState( - var winStarted: Boolean = false, - val dxwrapper: String = Container.DEFAULT_DXWRAPPER, - val dxwrapperConfig: KeyValueSet? = null, - val screenSize: String = Container.DEFAULT_SCREEN_SIZE, - val wineInfo: WineInfo = WineInfo.MAIN_WINE_VERSION, - val graphicsDriver: String = Container.DEFAULT_GRAPHICS_DRIVER, - val audioDriver: String = Container.DEFAULT_AUDIO_DRIVER, - - val gameName: String = "", - val appId: Int = -1, - var firstTimeBoot: Boolean = false, - var taskAffinityMask: Int = 0, - var taskAffinityMaskWoW64: Int = 0, -) +package com.OxGames.Pluvia.ui.screen.xserver + +import com.winlator.container.Container +import com.winlator.core.KeyValueSet +import com.winlator.core.WineInfo + +data class XServerState( + var winStarted: Boolean = false, + val dxwrapper: String = Container.DEFAULT_DXWRAPPER, + val dxwrapperConfig: KeyValueSet? = null, + val screenSize: String = Container.DEFAULT_SCREEN_SIZE, + val wineInfo: WineInfo = WineInfo.MAIN_WINE_VERSION, + val graphicsDriver: String = Container.DEFAULT_GRAPHICS_DRIVER, + val audioDriver: String = Container.DEFAULT_AUDIO_DRIVER, + + val currentTime: Long = 0L, + + val gameName: String = "", + val appId: Int = -1, + var firstTimeBoot: Boolean = false, + var taskAffinityMask: Int = 0, + var taskAffinityMaskWoW64: Int = 0, +) diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt index 6f8222ef..140eb9de 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt @@ -54,14 +54,18 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import java.io.File import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.json.JSONException import org.json.JSONObject @@ -99,7 +103,6 @@ class XServerViewModel @Inject constructor( sealed class XServerUiEvent { data object OnExit : XServerUiEvent() - data object OnKeyboard : XServerUiEvent() data object OnNavigateBack : XServerUiEvent() } @@ -197,6 +200,14 @@ class XServerViewModel @Inject constructor( } else { Timber.w("Did not find existing container") } + + viewModelScope.launch { + while (isActive) { + _state.update { it.copy(currentTime = System.currentTimeMillis()) } + ensureActive() + delay(1.seconds) + } + } } override fun onCleared() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index affd0632..e6b08dcd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -416,6 +416,7 @@ your game + Show Keyboard Credential Sign In From 051e6bd30612061ac879817d67e7199b3e78fc81 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 15 May 2025 11:00:07 -0500 Subject: [PATCH 4/9] Use new snapshot url for JavaSteam since ossrh is sunsetting. --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 78aee588..761f24e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } // JavaSteam + maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") } // JavaSteam } } From e4e247caf0f7277e9b3185be48407648b2448738 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 15 May 2025 11:03:40 -0500 Subject: [PATCH 5/9] Update SteamService with recent snapshot changes. --- app/src/main/java/com/OxGames/Pluvia/service/SteamService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/OxGames/Pluvia/service/SteamService.kt b/app/src/main/java/com/OxGames/Pluvia/service/SteamService.kt index b310d956..630a788f 100644 --- a/app/src/main/java/com/OxGames/Pluvia/service/SteamService.kt +++ b/app/src/main/java/com/OxGames/Pluvia/service/SteamService.kt @@ -365,7 +365,7 @@ class SteamService : Service(), IChallengeUrlChanged { fun getOwnedAppDlc(appId: Int): Map = getAppDlc(appId).filter { getPkgInfoOf(it.value.dlcAppId)?.let { pkg -> instance?.steamClient?.let { steamClient -> - pkg.ownerAccountId.contains(steamClient.steamID.accountID.toInt()) + pkg.ownerAccountId.contains(steamClient.steamID!!.accountID.toInt()) } } == true } From 7ac42dcf3e6558da594c0c45c57e4619c32fa68a Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 15 May 2025 11:19:25 -0500 Subject: [PATCH 6/9] Add game name to drawer. Slight style change. --- .../Pluvia/ui/screen/xserver/XServerScreen.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt index 1cf15ffe..d1fe3f47 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt @@ -293,10 +293,21 @@ private fun XServerDrawer( Text( text = stringResource(id = R.string.app_name), fontSize = 32.sp, + style = MaterialTheme.typography.bodyLarge, ) + if (state.gameName.isNotEmpty()) { + Text( + text = stringResource(R.string.friend_playing_game, state.gameName), + fontSize = 14.sp, + color = MaterialTheme.colorScheme.surfaceTint, + style = MaterialTheme.typography.bodyMedium, + ) + } Text( text = DateUtils.formatDateTime(LocalContext.current, state.currentTime, DateUtils.FORMAT_SHOW_TIME), - fontSize = 16.sp, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.surfaceTint, + style = MaterialTheme.typography.bodySmall, ) } } @@ -323,7 +334,7 @@ private fun Preview_XServerDrawer() { PluviaTheme { Surface { XServerDrawer( - state = XServerState(), + state = XServerState(gameName = "The Game"), onKeyboard = {}, onExit = {}, ) From 3b65697a205a6f6d5fed275d6456a8097d606120 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Thu, 15 May 2025 11:56:46 -0500 Subject: [PATCH 7/9] Keyboard inputs now working --- app/src/main/java/com/OxGames/Pluvia/MainActivity.kt | 2 +- .../com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt | 4 ++++ app/src/main/java/com/winlator/xserver/Keyboard.java | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/OxGames/Pluvia/MainActivity.kt b/app/src/main/java/com/OxGames/Pluvia/MainActivity.kt index e5fbed25..a3237551 100644 --- a/app/src/main/java/com/OxGames/Pluvia/MainActivity.kt +++ b/app/src/main/java/com/OxGames/Pluvia/MainActivity.kt @@ -164,7 +164,7 @@ class MainActivity : ComponentActivity() { @SuppressLint("RestrictedApi") override fun dispatchKeyEvent(event: KeyEvent): Boolean { - // Log.d("MainActivity$index", "dispatchKeyEvent(${event.keyCode}):\n$event") + // Timber.d("dispatchKeyEvent(${event.keyCode}):\n$event") var eventDispatched = PluviaApp.events.emit(AndroidEvent.KeyEvent(event)) { keyEvent -> keyEvent.any { it } diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt index 140eb9de..270c2404 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerViewModel.kt @@ -123,12 +123,16 @@ class XServerViewModel @Inject constructor( var handled = false if (isGamepad) { handled = xServerView!!.xServer.winHandler.onKeyEvent(it.event) + Timber.d("Gamepad Handled: $handled") // handled = ExternalController.onKeyEvent(xServer.winHandler, it.event) } if (!handled && isKeyboard) { handled = keyboard?.onKeyEvent(it.event) == true + Timber.d("Keyboard Handled: $handled") } + Timber.d("isKeyboard: $isKeyboard, isGamepad: $isGamepad \n Handled KeyEvent in XServer: (${it.event.keyCode}) with $handled") + handled } diff --git a/app/src/main/java/com/winlator/xserver/Keyboard.java b/app/src/main/java/com/winlator/xserver/Keyboard.java index d1eebb70..0ba9ef98 100644 --- a/app/src/main/java/com/winlator/xserver/Keyboard.java +++ b/app/src/main/java/com/winlator/xserver/Keyboard.java @@ -98,7 +98,7 @@ private void triggerOnKeyRelease(byte keycode) { public static boolean isKeyboardDevice(InputDevice device) { if (device == null) return false; int sources = device.getSources(); - return !device.isVirtual() && ((sources & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD); + return (sources & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD; } public boolean onKeyEvent(KeyEvent event) { @@ -364,4 +364,4 @@ else if (keycode == XKeycode.KEY_NUM_LOCK.getId()) { public static boolean isModifierSticky(byte keycode) { return keycode == XKeycode.KEY_CAPS_LOCK.getId() || keycode == XKeycode.KEY_NUM_LOCK.getId(); } -} \ No newline at end of file +} From 291c06e0273263c59f3ec4f6b6b78a287fbbdea0 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Mon, 19 May 2025 16:22:07 -0500 Subject: [PATCH 8/9] Add menu item to close drawer. --- .../Pluvia/ui/screen/xserver/XServerScreen.kt | 19 ++++++++++++++++++- app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt index d1fe3f47..ee158d1b 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt @@ -13,12 +13,14 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ExitToApp +import androidx.compose.material.icons.automirrored.filled.MenuOpen import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon @@ -173,6 +175,12 @@ fun XServerScreen( isExitDialogOpen = true } }, + onDrawerClose = { + scope.launch { + Timber.i("Close drawer called") + drawerState.close() + } + }, ) }, content = { @@ -271,6 +279,7 @@ private fun XServerDrawer( state: XServerState, onKeyboard: () -> Unit, onExit: () -> Unit, + onDrawerClose: () -> Unit, ) { ModalDrawerSheet { // Header @@ -319,11 +328,18 @@ private fun XServerDrawer( onClick = onKeyboard, ) NavigationDrawerItem( - label = { Text(text = stringResource(R.string.exit)) }, + label = { Text(text = stringResource(R.string.xserver_drawer_exit_game)) }, icon = { Icon(imageVector = Icons.AutoMirrored.Filled.ExitToApp, contentDescription = null) }, selected = false, onClick = onExit, ) + Spacer(modifier = Modifier.weight(1f)) + NavigationDrawerItem( + label = { Text(text = stringResource(R.string.xserver_drawer_close_drawer)) }, + icon = { Icon(imageVector = Icons.AutoMirrored.Filled.MenuOpen, contentDescription = null) }, + selected = false, + onClick = onDrawerClose, + ) } } @@ -337,6 +353,7 @@ private fun Preview_XServerDrawer() { state = XServerState(gameName = "The Game"), onKeyboard = {}, onExit = {}, + onDrawerClose = {}, ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6b08dcd..2e542e91 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -417,6 +417,8 @@ your game Show Keyboard + Close Drawer + Exit Game Credential Sign In From 43b8dffdcfb4ff2bf9924468854b61dbca0e4514 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Mon, 19 May 2025 16:37:47 -0500 Subject: [PATCH 9/9] Add todo about potential keyboard showing issue --- .../java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt index ee158d1b..9c9a5c75 100644 --- a/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/com/OxGames/Pluvia/ui/screen/xserver/XServerScreen.kt @@ -88,6 +88,8 @@ fun XServerScreen( val xServerState by viewModel.state.collectAsStateWithLifecycle() val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + // TODO switching between to on-screen keyboards breaks showing keyboard until the next time XServer screen is launched. + // This is also be a problem between HW and On-Screen keyboards. // imm.showSoftInput() doesn't work // Compose LocalSoftwareKeyboardController doesn't work. @Suppress("DEPRECATION")