diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp index c086a56f7..08159b3dd 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-1.icp @@ -35,7 +35,7 @@ "toggleSwitch": false, "text": "", "iconId": 0, - "range": "FROM_1_TO_12" + "range": "FROM_0_TO_9" }, { "type": "BUTTON", diff --git a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp index eeee11d71..85f7833b7 100644 --- a/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp +++ b/app/src/main/assets/inputcontrols/profiles-astra2/controls-6.icp @@ -35,7 +35,7 @@ "toggleSwitch": false, "text": "", "iconId": 0, - "range": "FROM_1_TO_12" + "range": "FROM_0_TO_9" }, { "type": "RADIAL_MENU", diff --git a/app/src/main/cpp/winlator/vulkan.c b/app/src/main/cpp/winlator/vulkan.c index 35b8ce128..09035ad4c 100644 --- a/app/src/main/cpp/winlator/vulkan.c +++ b/app/src/main/cpp/winlator/vulkan.c @@ -356,7 +356,7 @@ static VkResult enumerate_physical_devices() { } JNIEXPORT jstring JNICALL -Java_com_winlator_cmod_runtime_system_GPUInformation_getVulkanVersion( +Java_com_winlator_cmod_runtime_system_GPUInformation_getVulkanVersionNative( JNIEnv *env, jclass obj, jstring driverName, jobject context) { VkPhysicalDeviceProperties props = {}; char *driverVersion; @@ -392,7 +392,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_getVulkanVersion( } JNIEXPORT jint JNICALL -Java_com_winlator_cmod_runtime_system_GPUInformation_getVendorID( +Java_com_winlator_cmod_runtime_system_GPUInformation_getVendorIDNative( JNIEnv *env, jclass obj, jstring driverName, jobject context) { VkPhysicalDeviceProperties props = {}; uint32_t vendorID; @@ -421,7 +421,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_getVendorID( } JNIEXPORT jstring JNICALL -Java_com_winlator_cmod_runtime_system_GPUInformation_getRenderer( +Java_com_winlator_cmod_runtime_system_GPUInformation_getRendererNative( JNIEnv *env, jclass obj, jstring driverName, jobject context) { VkPhysicalDeviceProperties props = {}; @@ -449,7 +449,7 @@ Java_com_winlator_cmod_runtime_system_GPUInformation_getRenderer( } JNIEXPORT jobjectArray JNICALL -Java_com_winlator_cmod_runtime_system_GPUInformation_enumerateExtensions( +Java_com_winlator_cmod_runtime_system_GPUInformation_enumerateExtensionsNative( JNIEnv *env, jclass obj, jstring driverName, jobject context) { jobjectArray extensions; VkResult result; diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index c66487b02..0ff3f0897 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -502,29 +502,34 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( val themeInfo = WineThemeManager.ThemeInfo(desktopThemeValue) state.desktopBackgroundColor.value = String.format("#%06X", themeInfo.backgroundColor and 0x00FFFFFF) - // Mouse warp override lives in .wine/user.reg, not on the container. - var mouseWarp = "disable" - val rootDir = c?.getRootDir() - if (rootDir != null) { - val userRegFile = File(rootDir, ".wine/user.reg") - if (userRegFile.exists()) { - try { - WineRegistryEditor(userRegFile).use { reg -> - mouseWarp = reg.getStringValue( - "Software\\Wine\\DirectInput", - "MouseWarpOverride", - "disable" - ) - } - } catch (e: Throwable) { - Log.w(TAG, "Error reading MouseWarpOverride", e) + } + + private var pendingMouseWarpValue = "disable" + + private fun readMouseWarpOverride(): String { + val rootDir = container?.getRootDir() ?: return "disable" + val userRegFile = File(rootDir, ".wine/user.reg") + if (!userRegFile.exists()) return "disable" + val value = + try { + WineRegistryEditor(userRegFile).use { reg -> + reg.getStringValue("Software\\Wine\\DirectInput", "MouseWarpOverride", "disable") } + } catch (e: Throwable) { + Log.w(TAG, "Error reading MouseWarpOverride", e) + "disable" } - } - pendingMouseWarpValue = mouseWarp.lowercase() + return value.lowercase() } - private var pendingMouseWarpValue = "disable" + private fun applyMouseWarpOverride(value: String) { + pendingMouseWarpValue = value + state.selectedMouseWarpOverride.intValue = when (value) { + "enable" -> 1 + "force" -> 2 + else -> 0 + } + } private fun loadResourceArrays() { val c = container @@ -694,10 +699,12 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( private fun loadContentsAsync() { Executors.newSingleThreadExecutor().execute { + val mouseWarp = readMouseWarpOverride() try { contentsManager.syncContents() activity.runOnUiThread { try { + applyMouseWarpOverride(mouseWarp) populateContentsDependentData() } finally { state.isLoaded.value = true @@ -1304,69 +1311,49 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.graphicsDriverVersion.value = config.get("version") ?: "" } + private var extensionsRequest = 0 + + private fun savedGraphicsDriverConfig() = + GraphicsDriverConfigUtils.parseGraphicsDriverConfig( + container?.getGraphicsDriverConfig() ?: Container.DEFAULT_GRAPHICSDRIVERCONFIG, + ) + private fun loadGraphicsDriverVersions() { - val versions = mutableListOf() - try { - val defaults = context.resources.getStringArray(R.array.wrapper_graphics_driver_version_entries) - for (ver in defaults) { - try { - if (com.winlator.cmod.runtime.system.GPUInformation.isDriverSupported(ver, context)) - versions.add(ver) - } catch (e: Throwable) { - Log.w(TAG, "Driver support check failed: $ver", e) - } + val savedVersion = savedGraphicsDriverConfig().get("version") ?: "" + state.gfxDriverVersionEntries.value = listOf(if (savedVersion.isNotEmpty()) savedVersion else "System") + state.gfxSelectedDriverVersion.intValue = 0 + scope.launch { + val versions = withContext(Dispatchers.IO) { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.supportedVersions(context) } - try { - val adreno = com.winlator.cmod.runtime.content.AdrenotoolsManager(context) - val installed = adreno.enumarateInstalledDrivers() - if (installed != null) versions.addAll(installed) - } catch (e: Throwable) { - Log.w(TAG, "Error loading Adrenotools drivers", e) - } - } catch (e: Throwable) { - Log.e(TAG, "Error loading wrapper versions", e) - } - if (versions.isEmpty()) versions.add("System") - state.gfxDriverVersionEntries.value = versions - - val configStr2 = container?.getGraphicsDriverConfig() ?: Container.DEFAULT_GRAPHICSDRIVERCONFIG - val config = GraphicsDriverConfigUtils.parseGraphicsDriverConfig(configStr2) - val initialVersion = config.get("version") ?: "" - if (initialVersion.isNotEmpty()) { - val idx = versions.indexOfFirst { it.equals(initialVersion, ignoreCase = true) } - if (idx >= 0) state.gfxSelectedDriverVersion.intValue = idx + state.gfxDriverVersionEntries.value = versions + val idx = versions.indexOfFirst { it.equals(savedVersion, ignoreCase = true) } + state.gfxSelectedDriverVersion.intValue = if (idx >= 0) idx else 0 + loadExtensionsForVersion(state.gfxSelectedDriverVersion.intValue) } - loadExtensionsForVersion(state.gfxSelectedDriverVersion.intValue) } private fun loadExtensionsForVersion(versionIndex: Int) { - val versions = state.gfxDriverVersionEntries.value - val version = versions.getOrElse(versionIndex) { return } - try { - val extensions = com.winlator.cmod.runtime.system.GPUInformation.enumerateExtensions(version, context) - if (extensions != null) { - state.gfxAvailableExtensions.value = extensions.toList() - val configStr = container?.getGraphicsDriverConfig() ?: Container.DEFAULT_GRAPHICSDRIVERCONFIG - val config = GraphicsDriverConfigUtils.parseGraphicsDriverConfig(configStr) - val savedVersion = config.get("version") ?: "" - if (version == savedVersion) { + val version = state.gfxDriverVersionEntries.value.getOrElse(versionIndex) { return } + val request = ++extensionsRequest + scope.launch { + val extensions = withContext(Dispatchers.IO) { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.extensions(context, version) + } + if (request != extensionsRequest) return@launch + state.gfxAvailableExtensions.value = extensions + val config = savedGraphicsDriverConfig() + state.gfxBlacklistedExtensions.value = + if (version == (config.get("version") ?: "")) { val bl = config.get("blacklistedExtensions") ?: "" - state.gfxBlacklistedExtensions.value = - if (bl.isNotEmpty()) bl.split(",").toSet() else emptySet() + if (bl.isNotEmpty()) bl.split(",").toSet() else emptySet() } else { - state.gfxBlacklistedExtensions.value = emptySet() + emptySet() } - } else { - state.gfxAvailableExtensions.value = emptyList() - state.gfxBlacklistedExtensions.value = emptySet() - } - } catch (e: Throwable) { - Log.e(TAG, "Error loading extensions for $version", e) - state.gfxAvailableExtensions.value = emptyList() - state.gfxBlacklistedExtensions.value = emptySet() } } + private fun initialDxWrapperConfig(): String = container?.getDXWrapperConfig() ?: ContainerCreation.defaultDxWrapperConfig(contentsManager, isArm64EC) @@ -1618,7 +1605,12 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( private fun buildDesktopThemeString(): String { val themeEntries = state.desktopThemeEntries.value val themeIdx = state.selectedDesktopTheme.intValue - val theme = if (themeIdx in themeEntries.indices) themeEntries[themeIdx].uppercase() else "LIGHT" + val theme = + if (themeIdx in themeEntries.indices) { + WineThemeManager.Theme.values().getOrNull(themeIdx)?.name ?: "LIGHT" + } else { + "LIGHT" + } val typeIdx = state.selectedDesktopBackgroundType.intValue val type = WineThemeManager.BackgroundType.values().getOrNull(typeIdx)?.name ?: "IMAGE" diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 40bf1fc99..ad0f71278 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.CoroutineScope import com.winlator.cmod.app.config.DeviceProfileSettings import com.winlator.cmod.runtime.display.environment.components.NetworkingSettings +import com.winlator.cmod.runtime.wine.WineThemeManager import com.winlator.cmod.BuildConfig import com.winlator.cmod.R import com.winlator.cmod.app.PluviaApp @@ -727,10 +728,9 @@ class ShortcutSettingsComposeDialog private constructor( state.desktopThemeEntries.value = desktopThemeArr // Desktop theme is stored as compound "THEME,TYPE,COLOR" — extract theme name val savedDesktopTheme = getShortcutSetting("desktopTheme", container.getDesktopTheme()) - val themePart = savedDesktopTheme.split(",").firstOrNull()?.trim() ?: "" - // Match case-insensitively: enum is "LIGHT"/"DARK", entries are "Light"/"Dark" - val themeIdx = desktopThemeArr.indexOfFirst { it.equals(themePart, ignoreCase = true) } - state.selectedDesktopTheme.intValue = if (themeIdx >= 0) themeIdx else 0 + state.selectedDesktopTheme.intValue = + WineThemeManager.ThemeInfo(savedDesktopTheme).theme.ordinal + .coerceIn(0, (desktopThemeArr.size - 1).coerceAtLeast(0)) // Show Box64/FEXCore frames based on saved emulator selection immediately, // before the async content sync runs @@ -1457,8 +1457,12 @@ class ShortcutSettingsComposeDialog private constructor( if (state.desktopThemeEntries.value.isNotEmpty()) { val desktopThemeEntries = state.desktopThemeEntries.value val dtIdx = state.selectedDesktopTheme.intValue - val selectedLabel = if (dtIdx in desktopThemeEntries.indices) desktopThemeEntries[dtIdx] else "" - val themeName = selectedLabel.uppercase() + val themeName = + if (dtIdx in desktopThemeEntries.indices) { + WineThemeManager.Theme.values().getOrNull(dtIdx)?.name ?: "LIGHT" + } else { + "LIGHT" + } // Preserve existing compound value, only replace the theme portion val existing = getShortcutSetting("desktopTheme", container.getDesktopTheme()) val parts = existing.split(",").toMutableList() @@ -2168,75 +2172,52 @@ class ShortcutSettingsComposeDialog private constructor( state.graphicsDriverVersion.value = config["version"] ?: "" } - private fun loadGraphicsDriverVersions(container: Container = shortcut.container) { - val versions = mutableListOf() - try { - val defaults = context.resources.getStringArray(R.array.wrapper_graphics_driver_version_entries) - for (ver in defaults) { - try { - if (com.winlator.cmod.runtime.system.GPUInformation.isDriverSupported(ver, context)) - versions.add(ver) - } catch (e: Throwable) { - Log.w(TAG, "Error checking driver support: $ver", e) - } - } - try { - val adrenoManager = com.winlator.cmod.runtime.content.AdrenotoolsManager(context) - val installed = adrenoManager.enumarateInstalledDrivers() - if (installed != null) versions.addAll(installed) - } catch (e: Throwable) { - Log.w(TAG, "Error loading Adrenotools drivers", e) - } - } catch (e: Throwable) { - Log.e(TAG, "Error loading wrapper versions", e) - } - if (versions.isEmpty()) versions.add("System") - - state.gfxDriverVersionEntries.value = versions + private var extensionsRequest = 0 + private fun savedGraphicsDriverConfig(container: Container = shortcut.container): Map { val configStr = if (shouldUseShortcutOverrides(container)) getShortcutSetting("graphicsDriverConfig", container.getGraphicsDriverConfig()) else container.getGraphicsDriverConfig() - val config = GraphicsDriverConfigUtils.parseGraphicsDriverConfig(configStr) - val initialVersion = config["version"] ?: "" - if (initialVersion.isNotEmpty()) { - val idx = versions.indexOfFirst { it.equals(initialVersion, ignoreCase = true) } - if (idx >= 0) state.gfxSelectedDriverVersion.intValue = idx - } + return GraphicsDriverConfigUtils.parseGraphicsDriverConfig(configStr) + } - loadExtensionsForVersion(state.gfxSelectedDriverVersion.intValue) + private fun loadGraphicsDriverVersions(container: Container = shortcut.container) { + val savedVersion = savedGraphicsDriverConfig(container)["version"] ?: "" + state.gfxDriverVersionEntries.value = listOf(if (savedVersion.isNotEmpty()) savedVersion else "System") + state.gfxSelectedDriverVersion.intValue = 0 + Thread({ + val versions = com.winlator.cmod.runtime.system.GraphicsDriverCatalog.supportedVersions(context) + activity.runOnUiThread { + state.gfxDriverVersionEntries.value = versions + val idx = versions.indexOfFirst { it.equals(savedVersion, ignoreCase = true) } + state.gfxSelectedDriverVersion.intValue = if (idx >= 0) idx else 0 + loadExtensionsForVersion(state.gfxSelectedDriverVersion.intValue) + } + }, "GraphicsDriverProbe").start() } private fun loadExtensionsForVersion(versionIndex: Int) { - val versions = state.gfxDriverVersionEntries.value - val version = versions.getOrElse(versionIndex) { return } - try { - val extensions = com.winlator.cmod.runtime.system.GPUInformation.enumerateExtensions(version, context) - if (extensions != null) { - state.gfxAvailableExtensions.value = extensions.toList() - - // On initial load, set blacklisted from config; on version change, clear blacklist - val configStr = getShortcutSetting("graphicsDriverConfig", shortcut.container.getGraphicsDriverConfig()) - val config = GraphicsDriverConfigUtils.parseGraphicsDriverConfig(configStr) - val savedVersion = config["version"] ?: "" - if (version == savedVersion) { - val bl = config["blacklistedExtensions"] ?: "" - state.gfxBlacklistedExtensions.value = if (bl.isNotEmpty()) bl.split(",").toSet() else emptySet() - } else { - state.gfxBlacklistedExtensions.value = emptySet() - } - } else { - state.gfxAvailableExtensions.value = emptyList() - state.gfxBlacklistedExtensions.value = emptySet() + val version = state.gfxDriverVersionEntries.value.getOrElse(versionIndex) { return } + val request = ++extensionsRequest + Thread({ + val extensions = com.winlator.cmod.runtime.system.GraphicsDriverCatalog.extensions(context, version) + activity.runOnUiThread { + if (request != extensionsRequest) return@runOnUiThread + state.gfxAvailableExtensions.value = extensions + val config = savedGraphicsDriverConfig() + state.gfxBlacklistedExtensions.value = + if (version == (config["version"] ?: "")) { + val bl = config["blacklistedExtensions"] ?: "" + if (bl.isNotEmpty()) bl.split(",").toSet() else emptySet() + } else { + emptySet() + } } - } catch (e: Throwable) { - Log.e(TAG, "Error loading extensions for $version", e) - state.gfxAvailableExtensions.value = emptyList() - state.gfxBlacklistedExtensions.value = emptySet() - } + }, "GraphicsDriverExtensions").start() } + private fun loadDxvkConfigState(container: Container = shortcut.container) { val configStr = if (shouldUseShortcutOverrides(container)) getShortcutSetting("dxwrapperConfig", container.getDXWrapperConfig()) @@ -2442,9 +2423,9 @@ class ShortcutSettingsComposeDialog private constructor( // Desktop theme is stored as compound "THEME,TYPE,COLOR". val desktopThemeArr = state.desktopThemeEntries.value if (desktopThemeArr.isNotEmpty()) { - val themePart = container.getDesktopTheme().split(",").firstOrNull()?.trim() ?: "" - val themeIdx = desktopThemeArr.indexOfFirst { it.equals(themePart, ignoreCase = true) } - state.selectedDesktopTheme.intValue = if (themeIdx >= 0) themeIdx else 0 + state.selectedDesktopTheme.intValue = + WineThemeManager.ThemeInfo(container.getDesktopTheme()).theme.ordinal + .coerceIn(0, desktopThemeArr.size - 1) } val directX = mutableListOf() diff --git a/app/src/main/runtime/content/AdrenotoolsManager.java b/app/src/main/runtime/content/AdrenotoolsManager.java index 16b317ae5..f80943c2b 100644 --- a/app/src/main/runtime/content/AdrenotoolsManager.java +++ b/app/src/main/runtime/content/AdrenotoolsManager.java @@ -133,6 +133,7 @@ private void reloadContainers(String adrenoToolsDriverId) { } public void removeDriver(String adrenoToolsDriverId) { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.invalidate(); Log.d("AdrenotoolsManager", "Removing driver " + adrenoToolsDriverId); File driverPath = new File(adrenotoolsContentDir, adrenoToolsDriverId); reloadContainers(adrenoToolsDriverId); @@ -198,6 +199,7 @@ public String installDriver(Uri driverUri) { * detect that a remote GitHub asset with that filename is already installed. */ public String installDriver(Uri driverUri, String sourceAssetName) { + com.winlator.cmod.runtime.system.GraphicsDriverCatalog.invalidate(); File tmpDir = new File(adrenotoolsContentDir, "tmp"); if (tmpDir.exists()) tmpDir.delete(); tmpDir.mkdirs(); diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index de8a74ad8..e848139db 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -5180,6 +5180,7 @@ private void renderDrawerMenu() { List gestureProfileNames = new ArrayList<>(); int gestureSelectedIndex = 0; try { + if (gestureProfileManager == null) gestureProfileManager = new GestureProfileManager(this); gestureProfileNames = gestureProfileManager.getProfileNames(); gestureSelectedIndex = Math.max(0, gestureProfileManager.indexOfProfile(selectedGestureProfileId())); } catch (Throwable t) { diff --git a/app/src/main/runtime/input/controls/ControlsProfile.java b/app/src/main/runtime/input/controls/ControlsProfile.java index e25119de9..b8ad351c9 100644 --- a/app/src/main/runtime/input/controls/ControlsProfile.java +++ b/app/src/main/runtime/input/controls/ControlsProfile.java @@ -285,6 +285,7 @@ public void loadElements(InputControlsView inputControlsView) { for (int i = 0; i < elementsJSONArray.length(); i++) { JSONObject elementJSONObject = elementsJSONArray.getJSONObject(i); ControlElement element = new ControlElement(inputControlsView); + try { element.setType(ControlElement.Type.valueOf(elementJSONObject.getString("type"))); element.setShape(ControlElement.Shape.valueOf(elementJSONObject.getString("shape"))); element.setToggleSwitch(elementJSONObject.getBoolean("toggleSwitch")); @@ -316,6 +317,11 @@ public void loadElements(InputControlsView inputControlsView) { if (!tempVirtualGamepad && hasGamepadBinding) tempVirtualGamepad = true; tempElements.add(element); + } catch (Exception e) { + android.util.Log.w( + "ControlsProfile", + "Skipping element " + i + " of profile " + id + ": " + e.getMessage()); + } } synchronized (this) { diff --git a/app/src/main/runtime/input/controls/InputControlsManager.java b/app/src/main/runtime/input/controls/InputControlsManager.java index ad996525b..081ed5f74 100644 --- a/app/src/main/runtime/input/controls/InputControlsManager.java +++ b/app/src/main/runtime/input/controls/InputControlsManager.java @@ -26,7 +26,7 @@ import org.json.JSONObject; public class InputControlsManager { - private static final int ASSET_PROFILE_SYNC_REVISION = 12; + private static final int ASSET_PROFILE_SYNC_REVISION = 13; private static final String ASSET_PROFILES_DIR = "inputcontrols/profiles"; public static final int LAST_BUILTIN_PROFILE_ID = 8; public static final int VIRTUAL_GAMEPAD_BUILTIN_ID = 3; diff --git a/app/src/main/runtime/system/GPUInformation.java b/app/src/main/runtime/system/GPUInformation.java index 8900316e0..07141bfc8 100644 --- a/app/src/main/runtime/system/GPUInformation.java +++ b/app/src/main/runtime/system/GPUInformation.java @@ -4,6 +4,7 @@ import java.util.Locale; public abstract class GPUInformation { + private static final Object PROBE_LOCK = new Object(); public static boolean isAdrenoGPU(Context context) { return getRenderer(null, context).toLowerCase(Locale.ROOT).contains("adreno"); @@ -17,13 +18,37 @@ public static boolean isDriverSupported(String driverName, Context context) { return !renderer.toLowerCase(Locale.ROOT).contains("unknown"); } - public static native String getVulkanVersion(String driverName, Context context); + public static String getVulkanVersion(String driverName, Context context) { + synchronized (PROBE_LOCK) { + return getVulkanVersionNative(driverName, context); + } + } + + public static int getVendorID(String driverName, Context context) { + synchronized (PROBE_LOCK) { + return getVendorIDNative(driverName, context); + } + } + + public static String getRenderer(String driverName, Context context) { + synchronized (PROBE_LOCK) { + return getRendererNative(driverName, context); + } + } + + public static String[] enumerateExtensions(String driverName, Context context) { + synchronized (PROBE_LOCK) { + return enumerateExtensionsNative(driverName, context); + } + } + + private static native String getVulkanVersionNative(String driverName, Context context); - public static native int getVendorID(String driverName, Context context); + private static native int getVendorIDNative(String driverName, Context context); - public static native String getRenderer(String driverName, Context context); + private static native String getRendererNative(String driverName, Context context); - public static native String[] enumerateExtensions(String driverName, Context context); + private static native String[] enumerateExtensionsNative(String driverName, Context context); static { System.loadLibrary("winlator"); diff --git a/app/src/main/runtime/system/GraphicsDriverCatalog.kt b/app/src/main/runtime/system/GraphicsDriverCatalog.kt new file mode 100644 index 000000000..c621611fa --- /dev/null +++ b/app/src/main/runtime/system/GraphicsDriverCatalog.kt @@ -0,0 +1,62 @@ +package com.winlator.cmod.runtime.system + +import android.content.Context +import android.util.Log +import com.winlator.cmod.R +import com.winlator.cmod.runtime.content.AdrenotoolsManager +import java.util.concurrent.ConcurrentHashMap + +object GraphicsDriverCatalog { + private const val TAG = "GraphicsDriverCatalog" + private val supportCache = ConcurrentHashMap() + private val extensionCache = ConcurrentHashMap>() + + @JvmStatic + fun supportedVersions(context: Context): List { + val versions = mutableListOf() + try { + for (ver in context.resources.getStringArray(R.array.wrapper_graphics_driver_version_entries)) { + val supported = + supportCache[ver] ?: try { + GPUInformation.isDriverSupported(ver, context) + } catch (e: Throwable) { + Log.w(TAG, "Driver support check failed: $ver", e) + false + }.also { supportCache[ver] = it } + if (supported) versions.add(ver) + } + } catch (e: Throwable) { + Log.e(TAG, "Error loading wrapper versions", e) + } + try { + AdrenotoolsManager(context).enumarateInstalledDrivers()?.let { versions.addAll(it) } + } catch (e: Throwable) { + Log.w(TAG, "Error loading Adrenotools drivers", e) + } + if (versions.isEmpty()) versions.add("System") + return versions + } + + @JvmStatic + fun extensions( + context: Context, + version: String, + ): List { + extensionCache[version]?.let { return it } + val extensions = + try { + GPUInformation.enumerateExtensions(version, context)?.toList() ?: emptyList() + } catch (e: Throwable) { + Log.e(TAG, "Error loading extensions for $version", e) + emptyList() + } + if (extensions.isNotEmpty()) extensionCache[version] = extensions + return extensions + } + + @JvmStatic + fun invalidate() { + supportCache.clear() + extensionCache.clear() + } +} diff --git a/app/src/main/runtime/system/LogManager.kt b/app/src/main/runtime/system/LogManager.kt index 01077fd25..fce3780cc 100644 --- a/app/src/main/runtime/system/LogManager.kt +++ b/app/src/main/runtime/system/LogManager.kt @@ -34,6 +34,7 @@ object LogManager { private var logcatProcess: Process? = null private var appLogProcess: Process? = null + private var systemLogProcess: Process? = null private var eventWatchProcess: Process? = null private val logTimestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) @@ -471,15 +472,36 @@ object LogManager { arrayOf("logcat", "-f", logFile.absolutePath, "-r", "8192", "-n", "2", "--pid=$pid", "*:W"), ) closeProcessStdin(appLogProcess) + startSystemLogging(context) Timber.i("Application debug logging started (PID=$pid)") } catch (e: Exception) { logE(TAG,e) { "Failed to start application logging: ${e.message}" } } } + private fun startSystemLogging(context: Context) { + val logFile = File(getLogsDir(context), stamped("system.log")) + try { + systemLogProcess?.let(::destroyProcess) + systemLogProcess = + Runtime.getRuntime().exec( + arrayOf( + "logcat", "-f", logFile.absolutePath, "-r", "2048", "-n", "2", + "ActivityManager:E", "AndroidRuntime:E", "InputDispatcher:E", + "lowmemorykiller:I", "DEBUG:V", "libc:F", "*:S", + ), + ) + closeProcessStdin(systemLogProcess) + } catch (e: Exception) { + logE(TAG, e) { "Failed to start system logging: ${e.message}" } + } + } + @JvmStatic fun stopAppLogging() { try { + systemLogProcess?.let(::destroyProcess) + systemLogProcess = null appLogProcess?.let(::destroyProcess) appLogProcess = null } catch (e: Exception) { diff --git a/app/src/main/runtime/wine/WineThemeManager.java b/app/src/main/runtime/wine/WineThemeManager.java index 4eddffb8a..1f0fe04d0 100644 --- a/app/src/main/runtime/wine/WineThemeManager.java +++ b/app/src/main/runtime/wine/WineThemeManager.java @@ -5,6 +5,7 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; +import android.util.Log; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; @@ -15,6 +16,7 @@ import java.io.File; public abstract class WineThemeManager { + private static final String TAG = "WineThemeManager"; public enum Theme { LIGHT, DARK @@ -35,14 +37,40 @@ public static class ThemeInfo { public final int backgroundColor; public ThemeInfo(String value) { - String[] values = value.split(","); - theme = Theme.valueOf(values[0]); + String[] values = value != null ? value.split(",") : new String[0]; + theme = parseTheme(values.length > 0 ? values[0] : null); if (values.length < 3) { - backgroundColor = Color.parseColor(values[1]); + backgroundColor = parseColor(values.length > 1 ? values[1] : null); backgroundType = BackgroundType.IMAGE; } else { - backgroundType = BackgroundType.valueOf(values[1]); - backgroundColor = Color.parseColor(values[2]); + backgroundType = parseBackgroundType(values[1]); + backgroundColor = parseColor(values[2]); + } + } + + public static Theme parseTheme(String name) { + if (name == null) return Theme.LIGHT; + String normalized = name.trim().toUpperCase(java.util.Locale.ROOT); + for (Theme theme : Theme.values()) if (theme.name().equals(normalized)) return theme; + Log.w(TAG, "Unknown desktop theme '" + name + "', using LIGHT"); + return Theme.LIGHT; + } + + private static BackgroundType parseBackgroundType(String name) { + if (name != null) { + String normalized = name.trim().toUpperCase(java.util.Locale.ROOT); + for (BackgroundType type : BackgroundType.values()) { + if (type.name().equals(normalized)) return type; + } + } + return BackgroundType.IMAGE; + } + + private static int parseColor(String value) { + try { + return Color.parseColor(value.trim()); + } catch (RuntimeException e) { + return Color.parseColor("#0277bd"); } } }