2026-01-18 12:13:07 +00:00
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2026 A-La-Karte Contributors
import QtQuick
import QtQuick . Controls as QQC2
import QtQuick . Layouts
import org . kde . kirigami as Kirigami
import org . kde . kirigamiaddons . formcard as FormCard
import org . kde . alakarte
import "components"
ColumnLayout {
id: settingsPage
spacing: Kirigami . Units . mediumSpacing
2026-02-09 13:03:38 +00:00
property string runnerInstallUrl: ""
property string runnerInstallSha256: ""
property string runnerInstallName: ""
2026-02-12 13:48:39 +00:00
property string compatibilityGameId: ""
property string compatibilityLoadedRunnerId: ""
property string compatibilityLoadedRunner: ""
property string compatibilityLastError: ""
property int compatibilitySpecRevision: 0
readonly property bool compatibilityHasGame: ( compatibilityGameId || "" ) . trim ( ) !== ""
property int compatibilityRunnerIndex: 0
property string compatibilityRunnerPath: ""
property string compatibilityPrefixPath: ""
ListModel {
id: compatibilityGameChoicesModel
}
ListModel {
id: compatibilityRunnerChoicesModel
}
ListModel {
id: compatibilityEnvModel
}
ListModel {
id: compatibilityExtraArgsModel
}
ListModel {
id: compatibilityDllOverridesModel
}
property string lastControllerInput: ""
property string lastControllerInputTime: ""
property string profileMenuControllerId: ""
property string newProfileName: ""
property string pendingDeleteProfileId: ""
property string pendingDeleteProfileName: ""
2026-01-18 12:13:07 +00:00
function focusFirstControl ( ) {
showPlatformBadgesDelegate . forceActiveFocus ( )
}
2026-01-25 09:03:46 +00:00
property var pendingDisableImportApply: null
property string pendingDisableImportName: ""
2026-01-29 18:49:45 +00:00
readonly property bool anyConfirmOpen: disableImportConfirmDialog . opened
|| fetchAllCoversConfirmDialog . opened
|| removeMissingConfirmDialog . opened
|| clearConfirmDialog . opened
|| resetConfirmDialog . opened
2026-02-12 13:48:39 +00:00
|| deleteProfileConfirmDialog . opened
2026-01-29 18:49:45 +00:00
2026-02-12 13:48:39 +00:00
readonly property bool anyMenuOpen: uiModeMenu . visible || profileMenu . visible
2026-01-29 18:49:45 +00:00
function currentConfirmDialog ( ) {
if ( disableImportConfirmDialog . opened ) return disableImportConfirmDialog
if ( fetchAllCoversConfirmDialog . opened ) return fetchAllCoversConfirmDialog
if ( removeMissingConfirmDialog . opened ) return removeMissingConfirmDialog
if ( clearConfirmDialog . opened ) return clearConfirmDialog
if ( resetConfirmDialog . opened ) return resetConfirmDialog
2026-02-12 13:48:39 +00:00
if ( deleteProfileConfirmDialog . opened ) return deleteProfileConfirmDialog
2026-01-29 18:49:45 +00:00
return null
}
function closeCurrentConfirmDialog ( ) {
let d = currentConfirmDialog ( )
if ( ! d ) return
if ( typeof d . reject === "function" ) {
d . reject ( )
} else {
d . close ( )
}
}
function closeCurrentMenu ( ) {
2026-02-12 13:48:39 +00:00
let m = currentMenu ( )
if ( m && m . close ) m . close ( )
}
function currentMenu ( ) {
if ( profileMenu . visible ) return profileMenu
if ( uiModeMenu . visible ) return uiModeMenu
return null
2026-01-29 18:49:45 +00:00
}
function isDescendant ( item , ancestor ) {
let p = item
while ( p ) {
if ( p === ancestor ) return true
if ( ancestor . contentItem && p === ancestor . contentItem ) return true
if ( p . visualParent !== undefined && p . visualParent !== null ) {
if ( settingsPage . isDescendant ( p . visualParent , ancestor ) ) return true
} else if ( p . popup !== undefined && p . popup !== null && p . popup . visualParent !== undefined && p . popup . visualParent !== null ) {
if ( settingsPage . isDescendant ( p . popup . visualParent , ancestor ) ) return true
}
p = p . parent
}
return false
}
function focusNextInMenu ( forward ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
2026-02-12 13:48:39 +00:00
let menu = currentMenu ( )
if ( ! menu ) return
2026-01-29 18:49:45 +00:00
let next = w . activeFocusItem
for ( let i = 0 ; i < 50 ; i ++ ) {
next = next . nextItemInFocusChain ( forward )
if ( ! next ) return
2026-02-12 13:48:39 +00:00
if ( settingsPage . isDescendant ( next , menu ) ) {
2026-01-29 18:49:45 +00:00
next . forceActiveFocus ( )
return
}
}
}
2026-02-12 13:48:39 +00:00
function rebuildCompatibilityGameChoices ( ) {
compatibilityGameChoicesModel . clear ( )
if ( ! App || ! App . gameModel ) return
for ( let i = 0 ; i < App . gameModel . count ; i ++ ) {
let g = App . gameModel . gameAt ( i )
if ( ! g ) continue
compatibilityGameChoicesModel . append ( { gameId: String ( g . id || "" ) , label: String ( g . name || g . id || "" ) } )
}
if ( compatibilityGameId === "" ) {
if ( compatibilityGameChoicesModel . count > 0 ) {
compatibilityGameId = String ( compatibilityGameChoicesModel . get ( 0 ) . gameId || "" )
}
}
}
function rebuildCompatibilityRunnerChoices ( ) {
compatibilityRunnerChoicesModel . clear ( )
compatibilityRunnerChoicesModel . append ( { kind: "native" , label: i18n ( "Native" ) , runner: "" , runnerId: "" , runnerType: "" } )
compatibilityRunnerChoicesModel . append ( { kind: "wine" , label: i18n ( "Wine (system)" ) , runner: "wine" , runnerId: "" , runnerType: "wine" } )
compatibilityRunnerChoicesModel . append ( { kind: "proton" , label: i18n ( "Proton (auto)" ) , runner: "proton" , runnerId: "" , runnerType: "proton" } )
let runners = ( App . runnerManager && App . runnerManager . runners ) ? App.runnerManager.runners : [ ]
for ( let i = 0 ; i < runners . length ; i ++ ) {
let r = runners [ i ]
if ( ! r ) continue
let name = r . name ? String ( r . name ) : ""
let type = r . type ? String ( r . type ) : ""
let source = r . source ? String ( r . source ) : ""
let label = name
if ( type !== "" || source !== "" ) {
label = name + " (" + type + ( source !== "" ? ( " · " + source ) : "" ) + ")"
}
compatibilityRunnerChoicesModel . append ( { kind: "inventory" , label: label , runner: "" , runnerId: String ( r . id || "" ) , runnerType: type } )
}
compatibilityRunnerChoicesModel . append ( { kind: "custom" , label: i18n ( "Custom runner path" ) , runner: "custom" , runnerId: "" , runnerType: "custom" } )
}
function compatibilityIndexForGameId ( gameId ) {
let id = ( gameId || "" ) . trim ( )
if ( id === "" ) return 0
for ( let i = 0 ; i < compatibilityGameChoicesModel . count ; i ++ ) {
let row = compatibilityGameChoicesModel . get ( i )
if ( row && String ( row . gameId || "" ) === id ) return i
}
return 0
}
function compatibilityCurrentRunnerChoice ( ) {
if ( ! compatibilityRunnerChoicesModel || compatibilityRunnerChoicesModel . count <= 0 ) return null
let idx = settingsPage . compatibilityRunnerIndex
if ( idx < 0 || idx >= compatibilityRunnerChoicesModel . count ) idx = 0
return compatibilityRunnerChoicesModel . get ( idx )
}
function compatibilityCurrentRunnerChoiceKind ( ) {
let c = settingsPage . compatibilityCurrentRunnerChoice ( )
if ( ! c || ! c . kind ) return "native"
return String ( c . kind )
}
function compatibilityCurrentRunnerChoiceRunnerType ( ) {
let c = settingsPage . compatibilityCurrentRunnerChoice ( )
if ( ! c ) return ""
if ( c . runnerType ) return String ( c . runnerType )
if ( c . runner ) return String ( c . runner )
return ""
}
function compatibilityIndexForRunnerId ( runnerId ) {
let id = ( runnerId || "" ) . trim ( )
if ( id === "" ) return 0
for ( let i = 0 ; i < compatibilityRunnerChoicesModel . count ; i ++ ) {
let row = compatibilityRunnerChoicesModel . get ( i )
if ( row . kind === "inventory" && String ( row . runnerId || "" ) === id ) return i
}
return 0
}
function compatibilityIndexForRunner ( runner ) {
let r = ( runner || "" ) . trim ( ) . toLowerCase ( )
if ( r === "wine" ) return 1
if ( r === "proton" ) return 2
if ( r === "custom" ) return Math . max ( 0 , compatibilityRunnerChoicesModel . count - 1 )
return 0
}
function compatibilitySyncRunnerIndexFromLoaded ( ) {
if ( ( settingsPage . compatibilityLoadedRunnerId || "" ) !== "" ) {
settingsPage . compatibilityRunnerIndex = settingsPage . compatibilityIndexForRunnerId ( settingsPage . compatibilityLoadedRunnerId )
return
}
if ( ( settingsPage . compatibilityLoadedRunner || "" ) !== "" ) {
settingsPage . compatibilityRunnerIndex = settingsPage . compatibilityIndexForRunner ( settingsPage . compatibilityLoadedRunner )
return
}
settingsPage . compatibilityRunnerIndex = 0
}
function compatibilitySetEnvFromMap ( envMap ) {
compatibilityEnvModel . clear ( )
if ( ! envMap ) return
let keys = Object . keys ( envMap )
keys . sort ( )
for ( let i = 0 ; i < keys . length ; i ++ ) {
let k = keys [ i ]
compatibilityEnvModel . append ( { key: String ( k ) , value: String ( envMap [ k ] || "" ) } )
}
}
function compatibilityEnvModelToMap ( ) {
let result = ( { } )
for ( let i = 0 ; i < compatibilityEnvModel . count ; i ++ ) {
let row = compatibilityEnvModel . get ( i )
let k = ( row . key || "" ) . trim ( )
if ( k === "" ) continue
if ( k . indexOf ( "=" ) !== - 1 ) continue
result [ k ] = String ( row . value || "" )
}
return result
}
function compatibilitySetExtraArgsFromList ( list ) {
compatibilityExtraArgsModel . clear ( )
if ( ! list || ! Array . isArray ( list ) ) return
for ( let i = 0 ; i < list . length ; i ++ ) {
let a = String ( list [ i ] || "" ) . trim ( )
if ( a === "" ) continue
compatibilityExtraArgsModel . append ( { arg: a } )
}
}
function compatibilityExtraArgsModelToList ( ) {
let result = [ ]
for ( let i = 0 ; i < compatibilityExtraArgsModel . count ; i ++ ) {
let row = compatibilityExtraArgsModel . get ( i )
let a = ( row . arg || "" ) . trim ( )
if ( a === "" ) continue
result . push ( a )
}
return result
}
function compatibilitySetDllOverridesFromMap ( dllMap ) {
compatibilityDllOverridesModel . clear ( )
if ( ! dllMap ) return
let keys = Object . keys ( dllMap )
keys . sort ( )
for ( let i = 0 ; i < keys . length ; i ++ ) {
let k = String ( keys [ i ] || "" ) . trim ( )
if ( k === "" ) continue
compatibilityDllOverridesModel . append ( { key: k , value: String ( dllMap [ k ] || "" ) } )
}
}
function compatibilityDllOverridesModelToMap ( ) {
let result = ( { } )
for ( let i = 0 ; i < compatibilityDllOverridesModel . count ; i ++ ) {
let row = compatibilityDllOverridesModel . get ( i )
let k = ( row . key || "" ) . trim ( )
if ( k === "" ) continue
if ( k . indexOf ( "=" ) !== - 1 ) continue
if ( k . indexOf ( ";" ) !== - 1 ) continue
let v = ( row . value || "" ) . trim ( )
if ( v === "" ) continue
result [ k ] = String ( v )
}
return result
}
function compatibilitySetEnvVar ( key , value ) {
let k = ( key || "" ) . trim ( )
if ( k === "" ) return
for ( let i = 0 ; i < compatibilityEnvModel . count ; i ++ ) {
let row = compatibilityEnvModel . get ( i )
if ( ( row . key || "" ) . trim ( ) === k ) {
compatibilityEnvModel . setProperty ( i , "value" , String ( value || "" ) )
settingsPage . compatibilityBumpSpecRevision ( )
return
}
}
compatibilityEnvModel . append ( { key: k , value: String ( value || "" ) } )
settingsPage . compatibilityBumpSpecRevision ( )
}
function compatibilityBumpSpecRevision ( ) {
settingsPage . compatibilitySpecRevision = settingsPage . compatibilitySpecRevision + 1
}
function compatibilityResetEditor ( ) {
settingsPage . compatibilityLastError = ""
settingsPage . compatibilityLoadedRunnerId = ""
settingsPage . compatibilityLoadedRunner = ""
settingsPage . compatibilityRunnerPath = ""
settingsPage . compatibilityPrefixPath = ""
settingsPage . compatibilityRunnerIndex = 0
compatibilityEnvModel . clear ( )
compatibilityExtraArgsModel . clear ( )
compatibilityDllOverridesModel . clear ( )
settingsPage . compatibilityBumpSpecRevision ( )
}
function compatibilityApplyProfile ( profile ) {
let p = profile || ( { } )
settingsPage . compatibilityLastError = ""
settingsPage . compatibilityLoadedRunnerId = String ( p . runnerId || "" )
settingsPage . compatibilityLoadedRunner = String ( p . runner || "" )
settingsPage . compatibilityRunnerPath = String ( p . runnerPath || "" )
settingsPage . compatibilityPrefixPath = String ( p . prefixPath || "" )
settingsPage . compatibilitySyncRunnerIndexFromLoaded ( )
settingsPage . compatibilitySetEnvFromMap ( p . envOverrides || ( { } ) )
settingsPage . compatibilitySetExtraArgsFromList ( p . extraArgs || ( [ ] ) )
settingsPage . compatibilitySetDllOverridesFromMap ( p . dllOverrides || ( { } ) )
settingsPage . compatibilityBumpSpecRevision ( )
}
function compatibilityRequestProfile ( ) {
let id = ( settingsPage . compatibilityGameId || "" ) . trim ( )
if ( id === "" ) return
if ( ! App || ! App . runnerManager ) return
App . runnerManager . requestGameProfile ( id )
}
function compatibilityBuildProfileSpec ( ) {
let id = ( settingsPage . compatibilityGameId || "" ) . trim ( )
if ( id === "" ) return ( { } )
let choice = settingsPage . compatibilityCurrentRunnerChoice ( )
let kind = choice && choice . kind ? String ( choice . kind ) : "native"
let runnerType = settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( )
let runnerId = choice && choice . runnerId ? String ( choice . runnerId ) : ""
let runner = choice && choice . runner ? String ( choice . runner ) : ""
let spec = ( { gameId: id } )
if ( runnerId !== "" ) spec . runnerId = runnerId
if ( runner !== "" ) spec . runner = runner
if ( ( kind === "custom" || kind === "wine" || kind === "proton" ) && ( settingsPage . compatibilityRunnerPath || "" ) . trim ( ) !== "" ) {
spec . runnerPath = settingsPage . compatibilityRunnerPath . trim ( )
}
if ( ( runnerType === "wine" || runnerType === "proton" ) && ( settingsPage . compatibilityPrefixPath || "" ) . trim ( ) !== "" ) {
spec . prefixPath = settingsPage . compatibilityPrefixPath . trim ( )
}
let env = settingsPage . compatibilityEnvModelToMap ( )
if ( Object . keys ( env ) . length > 0 ) spec . envOverrides = env
let extraArgs = settingsPage . compatibilityExtraArgsModelToList ( )
if ( extraArgs . length > 0 ) spec . extraArgs = extraArgs
let dllOverrides = settingsPage . compatibilityDllOverridesModelToMap ( )
if ( Object . keys ( dllOverrides ) . length > 0 ) spec . dllOverrides = dllOverrides
return spec
}
function compatibilityCanSaveProfile ( ) {
let spec = settingsPage . compatibilityBuildProfileSpec ( )
if ( ! spec || Object . keys ( spec ) . length <= 1 ) return false
return true
}
function compatibilitySaveProfile ( ) {
let id = ( settingsPage . compatibilityGameId || "" ) . trim ( )
if ( id === "" ) return
if ( ! App || ! App . runnerManager ) return
let spec = settingsPage . compatibilityBuildProfileSpec ( )
if ( ! spec || Object . keys ( spec ) . length <= 1 ) return
App . runnerManager . setGameProfile ( spec )
}
function compatibilityClearProfile ( ) {
let id = ( settingsPage . compatibilityGameId || "" ) . trim ( )
if ( id === "" ) return
if ( ! App || ! App . runnerManager ) return
App . runnerManager . clearGameProfile ( id )
}
onCompatibilityGameIdChanged: {
settingsPage . compatibilityResetEditor ( )
settingsPage . compatibilityRequestProfile ( )
}
Component.onCompleted: {
settingsPage . rebuildCompatibilityGameChoices ( )
settingsPage . rebuildCompatibilityRunnerChoices ( )
settingsPage . compatibilityRequestProfile ( )
}
Connections {
target: App && App . gameModel ? App.gameModel : null
function onCountChanged ( ) {
settingsPage . rebuildCompatibilityGameChoices ( )
}
}
Connections {
target: App && App . runnerManager ? App.runnerManager : null
function onRunnersChanged ( ) {
settingsPage . rebuildCompatibilityRunnerChoices ( )
settingsPage . compatibilitySyncRunnerIndexFromLoaded ( )
}
function onPrefixEnsured ( gameId , prefixPath ) {
let id = String ( gameId || "" )
if ( id !== settingsPage . compatibilityGameId ) return
settingsPage . compatibilityLastError = ""
if ( ( settingsPage . compatibilityPrefixPath || "" ) . trim ( ) !== "" ) {
settingsPage . compatibilityPrefixPath = String ( prefixPath || "" )
}
}
function onPrefixDeleted ( gameId , prefixPath ) {
let id = String ( gameId || "" )
if ( id !== settingsPage . compatibilityGameId ) return
settingsPage . compatibilityLastError = ""
}
function onGameProfileFetched ( gameId , result ) {
let id = String ( gameId || "" )
if ( id !== settingsPage . compatibilityGameId ) return
if ( ! result || result . ok !== true ) {
settingsPage . compatibilityLastError = result && result . error ? String ( result . error ) : i18n ( "Failed to load profile" )
settingsPage . compatibilityApplyProfile ( { } )
return
}
settingsPage . compatibilityApplyProfile ( result . profile || ( { } ) )
}
function onGameProfileSaved ( gameId , result ) {
let id = String ( gameId || "" )
if ( id !== settingsPage . compatibilityGameId ) return
if ( ! result || result . ok !== true ) {
settingsPage . compatibilityLastError = result && result . error ? String ( result . error ) : i18n ( "Failed to save profile" )
return
}
settingsPage . compatibilityApplyProfile ( result . profile || ( { } ) )
}
function onGameProfileCleared ( gameId , result ) {
let id = String ( gameId || "" )
if ( id !== settingsPage . compatibilityGameId ) return
if ( ! result || result . ok !== true ) {
settingsPage . compatibilityLastError = result && result . error ? String ( result . error ) : i18n ( "Failed to clear profile" )
return
}
settingsPage . compatibilityResetEditor ( )
}
}
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Controller" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormTextDelegate {
text: GamepadManager . connected ? i18n ( "Connected" ) : i18n ( "Not connected" )
description: GamepadManager . connected ? ( GamepadManager . name !== "" ? GamepadManager.name : i18n ( "Gamepad" ) ) : i18n ( "Connect a controller to use couch navigation" )
textItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextDelegate {
text: i18n ( "Device" )
description: {
if ( ! GamepadManager . connected ) return i18n ( "-" )
let parts = [ ]
if ( GamepadManager . vendorId > 0 ) parts . push ( i18n ( "Vendor: %1" ) . arg ( GamepadManager . vendorId ) )
if ( GamepadManager . productId > 0 ) parts . push ( i18n ( "Product: %1" ) . arg ( GamepadManager . productId ) )
return parts . length > 0 ? parts . join ( " · " ) : i18n ( "-" )
}
textItem.wrapMode: Text . WordWrap
descriptionItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextDelegate {
text: i18n ( "Buttons" )
description: {
if ( ! GamepadManager . connected ) return i18n ( "-" )
return i18n ( "Confirm: %1 · Back: %2 · Details: %3 · Search: %4" )
. arg ( GamepadManager . confirmButtonLabel )
. arg ( GamepadManager . backButtonLabel )
. arg ( GamepadManager . detailsButtonLabel )
. arg ( GamepadManager . searchButtonLabel )
}
textItem.wrapMode: Text . WordWrap
descriptionItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextDelegate {
text: i18n ( "Active input" )
description: {
if ( InputManager . activeInput === InputManager . Gamepad ) return i18n ( "Gamepad" )
return i18n ( "Keyboard & Mouse" )
}
textItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextDelegate {
text: i18n ( "Input service" )
description: {
if ( App . inputService . available ) return i18n ( "Available" )
if ( App . inputService . lastError !== "" ) return App . inputService . lastError
return i18n ( "Unavailable" )
}
descriptionItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Refresh controllers" )
icon.name: "view-refresh"
onClicked: App . inputService . refreshControllers ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Refresh profiles" )
icon.name: "view-refresh"
onClicked: App . inputService . refreshProfiles ( )
}
}
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Controllers" )
}
FormCard . FormCard {
Layout.fillWidth: true
Repeater {
model: App . inputService . controllers
ColumnLayout {
Layout.fillWidth: true
readonly property var controller: modelData
readonly property string controllerId: controller && controller . controllerId ? controller.controllerId : ""
readonly property string controllerName: controller && controller . name ? controller.name : controllerId
readonly property string connectionState: controller && controller . connectionState ? controller.connectionState : ""
readonly property int batteryPercent: controller && controller . batteryPercent !== undefined ? controller.batteryPercent : - 1
readonly property string batteryState: controller && controller . batteryStateName ? controller.batteryStateName : ""
readonly property string activeProfileName: controller && controller . activeProfileName ? controller.activeProfileName : ""
readonly property string activeProfileId: controller && controller . activeProfileId ? controller.activeProfileId : "default"
FormCard . FormTextDelegate {
text: controllerName
description: {
let parts = [ ]
if ( connectionState !== "" ) parts . push ( connectionState )
if ( batteryPercent >= 0 ) {
parts . push ( i18n ( "Battery: %1%2" ) . arg ( batteryPercent ) . arg ( "%" ) )
} else if ( batteryState !== "" ) {
parts . push ( i18n ( "Battery: %1" ) . arg ( batteryState ) )
}
return parts . length > 0 ? parts . join ( " · " ) : ""
}
textItem.wrapMode: Text . WordWrap
descriptionItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Profile" )
description: activeProfileName !== "" ? activeProfileName : i18n ( "Default" )
icon.name: "preferences-desktop-gaming"
enabled: controllerId !== "" && App . inputService . profiles . length > 0
onClicked: {
settingsPage . profileMenuControllerId = controllerId
profileMenu . open ( )
}
}
FormCard . FormDelegateSeparator { }
}
}
FormCard . FormTextDelegate {
visible: App . inputService . controllers . length === 0
text: i18n ( "No controllers detected" )
textItem.font: Kirigami . Theme . smallFont
}
}
QQC2 . Menu {
id: profileMenu
focus: true
onOpened: Qt . callLater ( function ( ) {
if ( profileMenu . contentChildren . length > 0 && profileMenu . contentChildren [ 0 ] . forceActiveFocus ) {
profileMenu . contentChildren [ 0 ] . forceActiveFocus ( )
}
} )
Repeater {
model: App . inputService . profiles
delegate: QQC2 . MenuItem {
readonly property var profile: modelData
readonly property string profileId: profile && profile . id ? profile.id : ""
text: profile && profile . name ? profile.name : profileId
checkable: true
checked: profileId !== "" && profileId === settingsPage . currentProfileIdForMenu ( )
onTriggered: {
if ( profileId === "" || settingsPage . profileMenuControllerId === "" ) return
App . inputService . setActiveProfile ( settingsPage . profileMenuControllerId , profileId )
}
}
}
}
function currentProfileIdForMenu ( ) {
for ( let i = 0 ; i < App . inputService . controllers . length ; i ++ ) {
let c = App . inputService . controllers [ i ]
if ( c && c . controllerId === settingsPage . profileMenuControllerId ) {
return c . activeProfileId ? c.activeProfileId : "default"
}
}
return "default"
}
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Controller Profiles" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormTextFieldDelegate {
label: i18n ( "New profile name" )
text: settingsPage . newProfileName
placeholderText: i18n ( "e.g. Couch" )
onTextChanged: settingsPage . newProfileName = text
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Create profile" )
icon.name: "list-add"
enabled: settingsPage . newProfileName . trim ( ) !== "" && App . inputService . available
onClicked: {
App . inputService . createProfile ( settingsPage . newProfileName . trim ( ) )
settingsPage . newProfileName = ""
}
}
FormCard . FormDelegateSeparator { }
Repeater {
model: App . inputService . profiles
ColumnLayout {
Layout.fillWidth: true
readonly property var profile: modelData
readonly property string profileId: profile && profile . id ? profile.id : ""
readonly property string profileName: profile && profile . name ? profile.name : profileId
FormCard . FormTextDelegate {
text: profileName
description: profileId
descriptionItem.wrapMode: Text . WrapAnywhere
descriptionItem.font: Kirigami . Theme . smallFont
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Delete" )
icon.name: "edit-delete"
enabled: profileId !== "" && profileId !== "default" && App . inputService . available
onClicked: {
settingsPage . pendingDeleteProfileId = profileId
settingsPage . pendingDeleteProfileName = profileName
deleteProfileConfirmDialog . open ( )
}
}
FormCard . FormDelegateSeparator { }
}
}
}
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Input Test" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormTextDelegate {
text: settingsPage . lastControllerInput !== "" ? settingsPage.lastControllerInput : i18n ( "No input yet" )
description: settingsPage . lastControllerInputTime
textItem.wrapMode: Text . WordWrap
descriptionItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Clear" )
icon.name: "edit-clear"
enabled: settingsPage . lastControllerInput !== ""
onClicked: {
settingsPage . lastControllerInput = ""
settingsPage . lastControllerInputTime = ""
}
}
}
2026-01-29 18:49:45 +00:00
function activateFocusedInMenu ( ) {
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
2026-02-12 13:48:39 +00:00
let menu = currentMenu ( )
if ( ! menu ) return
2026-01-29 18:49:45 +00:00
let item = w . activeFocusItem
if ( typeof item . triggered === "function" ) {
item . triggered ( )
return
}
if ( typeof item . clicked === "function" ) {
item . clicked ( )
return
}
}
function focusNoButton ( dialog ) {
Qt . callLater ( function ( ) {
if ( ! dialog ) return
if ( typeof dialog . standardButton === "function" ) {
let noButton = dialog . standardButton ( Kirigami . Dialog . No )
if ( noButton ) {
noButton . forceActiveFocus ( )
return
}
}
dialog . forceActiveFocus ( )
} )
}
2026-01-25 13:08:33 +00:00
function requestDisableImport ( sourceName , applyFn ) {
2026-01-25 09:03:46 +00:00
pendingDisableImportName = sourceName
pendingDisableImportApply = applyFn
disableImportConfirmDialog . open ( )
}
2026-01-18 12:13:07 +00:00
FormCard . FormHeader {
Layout.fillWidth: true
title: i18n ( "Appearance" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormCheckDelegate {
id: showPlatformBadgesDelegate
text: i18n ( "Show platform badges" )
description: i18n ( "Display platform icons on game cards" )
checked: App . config . showPlatformBadges
onToggled: App . config . showPlatformBadges = checked
}
2026-01-24 12:27:30 +00:00
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
id: uiModeDelegate
text: i18n ( "UI mode" )
description: {
if ( App . config . uiMode === Config . Desktop ) return i18n ( "Desktop" )
2026-01-29 18:49:45 +00:00
if ( App . config . uiMode === Config . Couch ) return i18n ( "Couch" )
2026-01-24 12:27:30 +00:00
return i18n ( "Automatic" )
}
icon.name: "view-fullscreen"
onClicked: uiModeMenu . open ( )
QQC2 . Menu {
id: uiModeMenu
2026-01-29 18:49:45 +00:00
focus: true
onOpened: Qt . callLater ( function ( ) { uiModeAuto . forceActiveFocus ( ) } )
2026-01-24 12:27:30 +00:00
QQC2 . MenuItem {
2026-01-29 18:49:45 +00:00
id: uiModeAuto
2026-01-24 12:27:30 +00:00
text: i18n ( "Automatic" )
checkable: true
checked: App . config . uiMode === Config . Auto
onTriggered: App . config . uiMode = Config . Auto
}
QQC2 . MenuItem {
2026-01-29 18:49:45 +00:00
id: uiModeDesktop
2026-01-24 12:27:30 +00:00
text: i18n ( "Desktop" )
checkable: true
checked: App . config . uiMode === Config . Desktop
onTriggered: App . config . uiMode = Config . Desktop
}
QQC2 . MenuItem {
2026-01-29 18:49:45 +00:00
id: uiModeCouch
text: i18n ( "Couch" )
2026-01-24 12:27:30 +00:00
checkable: true
2026-01-29 18:49:45 +00:00
checked: App . config . uiMode === Config . Couch
onTriggered: App . config . uiMode = Config . Couch
2026-01-24 12:27:30 +00:00
}
}
}
2026-01-18 12:13:07 +00:00
}
2026-01-29 18:49:45 +00:00
Connections {
target: GamepadManager
function onNavigateUp ( ) {
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . anyMenuOpen ) return
let menu = settingsPage . currentMenu ( )
if ( ! menu ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . isDescendant ( w . activeFocusItem , menu ) ) return
2026-01-29 18:49:45 +00:00
settingsPage . focusNextInMenu ( false )
}
function onNavigateDown ( ) {
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . anyMenuOpen ) return
let menu = settingsPage . currentMenu ( )
if ( ! menu ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . isDescendant ( w . activeFocusItem , menu ) ) return
2026-01-29 18:49:45 +00:00
settingsPage . focusNextInMenu ( true )
}
function onNavigateLeft ( ) {
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . anyMenuOpen ) return
let menu = settingsPage . currentMenu ( )
if ( ! menu ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . isDescendant ( w . activeFocusItem , menu ) ) return
2026-01-29 18:49:45 +00:00
settingsPage . focusNextInMenu ( false )
}
function onNavigateRight ( ) {
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . anyMenuOpen ) return
let menu = settingsPage . currentMenu ( )
if ( ! menu ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . isDescendant ( w . activeFocusItem , menu ) ) return
2026-01-29 18:49:45 +00:00
settingsPage . focusNextInMenu ( true )
}
function onSelectPressed ( ) {
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . anyMenuOpen ) return
let menu = settingsPage . currentMenu ( )
if ( ! menu ) return
2026-01-29 18:49:45 +00:00
let w = applicationWindow ( )
if ( ! w || ! w . activeFocusItem ) return
2026-02-12 13:48:39 +00:00
if ( ! settingsPage . isDescendant ( w . activeFocusItem , menu ) ) return
2026-01-29 18:49:45 +00:00
settingsPage . activateFocusedInMenu ( )
}
2026-02-12 13:48:39 +00:00
function onBackPressed ( ) {
settingsPage . lastControllerInput = i18n ( "Back" )
settingsPage . lastControllerInputTime = new Date ( ) . toLocaleTimeString ( )
}
function onDetailsPressed ( ) {
settingsPage . lastControllerInput = i18n ( "Details" )
settingsPage . lastControllerInputTime = new Date ( ) . toLocaleTimeString ( )
}
function onMenuPressed ( ) {
settingsPage . lastControllerInput = i18n ( "Menu" )
settingsPage . lastControllerInputTime = new Date ( ) . toLocaleTimeString ( )
}
function onSearchPressed ( ) {
settingsPage . lastControllerInput = i18n ( "Search" )
settingsPage . lastControllerInputTime = new Date ( ) . toLocaleTimeString ( )
}
function onLeftBumperPressed ( ) {
settingsPage . lastControllerInput = i18n ( "Left bumper" )
settingsPage . lastControllerInputTime = new Date ( ) . toLocaleTimeString ( )
}
function onRightBumperPressed ( ) {
settingsPage . lastControllerInput = i18n ( "Right bumper" )
settingsPage . lastControllerInputTime = new Date ( ) . toLocaleTimeString ( )
}
}
Kirigami . PromptDialog {
id: deleteProfileConfirmDialog
title: i18n ( "Delete Profile" )
subtitle: settingsPage . pendingDeleteProfileName !== ""
? i18n ( "Delete '%1'?" , settingsPage . pendingDeleteProfileName )
: i18n ( "Delete this profile?" )
standardButtons: Kirigami . Dialog . Yes | Kirigami . Dialog . No
onOpened: settingsPage . focusNoButton ( deleteProfileConfirmDialog )
onAccepted: {
if ( settingsPage . pendingDeleteProfileId !== "" ) {
App . inputService . deleteProfile ( settingsPage . pendingDeleteProfileId )
}
settingsPage . pendingDeleteProfileId = ""
settingsPage . pendingDeleteProfileName = ""
}
onRejected: {
settingsPage . pendingDeleteProfileId = ""
settingsPage . pendingDeleteProfileName = ""
}
2026-01-29 18:49:45 +00:00
}
2026-01-24 19:50:20 +00:00
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Import Sources" )
}
2026-01-18 12:13:07 +00:00
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormCheckDelegate {
text: i18n ( "Steam" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.valvesoftware.Steam"
secondary: "steam"
resourceFallback: Qt . resolvedUrl ( "icons/brand/steam-symbolic.svg" )
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importSteam
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importSteam ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "Steam" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importSteam } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "Steam" ) , function ( ) { App . config . importSteam = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importSteam = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importSteam } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Lutris" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "lutris"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importLutris
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importLutris ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "Lutris" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importLutris } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "Lutris" ) , function ( ) { App . config . importLutris = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importLutris = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importLutris } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Heroic Games Launcher" )
description: i18n ( "Epic, GOG, and Amazon games" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.heroicgameslauncher.hgl"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importHeroic
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importHeroic ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "Heroic" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importHeroic } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "Heroic Games Launcher" ) , function ( ) { App . config . importHeroic = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importHeroic = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importHeroic } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Desktop Entries" )
description: i18n ( "Games from .desktop files" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "user-desktop"
secondary: "computer"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importDesktop
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importDesktop ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "Desktop" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importDesktop } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "Desktop Entries" ) , function ( ) { App . config . importDesktop = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importDesktop = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importDesktop } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Bottles" )
description: i18n ( "Wine applications" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "com.usebottles.bottles"
secondary: "application-x-executable"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importBottles
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importBottles ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "Bottles" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importBottles } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "Bottles" ) , function ( ) { App . config . importBottles = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importBottles = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importBottles } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Flatpak" )
description: i18n ( "Flatpak game applications" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "flatpak-discover"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importFlatpak
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importFlatpak ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "Flatpak" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importFlatpak } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "Flatpak" ) , function ( ) { App . config . importFlatpak = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importFlatpak = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importFlatpak } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "itch.io" )
description: i18n ( "Games from itch.io app" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "io.itch.itch"
secondary: "itch"
resourceFallback: Qt . resolvedUrl ( "icons/brand/itchdotio-symbolic.svg" )
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importItch
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importItch ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "itch.io" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importItch } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "itch.io" ) , function ( ) { App . config . importItch = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importItch = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importItch } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Legendary" )
description: i18n ( "Epic Games via Legendary CLI" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "legendary"
secondary: "applications-games"
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importLegendary
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importLegendary ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "Legendary" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importLegendary } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "Legendary" ) , function ( ) { App . config . importLegendary = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importLegendary = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importLegendary } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "RetroArch" )
description: i18n ( "Games from RetroArch playlists" )
icon.name: ""
leading: IconWithResourceFallback {
primary: "org.libretro.RetroArch"
secondary: "retroarch"
resourceFallback: Qt . resolvedUrl ( "icons/brand/retroarch-symbolic.svg" )
}
2026-01-25 09:03:46 +00:00
property bool restoring: false
2026-01-18 12:13:07 +00:00
checked: App . config . importRetroArch
2026-01-25 09:03:46 +00:00
onToggled: {
if ( restoring ) return
if ( checked === App . config . importRetroArch ) return
if ( ! checked && App . gameModel . hasPlatformPrefix ( "RetroArch" ) ) {
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importRetroArch } )
restoring = false
2026-01-25 13:08:33 +00:00
settingsPage . requestDisableImport ( i18n ( "RetroArch" ) , function ( ) { App . config . importRetroArch = false } )
2026-01-25 09:03:46 +00:00
return
}
App . config . importRetroArch = checked
restoring = true
checked = Qt . binding ( function ( ) { return App . config . importRetroArch } )
restoring = false
}
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Auto-import on startup" )
description: i18n ( "Scan for new games when launching" )
checked: App . config . autoImportOnStartup
onToggled: App . config . autoImportOnStartup = checked
}
}
FormCard . FormHeader {
2026-01-24 19:50:20 +00:00
Layout.topMargin: Kirigami . Units . mediumSpacing
2026-01-18 12:13:07 +00:00
Layout.fillWidth: true
title: i18n ( "Behavior" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormCheckDelegate {
text: i18n ( "Exit after launching game" )
description: i18n ( "Close A-La-Karte when a game starts" )
checked: App . config . exitAfterLaunch
onToggled: App . config . exitAfterLaunch = checked
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Click cover to launch" )
description: i18n ( "Clicking the cover launches the game instead of showing details" )
checked: App . config . coverLaunchesGame
onToggled: App . config . coverLaunchesGame = checked
}
}
2026-02-09 13:03:38 +00:00
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Runners" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormTextFieldDelegate {
label: i18n ( "Archive URL or local path" )
text: settingsPage . runnerInstallUrl
placeholderText: i18n ( "https://... or /home/.../file.tar.gz" )
enabled: ! App . runnerManager . busy
onTextChanged: settingsPage . runnerInstallUrl = text
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextFieldDelegate {
label: i18n ( "SHA256/SHA512 (optional)" )
text: settingsPage . runnerInstallSha256
placeholderText: i18n ( "Leave empty to skip verification" )
enabled: ! App . runnerManager . busy
onTextChanged: settingsPage . runnerInstallSha256 = text
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextFieldDelegate {
label: i18n ( "Name (optional)" )
text: settingsPage . runnerInstallName
placeholderText: i18n ( "Proton-GE" )
enabled: ! App . runnerManager . busy
onTextChanged: settingsPage . runnerInstallName = text
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Install from URL" )
description: App . runnerManager . busy ? App.runnerManager.status : i18n ( "Download and install runner" )
icon.name: "download"
enabled: settingsPage . runnerInstallUrl . trim ( ) !== "" && ! App . runnerManager . busy
onClicked: App . runnerManager . installRunnerFromUrl ( settingsPage . runnerInstallUrl , settingsPage . runnerInstallSha256 , settingsPage . runnerInstallName )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Cancel install" )
icon.name: "dialog-cancel"
enabled: App . runnerManager . busy
onClicked: App . runnerManager . cancelCurrentInstall ( )
}
FormCard . FormDelegateSeparator { }
Item {
Layout.fillWidth: true
Layout.margins: Kirigami . Units . largeSpacing
ColumnLayout {
anchors.left: parent . left
anchors.right: parent . right
spacing: Kirigami . Units . smallSpacing
QQC2 . Label {
Layout.fillWidth: true
text: App . runnerManager . status
wrapMode: Text . WordWrap
visible: App . runnerManager . status !== ""
}
QQC2 . Label {
Layout.fillWidth: true
text: App . runnerManager . lastError
wrapMode: Text . WordWrap
visible: App . runnerManager . lastError !== ""
color: Kirigami . Theme . negativeTextColor
}
QQC2 . ProgressBar {
Layout.fillWidth: true
from: 0
to: App . runnerManager . totalBytes > 0 ? App.runnerManager.totalBytes : 1
value: App . runnerManager . receivedBytes
indeterminate: App . runnerManager . busy && App . runnerManager . totalBytes <= 0
visible: App . runnerManager . busy
}
}
}
}
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Installed Runners" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormButtonDelegate {
text: i18n ( "Refresh" )
icon.name: "view-refresh"
enabled: ! App . runnerManager . busy
onClicked: App . runnerManager . refreshRunners ( )
}
FormCard . FormDelegateSeparator { }
Repeater {
model: App . runnerManager . runners
ColumnLayout {
Layout.fillWidth: true
readonly property var runner: modelData
readonly property string runnerId: ( runner && runner . id ) ? runner.id : ""
readonly property string runnerName: ( runner && runner . name ) ? runner.name : runnerId
readonly property string runnerType: ( runner && runner . type ) ? runner.type : ""
readonly property string runnerSource: ( runner && runner . source ) ? runner.source : ""
readonly property string runnerPath: ( runner && runner . path ) ? runner.path : ""
FormCard . FormTextDelegate {
text: runnerName
description: ( runnerType !== "" ? ( runnerType + " · " ) : "" ) + ( runnerSource !== "" ? runnerSource : "" )
textItem.wrapMode: Text . WordWrap
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextDelegate {
text: runnerPath
textItem.wrapMode: Text . WrapAnywhere
textItem.font: Kirigami . Theme . smallFont
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Uninstall" )
icon.name: "edit-delete"
enabled: runnerSource === "installed" && runnerId !== "" && ! App . runnerManager . busy
onClicked: App . runnerManager . uninstallRunner ( runnerId )
}
FormCard . FormDelegateSeparator { }
}
}
FormCard . FormTextDelegate {
visible: App . runnerManager . runners . length === 0
text: i18n ( "No runners found" )
textItem.font: Kirigami . Theme . smallFont
}
}
2026-02-12 13:48:39 +00:00
FormCard . FormHeader {
Layout.topMargin: Kirigami . Units . mediumSpacing
Layout.fillWidth: true
title: i18n ( "Compatibility" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormComboBoxDelegate {
text: i18n ( "Game" )
model: compatibilityGameChoicesModel
textRole: "label"
currentIndex: settingsPage . compatibilityIndexForGameId ( settingsPage . compatibilityGameId )
onActivated: {
if ( currentIndex < 0 || currentIndex >= compatibilityGameChoicesModel . count ) return
settingsPage . compatibilityGameId = String ( compatibilityGameChoicesModel . get ( currentIndex ) . gameId || "" )
}
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Reload profile" )
icon.name: "view-refresh"
enabled: settingsPage . compatibilityHasGame
onClicked: settingsPage . compatibilityRequestProfile ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextDelegate {
visible: settingsPage . compatibilityLastError !== ""
text: settingsPage . compatibilityLastError
textItem.wrapMode: Text . WordWrap
textItem.color: Kirigami . Theme . negativeTextColor
textItem.font: Kirigami . Theme . smallFont
}
FormCard . FormDelegateSeparator { }
FormCard . FormComboBoxDelegate {
text: i18n ( "Runner" )
model: compatibilityRunnerChoicesModel
textRole: "label"
currentIndex: settingsPage . compatibilityRunnerIndex
enabled: settingsPage . compatibilityHasGame
onActivated: {
settingsPage . compatibilityRunnerIndex = currentIndex
settingsPage . compatibilityBumpSpecRevision ( )
}
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextFieldDelegate {
label: i18n ( "Runner Path" )
text: settingsPage . compatibilityRunnerPath
placeholderText: settingsPage . compatibilityCurrentRunnerChoiceKind ( ) === "proton" ? i18n ( "Auto-detect Proton" ) : ( settingsPage . compatibilityCurrentRunnerChoiceKind ( ) === "wine" ? i18n ( "Use system Wine" ) : i18n ( "Required for Custom" ) )
enabled: settingsPage . compatibilityHasGame && ( settingsPage . compatibilityCurrentRunnerChoiceKind ( ) === "custom" || settingsPage . compatibilityCurrentRunnerChoiceKind ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceKind ( ) === "proton" )
onTextChanged: {
settingsPage . compatibilityRunnerPath = text
settingsPage . compatibilityBumpSpecRevision ( )
}
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextFieldDelegate {
label: i18n ( "Prefix Path" )
text: settingsPage . compatibilityPrefixPath
placeholderText: i18n ( "Default (AppDataLocation/prefixes/<gameId>)" )
enabled: settingsPage . compatibilityHasGame && ( settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton" )
onTextChanged: {
settingsPage . compatibilityPrefixPath = text
settingsPage . compatibilityBumpSpecRevision ( )
}
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Create Prefix" )
icon.name: "folder-new"
enabled: settingsPage . compatibilityHasGame
&& ( settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton" )
onClicked: App . runnerManager . ensurePrefix ( settingsPage . compatibilityGameId , settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) , settingsPage . compatibilityPrefixPath . trim ( ) )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Delete Prefix" )
icon.name: "edit-delete"
enabled: settingsPage . compatibilityHasGame
&& ( settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton" )
&& ( settingsPage . compatibilityPrefixPath . trim ( ) === "" )
onClicked: App . runnerManager . deletePrefix ( settingsPage . compatibilityGameId , settingsPage . compatibilityPrefixPath . trim ( ) )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Reset Prefix to Default" )
icon.name: "edit-clear"
enabled: settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton"
onClicked: settingsPage . compatibilityPrefixPath = ""
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Save Profile" )
icon.name: "document-save"
enabled: settingsPage . compatibilityHasGame && settingsPage . compatibilitySpecRevision >= 0 && settingsPage . compatibilityCanSaveProfile ( )
onClicked: settingsPage . compatibilitySaveProfile ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Clear Profile" )
icon.name: "edit-delete"
enabled: settingsPage . compatibilityHasGame
onClicked: settingsPage . compatibilityClearProfile ( )
}
}
FormCard . FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami . Units . largeSpacing
FormCard . FormHeader {
title: i18n ( "Environment Variables" )
}
FormCard . FormTextDelegate {
text: i18n ( "These variables are applied only when launching this game from A-La-Karte." )
textItem.wrapMode: Text . WordWrap
textItem.font: Kirigami . Theme . smallFont
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Add Variable" )
icon.name: "list-add"
enabled: settingsPage . compatibilityHasGame
onClicked: {
compatibilityEnvModel . append ( { key: "" , value: "" } )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Preset: MangoHud" )
description: i18n ( "Sets MANGOHUD=1" )
icon.name: "games-hint"
enabled: settingsPage . compatibilityHasGame
onClicked: settingsPage . compatibilitySetEnvVar ( "MANGOHUD" , "1" )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Preset: GameMode" )
description: i18n ( "Sets GAMEMODERUN=1" )
icon.name: "speedometer"
enabled: settingsPage . compatibilityHasGame
onClicked: settingsPage . compatibilitySetEnvVar ( "GAMEMODERUN" , "1" )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Clear Variables" )
icon.name: "edit-clear"
enabled: settingsPage . compatibilityHasGame && compatibilityEnvModel . count > 0
onClicked: {
compatibilityEnvModel . clear ( )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.margins: Kirigami . Units . largeSpacing
spacing: Kirigami . Units . smallSpacing
Repeater {
model: compatibilityEnvModel
RowLayout {
Layout.fillWidth: true
spacing: Kirigami . Units . smallSpacing
QQC2 . TextField {
Layout.fillWidth: true
Layout.preferredWidth: 1
placeholderText: i18n ( "KEY" )
text: key
enabled: settingsPage . compatibilityHasGame
onTextChanged: {
compatibilityEnvModel . setProperty ( index , "key" , text )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
QQC2 . TextField {
Layout.fillWidth: true
Layout.preferredWidth: 2
placeholderText: i18n ( "VALUE" )
text: value
enabled: settingsPage . compatibilityHasGame
onTextChanged: {
compatibilityEnvModel . setProperty ( index , "value" , text )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
QQC2 . ToolButton {
icon.name: "list-remove"
enabled: settingsPage . compatibilityHasGame
onClicked: {
compatibilityEnvModel . remove ( index )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
}
}
}
}
FormCard . FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami . Units . largeSpacing
FormCard . FormHeader {
title: i18n ( "Extra Launch Arguments" )
}
FormCard . FormTextDelegate {
text: i18n ( "These arguments are appended when launching this game from A-La-Karte." )
textItem.wrapMode: Text . WordWrap
textItem.font: Kirigami . Theme . smallFont
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Add Argument" )
icon.name: "list-add"
enabled: settingsPage . compatibilityHasGame
onClicked: {
compatibilityExtraArgsModel . append ( { arg: "" } )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Clear Arguments" )
icon.name: "edit-clear"
enabled: settingsPage . compatibilityHasGame && compatibilityExtraArgsModel . count > 0
onClicked: {
compatibilityExtraArgsModel . clear ( )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.margins: Kirigami . Units . largeSpacing
spacing: Kirigami . Units . smallSpacing
Repeater {
model: compatibilityExtraArgsModel
RowLayout {
Layout.fillWidth: true
spacing: Kirigami . Units . smallSpacing
QQC2 . TextField {
Layout.fillWidth: true
placeholderText: i18n ( "ARG" )
text: arg
enabled: settingsPage . compatibilityHasGame
onTextChanged: {
compatibilityExtraArgsModel . setProperty ( index , "arg" , text )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
QQC2 . ToolButton {
icon.name: "list-remove"
enabled: settingsPage . compatibilityHasGame
onClicked: {
compatibilityExtraArgsModel . remove ( index )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
}
}
}
}
FormCard . FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami . Units . largeSpacing
FormCard . FormHeader {
title: i18n ( "Wine DLL Overrides" )
}
FormCard . FormTextDelegate {
text: i18n ( "These overrides are applied to Wine/Proton launches via WINEDLLOVERRIDES." )
textItem.wrapMode: Text . WordWrap
textItem.font: Kirigami . Theme . smallFont
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Add Override" )
icon.name: "list-add"
enabled: settingsPage . compatibilityHasGame && ( settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton" )
onClicked: {
compatibilityDllOverridesModel . append ( { key: "" , value: "" } )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Clear Overrides" )
icon.name: "edit-clear"
enabled: settingsPage . compatibilityHasGame && compatibilityDllOverridesModel . count > 0
onClicked: {
compatibilityDllOverridesModel . clear ( )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.margins: Kirigami . Units . largeSpacing
spacing: Kirigami . Units . smallSpacing
Repeater {
model: compatibilityDllOverridesModel
RowLayout {
Layout.fillWidth: true
spacing: Kirigami . Units . smallSpacing
QQC2 . TextField {
Layout.fillWidth: true
Layout.preferredWidth: 1
placeholderText: i18n ( "DLL" )
text: key
enabled: settingsPage . compatibilityHasGame && ( settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton" )
onTextChanged: {
compatibilityDllOverridesModel . setProperty ( index , "key" , text )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
QQC2 . TextField {
Layout.fillWidth: true
Layout.preferredWidth: 2
placeholderText: i18n ( "OVERRIDE" )
text: value
enabled: settingsPage . compatibilityHasGame && ( settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton" )
onTextChanged: {
compatibilityDllOverridesModel . setProperty ( index , "value" , text )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
QQC2 . ToolButton {
icon.name: "list-remove"
enabled: settingsPage . compatibilityHasGame && ( settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "wine" || settingsPage . compatibilityCurrentRunnerChoiceRunnerType ( ) === "proton" )
onClicked: {
compatibilityDllOverridesModel . remove ( index )
settingsPage . compatibilityBumpSpecRevision ( )
}
}
}
}
}
}
2026-01-18 12:13:07 +00:00
FormCard . FormHeader {
2026-01-24 19:50:20 +00:00
Layout.topMargin: Kirigami . Units . mediumSpacing
2026-01-18 12:13:07 +00:00
Layout.fillWidth: true
title: i18n ( "Cover Art" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormCheckDelegate {
text: i18n ( "High quality images" )
description: i18n ( "Sharper covers at the cost of memory" )
checked: App . config . highQualityImages
onToggled: App . config . highQualityImages = checked
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Animated covers" )
description: i18n ( "Play animated cover art when available" )
checked: App . config . animatedCovers
onToggled: App . config . animatedCovers = checked
}
}
FormCard . FormHeader {
2026-01-24 19:50:20 +00:00
Layout.topMargin: Kirigami . Units . mediumSpacing
2026-01-18 12:13:07 +00:00
Layout.fillWidth: true
title: i18n ( "SteamGridDB" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormCheckDelegate {
text: i18n ( "Enable SteamGridDB" )
description: i18n ( "Automatically download cover art" )
checked: App . steamGridDB . enabled
onToggled: App . steamGridDB . enabled = checked
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextFieldDelegate {
label: i18n ( "SteamGridDB API Key" )
text: App . steamGridDB . apiKey
placeholderText: i18n ( "Enter your API key" )
echoMode: TextInput . Password
enabled: App . steamGridDB . enabled
onTextChanged: App . steamGridDB . apiKey = text
}
FormCard . FormDelegateSeparator { }
FormCard . FormCheckDelegate {
text: i18n ( "Prefer SteamGridDB covers" )
description: i18n ( "Replace existing covers with SteamGridDB art" )
checked: App . steamGridDB . preferSteamGridDB
enabled: App . steamGridDB . enabled
onToggled: App . steamGridDB . preferSteamGridDB = checked
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Fetch All Covers" )
description: App . steamGridDB . busy ? i18n ( "Fetching..." ) : i18n ( "Download covers for all games" )
icon.name: "download"
enabled: App . steamGridDB . enabled && App . steamGridDB . apiKey . length > 0 && ! App . steamGridDB . busy
2026-01-25 09:03:46 +00:00
onClicked: fetchAllCoversConfirmDialog . open ( )
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormTextDelegate {
text: i18n ( "Get a free API key at steamgriddb.com" )
textItem.font: Kirigami . Theme . smallFont
textItem.color: Kirigami . Theme . linkColor
TapHandler {
onTapped: Qt . openUrlExternally ( "https://www.steamgriddb.com/profile/preferences/api" )
}
}
}
FormCard . FormHeader {
2026-01-24 19:50:20 +00:00
Layout.topMargin: Kirigami . Units . mediumSpacing
2026-01-18 12:13:07 +00:00
Layout.fillWidth: true
title: i18n ( "Library" )
}
FormCard . FormCard {
Layout.fillWidth: true
FormCard . FormButtonDelegate {
text: i18n ( "Import All Games" )
description: i18n ( "Scan all enabled sources" )
icon.name: "document-import"
2026-01-24 12:27:30 +00:00
enabled: ! App . importing && App . gameModel . count >= 0 && ( ( App . config . importSteam && ! App . gameModel . hasPlatformPrefix ( "Steam" ) )
|| ( App . config . importLutris && ! App . gameModel . hasPlatformPrefix ( "Lutris" ) )
|| ( App . config . importHeroic && ! App . gameModel . hasPlatformPrefix ( "Heroic" ) )
|| ( App . config . importDesktop && ! App . gameModel . hasPlatformPrefix ( "Desktop" ) )
|| ( App . config . importBottles && ! App . gameModel . hasPlatformPrefix ( "Bottles" ) )
|| ( App . config . importFlatpak && ! App . gameModel . hasPlatformPrefix ( "Flatpak" ) )
|| ( App . config . importItch && ! App . gameModel . hasPlatformPrefix ( "itch.io" ) )
|| ( App . config . importLegendary && ! App . gameModel . hasPlatformPrefix ( "Legendary" ) )
|| ( App . config . importRetroArch && ! App . gameModel . hasPlatformPrefix ( "RetroArch" ) ) )
2026-01-18 12:13:07 +00:00
onClicked: App . importAllGames ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Remove Missing Games" )
description: i18n ( "Remove games whose executables no longer exist" )
icon.name: "edit-delete"
2026-01-25 09:03:46 +00:00
onClicked: removeMissingConfirmDialog . open ( )
2026-01-18 12:13:07 +00:00
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Clear Library" )
description: i18n ( "Remove all games" )
icon.name: "edit-clear-all"
onClicked: clearConfirmDialog . open ( )
}
FormCard . FormDelegateSeparator { }
FormCard . FormButtonDelegate {
text: i18n ( "Reset Application" )
description: i18n ( "Clear library and reset all settings to defaults" )
icon.name: "edit-reset"
onClicked: resetConfirmDialog . open ( )
}
}
2026-01-25 09:03:46 +00:00
Kirigami . PromptDialog {
id: disableImportConfirmDialog
title: i18n ( "Disable Import Source" )
subtitle: i18n ( "Disabling %1 will remove all games imported from that source. Are you sure?" , settingsPage . pendingDisableImportName )
standardButtons: Kirigami . Dialog . Yes | Kirigami . Dialog . No
2026-01-29 18:49:45 +00:00
onOpened: settingsPage . focusNoButton ( disableImportConfirmDialog )
2026-01-25 09:03:46 +00:00
onAccepted: {
if ( settingsPage . pendingDisableImportApply ) {
settingsPage . pendingDisableImportApply ( )
}
settingsPage . pendingDisableImportApply = null
settingsPage . pendingDisableImportName = ""
}
onRejected: {
settingsPage . pendingDisableImportApply = null
settingsPage . pendingDisableImportName = ""
}
}
Kirigami . PromptDialog {
id: fetchAllCoversConfirmDialog
title: i18n ( "Fetch All Covers" )
subtitle: App . steamGridDB . preferSteamGridDB
? i18n ( "This will download cover art for all games and may replace existing covers. Continue?" )
: i18n ( "This will download cover art for games that are missing covers. Continue?" )
standardButtons: Kirigami . Dialog . Yes | Kirigami . Dialog . No
2026-01-29 18:49:45 +00:00
onOpened: settingsPage . focusNoButton ( fetchAllCoversConfirmDialog )
2026-01-25 09:03:46 +00:00
onAccepted: App . steamGridDB . fetchAllCovers ( )
}
Kirigami . PromptDialog {
id: removeMissingConfirmDialog
title: i18n ( "Remove Missing Games" )
subtitle: i18n ( "This will remove games whose executables cannot be found. This cannot be undone. Continue?" )
standardButtons: Kirigami . Dialog . Yes | Kirigami . Dialog . No
2026-01-29 18:49:45 +00:00
onOpened: settingsPage . focusNoButton ( removeMissingConfirmDialog )
2026-01-25 09:03:46 +00:00
onAccepted: App . removeMissingGames ( )
}
2026-01-18 12:13:07 +00:00
Kirigami . PromptDialog {
id: clearConfirmDialog
title: i18n ( "Clear Library" )
subtitle: i18n ( "Are you sure you want to remove all games?" )
standardButtons: Kirigami . Dialog . Yes | Kirigami . Dialog . No
2026-01-29 18:49:45 +00:00
onOpened: settingsPage . focusNoButton ( clearConfirmDialog )
2026-01-18 12:13:07 +00:00
onAccepted: App . clearLibrary ( )
}
Kirigami . PromptDialog {
id: resetConfirmDialog
title: i18n ( "Reset Application" )
subtitle: i18n ( "This will remove all games and reset all settings to defaults. Are you sure?" )
standardButtons: Kirigami . Dialog . Yes | Kirigami . Dialog . No
2026-01-29 18:49:45 +00:00
onOpened: settingsPage . focusNoButton ( resetConfirmDialog )
2026-01-18 12:13:07 +00:00
onAccepted: {
App . clearLibrary ( )
App . config . resetToDefaults ( )
}
}
}