Add GUI to standalone

This commit is contained in:
rtm516 2020-07-02 20:10:43 -04:00 committed by DoctorMacc
parent 699ae0b88e
commit 61072948b9
10 changed files with 807 additions and 10 deletions

View file

@ -20,7 +20,7 @@
<dependency> <dependency>
<groupId>net.minecrell</groupId> <groupId>net.minecrell</groupId>
<artifactId>terminalconsoleappender</artifactId> <artifactId>terminalconsoleappender</artifactId>
<version>1.1.1</version> <version>1.2.0</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>

View file

@ -25,16 +25,23 @@
package org.geysermc.platform.standalone; package org.geysermc.platform.standalone;
import org.geysermc.connector.common.PlatformType; import lombok.Getter;
import net.minecrell.terminalconsole.TerminalConsoleAppender;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.common.PlatformType;
import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.dump.BootstrapDumpInfo;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.FileUtils;
import org.geysermc.platform.standalone.command.GeyserCommandManager; import org.geysermc.platform.standalone.command.GeyserCommandManager;
import org.geysermc.platform.standalone.gui.GeyserStandaloneGUI;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -49,14 +56,49 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
private GeyserStandaloneLogger geyserLogger; private GeyserStandaloneLogger geyserLogger;
private IGeyserPingPassthrough geyserPingPassthrough; private IGeyserPingPassthrough geyserPingPassthrough;
private GeyserStandaloneGUI gui;
@Getter
private boolean useGui = System.console() == null;
private GeyserConnector connector; private GeyserConnector connector;
public static void main(String[] args) { public static void main(String[] args) {
for (String arg : args) {
// By default, standalone Geyser will check if it should open the GUI based on if the GUI is null
// Optionally, you can force the use of a GUI or no GUI by specifying args
if (arg.equals("gui")) {
new GeyserStandaloneBootstrap().onEnable(true);
return;
} else if (arg.equals("nogui")) {
new GeyserStandaloneBootstrap().onEnable(false);
return;
}
}
new GeyserStandaloneBootstrap().onEnable(); new GeyserStandaloneBootstrap().onEnable();
} }
public void onEnable(boolean useGui) {
this.useGui = useGui;
this.onEnable();
}
@Override @Override
public void onEnable() { public void onEnable() {
Logger logger = (Logger) LogManager.getRootLogger();
for (Appender appender : logger.getAppenders().values()) {
// Remove the appender that is not in use
// Prevents multiple appenders/double logging and removes harmless errors
if ((useGui && appender instanceof TerminalConsoleAppender) || (!useGui && appender instanceof ConsoleAppender)) {
logger.removeAppender(appender);
}
}
if (useGui && gui == null) {
gui = new GeyserStandaloneGUI();
gui.redirectSystemStreams();
gui.startUpdateThread();
}
geyserLogger = new GeyserStandaloneLogger(); geyserLogger = new GeyserStandaloneLogger();
LoopbackUtil.checkLoopback(geyserLogger); LoopbackUtil.checkLoopback(geyserLogger);
@ -73,9 +115,15 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
connector = GeyserConnector.start(PlatformType.STANDALONE, this); connector = GeyserConnector.start(PlatformType.STANDALONE, this);
geyserCommandManager = new GeyserCommandManager(connector); geyserCommandManager = new GeyserCommandManager(connector);
if (gui != null) {
gui.setupInterface(geyserLogger, geyserCommandManager);
}
geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector); geyserPingPassthrough = GeyserLegacyPingPassthrough.init(connector);
geyserLogger.start(); if (!useGui) {
geyserLogger.start(); // Throws an error otherwise
}
} }
@Override @Override

View file

