mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 19:35:00 -04:00
MacOS: warn if eros enabled or -XstartOnFirstThread is not set, #29
This commit is contained in:
parent
a1035542db
commit
c91f18d73d
@ -24,6 +24,7 @@ import de.bixilon.minosoft.data.registries.ResourceLocation
|
||||
import de.bixilon.minosoft.data.registries.versions.Version
|
||||
import de.bixilon.minosoft.data.registries.versions.Versions
|
||||
import de.bixilon.minosoft.gui.eros.Eros
|
||||
import de.bixilon.minosoft.gui.eros.XStartOnFirstThreadWarning
|
||||
import de.bixilon.minosoft.gui.eros.crash.ErosCrashReport.Companion.crash
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXInitializer
|
||||
import de.bixilon.minosoft.gui.rendering.Rendering
|
||||
@ -69,13 +70,15 @@ object Minosoft {
|
||||
private set
|
||||
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
CommandLineArguments.parse(args)
|
||||
Util.initUtilClasses()
|
||||
|
||||
Log.log(LogMessageType.OTHER, LogLevels.INFO) { "Starting minosoft" }
|
||||
if (OSUtil.OS == OSUtil.OSs.MAC && !RunConfiguration.X_START_ON_FIRST_THREAD_SET && !RunConfiguration.DISABLE_RENDERING) {
|
||||
Log.log(LogMessageType.GENERAL, LogLevels.WARN) { "You are using MacOS, but have not enabled -XstartOnFirstThread. Rendering will not work!" }
|
||||
}
|
||||
GitInfo.load()
|
||||
|
||||
val taskWorker = TaskWorker(criticalErrorHandler = { _, exception -> exception.crash() })
|
||||
@ -124,9 +127,9 @@ object Minosoft {
|
||||
|
||||
taskWorker += Task(identifier = StartupTasks.INITIALIZE_CLI, executor = { CLI.initialize() })
|
||||
|
||||
|
||||
if (!RunConfiguration.DISABLE_EROS) {
|
||||
taskWorker += Task(identifier = StartupTasks.INITIALIZE_JAVAFX, executor = { JavaFXInitializer.start() })
|
||||
taskWorker += Task(identifier = StartupTasks.X_START_ON_FIRST_THREAD_WARNING, executor = { XStartOnFirstThreadWarning.show() }, dependencies = arrayOf(StartupTasks.LOAD_CONFIG, StartupTasks.LOAD_LANGUAGE_FILES, StartupTasks.INITIALIZE_JAVAFX))
|
||||
|
||||
// ToDo: Show start up progress window
|
||||
|
||||
@ -141,6 +144,7 @@ object Minosoft {
|
||||
initialized = true
|
||||
Log.log(LogMessageType.OTHER, LogLevels.INFO) { "All startup tasks executed!" }
|
||||
|
||||
|
||||
GlobalEventMaster.fireEvent(FinishInitializingEvent())
|
||||
|
||||
RunConfiguration.AUTO_CONNECT_TO?.let { autoConnect(it) }
|
||||
|
@ -23,4 +23,5 @@ data class GeneralConfig(
|
||||
@Json(name = "log") var log: MutableMap<LogMessageType, LogLevels> = LogMessageType.DEFAULT_LOG_MAP.toMutableMap(),
|
||||
@Json(name = "reduce_protocol_log") var reduceProtocolLog: Boolean = true,
|
||||
var language: String = "en_US",
|
||||
@Json(name = "ignore_x_start_on_first_thread_warning") var ignoreXStartOnFirstThreadWarning: Boolean = false,
|
||||
)
|
||||
|
@ -23,6 +23,7 @@ object TranslatableComponents {
|
||||
val GENERAL_CANCEL = "minosoft:general.cancel".toResourceLocation()
|
||||
val GENERAL_CONFIRM = "minosoft:general.confirm".toResourceLocation()
|
||||
val GENERAL_DELETE = "minosoft:general.delete".toResourceLocation()
|
||||
val GENERAL_IGNORE = "minosoft:general.ignore".toResourceLocation()
|
||||
|
||||
val EROS_DELETE_SERVER_CONFIRM_DESCRIPTION = { name: ChatComponent, address: String -> Minosoft.LANGUAGE_MANAGER.translate("minosoft:server_info.delete.dialog.description".toResourceLocation(), null, name, address) }
|
||||
val ACCOUNT_CARD_CONNECTION_COUNT = { count: Int -> Minosoft.LANGUAGE_MANAGER.translate("minosoft:main.account.card.connection_count".toResourceLocation(), null, count) }
|
||||
|
@ -26,13 +26,22 @@ object Eros {
|
||||
|
||||
lateinit var mainErosController: MainErosController
|
||||
|
||||
var skipErosStartup = false
|
||||
|
||||
|
||||
init {
|
||||
GlobalEventMaster.registerEvent(CallbackEventInvoker.of<FinishInitializingEvent> {
|
||||
JavaFXUtil.runLater {
|
||||
mainErosController = JavaFXUtil.openModal(TITLE, LAYOUT)
|
||||
mainErosController.stage.show()
|
||||
if (skipErosStartup) {
|
||||
return@of
|
||||
}
|
||||
start()
|
||||
})
|
||||
}
|
||||
|
||||
fun start() {
|
||||
JavaFXUtil.runLater {
|
||||
mainErosController = JavaFXUtil.openModal(TITLE, LAYOUT)
|
||||
mainErosController.stage.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
package de.bixilon.minosoft.gui.eros
|
||||
|
||||
import de.bixilon.minosoft.Minosoft
|
||||
import de.bixilon.minosoft.gui.eros.dialog.SimpleErosWarningDialog
|
||||
import de.bixilon.minosoft.terminal.RunConfiguration
|
||||
import de.bixilon.minosoft.util.KUtil.toResourceLocation
|
||||
import de.bixilon.minosoft.util.OSUtil
|
||||
import javafx.stage.Modality
|
||||
|
||||
object XStartOnFirstThreadWarning {
|
||||
|
||||
private fun showJavaFXRunningWarning() {
|
||||
val dialog = SimpleErosWarningDialog(
|
||||
title = "minosoft:x_start_on_first_thread_warning.eros_running.title".toResourceLocation(),
|
||||
header = "minosoft:x_start_on_first_thread_warning.eros_running.header".toResourceLocation(),
|
||||
description = "minosoft:x_start_on_first_thread_warning.eros_running.description".toResourceLocation(),
|
||||
onIgnore = { Eros.start() },
|
||||
modality = Modality.APPLICATION_MODAL,
|
||||
)
|
||||
dialog.show()
|
||||
Eros.skipErosStartup = true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun show() {
|
||||
if (OSUtil.OS != OSUtil.OSs.MAC || RunConfiguration.DISABLE_RENDERING) { // ToDo
|
||||
return
|
||||
}
|
||||
if (Minosoft.config.config.general.ignoreXStartOnFirstThreadWarning) {
|
||||
return
|
||||
}
|
||||
if (RunConfiguration.X_START_ON_FIRST_THREAD_SET) {
|
||||
return showJavaFXRunningWarning()
|
||||
}
|
||||
|
||||
val dialog = SimpleErosWarningDialog(
|
||||
title = "minosoft:x_start_on_first_thread_warning.title".toResourceLocation(),
|
||||
header = "minosoft:x_start_on_first_thread_warning.header".toResourceLocation(),
|
||||
description = "minosoft:x_start_on_first_thread_warning.description".toResourceLocation(),
|
||||
onIgnore = { Eros.start() },
|
||||
modality = Modality.APPLICATION_MODAL,
|
||||
)
|
||||
dialog.show()
|
||||
Eros.skipErosStartup = true
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
package de.bixilon.minosoft.gui.eros.dialog
|
||||
|
||||
import de.bixilon.minosoft.Minosoft
|
||||
import de.bixilon.minosoft.data.text.TranslatableComponents.GENERAL_IGNORE
|
||||
import de.bixilon.minosoft.gui.eros.controller.DialogController
|
||||
import de.bixilon.minosoft.gui.eros.crash.ErosCrashReport.Companion.crash
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil
|
||||
@ -58,7 +59,7 @@ class ErosErrorReport : DialogController() {
|
||||
headerFX.text = HEADER
|
||||
descriptionFX.text = DESCRIPTION
|
||||
|
||||
ignoreFX.ctext = IGNORE
|
||||
ignoreFX.ctext = GENERAL_IGNORE
|
||||
fatalCrashFX.ctext = FATAL_CRASH
|
||||
}
|
||||
|
||||
@ -68,7 +69,6 @@ class ErosErrorReport : DialogController() {
|
||||
private val TITLE = { exception: Throwable? -> Minosoft.LANGUAGE_MANAGER.translate("minosoft:error.title".toResourceLocation(), null, exception?.let { it::class.java.realName }) }
|
||||
private val HEADER = "minosoft:error.header".toResourceLocation()
|
||||
private val DESCRIPTION = "minosoft:error.description".toResourceLocation()
|
||||
private val IGNORE = "minosoft:error.ignore".toResourceLocation()
|
||||
private val FATAL_CRASH = "minosoft:error.fatal_crash".toResourceLocation()
|
||||
|
||||
fun Throwable?.report() {
|
||||
|
@ -25,6 +25,7 @@ import javafx.scene.control.Button
|
||||
import javafx.scene.input.KeyCode
|
||||
import javafx.scene.input.KeyEvent
|
||||
import javafx.scene.text.TextFlow
|
||||
import javafx.stage.Modality
|
||||
|
||||
class SimpleErosConfirmationDialog(
|
||||
val title: Any = DEFAULT_TITLE_TEXT,
|
||||
@ -34,6 +35,7 @@ class SimpleErosConfirmationDialog(
|
||||
val confirmButtonText: Any = DEFAULT_CONFIRM_TEXT,
|
||||
val onCancel: () -> Unit = {},
|
||||
val onConfirm: () -> Unit,
|
||||
val modality: Modality = Modality.WINDOW_MODAL,
|
||||
) : DialogController() {
|
||||
@FXML private lateinit var headerFX: TextFlow
|
||||
@FXML private lateinit var descriptionFX: TextFlow
|
||||
@ -42,7 +44,7 @@ class SimpleErosConfirmationDialog(
|
||||
|
||||
fun show() {
|
||||
JavaFXUtil.runLater {
|
||||
JavaFXUtil.openModal(title, LAYOUT, this)
|
||||
JavaFXUtil.openModal(title, LAYOUT, this, modality)
|
||||
stage.show()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2021 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.eros.dialog
|
||||
|
||||
import de.bixilon.minosoft.Minosoft
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
import de.bixilon.minosoft.gui.eros.controller.DialogController
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text
|
||||
import de.bixilon.minosoft.util.KUtil.toResourceLocation
|
||||
import de.bixilon.minosoft.util.task.pool.DefaultThreadPool
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.input.KeyCode
|
||||
import javafx.scene.input.KeyEvent
|
||||
import javafx.scene.text.TextFlow
|
||||
import javafx.stage.Modality
|
||||
|
||||
class SimpleErosWarningDialog(
|
||||
val title: Any = DEFAULT_TITLE_TEXT,
|
||||
val header: Any = DEFAULT_TITLE_TEXT,
|
||||
val description: Any? = null,
|
||||
val ignoreButtonText: Any = DEFAULT_IGNORE_TEXT,
|
||||
val onIgnore: () -> Unit = {},
|
||||
val modality: Modality = Modality.WINDOW_MODAL,
|
||||
) : DialogController() {
|
||||
@FXML private lateinit var headerFX: TextFlow
|
||||
@FXML private lateinit var descriptionFX: TextFlow
|
||||
@FXML private lateinit var ignoreButtonFX: Button
|
||||
|
||||
fun show() {
|
||||
JavaFXUtil.runLater {
|
||||
JavaFXUtil.openModal(title, LAYOUT, this, modality)
|
||||
stage.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
headerFX.text = Minosoft.LANGUAGE_MANAGER.translate(header)
|
||||
descriptionFX.text = description?.let { Minosoft.LANGUAGE_MANAGER.translate(it) } ?: ChatComponent.EMPTY
|
||||
ignoreButtonFX.text = Minosoft.LANGUAGE_MANAGER.translate(ignoreButtonText).message
|
||||
}
|
||||
|
||||
override fun postInit() {
|
||||
stage.setOnCloseRequest {
|
||||
DefaultThreadPool += onIgnore
|
||||
}
|
||||
|
||||
stage.scene.root.addEventFilter(KeyEvent.KEY_PRESSED) {
|
||||
if (it.code == KeyCode.ESCAPE) {
|
||||
ignore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
fun ignore() {
|
||||
DefaultThreadPool += onIgnore
|
||||
stage.close()
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private val LAYOUT = "minosoft:eros/dialog/simple_warning.fxml".toResourceLocation()
|
||||
private val DEFAULT_TITLE_TEXT = "minosoft:general.dialog.warning".toResourceLocation()
|
||||
private val DEFAULT_IGNORE_TEXT = "minosoft:general.ignore".toResourceLocation()
|
||||
}
|
||||
}
|
@ -217,7 +217,6 @@ class ParticleRenderer(
|
||||
companion object : RendererBuilder<ParticleRenderer> {
|
||||
override val RESOURCE_LOCATION = ResourceLocation("minosoft:particle")
|
||||
|
||||
|
||||
override fun build(connection: PlayConnection, renderWindow: RenderWindow): ParticleRenderer {
|
||||
return ParticleRenderer(connection, renderWindow)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import de.bixilon.minosoft.config.StaticConfiguration
|
||||
import de.bixilon.minosoft.data.registries.ResourceLocation
|
||||
import de.bixilon.minosoft.util.OSUtil
|
||||
import java.io.File
|
||||
import java.lang.management.ManagementFactory
|
||||
|
||||
object RunConfiguration {
|
||||
var CONFIG_FILENAME = "minosoft.json" // Filename of minosoft's base configuration (located in AppData/Minosoft/config)
|
||||
@ -56,6 +57,8 @@ object RunConfiguration {
|
||||
|
||||
val TEMPORARY_FOLDER = System.getProperty("java.io.tmpdir", "$HOME_DIRECTORY/tmp/") + "/"
|
||||
|
||||
val X_START_ON_FIRST_THREAD_SET = ManagementFactory.getRuntimeMXBean().inputArguments.contains("-XstartOnFirstThread")
|
||||
|
||||
var VERSION_STRING = "Minosoft ${StaticConfiguration.VERSION}"
|
||||
|
||||
var SKIP_RENDERERS: List<ResourceLocation> = listOf()
|
||||
|
@ -22,5 +22,6 @@ enum class StartupTasks {
|
||||
LOAD_MODS,
|
||||
INITIALIZE_CLI,
|
||||
INITIALIZE_JAVAFX,
|
||||
X_START_ON_FIRST_THREAD_WARNING,
|
||||
;
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ import de.bixilon.minosoft.util.KUtil.synchronizedMapOf
|
||||
import de.bixilon.minosoft.util.KUtil.synchronizedSetOf
|
||||
import de.bixilon.minosoft.util.KUtil.toSynchronizedList
|
||||
import de.bixilon.minosoft.util.KUtil.toSynchronizedMap
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
import de.bixilon.minosoft.util.task.pool.DefaultThreadPool
|
||||
import de.bixilon.minosoft.util.task.pool.ThreadPoolRunnable
|
||||
import de.bixilon.minosoft.util.task.worker.tasks.Task
|
||||
@ -32,7 +35,13 @@ class TaskWorker(
|
||||
|
||||
operator fun plusAssign(task: Task) {
|
||||
check(state == TaskWorkerStates.PREPARING) { "Task worker is already working!" }
|
||||
todo[task.identifier] = task
|
||||
if (task.dependencies.contains(task.identifier)) {
|
||||
throw IllegalArgumentException("Task can not depend on itself!")
|
||||
}
|
||||
val previous = todo.put(task.identifier, task)
|
||||
if (previous != null) {
|
||||
Log.log(LogMessageType.OTHER, LogLevels.WARN) { "Task ${task.identifier} replaced existing task!" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml/1" maxHeight="-Infinity" prefHeight="230.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/16"> <!--fx:controller="de.bixilon.minosoft.gui.eros.dialog.SimpleErosWarningDialog" -->
|
||||
<GridPane HBox.hgrow="ALWAYS">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="ALWAYS"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="ALWAYS"/>
|
||||
<RowConstraints vgrow="NEVER"/>
|
||||
</rowConstraints>
|
||||
<GridPane>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="NEVER"/>
|
||||
<ColumnConstraints hgrow="ALWAYS"/>
|
||||
</columnConstraints>
|
||||
<GridPane GridPane.columnIndex="1">
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="NEVER"/>
|
||||
<RowConstraints vgrow="ALWAYS"/>
|
||||
</rowConstraints>
|
||||
<GridPane.margin>
|
||||
<Insets top="50.0"/>
|
||||
</GridPane.margin>
|
||||
<TextFlow fx:id="descriptionFX" GridPane.rowIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets left="5.0" right="5.0"/>
|
||||
</GridPane.margin>
|
||||
<Text text="This is a really important warning, please click on cancel!"/>
|
||||
</TextFlow>
|
||||
<TextFlow fx:id="headerFX" style="-fx-font-size: 30;">
|
||||
<GridPane.margin>
|
||||
<Insets bottom="20.0" left="5.0" right="5.0"/>
|
||||
</GridPane.margin>
|
||||
<opaqueInsets>
|
||||
<Insets/>
|
||||
</opaqueInsets>
|
||||
<Text text="Are you sure?"/>
|
||||
</TextFlow>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="ALWAYS"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
||||
<FontIcon iconColor="#e4e44b" iconLiteral="fas-exclamation-triangle" iconSize="150">
|
||||
<GridPane.margin>
|
||||
<Insets left="5.0" right="10.0"/>
|
||||
</GridPane.margin>
|
||||
</FontIcon>
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="ALWAYS"/>
|
||||
</rowConstraints>
|
||||
</GridPane>
|
||||
<GridPane GridPane.rowIndex="1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="ALWAYS"/>
|
||||
<ColumnConstraints hgrow="NEVER"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="NEVER"/>
|
||||
</rowConstraints>
|
||||
<Button fx:id="ignoreButtonFX" mnemonicParsing="false" onAction="#ignore" text="Ignore" GridPane.columnIndex="1">
|
||||
<GridPane.margin>
|
||||
<Insets left="5.0" right="5.0"/>
|
||||
</GridPane.margin>
|
||||
</Button>
|
||||
<GridPane.margin>
|
||||
<Insets bottom="10.0" right="5.0"/>
|
||||
</GridPane.margin>
|
||||
</GridPane>
|
||||
</GridPane>
|
||||
</HBox>
|
@ -2,6 +2,7 @@ minosoft:general.empty=
|
||||
minosoft:general.cancel=Cancel
|
||||
minosoft:general.confirm=Confirm
|
||||
minosoft:general.delete=Delete
|
||||
minosoft:general.ignore=Ignore
|
||||
|
||||
minosoft:eros_window_title=Minosoft
|
||||
|
||||
@ -32,6 +33,7 @@ minosoft:connection.status.state.error=Error in connection!
|
||||
|
||||
|
||||
minosoft:general.dialog.are_you_sure=Are you sure?
|
||||
minosoft:general.dialog.warning=Warning!
|
||||
|
||||
|
||||
minosoft:update_server.name.label=Server name
|
||||
@ -104,5 +106,13 @@ minosoft:connection.login_kick.description=You got kicked while logging in from
|
||||
minosoft:error.title=%1$s - Minosoft
|
||||
minosoft:error.header=An error occurred!
|
||||
minosoft:error.description=An error in minosoft occurred. You can continue like before, but the behavior might not be the expected one. If this error persists, feel free to open an issue here: https://gitlab.bixilon.de/bixilon/minosoft/-/issues/
|
||||
minosoft:error.ignore=Ignore
|
||||
minosoft:error.fatal_crash=Fatal crash
|
||||
|
||||
|
||||
minosoft:x_start_on_first_thread_warning.title=-XstartOnFirstThread not set
|
||||
minosoft:x_start_on_first_thread_warning.header=-XstartOnFirstThread is not set
|
||||
minosoft:x_start_on_first_thread_warning.description=It looks like you are using MacOS. Due to some design decisions made by apple, you have to set §7-XstartOnFirstThread§r as JVM Argument if you plan to use rendering. If you don't know how or just think this is shit, take a look at https://gitlab.bixilon.de/bixilon/minosoft/-/issues/29
|
||||
|
||||
minosoft:x_start_on_first_thread_warning.eros_running.title=Eros is running
|
||||
minosoft:x_start_on_first_thread_warning.eros_running.header=Eros is running
|
||||
minosoft:x_start_on_first_thread_warning.eros_running.description=It looks like eros is running and you plan to use rendering. This is (on MacOS) currently not possible. You have to set §7-XstartOnFirstThread §ras jvm argument and use the §7auto-connect §rcommand line option to connect to a server. Sorry for this. If you don't know how or just think that this is shit. take a look at https://gitlab.bixilon.de/bixilon/minosoft/-/issues/29
|
||||
|
Loading…
x
Reference in New Issue
Block a user