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

feat: Launch arguments #301

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
dc9337e
feat: add component skeleton
Alystrasz Apr 19, 2023
264356e
feat: list launch arguments
Alystrasz Apr 19, 2023
3fba986
feat: list launch args as tags
Alystrasz Apr 20, 2023
8a7a82f
feat: sort arguments by name
Alystrasz Apr 20, 2023
4b047e6
feat: arguments' description is displayed in tooltips
Alystrasz Apr 20, 2023
1a0e7fb
feat: translate tags container to align tags with other interface com…
Alystrasz Apr 20, 2023
7340a90
feat: tags are unclickable if no game path is selected
Alystrasz Apr 20, 2023
dfed835
feat: store arguments activation state in component state
Alystrasz Apr 20, 2023
bb38eaf
feat: backend get_launch_arguments method reads arguments from file
Alystrasz Apr 22, 2023
d2c96a9
feat: highlight arguments present in args file
Alystrasz Apr 22, 2023
735df33
Merge branch 'R2NorthstarTools:main' into feat/launch-arguments
Alystrasz Apr 30, 2023
4cfdbfb
feat: add set_launch_arguments Rust method to update launch args file
Alystrasz Apr 30, 2023
1ac3360
feat: update launch args file on argument (de)selection
Alystrasz Apr 30, 2023
73ab8a9
feat: display custom arguments
Alystrasz May 2, 2023
da58e2e
fix: do not display tooltip for custom arguments
Alystrasz May 2, 2023
e4a81cb
feat: add button to add new tags
Alystrasz May 2, 2023
f817509
feat: save arguments when inputting a new one
Alystrasz May 2, 2023
bc236d0
feat: LaunchArgument.i18nEntry is an empty string by default
Alystrasz May 2, 2023
c5d4350
feat: prevent adding the same argument multiple times
Alystrasz May 2, 2023
103c759
style: format launch_arguments.rs file
Alystrasz May 2, 2023
25d32c0
fix: don't throw if ns_startup_args.txt file does not exist
Alystrasz May 3, 2023
147ecd5
docs: add documentation to Rust methods
Alystrasz May 3, 2023
81cbd6d
fix: do not list official arguments twice if they're in local argumen…
Alystrasz May 3, 2023
b72a996
feat: filter out argument duplicates
Alystrasz May 5, 2023
0afed65
feat: localize user interface
Alystrasz May 5, 2023
a17a3ec
fix: display an error if arguments saving fails
Alystrasz May 5, 2023
51ee713
feat: add a selector component to input language launch argument
Alystrasz May 5, 2023
e33ad0a
feat: display language selector only if there's no language argument
Alystrasz May 5, 2023
fe647e7
refactor: display custom arguments with simple tags
Alystrasz May 8, 2023
33bfb85
feat: custom arguments are now closable
Alystrasz May 8, 2023
32c1118
feat: use same style for all argument HTML elements
Alystrasz May 8, 2023
b254058
refactor: adjust positioning of argument inputs
Alystrasz May 8, 2023
d9bf227
refactor: translate argument input placeholder
Alystrasz May 8, 2023
52dfaa5
fix: reset language input model on language selection
Alystrasz May 8, 2023
15bdcde
refactor: remove map operator from set_launch_arguments method
Alystrasz May 11, 2023
d1ecd49
Merge branch 'main' into feat/launch-arguments
Alystrasz May 11, 2023
bd36927
refactor: use `if let` syntax to catch error in set_launch_arguments …
Alystrasz May 11, 2023
936f70c
style: fix fmt warnings
Alystrasz May 11, 2023
a266c3e
style: fix clippy warnings
Alystrasz May 11, 2023
fd89d60
feat: remove tag transitions
Alystrasz May 11, 2023
75ac5f3
Merge branch 'main' into feat/launch-arguments
Alystrasz Jul 7, 2023
62493a6
fix: remove old function import
Alystrasz Jul 7, 2023
ee303a1
fix: create language argument with whitespace between argument key an…
Alystrasz Jul 8, 2023
7383bd5
feat: look for language argument value
Alystrasz Jul 9, 2023
65488cd
feat: associate language key and value
Alystrasz Jul 9, 2023
54f68bc
fix: do not display language selector if a language argument is alrea…
Alystrasz Jul 26, 2023
b4cf4b7
Merge branch 'main' into feat/launch-arguments
Alystrasz Jan 9, 2024
0c6369e
feat: store launch arguments in persistent store
Alystrasz Jan 9, 2024
7ef6c11
refactor: remove unused rust package
Alystrasz Jan 11, 2024
d50c4e1
Merge branch 'R2NorthstarTools:main' into feat/launch-arguments
Alystrasz Jan 16, 2024
073ddeb
fix: save arguments in store on argument change
Alystrasz Jan 16, 2024
abc1d8e
Merge branch 'R2NorthstarTools:main' into feat/launch-arguments
Alystrasz Jan 19, 2024
6797de5
Merge branch 'R2NorthstarTools:main' into feat/launch-arguments
Alystrasz Jan 28, 2024
16107b6
refactor: move store key to main package
Alystrasz Jan 28, 2024
ce438fe
fix: container grayscale filter
Alystrasz Jan 28, 2024
0bccb19
feat: pass launch arguments to backend
Alystrasz Jan 28, 2024
ab6de22
fix: language argument format
Alystrasz Jan 28, 2024
6525b7d
fix: adjust language selector width
Alystrasz Jan 28, 2024
ebe67f1
feat: pass arguments when launching game
Alystrasz Jan 28, 2024
5e76d47
style: formatting
Alystrasz Jan 28, 2024
2a4fc2f
fix: treat arguments as references
Alystrasz Jan 28, 2024
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: 3 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ fn main() {
find_game_install_location_caller,
get_flightcore_version_number,
get_northstar_version_number_caller,
northstar::launch_arguments::get_launch_arguments,
northstar::launch_arguments::set_launch_arguments,
check_is_northstar_outdated,
verify_install_location,
get_host_os_caller,
Expand Down Expand Up @@ -145,7 +147,7 @@ fn main() {
apply_launcher_pr,
apply_mods_pr,
get_launcher_download_link,
close_application,
close_application
GeckoEidechse marked this conversation as resolved.
Show resolved Hide resolved
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
40 changes: 40 additions & 0 deletions src-tauri/src/northstar/launch_arguments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#[tauri::command]
/// This method loads arguments from the ns_startup_args.txt Northstar launch
/// arguments files, filtering out eventual argument duplicates.
/// If this file does not exist, this will return an empty array.
pub fn get_launch_arguments(game_path: &str) -> Result<Vec<String>, ()> {
let launch_args_path = format!("{}/ns_startup_args.txt", game_path);
if !std::path::Path::new(&launch_args_path).exists() {
return Ok(vec![]);
}

let data = match std::fs::read_to_string(launch_args_path.clone()) {
Ok(content) => content,
Err(_) => {
return Ok(vec![]);
}
};

let mut arguments = data.split_whitespace()
.map(|arg| arg.to_string())
.collect::<Vec<_>>();
arguments.sort_unstable();
arguments.dedup();
Ok(arguments)
}

#[tauri::command]
/// This method puts an array of arguments into the ns_startup_args.txt Northstar
/// launch arguments files.
/// If the ns_startup_args.txt file does not exist, this will create it.
pub fn set_launch_arguments(game_path: &str, arguments: Vec<String>) -> Result<(), String> {
let launch_args_path = format!("{}/ns_startup_args.txt", game_path);

match std::fs::write(launch_args_path.clone(), arguments.join(" ")) {
Alystrasz marked this conversation as resolved.
Show resolved Hide resolved
Ok(..) => (),
Err(_) => return Err("Failed to save launch arguments.".to_string()),
}

log::info!("Launch arguments updated.");
Ok(())
}
1 change: 1 addition & 0 deletions src-tauri/src/northstar/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use crate::{check_mod_version_number, constants::CORE_MODS};
use anyhow::anyhow;
pub mod launch_arguments;

/// Returns the current Northstar version number as a string
pub fn get_northstar_version_number(game_path: &str) -> Result<String, anyhow::Error> {
Expand Down
238 changes: 238 additions & 0 deletions src-vue/src/components/LaunchArgumentsSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<template>
<div>
<div :class="containerClasses">
<div class="fc-launch_arg_tag_container"
v-for="(argument, index) in arguments">
<!-- Official arguments -->
<el-tooltip
v-if="argument.i18nEntry.length !== 0"
class="box-item"
:content="$t(argument.i18nEntry)"
placement="bottom"
>
<el-check-tag
:checked="values[index]"
@change="onChange(index)"
>
{{ argument.argumentName }}
</el-check-tag>
</el-tooltip>

<!-- Custom arguments -->
<el-tag
v-else
closable
@close="onClose(index, argument.argumentName)"
>
{{ argument.argumentName }}
</el-tag>
</div>

<!-- User-input tag -->
<div class="fc-launch_arg_tag_container">
<el-input
v-if="inputVisible"
ref="InputRef"
class="fc-tag__input"
v-model="inputValue"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
<el-button v-else class="button-new-tag ml-1 fc-tag__input" @click="showInput">
{{ $t('settings.launch_args.new_arg_btn') }}
</el-button>
</div>
</div>


<!-- Language selector -->
<div :class="containerClasses">
<el-select v-if="displayLanguageSelector"
v-model="langArgumentValue"
class="m-2 fc-launch_arg_tag_container fc-tag__input"
:placeholder="$t('settings.launch_args.select_game_language')"
@change="onLanguageSelection"
>
<el-option
v-for="item in langArgumentOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { LaunchArgument } from '../utils/LaunchArgument';
import { NorthstarState } from '../utils/NorthstarState';
import {invoke} from "@tauri-apps/api";
import { showErrorNotification } from '../utils/ui';

export default defineComponent({
name: 'LaunchArgumentsSelector',
computed: {
arguments(): LaunchArgument[] {
return (this.localCustomArgs.concat(this.officialArguments))
.sort((a, b) => a.argumentName.localeCompare(b.argumentName));
},
displayLanguageSelector(): boolean {
const langArgPrefix = '-language="';
return this.arguments
.map(arg => arg.argumentName)
.filter(name => name.substring(0, langArgPrefix.length) === langArgPrefix)
.length === 0;
},
officialArguments(): LaunchArgument[] {
return [
new LaunchArgument("-disablelogs", "settings.launch_args.descriptions.disablelogs"),
new LaunchArgument("-vanilla", "settings.launch_args.descriptions.vanilla"),
new LaunchArgument("-northstar", "settings.launch_args.descriptions.northstar"),
new LaunchArgument("-dedicated", "settings.launch_args.descriptions.dedicated"),
new LaunchArgument("-waitfordebugger", "settings.launch_args.descriptions.waitfordebugger"),
new LaunchArgument("-enablechathooks", "settings.launch_args.descriptions.enablechathooks"),
new LaunchArgument("-noplugins", "settings.launch_args.descriptions.noplugins"),
new LaunchArgument("-novid", "settings.launch_args.descriptions.novid"),
new LaunchArgument("-nosound", "settings.launch_args.descriptions.nosound")
];
},
containerClasses(): string {
return this.gamePathIsSelected ? 'fc-tags_container' : 'fc-tags_container disabled_container';
},
gamePathIsSelected(): boolean {
return this.$store.state.northstar_state !== NorthstarState.GAME_NOT_FOUND;
},
langArgumentOptions() {
const languages = ['english', 'french', 'german', 'italian', 'japanese', 'mspanish', 'portuguese', 'russian', 'spanish', 'tchinese'];
return languages.map(lang => ({value: lang, label: lang}));
}
},
data: () => ({
inputValue: '',
inputVisible: false,

langArgumentValue: '',
values: [] as boolean[],
localCustomArgs: [] as LaunchArgument[]
}),
methods: {
onLanguageSelection( lang: string ) {
this.createNewArgument( `-language="${lang}"` );
this.langArgumentValue = '';
},
createNewArgument(arg: string) {
let allArgumentsNames: string[] = this.arguments.map(arg => arg.argumentName);
if (allArgumentsNames.indexOf(arg) !== -1) {
console.warn(`Argument "${arg}" already present, ignoring.`);
} else {
const newArgument: LaunchArgument = new LaunchArgument(arg);
this.localCustomArgs.push( newArgument );
allArgumentsNames = this.arguments.map(arg => arg.argumentName);

const index: number = allArgumentsNames.indexOf(newArgument.argumentName);
this.values.splice(index, 0, true);
this.saveLaunchArgumentsToFile();
}
},
onChange(index: number) {
this.values[index] = !this.values[index];
this.saveLaunchArgumentsToFile();
},
onClose(index: number, argumentName: string) {
// remove item state value
this.values.splice(index, 1);

// remove item from list of custom arguments
const localIndex = this.localCustomArgs.map(arg => arg.argumentName).indexOf(argumentName);
if (localIndex === -1) {
console.error(`Failed removing argument "${argumentName}".`);
return;
}
this.localCustomArgs.splice(localIndex, 1);

this.saveLaunchArgumentsToFile();
},
saveLaunchArgumentsToFile() {
const newArgs = this.arguments
.filter((value: LaunchArgument, index: number) => {
return this.values[index];
})
.map((value: LaunchArgument) => value.argumentName)

invoke<string[]>("set_launch_arguments", {
gamePath: this.$store.state.game_path, arguments: newArgs
}).catch((err: any) => {
showErrorNotification(err);
});
},
showInput() {
this.inputVisible = true;
this.$nextTick(() => {
// @ts-ignore
this.$refs.InputRef.input.focus();
});
},
handleInputConfirm() {
if (this.inputValue.length !== 0) {
this.createNewArgument(this.inputValue);
}
this.inputVisible = false;
this.inputValue = '';
},
},
async mounted() {
this.values = this.arguments.map(a => false);

// Only add to local arguments those who are not in official arguments array
const fileArgs = await invoke<string[]>("get_launch_arguments", { gamePath: this.$store.state.game_path});
this.localCustomArgs = fileArgs
.filter(arg => this.officialArguments.map(oArg => oArg.argumentName).indexOf(arg) === -1)
.map(arg => new LaunchArgument(arg));

this.arguments.forEach((argument, index) => {
if (fileArgs.includes(argument.argumentName)) {
this.values[index] = true;
}
});
}
});
</script>

<style scoped>

.fc-launch_arg_tag_container {
display: inline-block;
margin: 0 8px 8px 8px;
}

.fc-tags_container {
transform: translateX(-8px);
}

.el-tag {
background-color: var(--el-color-primary-light-8);
color: var(--el-color-primary);
font-size: var(--el-font-size-base);
line-height: var(--el-font-size-base);
padding: 14px 5px 12px 15px;
transition: var(--el-transition-all);
font-weight: 700;
}

.el-check-tag {
padding: 9px 15px 5px 15px;
}

.disabled_container {
pointer-events: none;
filter: grayscale();
}

.fc-tag__input {
width: auto;
height: 28px;
--el-component-size: 28px;
}
</style>
17 changes: 17 additions & 0 deletions src-vue/src/i18n/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,23 @@
"reinstall_text": "Please wait",
"reinstall_success": "Successfully reinstalled Northstar"
}
},

"launch_args": {
"title": "Launch arguments",
"new_arg_btn": "+ New launch argument",
"select_game_language": "Select game language",
"descriptions": {
"disablelogs": "Disable logging and creation of log files",
"vanilla": "Disables Northstar loading",
"northstar": "Enables Northstar loading",
"dedicated": "Starts a dedicated server without video output",
"waitfordebugger": "Waits for debugger to connect before launching game",
"enablechathooks": "Enables the use of chathooks for use by mods",
"noplugins": "Disables the plugin system",
"novid": "Disables startup videos",
"nosound": "Disables all game sounds"
}
}
},