@ -30,6 +30,7 @@ import lombok.extern.log4j.Log4j2;
import net.minecrell.terminalconsole.SimpleTerminalConsole; import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator; import org.apache.logging.log4j.core.config.Configurator;
import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
@ -96,7 +97,15 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements org
@Override @Override
public void setDebug(boolean debug) { public void setDebug(boolean debug) {
Configurator.setLevel(log.getName(), debug ? org.apache.logging.log4j.Level.DEBUG : log.getLevel()); Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO);
}
/**
* Used for setting debug mode in GUI mode
* @return if debug is enabled
*/
public boolean isDebug() {
return log.isDebugEnabled();
} }
@Override @Override

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*
*/
package org.geysermc.platform.standalone.gui;
import lombok.Getter;
import java.awt.*;
import java.util.regex.Pattern;
public enum ANSIColor {
// Normal colors
BLACK("(0;)?30(0;)?m", Color.getHSBColor(0.000f, 0.000f, 0.000f)),
RED("(0;)?31(0;)?m", Color.getHSBColor(0.000f, 1.000f, 0.502f)),
GREEN("(0;)?32(0;)?m", Color.getHSBColor(0.333f, 1.000f, 0.502f)),
YELLOW("(0;)?33(0;)?m", Color.getHSBColor(0.167f, 1.000f, 0.502f)),
BLUE("(0;)?34(0;)?m", Color.getHSBColor(0.667f, 1.000f, 0.502f)),
MAGENTA("(0;)?35(0;)?m", Color.getHSBColor(0.833f, 1.000f, 0.502f)),
CYAN("(0;)?36(0;)?m", Color.getHSBColor(0.500f, 1.000f, 0.502f)),
WHITE("(0;)?37(0;)?m", Color.getHSBColor(0.000f, 0.000f, 0.753f)),
// Bold colors
B_BLACK("(1;30|30;1)m", Color.getHSBColor(0.000f, 0.000f, 0.502f)),
B_RED("(1;31|31;1)m", Color.getHSBColor(0.000f, 1.000f, 1.000f)),
B_GREEN("(1;32|32;1)m", Color.getHSBColor(0.333f, 1.000f, 1.000f)),
B_YELLOW("(1;33|33;1)m", Color.getHSBColor(0.167f, 1.000f, 1.000f)),
B_BLUE("(1;34|34;1)m", Color.getHSBColor(0.667f, 1.000f, 1.000f)),
B_MAGENTA("(1;35|35;1)m", Color.getHSBColor(0.833f, 1.000f, 1.000f)),
B_CYAN("(1;36|36;1)m", Color.getHSBColor(0.500f, 1.000f, 1.000f)),
B_WHITE("(1;37|37;1)m", Color.getHSBColor(0.000f, 0.000f, 1.000f)),
RESET("0m", Color.getHSBColor(0.000f, 0.000f, 1.000f));
private static final ANSIColor[] VALUES = values();
private static final String PREFIX = Pattern.quote("\u001B[");
private final String ANSICode;
@Getter
private final Color color;
ANSIColor(String ANSICode, Color color) {
this.ANSICode = ANSICode;
this.color = color;
}
public static ANSIColor fromANSI(String code) {
for (ANSIColor value : VALUES) {
if (code.matches(PREFIX + value.ANSICode)) {
return value;
}
}
return B_WHITE;
}
}

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*
*/
package org.geysermc.platform.standalone.gui;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
/**
* This class was based on this code: https://stackoverflow.com/a/6899478/5299903
*/
public class ColorPane extends JTextPane {
private static Color colorCurrent = ANSIColor.RESET.getColor();
private String remaining = "";
/**
* Append the given string in the given color to the text pane
* @param c The color
* @param s The text
*/
private void append(Color c, String s) {
StyleContext sc = StyleContext.getDefaultStyleContext();
AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, c);
int len = getDocument().getLength();
try {
getDocument().insertString(len, s, aset);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
/**
* Extract the ANSI color codes from the string and add each part to the text pane
*
* @param s The text to parse
*/
public void appendANSI(String s) { // convert ANSI color codes first
int aPos = 0; // current char position in addString
int aIndex = 0; // index of next Escape sequence
int mIndex = 0; // index of "m" terminating Escape sequence
String tmpString = "";
boolean stillSearching = true; // true until no more Escape sequences
String addString = remaining + s;
remaining = "";
if (addString.length() > 0) {
aIndex = addString.indexOf("\u001B"); // find first escape
if (aIndex == -1) { // no escape/color change in this string, so just send it with current color
append(colorCurrent, addString);
return;
}
// otherwise There is an escape character in the string, so we must process it
if (aIndex > 0) { // Escape is not first char, so send text up to first escape
tmpString = addString.substring(0, aIndex);
append(colorCurrent, tmpString);
aPos = aIndex; // aPos is now at the beginning of the first escape sequence
}
// while there's text in the input buffer
stillSearching = true;
while (stillSearching) {
mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence
if (mIndex < 0) { // the buffer ends halfway through the ansi string!
remaining = addString.substring(aPos, addString.length());
stillSearching = false;
continue;
} else {
tmpString = addString.substring(aPos, mIndex+1);
colorCurrent = ANSIColor.fromANSI(tmpString).getColor();
}
aPos = mIndex + 1;
// now we have the color, send text that is in that color (up to next escape)
aIndex = addString.indexOf("\u001B", aPos);
if (aIndex == -1) { // if that was the last sequence of the input, send remaining text
tmpString = addString.substring(aPos, addString.length());
append(colorCurrent, tmpString);
stillSearching = false;
continue; // jump out of loop early, as the whole string has been sent now
}
// there is another escape sequence, so send part of the string and prepare for the next
tmpString = addString.substring(aPos, aIndex);
aPos = aIndex;
append(colorCurrent, tmpString);
}
}
}
}

View file

@ -0,0 +1,336 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*
*/
package org.geysermc.platform.standalone.gui;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.GeyserCommand;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.platform.standalone.GeyserStandaloneLogger;
import org.geysermc.platform.standalone.command.GeyserCommandManager;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.Document;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class GeyserStandaloneGUI {
private static final String[] playerTableHeadings = new String[] {"IP", "Username"};
private static final List<Integer> ramValues = new ArrayList<>();
private static final ColorPane consolePane = new ColorPane();
private static final GraphPanel ramGraph = new GraphPanel();
private static final JTable playerTable = new JTable(new String[][] { }, playerTableHeadings);
private static final int originalFontSize = consolePane.getFont().getSize();
private static final long MEGABYTE = 1024L * 1024L;
private final JMenu commandsMenu;
private final JMenu optionsMenu;
public GeyserStandaloneGUI() {
// Create the frame and setup basic settings
JFrame frame = new JFrame("Geyser Standalone");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setSize(800, 400);
frame.setMinimumSize(frame.getSize());
// Remove Java UI look
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ignored) { }
// Show a confirm dialog on close
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent we)
{
String[] buttons = {"Yes", "No"};
int result = JOptionPane.showOptionDialog(frame, "Are you sure you want to exit?", frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]);
if (result == JOptionPane.YES_OPTION) {
System.exit(0);
}
}
});
Container cp = frame.getContentPane();
// Fetch and set the icon for the frame
URL image = getClass().getClassLoader().getResource("icon.png");
if (image != null) {
ImageIcon icon = new ImageIcon(image);
frame.setIconImage(icon.getImage());
}
// Setup the split pane and event listeners
JSplitPane splitPane = new JSplitPane();
splitPane.setDividerLocation(600);
splitPane.addPropertyChangeListener("dividerLocation", e -> splitPaneLimit((JSplitPane)e.getSource()));
splitPane.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
splitPaneLimit((JSplitPane)e.getSource());
}
});
cp.add(splitPane, BorderLayout.CENTER);
// Set the background and disable input for the text pane
consolePane.setBackground(Color.BLACK);
consolePane.setEditable(false);
// Wrap the text pane in a scroll pane and add it to the form
JScrollPane consoleScrollPane = new JScrollPane(consolePane);
//cp.add(consoleScrollPane, BorderLayout.CENTER);
splitPane.setLeftComponent(consoleScrollPane);
// Create a new menu bar for the top of the frame
JMenuBar menuBar = new JMenuBar();
// Create 'File'
JMenu fileMenu = new JMenu("File");
fileMenu.setMnemonic(KeyEvent.VK_F);
menuBar.add(fileMenu);
// 'Open Geyser folder' button
JMenuItem openButton = new JMenuItem("Open Geyser folder", KeyEvent.VK_O);
openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
openButton.addActionListener(e -> {
try {
Desktop.getDesktop().open(new File("./"));
} catch (IOException ignored) { }
});
fileMenu.add(openButton);
fileMenu.addSeparator();
// 'Exit' button
JMenuItem exitButton = new JMenuItem("Exit", KeyEvent.VK_X);
exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_MASK));
exitButton.addActionListener(e -> System.exit(0));
fileMenu.add(exitButton);
// Create 'Commands'
commandsMenu = new JMenu("Commands");
commandsMenu.setMnemonic(KeyEvent.VK_C);
menuBar.add(commandsMenu);
// Create 'View'
JMenu viewMenu = new JMenu("View");
viewMenu.setMnemonic(KeyEvent.VK_V);
menuBar.add(viewMenu);
// 'Zoom in' button
JMenuItem zoomInButton = new JMenuItem("Zoom In");
zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK));
zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1)));
viewMenu.add(zoomInButton);
// 'Zoom in' button
JMenuItem zoomOutButton = new JMenuItem("Zoom Out");
zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1)));
viewMenu.add(zoomOutButton);
// 'Reset Zoom' button
JMenuItem resetZoomButton = new JMenuItem("Reset Zoom");
resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize)));
viewMenu.add(resetZoomButton);
// create 'Options'
optionsMenu = new JMenu("Options");
viewMenu.setMnemonic(KeyEvent.VK_O);
menuBar.add(optionsMenu);
// Set the frames menu bar
frame.setJMenuBar(menuBar);
JPanel rightPane = new JPanel();
rightPane.setLayout(new CardLayout(5, 5));
//cp.add(rightPane, BorderLayout.EAST);
splitPane.setRightComponent(rightPane);
JPanel rightContentPane = new JPanel();
rightContentPane.setLayout(new GridLayout(2, 1, 5, 5));
rightPane.add(rightContentPane);
// Set the ram graph to 0
for (int i = 0; i < 10; i++) {
ramValues.add(0);
}
ramGraph.setValues(ramValues);
ramGraph.setXLabel("Loading...");
rightContentPane.add(ramGraph);
JScrollPane playerScrollPane = new JScrollPane(playerTable);
rightContentPane.add(playerScrollPane);
// This has to be done last
frame.setVisible(true);
}
/**
* Queue up an update to the text pane so we don't block the main thread
*
* @param text The text to append
*/
private void updateTextPane(final String text) {
SwingUtilities.invokeLater(() -> {
consolePane.appendANSI(text);
Document doc = consolePane.getDocument();
consolePane.setCaretPosition(doc.getLength());
});
}
/**
* Redirect the default io streams to the text pane
*/
public void redirectSystemStreams() {
// Setup a new output stream to forward it to the text pane
OutputStream out = new OutputStream() {
@Override
public void write(final int b) {
updateTextPane(String.valueOf((char) b));
}
@Override
public void write(byte[] b, int off, int len) {
updateTextPane(new String(b, off, len));
}
@Override
public void write(byte[] b) {
write(b, 0, b.length);
}
};
// Override the system output streams
System.setOut(new PrintStream(out, true));
System.setErr(new PrintStream(out, true));
}
/**
* Add all the Geyser commands to the commands menu, and setup the debug mode toggle
*
* @param geyserStandaloneLogger The current logger
* @param geyserCommandManager The commands manager
*/
public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) {
commandsMenu.removeAll();
for (Map.Entry<String, GeyserCommand> command : geyserCommandManager.getCommands().entrySet()) {
// Remove the offhand command and any alias commands to prevent duplicates in the list
if ("offhand".equals(command.getValue().getName()) || command.getValue().getAliases().contains(command.getKey())) {
continue;
}
// Create the button that runs the command
JMenuItem commandButton = new JMenuItem(command.getValue().getName());
commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription());
commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ }));
commandsMenu.add(commandButton);
}
// 'Debug Mode' toggle
JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem("Debug Mode");
debugMode.setSelected(geyserStandaloneLogger.isDebug());
debugMode.addActionListener(e -> geyserStandaloneLogger.setDebug(!geyserStandaloneLogger.isDebug()));
optionsMenu.add(debugMode);
}
/**
* Start the thread to update the form information every 1s
*/
public void startUpdateThread() {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
Runnable periodicTask = () -> {
if (GeyserConnector.getInstance() != null) {
// Update player table
String[][] playerNames = new String[GeyserConnector.getInstance().getPlayers().size()][2];
int i = 0;
for (Map.Entry<InetSocketAddress, GeyserSession> player : GeyserConnector.getInstance().getPlayers().entrySet()) {
playerNames[i][0] = player.getKey().getHostName();
playerNames[i][1] = player.getValue().getPlayerEntity().getUsername();
i++;
}
DefaultTableModel model = new DefaultTableModel(playerNames, playerTableHeadings);
playerTable.setModel(model);
model.fireTableDataChanged();
}
// Update ram graph
final long freeMemory = Runtime.getRuntime().freeMemory();
final long totalMemory = Runtime.getRuntime().totalMemory();
final int freePercent = (int)(freeMemory * 100.0 / totalMemory + 0.5);
ramValues.add(100 - freePercent);
ramGraph.setXLabel("Usage: " + String.format("%,d", (totalMemory - freeMemory) / MEGABYTE) + "mb (" + freePercent + "% free)");
// Trim the list
int k = ramValues.size();
if ( k > 10 )
ramValues.subList(0, k - 10).clear();
// Update the graph
ramGraph.setValues(ramValues);
};
// SwingUtilities.invokeLater is called so we don't run into threading issues with the GUI
executor.scheduleAtFixedRate(() -> SwingUtilities.invokeLater(periodicTask), 0, 1, TimeUnit.SECONDS);
}
/**
* Make sure the JSplitPane divider is within a set of bounds
*
* @param splitPane The JSplitPane to check
*/
private void splitPaneLimit(JSplitPane splitPane) {
JRootPane frame = splitPane.getRootPane();
int location = splitPane.getDividerLocation();
if (location < frame.getWidth() - frame.getWidth() * 0.4f) {
splitPane.setDividerLocation(Math.round(frame.getWidth() - frame.getWidth() * 0.4f));
} else if (location > frame.getWidth() - 200) {
splitPane.setDividerLocation(frame.getWidth() - 200);
}
}
}

