Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UX] UMU used by default if linux and proton, not exprimental anymore, allow disabling as needed #4175

Merged
merged 3 commits into from
Dec 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@
"disable_controller": "Disable Heroic navigation using controller",
"disable_logs": "Disable Logs",
"disablePlaytimeSync": "Disable playtime synchronization",
"disableUMU": "Disable UMU",
"discordRPC": "Enable Discord Rich Presence",
"download-no-https": "Download games without HTTPS (useful for CDNs e.g. LanCache)",
"dxvkfpslimit": "Limit FPS (DX9, 10 and 11)",
Expand Down Expand Up @@ -635,8 +636,7 @@
"experimental_features": {
"cometSupport": "Comet support",
"enableHelp": "Help component",
"enableNewDesign": "New design",
"umuSupport": "Use UMU as Proton runtime"
"enableNewDesign": "New design"
},
"frameless-window": {
"confirmation": {
Expand Down
3 changes: 2 additions & 1 deletion src/backend/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ class GlobalConfigV0 extends GlobalConfig {
battlEyeRuntime: isLinux,
framelessWindow: false,
beforeLaunchScriptPath: '',
afterLaunchScriptPath: ''
afterLaunchScriptPath: '',
disableUMU: false
}
// @ts-expect-error TODO: We need to settle on *one* place to define settings defaults
return settings
Expand Down
10 changes: 5 additions & 5 deletions src/backend/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ async function prepareLaunch(
}

if (
(await isUmuSupported(gameSettings.wineVersion.type, false)) &&
(await isUmuSupported(gameSettings, false)) &&
isOnline() &&
!(await isInstalled('umu')) &&
(await getUmuPath()) === defaultUmuPath
Expand All @@ -246,7 +246,7 @@ async function prepareLaunch(
const shouldUseRuntime =
gameSettings.useSteamRuntime &&
(isNative ||
(!(await isUmuSupported(gameSettings.wineVersion.type)) &&
(!(await isUmuSupported(gameSettings)) &&
gameSettings.wineVersion.type === 'proton'))

if (shouldUseRuntime) {
Expand Down Expand Up @@ -819,7 +819,7 @@ export async function verifyWinePrefix(
return { res: { stdout: '', stderr: '' } }
}

if (!existsSync(winePrefix) && !(await isUmuSupported(wineVersion.type))) {
if (!existsSync(winePrefix) && !(await isUmuSupported(settings))) {
mkdirSync(winePrefix, { recursive: true })
}

Expand All @@ -831,7 +831,7 @@ export async function verifyWinePrefix(
const haveToWait = !existsSync(systemRegPath)

const command = runWineCommand({
commandParts: (await isUmuSupported(wineVersion.type))
commandParts: (await isUmuSupported(settings))
? ['createprefix']
: ['wineboot', '--init'],
wait: haveToWait,
Expand Down Expand Up @@ -926,7 +926,7 @@ async function runWineCommand({
}

const wineBin = wineVersion.bin.replaceAll("'", '')
const umuSupported = await isUmuSupported(wineVersion.type)
const umuSupported = await isUmuSupported(settings)
const runnerBin = umuSupported ? await getUmuPath() : wineBin

if (wineVersion.type === 'proton' && !umuSupported) {
Expand Down
12 changes: 2 additions & 10 deletions src/backend/storeManagers/gog/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,22 +576,14 @@ export async function launch(
...wineEnvVars
}

const { bin: wineExec, type: wineType } = gameSettings.wineVersion

if (await isUmuSupported(wineType)) {
if (await isUmuSupported(gameSettings)) {
const umuId = await getUmuId(gameInfo.app_name, gameInfo.runner)
if (umuId) {
commandEnv['GAMEID'] = umuId
}
}

// Fix for people with old config
const wineBin =
wineExec.startsWith("'") && wineExec.endsWith("'")
? wineExec.replaceAll("'", '')
: wineExec

wineFlag = await getWineFlagsArray(wineBin, wineType, shlex.join(wrappers))
wineFlag = await getWineFlagsArray(gameSettings, shlex.join(wrappers))
}

const commandParts = [
Expand Down
11 changes: 2 additions & 9 deletions src/backend/storeManagers/legendary/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -912,21 +912,14 @@ export async function launch(
...wineEnvVars
}

const { bin: wineExec, type: wineType } = gameSettings.wineVersion

if (await isUmuSupported(wineType)) {
if (await isUmuSupported(gameSettings)) {
const umuId = await getUmuId(gameInfo.app_name, gameInfo.runner)
if (umuId) {
commandEnv['GAMEID'] = umuId
}
}
// Fix for people with old config
const wineBin =
wineExec.startsWith("'") && wineExec.endsWith("'")
? wineExec.replaceAll("'", '')
: wineExec

wineFlags = await getWineFlags(wineBin, wineType, shlex.join(wrappers))
wineFlags = await getWineFlags(gameSettings, shlex.join(wrappers))
}

const appNameToLaunch =
Expand Down
12 changes: 2 additions & 10 deletions src/backend/storeManagers/nile/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,23 +385,15 @@ export async function launch(
...wineEnvVars
}

const { bin: wineExec, type: wineType } = gameSettings.wineVersion

if (await isUmuSupported(wineType)) {
if (await isUmuSupported(gameSettings)) {
const umuId = await getUmuId(gameInfo.app_name, gameInfo.runner)
if (umuId) {
commandEnv['GAMEID'] = umuId
}
}

// Fix for people with old config
const wineBin =
wineExec.startsWith("'") && wineExec.endsWith("'")
? wineExec.replaceAll("'", '')
: wineExec

wineFlag = [
...(await getWineFlagsArray(wineBin, wineType, shlex.join(wrappers))),
...(await getWineFlagsArray(gameSettings, shlex.join(wrappers))),
'--wine-prefix',
gameSettings.winePrefix
]
Expand Down
10 changes: 3 additions & 7 deletions src/backend/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,6 @@ export const Winetricks = {
const gameSettings = await gameManagerMap[runner].getSettings(appName)

const { wineVersion } = gameSettings
const baseWinePrefix = gameSettings.winePrefix

if (!(await validWine(wineVersion))) {
return
Expand All @@ -533,7 +532,7 @@ export const Winetricks = {
await Winetricks.download()
}

if (await isUmuSupported(wineVersion.type)) {
if (await isUmuSupported(gameSettings)) {
winetricks = await getUmuPath()

if (args.includes('-q')) {
Expand All @@ -549,7 +548,7 @@ export const Winetricks = {
}

const { winePrefix, wineVersion: alwaysWine_wineVersion } =
await getWineFromProton(wineVersion, baseWinePrefix)
await getWineFromProton(gameSettings)
return new Promise<string[] | null>((resolve) => {
const wineBin = alwaysWine_wineVersion.bin
// We have to run Winetricks with an actual `wine` binary, meaning we
Expand Down Expand Up @@ -719,10 +718,7 @@ export const Winetricks = {
},
listInstalled: async (runner: Runner, appName: string) => {
const gameSettings = await gameManagerMap[runner].getSettings(appName)
const { winePrefix } = await getWineFromProton(
gameSettings.wineVersion,
gameSettings.winePrefix
)
const { winePrefix } = await getWineFromProton(gameSettings)
const winetricksLogPath = join(winePrefix, 'winetricks.log')
try {
const winetricksLog = await readFile(winetricksLogPath, 'utf8')
Expand Down
11 changes: 5 additions & 6 deletions src/backend/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,12 @@ function semverGt(target: string, base: string) {
const getFileSize = fileSize.partial({ base: 2 }) as (arg: unknown) => string

async function getWineFromProton(
wineVersion: WineInstallation,
winePrefix: string
gameSettings: GameSettings
): Promise<{ winePrefix: string; wineVersion: WineInstallation }> {
if (
wineVersion.type !== 'proton' ||
(await isUmuSupported(wineVersion.type))
) {
const wineVersion = gameSettings.wineVersion
let winePrefix = gameSettings.winePrefix

if (wineVersion.type !== 'proton' || (await isUmuSupported(gameSettings))) {
return { winePrefix, wineVersion }
}

Expand Down
35 changes: 21 additions & 14 deletions src/backend/utils/compatibility_layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import {
configPath,
defaultUmuPath,
getSteamLibraries,
isLinux,
isMac,
toolsPath,
userHome
} from 'backend/constants'
import { logError, LogPrefix, logInfo } from 'backend/logger/logger'
import { execAsync } from 'backend/utils'
import { execSync } from 'child_process'
import { WineInstallation } from 'common/types'
import { GameSettings, WineInstallation } from 'common/types'
import { existsSync, mkdirSync, readFileSync, readdirSync } from 'graceful-fs'
import { homedir } from 'os'
import { dirname, join } from 'path'
Expand Down Expand Up @@ -489,12 +490,18 @@ export type AllowedWineFlags = Pick<
* @param wrapper Any wrappers to be used, may be `''`
*/
export async function getWineFlags(
wineBin: string,
wineType: WineInstallation['type'],
gameSettings: GameSettings,
wrapper: string
): Promise<AllowedWineFlags> {
let partialCommand: AllowedWineFlags = {}
const umuSupported = await isUmuSupported(wineType)
const { type: wineType, bin: wineExec } = gameSettings.wineVersion

// Fix for people with old config
const wineBin =
wineExec.startsWith("'") && wineExec.endsWith("'")
? wineExec.replaceAll("'", '')
: wineExec

switch (wineType) {
case 'wine':
case 'toolkit':
Expand All @@ -508,7 +515,7 @@ export async function getWineFlags(
`${wrapper} "${wineBin}" waitforexitandrun`
)
}
if (umuSupported) {
if (await isUmuSupported(gameSettings)) {
partialCommand['--wrapper'] = NonEmptyString.parse(
(wrapper ? `${wrapper} ` : '') + `"${await getUmuPath()}"`
)
Expand All @@ -530,11 +537,10 @@ export async function getWineFlags(
* Like {@link getWineFlags}, but returns a `string[]` with the flags instead
*/
export async function getWineFlagsArray(
wineBin: string,
wineType: WineInstallation['type'],
gameSettings: GameSettings,
wrapper: string
): Promise<string[]> {
const partialCommand = await getWineFlags(wineBin, wineType, wrapper)
const partialCommand = await getWineFlags(gameSettings, wrapper)

const commandArray: string[] = []
for (const [key, value] of Object.entries(partialCommand)) {
Expand All @@ -548,13 +554,14 @@ export const getUmuPath = async () =>
searchForExecutableOnPath('umu-run').then((path) => path ?? defaultUmuPath)

export async function isUmuSupported(
wineType: WineInstallation['type'],
gameSettings: GameSettings,
checkUmuInstalled = true
): Promise<boolean> {
const umuEnabled =
GlobalConfig.get().getSettings().experimentalFeatures?.umuSupport === true
const wineVersionSupported = wineType === 'proton'
const umuInstalled = checkUmuInstalled ? existsSync(await getUmuPath()) : true
if (!isLinux) return false
if (gameSettings.wineVersion.type !== 'proton') return false
if (gameSettings.disableUMU) return false
if (!checkUmuInstalled) return true
if (!existsSync(await getUmuPath())) return false
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using individual returns for each condition cause I think it's easier to understand what's being checked and we can avoid more expensive operations if we can return earlier


return umuEnabled && wineVersionSupported && umuInstalled
return true
}
3 changes: 2 additions & 1 deletion src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export type ExperimentalFeatures = {
enableNewDesign: boolean
enableHelp: boolean
cometSupport: boolean
umuSupport: boolean
}

export interface AppSettings extends GameSettings {
Expand Down Expand Up @@ -102,6 +101,7 @@ export interface AppSettings extends GameSettings {
minimizeOnLaunch: boolean
startInTray: boolean
allowInstallationBrokenAnticheat: boolean
disableUMU: boolean
}

export type LibraryTopSectionOptions =
Expand Down Expand Up @@ -202,6 +202,7 @@ export interface GameSettings {
gogSaves?: GOGCloudSavesLocation[]
beforeLaunchScriptPath: string
afterLaunchScriptPath: string
disableUMU: boolean
}

export type Status =
Expand Down
31 changes: 31 additions & 0 deletions src/frontend/screens/Settings/components/DisableUMU.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { ToggleSwitch } from 'frontend/components/UI'
import useSetting from 'frontend/hooks/useSetting'
import SettingsContext from '../SettingsContext'
import ContextProvider from 'frontend/state/ContextProvider'
import { defaultWineVersion } from '..'

const DisableUMU = () => {
const { t } = useTranslation()
const { isDefault } = useContext(SettingsContext)
const { platform } = useContext(ContextProvider)

const [disableUMU, setDisableUMU] = useSetting('disableUMU', false)
const [wineVersion] = useSetting('wineVersion', defaultWineVersion)

if (isDefault || platform !== 'linux' || wineVersion.type !== 'proton') {
return <></>
}

return (
<ToggleSwitch
htmlId="disableUMU"
value={disableUMU}
handleChange={() => setDisableUMU(!disableUMU)}
title={t('setting.disableUMU', 'Disable UMU')}
/>
)
}

export default DisableUMU
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,15 @@ import { ToggleSwitch } from 'frontend/components/UI'
import ContextProvider from 'frontend/state/ContextProvider'

const ExperimentalFeatures = () => {
const { platform } = useContext(ContextProvider)

const FEATURES = ['enableNewDesign', 'enableHelp', 'cometSupport']

if (platform === 'linux') {
FEATURES.push('umuSupport')
}

const { t } = useTranslation()
const [experimentalFeatures, setExperimentalFeatures] = useSetting(
'experimentalFeatures',
{
enableNewDesign: false,
enableHelp: false,
cometSupport: true,
umuSupport: false
cometSupport: true
}
)
const { handleExperimentalFeatures } = useContext(ContextProvider)
Expand All @@ -39,7 +32,6 @@ const ExperimentalFeatures = () => {
t('setting.experimental_features.enableNewDesign', 'New design')
t('setting.experimental_features.enableHelp', 'Help component')
t('setting.experimental_features.cometSupport', 'Comet support')
t('setting.experimental_features.umuSupport', 'Use UMU as Proton runtime')
*/

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import SyncSaves from '../SyncSaves'
import FooterInfo from '../FooterInfo'
import { Tabs, Tab } from '@mui/material'
import { GameInfo } from 'common/types'
import DisableUMU from '../../components/DisableUMU'

const windowsPlatforms = ['Win32', 'Windows', 'windows']
function getStartingTab(platform: string, gameInfo?: GameInfo | null): string {
Expand Down Expand Up @@ -171,6 +172,7 @@ export default function GamesSettings() {

<TabPanel value={value} index={'wine'}>
<WineVersionSelector />
<DisableUMU />
CommandMC marked this conversation as resolved.
Show resolved Hide resolved
<WinePrefix />
<CrossoverBottle />
{!isCrossover && (
Expand Down
Loading
Loading