diff --git a/.gitignore b/.gitignore index 52052d7600..5923b5be9d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,10 @@ app/release/output-metadata.json local.properties app/src/main/assets/recommendations.json +# Locally built gamescope payloads: fetched at runtime unless one is present, +# and never committed — a payload is an artefact of the branch that built it. +app/src/main/assets/gamescope-gamenative-*.tzst + # Proguard folder generated by Eclipse proguard/ diff --git a/app/src/main/java/app/gamenative/PrefManager.kt b/app/src/main/java/app/gamenative/PrefManager.kt index 8f0b36c3e3..e69afa42ad 100644 --- a/app/src/main/java/app/gamenative/PrefManager.kt +++ b/app/src/main/java/app/gamenative/PrefManager.kt @@ -229,7 +229,7 @@ object PrefManager { private val DISPLAY_SERVER = stringPreferencesKey("display_server") var displayServer: String - get() = getPref(DISPLAY_SERVER, Container.DEFAULT_DISPLAY_SERVER) + get() = getPref(DISPLAY_SERVER, DefaultVersion.DISPLAY_SERVER) set(value) { setPref(DISPLAY_SERVER, value) } diff --git a/app/src/main/java/app/gamenative/display/GamescopePayload.kt b/app/src/main/java/app/gamenative/display/GamescopePayload.kt index 7504ea5d65..82b7b88f67 100644 --- a/app/src/main/java/app/gamenative/display/GamescopePayload.kt +++ b/app/src/main/java/app/gamenative/display/GamescopePayload.kt @@ -11,7 +11,7 @@ import java.io.File /** Versioned gamescope payload (gamescope, XWayland, scaling clients), fetched on the first gamescope launch. */ object GamescopePayload { - const val VERSION = "20261022.13" + const val VERSION = "20261023.3" const val ARCHIVE = "gamescope-gamenative-$VERSION.tzst" private const val STAMP = ".payload.version" @@ -29,16 +29,39 @@ object GamescopePayload { val archive = File(filesDir, ARCHIVE) if (!archive.isFile || archive.length() == 0L) { archive.delete() - try { - SteamService.fetchFileWithFallback(ARCHIVE, archive, context, onProgress) - } catch (e: Exception) { - archive.delete() - throw e + // A payload shipped inside the APK wins over the download: it is the + // build that was exercised with this app, and it needs no network. + // Only when the asset is absent does the published archive get + // fetched. + if (copyFromAssets(context, archive)) { + onProgress(1.0f) + } else { + try { + SteamService.fetchFileWithFallback(ARCHIVE, archive, context, onProgress) + } catch (e: Exception) { + archive.delete() + throw e + } } } install(archive, filesDir) } + /** Whether this APK carries the payload, and if so, copy it out to [dest]. */ + private fun copyFromAssets(context: Context, dest: File): Boolean { + val assets = context.assets + val present = runCatching { assets.open(ARCHIVE).close() }.isSuccess + if (!present) return false + return runCatching { + assets.open(ARCHIVE).use { input -> dest.outputStream().use { output -> input.copyTo(output) } } + } + .onFailure { + Timber.w(it, "Failed to copy $ARCHIVE from assets") + dest.delete() + } + .isSuccess + } + @Synchronized internal fun install(archive: File, filesDir: File): File { val target = directory(filesDir) diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt index 0579000af6..27e9e12fd9 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt @@ -2423,6 +2423,7 @@ fun XServerScreen( envVars, contentsManager, onExtractFileListener, + gameSession = !bootToContainer, ) } if (isGamescopeSession) { @@ -5732,6 +5733,7 @@ private suspend fun setupWineSystemFiles( envVars: EnvVars, contentsManager: ContentsManager, onExtractFileListener: OnExtractFileListener?, + gameSession: Boolean, ) { val imageFs = ImageFs.find(context) val appVersion = AppUtils.getVersionCode(context).toString() @@ -5866,10 +5868,11 @@ private suspend fun setupWineSystemFiles( cleanupBionicSteamAssets(imageFs) } - val desktopTheme = container.desktopTheme - if ((desktopTheme + "," + screenInfo) != container.getExtra("desktopTheme")) { - WineThemeManager.apply(context, WineThemeManager.ThemeInfo(desktopTheme), screenInfo) - container.putExtra("desktopTheme", desktopTheme + "," + screenInfo) + val desktopTheme = if (gameSession) WineThemeManager.gameSessionTheme(container.desktopTheme) else container.desktopTheme + val desktopThemeKey = desktopTheme + "," + screenInfo + if (gameSession) ",game" else "" + if (desktopThemeKey != container.getExtra("desktopTheme")) { + WineThemeManager.apply(context, WineThemeManager.ThemeInfo(desktopTheme), screenInfo, !gameSession) + container.putExtra("desktopTheme", desktopThemeKey) containerDataChanged = true } diff --git a/app/src/main/java/app/gamenative/utils/ContainerUtils.kt b/app/src/main/java/app/gamenative/utils/ContainerUtils.kt index e2d212697d..5d84946f1a 100644 --- a/app/src/main/java/app/gamenative/utils/ContainerUtils.kt +++ b/app/src/main/java/app/gamenative/utils/ContainerUtils.kt @@ -98,6 +98,9 @@ object ContainerUtils { DefaultVersion.STEAM_TYPE = Container.STEAM_TYPE_HEADLESS DefaultVersion.ASYNC_CACHE = "0" } + DefaultVersion.DISPLAY_SERVER = Container.defaultDisplayServer( + GPUInformation.getRenderer(context).orEmpty().contains("adreno", ignoreCase = true), + ) } fun getGPUCards(context: Context): Map { diff --git a/app/src/main/java/com/winlator/container/Container.java b/app/src/main/java/com/winlator/container/Container.java index 03c5e36201..8fda29fb66 100644 --- a/app/src/main/java/com/winlator/container/Container.java +++ b/app/src/main/java/com/winlator/container/Container.java @@ -44,7 +44,6 @@ public enum XrControllerMapping { public static final String DEFAULT_DISPLAY_RENDERER = "vulkan"; public static final String DISPLAY_SERVER_X11 = "x11"; public static final String DISPLAY_SERVER_GAMESCOPE = "gamescope"; - public static final String DEFAULT_DISPLAY_SERVER = DISPLAY_SERVER_GAMESCOPE; public static final String DEFAULT_DDRAWRAPPER = "none"; public static final String DEFAULT_DXWRAPPERCONFIG = "version=" + DefaultVersion.DXVK + ",framerate=0,maxDeviceMemory=0,async=" + DefaultVersion.ASYNC + ",asyncCache=" + DefaultVersion.ASYNC_CACHE + ",vkd3dVersion=" + DefaultVersion.VKD3D + ",vkd3dLevel=12_1" + ",ddrawrapper=" + Container.DEFAULT_DDRAWRAPPER + ",csmt=3" + ",gpuName=NVIDIA GeForce GTX 480" + ",videoMemorySize=2048" + ",strict_shader_math=1" + ",OffscreenRenderingMode=fbo" + ",renderer=gl";; public static final String DEFAULT_GRAPHICSDRIVERCONFIG = "vulkanVersion=1.3" + ",version=" + DefaultVersion.WRAPPER + ",blacklistedExtensions=" + ",maxDeviceMemory=0" + ",presentMode=mailbox" + ",syncFrame=0" + ",disablePresentWait=0" + ",resourceType=auto" + ",bcnEmulation=auto" + ",bcnEmulationType=compute" + ",bcnEmulationCache=0" + ",gpuName=Device"; @@ -87,7 +86,7 @@ public enum XrControllerMapping { private String graphicsDriverConfig = DEFAULT_GRAPHICSDRIVERCONFIG; private String rendererPresentMode = "fifo"; private String displayRenderer = Container.DEFAULT_DISPLAY_RENDERER; - private String displayServer = Container.DEFAULT_DISPLAY_SERVER; + private String displayServer = DefaultVersion.DISPLAY_SERVER; private int xrRefreshRate = 72; private int xrRenderScale = 100; private boolean sfCompatMode = true; @@ -309,6 +308,10 @@ public void setGraphicsDriverConfig(String graphicsDriverConfig) { public void setDisplayServer(String v) { this.displayServer = v; } + public static String defaultDisplayServer(boolean isAdreno) { + return isAdreno ? DISPLAY_SERVER_GAMESCOPE : DISPLAY_SERVER_X11; + } + public static boolean isX11Display(String containerVariant, String displayServer) { return !BIONIC.equalsIgnoreCase(containerVariant) || !DISPLAY_SERVER_GAMESCOPE.equalsIgnoreCase(displayServer); } diff --git a/app/src/main/java/com/winlator/container/ContainerData.kt b/app/src/main/java/com/winlator/container/ContainerData.kt index 8fdb4a64b0..c9f66306d3 100644 --- a/app/src/main/java/com/winlator/container/ContainerData.kt +++ b/app/src/main/java/com/winlator/container/ContainerData.kt @@ -18,7 +18,7 @@ data class ContainerData( val graphicsDriverConfig: String = "", val rendererPresentMode: String = "fifo", val displayRenderer: String = Container.DEFAULT_DISPLAY_RENDERER, - val displayServer: String = Container.DEFAULT_DISPLAY_SERVER, + val displayServer: String = DefaultVersion.DISPLAY_SERVER, val xrRefreshRate: Int = 72, val xrRenderScale: Int = 100, val sfCompatMode: Boolean = true, @@ -214,7 +214,7 @@ data class ContainerData( graphicsDriverConfig = (savedMap["graphicsDriverConfig"] as? String) ?: "", rendererPresentMode = (savedMap["rendererPresentMode"] as? String) ?: "fifo", displayRenderer = (savedMap["displayRenderer"] as? String) ?: "vulkan", - displayServer = (savedMap["displayServer"] as? String) ?: Container.DEFAULT_DISPLAY_SERVER, + displayServer = (savedMap["displayServer"] as? String) ?: DefaultVersion.DISPLAY_SERVER, xrRefreshRate = (savedMap["xrRefreshRate"] as? Int) ?: 72, xrRenderScale = (savedMap["xrRenderScale"] as? Int) ?: 100, sfCompatMode = (savedMap["sfCompatMode"] as? Boolean) ?: true, diff --git a/app/src/main/java/com/winlator/core/DefaultVersion.java b/app/src/main/java/com/winlator/core/DefaultVersion.java index e12b591870..150d770152 100644 --- a/app/src/main/java/com/winlator/core/DefaultVersion.java +++ b/app/src/main/java/com/winlator/core/DefaultVersion.java @@ -26,4 +26,5 @@ public abstract class DefaultVersion { public static String WINE_VERSION = com.winlator.core.WineInfo.MAIN_WINE_VERSION.identifier(); public static String ASYNC = "1"; public static String ASYNC_CACHE = "0"; + public static String DISPLAY_SERVER = Container.DISPLAY_SERVER_X11; } diff --git a/app/src/main/java/com/winlator/core/WineThemeManager.java b/app/src/main/java/com/winlator/core/WineThemeManager.java index ac18c3f437..de28f1fda9 100644 --- a/app/src/main/java/com/winlator/core/WineThemeManager.java +++ b/app/src/main/java/com/winlator/core/WineThemeManager.java @@ -41,7 +41,22 @@ public ThemeInfo(String value) { } } + // Game sessions get a black desktop: gamescope composites Wine's desktop + // surface, so the wallpaper would show through before the game's first frame + // and around its dialogs. The X11 renderers never draw explorer, so it is + // invisible there either way. + public static String gameSessionTheme(String value) { + ThemeInfo info = new ThemeInfo(value); + return info.theme + "," + BackgroundType.COLOR + ",#000000"; + } + public static void apply(Context context, ThemeInfo themeInfo, ScreenInfo screenInfo) { + apply(context, themeInfo, screenInfo, true); + } + + // The desktop is named "shell", which makes explorer draw its taskbar and + // start menu; game sessions turn that off along with the standalone systray. + public static void apply(Context context, ThemeInfo themeInfo, ScreenInfo screenInfo, boolean enableShell) { File rootDir = ImageFs.find(context).getRootDir(); File userRegFile = new File(rootDir, ImageFs.WINEPREFIX+"/user.reg"); String background = Color.red(themeInfo.backgroundColor)+" "+Color.green(themeInfo.backgroundColor)+" "+Color.blue(themeInfo.backgroundColor); @@ -54,6 +69,15 @@ public static void apply(Context context, ThemeInfo themeInfo, ScreenInfo screen } else registryEditor.removeValue("Control Panel\\Desktop", "Wallpaper"); + if (enableShell) { + registryEditor.removeValue("Software\\Wine\\Explorer\\Desktops\\shell", "EnableShell"); + registryEditor.removeValue("Software\\Wine\\Explorer\\Desktops\\shell", "ShowSystray"); + } + else { + registryEditor.setDwordValue("Software\\Wine\\Explorer\\Desktops\\shell", "EnableShell", 0); + registryEditor.setDwordValue("Software\\Wine\\Explorer\\Desktops\\shell", "ShowSystray", 0); + } + if (themeInfo.theme == Theme.LIGHT) { registryEditor.setStringValue("Control Panel\\Colors", "ActiveBorder", "245 245 245"); registryEditor.setStringValue("Control Panel\\Colors", "ActiveTitle", "96 125 139"); diff --git a/app/src/main/java/com/winlator/xenvironment/components/BionicProgramLauncherComponent.java b/app/src/main/java/com/winlator/xenvironment/components/BionicProgramLauncherComponent.java index 6f52281a68..e7e56c3d18 100644 --- a/app/src/main/java/com/winlator/xenvironment/components/BionicProgramLauncherComponent.java +++ b/app/src/main/java/com/winlator/xenvironment/components/BionicProgramLauncherComponent.java @@ -514,19 +514,7 @@ private void applyGamescopeEnv(Context context, EnvVars envVars, File rootDir) { envVars.put("LD_PRELOAD", preload.isEmpty() ? gsDir + "/lib/libgndrm_shim.so" : preload + ":" + gsDir + "/lib/libgndrm_shim.so"); - // The shim pulls in libandroid.so, whose OEM deps must bind to system libs, not imagefs's same-named ones: - // Samsung's libsqlite.so needs the BoringSSL libcrypto.so, HyperOS's libjpeg-hyper.so needs libjpeg.so's jsimd_* exports. String gamescopeLibPath = gsDir + "/lib"; - File systemLibDir = new File(context.getFilesDir(), "gamescope_system_libs"); - boolean linkedSystemLib = false; - for (String lib : new String[]{"libcrypto.so", "libjpeg.so"}) { - File systemLib = new File("/system/lib64", lib); - if (!systemLib.exists()) continue; - systemLibDir.mkdirs(); - FileUtils.symlink(systemLib, new File(systemLibDir, lib)); - linkedSystemLib = true; - } - if (linkedSystemLib) gamescopeLibPath = systemLibDir.getPath() + ":" + gamescopeLibPath; String libPath = envVars.get("LD_LIBRARY_PATH"); envVars.put("LD_LIBRARY_PATH", libPath.isEmpty() ? gamescopeLibPath : gamescopeLibPath + ":" + libPath); // gamescope execvp's gamescopereaper and wlroots spawns xkbcomp. diff --git a/app/src/main/jniLibs/arm64-v8a/libgamenative_drm.so b/app/src/main/jniLibs/arm64-v8a/libgamenative_drm.so index 9be0b341c4..03e27f6362 100755 Binary files a/app/src/main/jniLibs/arm64-v8a/libgamenative_drm.so and b/app/src/main/jniLibs/arm64-v8a/libgamenative_drm.so differ