mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-08 19:35:36 -04:00
Log window category and show lines
This commit is contained in:
parent
be202a6e1a
commit
a67bfabea4
@ -31,12 +31,13 @@ import org.jackhuang.hmcl.task.*
|
||||
import org.jackhuang.hmcl.ui.*
|
||||
import org.jackhuang.hmcl.util.JavaProcess
|
||||
import org.jackhuang.hmcl.util.Log4jLevel
|
||||
import java.util.concurrent.ConcurrentSkipListSet
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
|
||||
object LauncherHelper {
|
||||
private val launchingStepsPane = LaunchingStepsPane()
|
||||
val PROCESS = ConcurrentSkipListSet<JavaProcess>()
|
||||
val PROCESS = ConcurrentLinkedQueue<JavaProcess>()
|
||||
|
||||
fun launch() {
|
||||
val profile = Settings.selectedProfile
|
||||
@ -76,6 +77,7 @@ object LauncherHelper {
|
||||
})
|
||||
.then { it.get<DefaultLauncher>("launcher").launchAsync() }
|
||||
.then(task {
|
||||
PROCESS.add(it[DefaultLauncher.LAUNCH_ASYNC_ID])
|
||||
if (setting.launcherVisibility == LauncherVisibility.CLOSE)
|
||||
Main.stop()
|
||||
})
|
||||
@ -120,6 +122,7 @@ object LauncherHelper {
|
||||
private lateinit var process: JavaProcess
|
||||
private var lwjgl = false
|
||||
private var logWindow: LogWindow? = null
|
||||
private val logs = LinkedList<Pair<String, Log4jLevel>>()
|
||||
override fun setProcess(process: JavaProcess) {
|
||||
this.process = process
|
||||
|
||||
@ -129,12 +132,21 @@ object LauncherHelper {
|
||||
}
|
||||
|
||||
override fun onLog(log: String, level: Log4jLevel) {
|
||||
var newLog = log
|
||||
for ((original, replacement) in forbiddenTokens)
|
||||
newLog = newLog.replace(original, replacement)
|
||||
|
||||
if (level.lessOrEqual(Log4jLevel.ERROR))
|
||||
System.err.print(log)
|
||||
else
|
||||
System.out.print(log)
|
||||
|
||||
runOnUiThread { logWindow?.logLine(log, level) }
|
||||
runOnUiThread {
|
||||
logs += log to level
|
||||
if (logs.size > Settings.logLines)
|
||||
logs.removeFirst()
|
||||
logWindow?.logLine(log, level)
|
||||
}
|
||||
|
||||
if (!lwjgl && log.contains("LWJGL Version: ")) {
|
||||
lwjgl = true
|
||||
@ -162,6 +174,16 @@ object LauncherHelper {
|
||||
}
|
||||
|
||||
override fun onExit(exitCode: Int, exitType: ProcessListener.ExitType) {
|
||||
if (exitType != ProcessListener.ExitType.NORMAL && logWindow == null){
|
||||
runOnUiThread {
|
||||
LogWindow().apply {
|
||||
for ((line, level) in logs)
|
||||
logLine(line, level)
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkExit(launcherVisibility)
|
||||
}
|
||||
|
||||
@ -181,8 +203,11 @@ object LauncherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun stopManagedProcess() {
|
||||
PROCESS.forEach(JavaProcess::stop)
|
||||
fun stopManagedProcesses() {
|
||||
synchronized(PROCESS) {
|
||||
while (PROCESS.isNotEmpty())
|
||||
PROCESS.poll()?.stop()
|
||||
}
|
||||
}
|
||||
|
||||
enum class LoadingState {
|
||||
|
@ -200,6 +200,12 @@ object Settings {
|
||||
SETTINGS.fontSize = value.size
|
||||
}
|
||||
|
||||
var logLines: Int
|
||||
get() = maxOf(SETTINGS.logLines, 100)
|
||||
set(value) {
|
||||
SETTINGS.logLines = value
|
||||
}
|
||||
|
||||
var downloadProvider: DownloadProvider
|
||||
get() = when (SETTINGS.downloadtype) {
|
||||
0 -> MojangDownloadProvider
|
||||
|
@ -17,49 +17,133 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox
|
||||
import javafx.beans.Observable
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.beans.property.SimpleIntegerProperty
|
||||
import javafx.concurrent.Worker
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.control.ToggleButton
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.StackPane
|
||||
import javafx.scene.web.WebEngine
|
||||
import javafx.scene.web.WebView
|
||||
import javafx.stage.Stage
|
||||
import org.jackhuang.hmcl.game.LauncherHelper
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.util.Log4jLevel
|
||||
import org.jackhuang.hmcl.util.inc
|
||||
import org.jackhuang.hmcl.util.readFullyAsString
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
class LogWindow : Stage() {
|
||||
val contentPane = WebView()
|
||||
val rootPane = StackPane().apply {
|
||||
children.setAll(contentPane)
|
||||
}
|
||||
val engine: WebEngine
|
||||
lateinit var body: Node
|
||||
lateinit var document: Document
|
||||
val fatalProperty = SimpleIntegerProperty(0)
|
||||
val errorProperty = SimpleIntegerProperty(0)
|
||||
val warnProperty = SimpleIntegerProperty(0)
|
||||
val infoProperty = SimpleIntegerProperty(0)
|
||||
val debugProperty = SimpleIntegerProperty(0)
|
||||
|
||||
val impl = LogWindowImpl()
|
||||
|
||||
init {
|
||||
scene = Scene(rootPane, 800.0, 480.0)
|
||||
scene = Scene(impl, 800.0, 480.0)
|
||||
scene.stylesheets.addAll(*stylesheets)
|
||||
title = i18n("logwindow.title")
|
||||
|
||||
contentPane.onScroll
|
||||
engine = contentPane.engine
|
||||
engine.loadContent(javaClass.getResourceAsStream("/assets/log-window-content.html").readFullyAsString().replace("\${FONT}", "${Settings.font.size}px \"${Settings.font.family}\""))
|
||||
engine.loadWorker.stateProperty().addListener { _, _, newValue ->
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
document = engine.document
|
||||
body = document.getElementsByTagName("body").item(0)
|
||||
}
|
||||
}
|
||||
|
||||
icons += Image("/assets/img/icon.png")
|
||||
}
|
||||
|
||||
fun logLine(line: String, level: Log4jLevel) {
|
||||
body.appendChild(contentPane.engine.document.createElement("div").apply {
|
||||
setAttribute("style", "background-color: #${level.color.toString().substring(2)};")
|
||||
impl.body.appendChild(impl.engine.document.createElement("div").apply {
|
||||
textContent = line
|
||||
})
|
||||
engine.executeScript("scrollToBottom()")
|
||||
impl.engine.executeScript("checkNewLog(\"${level.name.toLowerCase()}\");scrollToBottom();")
|
||||
|
||||
when (level) {
|
||||
Log4jLevel.FATAL -> fatalProperty.inc()
|
||||
Log4jLevel.ERROR -> errorProperty.inc()
|
||||
Log4jLevel.WARN -> warnProperty.inc()
|
||||
Log4jLevel.INFO -> infoProperty.inc()
|
||||
Log4jLevel.DEBUG -> debugProperty.inc()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
inner class LogWindowImpl: StackPane() {
|
||||
@FXML lateinit var webView: WebView
|
||||
@FXML lateinit var btnFatals: ToggleButton
|
||||
@FXML lateinit var btnErrors: ToggleButton
|
||||
@FXML lateinit var btnWarns: ToggleButton
|
||||
@FXML lateinit var btnInfos: ToggleButton
|
||||
@FXML lateinit var btnDebugs: ToggleButton
|
||||
|
||||
@FXML lateinit var cboLines: JFXComboBox<String>
|
||||
val engine: WebEngine
|
||||
lateinit var body: Node
|
||||
lateinit var document: Document
|
||||
|
||||
init {
|
||||
loadFXML("/assets/fxml/log.fxml")
|
||||
|
||||
engine = webView.engine
|
||||
engine.loadContent(javaClass.getResourceAsStream("/assets/log-window-content.html").readFullyAsString().replace("\${FONT}", "${Settings.font.size}px \"${Settings.font.family}\""))
|
||||
engine.loadWorker.stateProperty().addListener { _, _, newValue ->
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
document = engine.document
|
||||
body = document.getElementsByTagName("body").item(0)
|
||||
engine.executeScript("limitedLogs=${Settings.logLines};")
|
||||
}
|
||||
}
|
||||
|
||||
var flag = false
|
||||
for (i in cboLines.items) {
|
||||
if (i == Settings.logLines.toString()) {
|
||||
cboLines.selectionModel.select(i)
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
cboLines.selectionModel.selectedItemProperty().addListener { _, _, newValue ->
|
||||
Settings.logLines = newValue.toInt()
|
||||
engine.executeScript("limitedLogs=${Settings.logLines};")
|
||||
}
|
||||
if (!flag) {
|
||||
cboLines.selectionModel.select(0)
|
||||
}
|
||||
|
||||
btnFatals.textProperty().bind(Bindings.createStringBinding(Callable { fatalProperty.get().toString() + " fatals" }, fatalProperty))
|
||||
btnErrors.textProperty().bind(Bindings.createStringBinding(Callable { errorProperty.get().toString() + " errors" }, errorProperty))
|
||||
btnWarns.textProperty().bind(Bindings.createStringBinding(Callable { warnProperty.get().toString() + " warns" }, warnProperty))
|
||||
btnInfos.textProperty().bind(Bindings.createStringBinding(Callable { infoProperty.get().toString() + " infos" }, infoProperty))
|
||||
btnDebugs.textProperty().bind(Bindings.createStringBinding(Callable { debugProperty.get().toString() + " debugs" }, debugProperty))
|
||||
|
||||
btnFatals.selectedProperty().addListener(this::specificChanged)
|
||||
btnErrors.selectedProperty().addListener(this::specificChanged)
|
||||
btnWarns.selectedProperty().addListener(this::specificChanged)
|
||||
btnInfos.selectedProperty().addListener(this::specificChanged)
|
||||
btnDebugs.selectedProperty().addListener(this::specificChanged)
|
||||
}
|
||||
|
||||
private fun specificChanged(observable: Observable) {
|
||||
var res = ""
|
||||
if (btnFatals.isSelected) res += "\"fatal\", "
|
||||
if (btnErrors.isSelected) res += "\"error\", "
|
||||
if (btnWarns.isSelected) res += "\"warn\", "
|
||||
if (btnInfos.isSelected) res += "\"info\", "
|
||||
if (btnDebugs.isSelected) res += "\"debug\", "
|
||||
if (res.isNotBlank())
|
||||
res = res.substringBeforeLast(", ")
|
||||
engine.executeScript("specific([$res])")
|
||||
}
|
||||
|
||||
fun onTerminateGame() {
|
||||
LauncherHelper.stopManagedProcesses()
|
||||
}
|
||||
|
||||
fun onClear() {
|
||||
engine.executeScript("clear()")
|
||||
}
|
||||
}
|
||||
}
|
@ -794,6 +794,20 @@
|
||||
-fx-background-color: null;
|
||||
}
|
||||
|
||||
.log-toggle:selected {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border: 1px;
|
||||
-fx-border-color: black;
|
||||
-fx-text-fill: black;
|
||||
}
|
||||
|
||||
.log-toggle {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border: 1px;
|
||||
-fx-border-color: gray;
|
||||
-fx-text-fill: gray;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Spinner *
|
||||
|
61
HMCL/src/main/resources/assets/fxml/log.fxml
Normal file
61
HMCL/src/main/resources/assets/fxml/log.fxml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.scene.web.WebView?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import com.jfoenix.controls.JFXComboBox?>
|
||||
<?import javafx.collections.FXCollections?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.control.ToggleButton?>
|
||||
<?import javafx.scene.control.ToggleGroup?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane"
|
||||
style="-fx-background-color: white; -fx-padding: 3 0 3 0;">
|
||||
<VBox spacing="3">
|
||||
<BorderPane style="-fx-padding: 0 3 0 3;">
|
||||
<left>
|
||||
<HBox alignment="CENTER_LEFT" style="-fx-padding: 0 0 0 4;" spacing="3">
|
||||
<Label text="%logwindow.show_lines" />
|
||||
<JFXComboBox fx:id="cboLines">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<String fx:value="500" />
|
||||
<String fx:value="2000" />
|
||||
<String fx:value="5000" />
|
||||
</FXCollections>
|
||||
</items>
|
||||
</JFXComboBox>
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox spacing="3">
|
||||
<ToggleButton styleClass="log-toggle" style="-fx-background-color: #F7A699;" fx:id="btnFatals" text="0 fatals" selected="true">
|
||||
<toggleGroup><ToggleGroup /></toggleGroup>
|
||||
</ToggleButton>
|
||||
<ToggleButton styleClass="log-toggle" style="-fx-background-color: #FFCCBB;" fx:id="btnErrors" text="0 errors" selected="true">
|
||||
<toggleGroup><ToggleGroup /></toggleGroup>
|
||||
</ToggleButton>
|
||||
<ToggleButton styleClass="log-toggle" style="-fx-background-color: #FFEECC;" fx:id="btnWarns" text="0 warns" selected="true">
|
||||
<toggleGroup><ToggleGroup /></toggleGroup>
|
||||
</ToggleButton>
|
||||
<ToggleButton styleClass="log-toggle" style="-fx-background-color: #FBFBFB;" fx:id="btnInfos" text="0 infos" selected="true">
|
||||
<toggleGroup><ToggleGroup /></toggleGroup>
|
||||
</ToggleButton>
|
||||
<ToggleButton styleClass="log-toggle" style="-fx-background-color: #EEE9E0;" fx:id="btnDebugs" text="0 debugs" selected="true">
|
||||
<toggleGroup><ToggleGroup /></toggleGroup>
|
||||
</ToggleButton>
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
<StackPane style="-fx-border: 1 0 1 0; -fx-border-color: #dddddd;" VBox.vgrow="ALWAYS">
|
||||
<WebView fx:id="webView" />
|
||||
</StackPane>
|
||||
<HBox alignment="CENTER_RIGHT" style="-fx-padding: 0 3 0 3;" spacing="3">
|
||||
<JFXButton onMouseClicked="#onTerminateGame" text="%logwindow.terminate_game" />
|
||||
<JFXButton onMouseClicked="#onClear" text="%ui.button.clear" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
</fx:root>
|
@ -8,6 +8,7 @@
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
div {
|
||||
font: ${FONT};
|
||||
margin: 0px;
|
||||
@ -16,10 +17,55 @@
|
||||
border-width: 0 0 1px 0;
|
||||
border-color: #dddddd;
|
||||
}
|
||||
|
||||
.fatal { background-color: #F7A699; }
|
||||
.error { background-color: #FFCCBB; }
|
||||
.warn { background-color: #FFEECC; }
|
||||
.info { background-color: #FFFFFF; }
|
||||
.debug { background-color: #EEE9E0; }
|
||||
.trace { background-color: blue; }
|
||||
</style>
|
||||
<script>
|
||||
var colors = ["fatal", "error", "warn", "info", "debug", "trace"]
|
||||
var limitedLogs = 100;
|
||||
function appendLog(log, level) {
|
||||
var e = document.createElement("div");
|
||||
e.textContent = log;
|
||||
document.body.appendChild(e);
|
||||
checkNewLog(level);
|
||||
}
|
||||
function checkNewLog(level) {
|
||||
var e = document.body.lastElementChild;
|
||||
e.className = level;
|
||||
redisplay(e);
|
||||
while (document.body.children.length > limitedLogs)
|
||||
removeFirst()
|
||||
}
|
||||
function scrollToBottom() {
|
||||
window.scrollTo(0, document.body.scrollHeight)
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
function removeFirst() {
|
||||
document.body.removeChild(document.body.firstElementChild);
|
||||
}
|
||||
function clear() {
|
||||
while (document.body.children.length > 0) removeFirst();
|
||||
}
|
||||
function specific(newColors) {
|
||||
colors = newColors;
|
||||
var c = document.body.children;
|
||||
for (var i = 0; i < c.length; ++i)
|
||||
redisplay(c[i]);
|
||||
}
|
||||
function redisplay(div) {
|
||||
var flag = false
|
||||
for (var j = 0; j < colors.length; ++j) {
|
||||
if (div.className == colors[j]) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
div.hidden = div.className != colors[j];
|
||||
}
|
||||
div.hidden = !flag;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
@ -286,11 +286,11 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
|
||||
private fun startMonitors(javaProcess: JavaProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
|
||||
processListener.setProcess(javaProcess)
|
||||
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); javaProcess.stdOutLines += line }.apply { start() }
|
||||
val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); javaProcess.lines += line }.apply { start() }
|
||||
javaProcess.relatedThreads += logHandler
|
||||
val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.inputStream, { logHandler.newLine(it) } )::run)
|
||||
javaProcess.relatedThreads += stdout
|
||||
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); javaProcess.stdErrLines += it })::run)
|
||||
val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); javaProcess.lines += it })::run)
|
||||
javaProcess.relatedThreads += stderr
|
||||
javaProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run)
|
||||
}
|
||||
|
@ -15,13 +15,15 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
package org.jackhuang.hmcl.launch
|
||||
|
||||
import org.jackhuang.hmcl.event.EVENT_BUS
|
||||
import org.jackhuang.hmcl.event.JVMLaunchFailedEvent
|
||||
import org.jackhuang.hmcl.event.JavaProcessExitedAbnormallyEvent
|
||||
import org.jackhuang.hmcl.event.JavaProcessStoppedEvent
|
||||
import org.jackhuang.hmcl.launch.ProcessListener
|
||||
import org.jackhuang.hmcl.util.JavaProcess
|
||||
import org.jackhuang.hmcl.util.containsOne
|
||||
import org.jackhuang.hmcl.util.guessLogLineError
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -36,10 +38,7 @@ internal class ExitWaiter(val process: JavaProcess, val joins: Collection<Thread
|
||||
joins.forEach { it.join() }
|
||||
|
||||
val exitCode = process.exitCode
|
||||
val lines = LinkedList<String>()
|
||||
lines.addAll(process.stdErrLines)
|
||||
lines.addAll(process.stdOutLines)
|
||||
val errorLines = lines.filter(::guessLogLineError)
|
||||
val errorLines = process.lines.filter(::guessLogLineError)
|
||||
val exitType: ProcessListener.ExitType
|
||||
// LaunchWrapper will catch the exception logged and will exit normally.
|
||||
if (exitCode != 0 || errorLines.containsOne("Unable to launch")) {
|
@ -24,10 +24,12 @@ import org.xml.sax.Attributes
|
||||
import org.xml.sax.InputSource
|
||||
import org.xml.sax.helpers.DefaultHandler
|
||||
import org.xml.sax.helpers.XMLReaderFactory
|
||||
import java.io.InterruptedIOException
|
||||
import java.io.PipedInputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* This class is to parse log4j classic XML layout logging, since only vanilla Minecraft will enable this layout.
|
||||
@ -38,18 +40,28 @@ internal class Log4jHandler(val callback: (String, Log4jLevel) -> Unit) : Thread
|
||||
}
|
||||
private val outputStream = PipedOutputStream()
|
||||
private val inputStream = PipedInputStream(outputStream)
|
||||
private val interrupted = AtomicBoolean(false)
|
||||
|
||||
override fun run() {
|
||||
name = "log4j-handler"
|
||||
newLine("<output>")
|
||||
reader.parse(InputSource(inputStream))
|
||||
try {
|
||||
reader.parse(InputSource(inputStream))
|
||||
} catch (e: InterruptedIOException) {
|
||||
// Game has been interrupted.
|
||||
interrupted.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun onStopped() {
|
||||
if (interrupted.get())
|
||||
return
|
||||
Scheduler.NEW_THREAD.schedule {
|
||||
newLine("</output>")?.get()
|
||||
outputStream.close()
|
||||
join()
|
||||
if (!interrupted.get()) {
|
||||
newLine("</output>")?.get()
|
||||
outputStream.close()
|
||||
join()
|
||||
}
|
||||
}!!.get()
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,9 @@ interface ProcessListener {
|
||||
fun setProcess(process: JavaProcess) {}
|
||||
|
||||
/**
|
||||
* Called when receiving a log from stdout
|
||||
* Called when receiving a log from stdout/stderr.
|
||||
*
|
||||
* Does not guarantee that this method is thread safe.
|
||||
*
|
||||
* @param log the log
|
||||
*/
|
||||
|
@ -15,8 +15,10 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
package org.jackhuang.hmcl.launch
|
||||
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import org.jackhuang.hmcl.util.SYSTEM_CHARSET
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.logging.Level
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
class JavaProcess(
|
||||
@ -24,8 +25,7 @@ class JavaProcess(
|
||||
val commands: List<String>
|
||||
) {
|
||||
val properties = mutableMapOf<String, Any>()
|
||||
val stdOutLines: MutableCollection<String> = ConcurrentLinkedQueue<String>()
|
||||
val stdErrLines: MutableCollection<String> = ConcurrentLinkedQueue<String>()
|
||||
val lines: Queue<String> = ConcurrentLinkedQueue<String>()
|
||||
val relatedThreads = mutableListOf<Thread>()
|
||||
val isRunning: Boolean = try {
|
||||
process.exitValue()
|
||||
|
@ -18,6 +18,8 @@
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import javafx.scene.input.Clipboard
|
||||
import javafx.scene.input.ClipboardContent
|
||||
import java.io.File
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.nio.charset.Charset
|
||||
@ -65,5 +67,12 @@ enum class OS {
|
||||
|
||||
val SYSTEM_VERSION: String by lazy { System.getProperty("os.version") }
|
||||
val SYSTEM_ARCH: String by lazy { System.getProperty("os.arch") }
|
||||
|
||||
fun setClipboard(string: String) {
|
||||
val clipboard = Clipboard.getSystemClipboard()
|
||||
clipboard.setContent(ClipboardContent().apply {
|
||||
putString(string)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user