View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*
*/
package org.geysermc.platform.standalone.gui;
import lombok.Setter;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* This has been modified to fit Geyser more but is based on
* https://gist.github.com/roooodcastro/6325153#gistcomment-3107524
*/
public final class GraphPanel extends JPanel {
private final static int padding = 10;
private final static int labelPadding = 25;
private final static int pointWidth = 4;
private final static int numberYDivisions = 10;
private final static Color lineColor = new Color(44, 102, 230, 180);
private final static Color pointColor = new Color(100, 100, 100, 180);
private final static Color gridColor = new Color(200, 200, 200, 200);
private static final Stroke graphStroke = new BasicStroke(2f);
private List<Integer> values = new ArrayList<>(10);
@Setter
private String xLabel = "";
public GraphPanel() {
setPreferredSize(new Dimension(200 - (padding * 2), 150 - (padding * 2)));
}
public void setValues(Collection<Integer> newValues) {
values.clear();
addValues(newValues);
}
public void addValues(Collection<Integer> newValues) {
values.addAll(newValues);
updateUI();
}
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
if (!(graphics instanceof Graphics2D)) {
graphics.drawString("Graphics is not Graphics2D, unable to render", 0, 0);
return;
}
final Graphics2D g = (Graphics2D) graphics;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
final int length = values.size();
final int width = getWidth();
final int height = getHeight();
final int maxScore = getMaxScore();
final int minScore = getMinScore();
final int scoreRange = maxScore - minScore;
// draw white background
g.setColor(Color.WHITE);
g.fillRect(
padding + labelPadding,
padding,
width - (2 * padding) - labelPadding,
height - 2 * padding - labelPadding);
g.setColor(Color.BLACK);
final FontMetrics fontMetrics = g.getFontMetrics();
final int fontHeight = fontMetrics.getHeight();
// create hatch marks and grid lines for y axis.
for (int i = 0; i < numberYDivisions + 1; i++) {
final int x1 = padding + labelPadding;
final int x2 = pointWidth + padding + labelPadding;
final int y = height - ((i * (height - padding * 2 - labelPadding)) / numberYDivisions + padding + labelPadding);
if (length > 0) {
g.setColor(gridColor);
g.drawLine(padding + labelPadding + 1 + pointWidth, y, width - padding, y);
g.setColor(Color.BLACK);
final int tickValue = (int) (minScore + ((scoreRange * i) / numberYDivisions));
final String yLabel = tickValue + "";
final int labelWidth = fontMetrics.stringWidth(yLabel);
g.drawString(yLabel, x1 - labelWidth - 5, y + (fontHeight / 2) - 3);
}
g.drawLine(x1, y, x2, y);
}
// and for x axis
if (length > 1) {
for (int i = 0; i < length; i++) {
final int x = i * (width - padding * 2 - labelPadding) / (length - 1) + padding + labelPadding;
final int y1 = height - padding - labelPadding;
final int y2 = y1 - pointWidth;
if ((i % ((int) ((length / 20.0)) + 1)) == 0) {
g.setColor(gridColor);
g.drawLine(x, height - padding - labelPadding - 1 - pointWidth, x, padding);
g.setColor(Color.BLACK);
/*g.setColor(Color.BLACK);
final String xLabel = i + "";
final int labelWidth = fontMetrics.stringWidth(xLabel);
g.drawString(xLabel, x - labelWidth / 2, y1 + fontHeight + 3);*/
}
g.drawLine(x, y1, x, y2);
}
}
// create x and y axes
g.drawLine(padding + labelPadding, height - padding - labelPadding, padding + labelPadding, padding);
g.drawLine(padding + labelPadding, height - padding - labelPadding, width - padding, height - padding - labelPadding);
g.setColor(Color.BLACK);
final int labelWidth = fontMetrics.stringWidth(xLabel);
final int labelX = ((padding + labelPadding) + (width - padding)) / 2;
final int labelY = height - padding - labelPadding;
g.drawString(xLabel, labelX - labelWidth / 2, labelY + fontHeight + 3);
final Stroke oldStroke = g.getStroke();
g.setColor(lineColor);
g.setStroke(graphStroke);
final double xScale = ((double) width - (2 * padding) - labelPadding) / (length - 1);
final double yScale = ((double) height - 2 * padding - labelPadding) / scoreRange;
final List<Point> graphPoints = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
final int x1 = (int) (i * xScale + padding + labelPadding);
final int y1 = (int) ((maxScore - values.get(i)) * yScale + padding);
graphPoints.add(new Point(x1, y1));
}
for (int i = 0; i < graphPoints.size() - 1; i++) {
final int x1 = graphPoints.get(i).x;
final int y1 = graphPoints.get(i).y;
final int x2 = graphPoints.get(i + 1).x;
final int y2 = graphPoints.get(i + 1).y;
g.drawLine(x1, y1, x2, y2);
}
boolean drawDots = width > (length * pointWidth);
if (drawDots) {
g.setStroke(oldStroke);
g.setColor(pointColor);
for (Point graphPoint : graphPoints) {
final int x = graphPoint.x - pointWidth / 2;
final int y = graphPoint.y - pointWidth / 2;
g.fillOval(x, y, pointWidth, pointWidth);
}
}
}
private int getMinScore() {
return 0;
}
private int getMaxScore() {
return 100;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View file

@ -1,9 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN"> <Configuration status="WARN">
<Appenders> <Appenders>
<TerminalConsole name="Console"> <TerminalConsole name="TerminalConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/> <PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
</TerminalConsole> </TerminalConsole>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
</Console>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz"> <RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %level{length=1} - %msg%n"/> <PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %level{length=1} - %msg%n"/>
<Policies> <Policies>
@ -14,6 +17,7 @@
</Appenders> </Appenders>
<Loggers> <Loggers>
<Root level="INFO"> <Root level="INFO">
<AppenderRef ref="TerminalConsole"/>
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
<AppenderRef ref="File"/> <AppenderRef ref="File"/>
</Root> </Root>

View file

@ -44,17 +44,17 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.BiomeTranslator; import org.geysermc.connector.network.translators.BiomeTranslator;
import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.EntityIdentifierRegistry;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.effect.EffectRegistry;
import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry; import org.geysermc.connector.network.translators.sound.SoundHandlerRegistry;
import org.geysermc.connector.network.translators.sound.SoundRegistry;
import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.WorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.effect.EffectRegistry;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.DockerCheck;
import org.geysermc.connector.utils.LocaleUtils; import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.network.translators.sound.SoundRegistry;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@ -158,8 +158,23 @@ public class GeyserConnector {
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName)); metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
} }
boolean isGui = false;
// This will check if we are in standalone and get the 'useGui' variable from there
if (platformType == PlatformType.STANDALONE) {
try {
Class<?> cls = Class.forName("org.geysermc.platform.standalone.GeyserStandaloneBootstrap");
isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap));
} catch (Exception e) { e.printStackTrace(); }
}
double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;
logger.info(String.format("Done (%ss)! Run /geyser help for help!", new DecimalFormat("#.###").format(completeTime))); String message = String.format("Done (%ss)!", new DecimalFormat("#.###").format(completeTime));
if (isGui) {
message += " Run Commands -> help for help!";
} else {
message += " Run /geyser help for help!";
}
logger.info(message);
} }
public void shutdown() { public void shutdown() {