Expand Down
16 changes: 16 additions & 0 deletions src-vue/src/i18n/lang/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,22 @@
"reinstall_text": "Veuillez patienter",
"reinstall_success": "Northstar réinstallé avec succès"
}
},

"launch_args": {
"title": "Paramètres de démarrage",
"new_arg_btn": "+ Nouveau paramètre",
"descriptions": {
"disablelogs": "Désactive la journalisation et la création de journaux",
"vanilla": "Désactive le chargement de Northstar",
"northstar": "Active le chargement de Northstar",
"dedicated": "Démarre un serveur dédié sans sortie graphique",
"waitfordebugger": "Attend la connexion au débuggueur avant de lancer le jeu",
"enablechathooks": "Active les chathooks pour les mods",
"noplugins": "Désactive le système de plugins",
"novid": "Désactive les vidéos au démarrage",
"nosound": "Désactive tous les sons du jeu"
}
}
},

Expand Down
9 changes: 9 additions & 0 deletions src-vue/src/utils/LaunchArgument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class LaunchArgument {
public argumentName: string;
public i18nEntry: string;

constructor(argumentName: string, i18nEntry: string = '') {
this.argumentName = argumentName;
this.i18nEntry = i18nEntry;
}
}
10 changes: 9 additions & 1 deletion src-vue/src/views/SettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
</el-input>
</div>

<!-- Launch arguments selection -->
<div class="fc_parameter__panel">
<h3>{{ $t('settings.launch_args.title') }}</h3>
<launch-arguments-selector/>
</div>

<!-- Thunderstore mods per page configuration -->
<div class="fc_parameter__panel">
<h3>{{ $t('settings.nb_ts_mods_per_page') }}</h3>
Expand Down Expand Up @@ -77,12 +83,14 @@ import { ReleaseCanal } from "../utils/ReleaseCanal";
import { Store } from 'tauri-plugin-store-api';
import { showErrorNotification, showNotification } from "../utils/ui";
import LanguageSelector from "../components/LanguageSelector.vue";
import LaunchArgumentsSelector from "../components/LaunchArgumentsSelector.vue";
const persistentStore = new Store('flight-core-settings.json');

export default defineComponent({
name: "SettingsView",
components: {
LanguageSelector
LanguageSelector,
LaunchArgumentsSelector
},
data() {
return {
Expand Down