From 74ed018fbdd5ca1f250f27f1069d7c404e51991d Mon Sep 17 00:00:00 2001 From: granny Date: Sun, 14 May 2023 03:50:12 -0700 Subject: [PATCH] re-add GUI patch supercedes #676 --- .../server/0309-Make-GUI-Great-Again.patch | 412 ++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 patches/server/0309-Make-GUI-Great-Again.patch diff --git a/patches/server/0309-Make-GUI-Great-Again.patch b/patches/server/0309-Make-GUI-Great-Again.patch new file mode 100644 index 000000000..1847ecf3e --- /dev/null +++ b/patches/server/0309-Make-GUI-Great-Again.patch @@ -0,0 +1,412 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 16 Jan 2020 14:59:16 -0600 +Subject: [PATCH] Make GUI Great Again + + +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 582467e3419c23446b20d3076fbfce22115250a8..6ecc75621867390738e804e06ac284524664473d 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -99,6 +99,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + return; + } + // Paper start - Use TerminalConsoleAppender ++ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - has no GUI or has console (did not double-click) + new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); + /* + jline.console.ConsoleReader bufferedreader = reader; +diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +index c07918aa1ed2469ad7a76a0add60ab648ff7f421..56cf3d5b8e365ce6b1ec88464d9079d774206755 100644 +--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java +@@ -43,6 +43,11 @@ public class MinecraftServerGui extends JComponent { + private Thread logAppenderThread; + private final Collection finalizers = Lists.newArrayList(); + final AtomicBoolean isClosing = new AtomicBoolean(); ++ // Purpur start ++ private final CommandHistory history = new CommandHistory(); ++ private String currentCommand = ""; ++ private int historyIndex = 0; ++ // Purpur end + + public static MinecraftServerGui showFrameFor(final DedicatedServer server) { + try { +@@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent { + ; + } + +- final JFrame jframe = new JFrame("Minecraft server"); ++ final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur + final MinecraftServerGui servergui = new MinecraftServerGui(server); + + jframe.setDefaultCloseOperation(2); +@@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent { + jframe.pack(); + jframe.setLocationRelativeTo((Component) null); + jframe.setVisible(true); +- jframe.setName("Minecraft server"); // Paper ++ jframe.setName("Purpur Minecraft server"); // Paper // Purpur + + // Paper start - Add logo as frame image + try { +@@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent { + jframe.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent windowevent) { + if (!servergui.isClosing.getAndSet(true)) { +- jframe.setTitle("Minecraft server - shutting down!"); ++ jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur + server.halt(true); + servergui.runFinalizers(); + } +@@ -125,7 +130,7 @@ public class MinecraftServerGui extends JComponent { + + private JComponent buildChatPanel() { + JPanel jpanel = new JPanel(new BorderLayout()); +- JTextArea jtextarea = new JTextArea(); ++ org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur + JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30); + + jtextarea.setEditable(false); +@@ -137,10 +142,43 @@ public class MinecraftServerGui extends JComponent { + + if (!s.isEmpty()) { + this.server.handleConsoleInput(s, this.server.createCommandSourceStack()); ++ // Purpur start ++ history.add(s); ++ historyIndex = -1; ++ // Purpur end + } + + jtextfield.setText(""); + }); ++ // Purpur start ++ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); ++ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); ++ jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() { ++ @Override ++ public void actionPerformed(java.awt.event.ActionEvent actionEvent) { ++ if (historyIndex < 0) { ++ currentCommand = jtextfield.getText(); ++ } ++ if (historyIndex < history.size() - 1) { ++ jtextfield.setText(history.get(historyIndex)); ++ } ++ } ++ }); ++ jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() { ++ @Override ++ public void actionPerformed(java.awt.event.ActionEvent actionEvent) { ++ if (historyIndex >= 0) { ++ if (historyIndex == 0) { ++ --historyIndex; ++ jtextfield.setText(currentCommand); ++ } else { ++ --historyIndex; ++ jtextfield.setText(history.get(historyIndex)); ++ } ++ } ++ } ++ }); ++ // Purpur end + jtextarea.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent focusevent) {} + }); +@@ -176,7 +214,7 @@ public class MinecraftServerGui extends JComponent { + } + + private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]"); // CraftBukkit +- public void print(JTextArea textArea, JScrollPane scrollPane, String message) { ++ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String message) { // Purpur + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(() -> { + this.print(textArea, scrollPane, message); +@@ -190,11 +228,14 @@ public class MinecraftServerGui extends JComponent { + flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum(); + } + ++ /* // Purpur + try { + document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit + } catch (BadLocationException badlocationexception) { + ; + } ++ */ // Purpur ++ textArea.append(message); // Purpur + + if (flag) { + jscrollbar.setValue(Integer.MAX_VALUE); +@@ -202,4 +243,16 @@ public class MinecraftServerGui extends JComponent { + + } + } ++ ++ // Purpur start ++ public static class CommandHistory extends java.util.LinkedList { ++ @Override ++ public boolean add(String command) { ++ if (size() > 1000) { ++ remove(); ++ } ++ return super.offerFirst(command); ++ } ++ } ++ // Purpur end + } +diff --git a/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0f2e7e0b81620c8581949bd5f0bdb567cd93c17e +--- /dev/null ++++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java +@@ -0,0 +1,54 @@ ++package org.purpurmc.purpur.gui; ++ ++import net.md_5.bungee.api.ChatColor; ++ ++import java.awt.Color; ++import java.util.HashMap; ++import java.util.Map; ++ ++public enum GUIColor { ++ BLACK(ChatColor.BLACK, new Color(0x000000)), ++ DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)), ++ DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)), ++ DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)), ++ DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)), ++ DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)), ++ GOLD(ChatColor.GOLD, new Color(0xBB8800)), ++ GRAY(ChatColor.GRAY, new Color(0x888888)), ++ DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)), ++ BLUE(ChatColor.BLUE, new Color(0x5555FF)), ++ GREEN(ChatColor.GREEN, new Color(0x55FF55)), ++ AQUA(ChatColor.AQUA, new Color(0x55DDDD)), ++ RED(ChatColor.RED, new Color(0xFF5555)), ++ LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)), ++ YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)), ++ WHITE(ChatColor.WHITE, new Color(0xBBBBBB)); ++ ++ private final ChatColor chat; ++ private final Color color; ++ ++ private static final Map BY_CHAT = new HashMap<>(); ++ ++ GUIColor(ChatColor chat, Color color) { ++ this.chat = chat; ++ this.color = color; ++ } ++ ++ public Color getColor() { ++ return color; ++ } ++ ++ public String getCode() { ++ return chat.toString(); ++ } ++ ++ public static GUIColor getColor(ChatColor chat) { ++ return BY_CHAT.get(chat); ++ } ++ ++ static { ++ for (GUIColor color : values()) { ++ BY_CHAT.put(color.chat, color); ++ } ++ } ++} +diff --git a/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java +new file mode 100644 +index 0000000000000000000000000000000000000000..33e89b4c00fa8318506b36cbe49fe4e412e0a9a1 +--- /dev/null ++++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java +@@ -0,0 +1,78 @@ ++package org.purpurmc.purpur.gui; ++ ++import com.google.common.collect.Sets; ++import net.md_5.bungee.api.ChatColor; ++import net.md_5.bungee.api.chat.BaseComponent; ++import net.md_5.bungee.api.chat.TextComponent; ++ ++import javax.swing.JTextPane; ++import javax.swing.Timer; ++import javax.swing.text.AttributeSet; ++import javax.swing.text.BadLocationException; ++import javax.swing.text.SimpleAttributeSet; ++import javax.swing.text.StyleConstants; ++import javax.swing.text.StyleContext; ++import java.util.Set; ++ ++public class JColorTextPane extends JTextPane { ++ private static final GUIColor DEFAULT_COLOR = GUIColor.BLACK; ++ ++ public void append(String msg) { ++ // TODO: update to use adventure instead ++ BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, ChatColor.BLACK); ++ for (BaseComponent component : components) { ++ String text = component.toPlainText(); ++ if (text == null || text.isEmpty()) { ++ continue; ++ } ++ ++ GUIColor guiColor = GUIColor.getColor(component.getColor()); ++ ++ StyleContext context = StyleContext.getDefaultStyleContext(); ++ AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor()); ++ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR); ++ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic()); ++ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined()); ++ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough()); ++ //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly ++ ++ try { ++ int pos = getDocument().getLength(); ++ getDocument().insertString(pos, text, attr); ++ ++ if (component.isObfuscated()) { ++ // dirty hack to blink some text ++ Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground())); ++ BLINKS.add(blink); ++ } ++ } catch (BadLocationException ignore) { ++ } ++ } ++ } ++ ++ private static final Set BLINKS = Sets.newHashSet(); ++ private static boolean SYNC_BLINK; ++ ++ static { ++ new Timer(500, e -> { ++ SYNC_BLINK = !SYNC_BLINK; ++ BLINKS.forEach(Blink::blink); ++ }).start(); ++ } ++ ++ public class Blink { ++ private final int start, length; ++ private final AttributeSet attr1, attr2; ++ ++ private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) { ++ this.start = start; ++ this.length = length; ++ this.attr1 = attr1; ++ this.attr2 = attr2; ++ } ++ ++ private void blink() { ++ getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true); ++ } ++ } ++} +diff --git a/src/main/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java b/src/main/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..15a226e3854d731f7724025ea3459c8ace07630c +--- /dev/null ++++ b/src/main/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java +@@ -0,0 +1,85 @@ ++package org.purpurmc.purpur.gui.util; ++ ++import org.apache.logging.log4j.Level; ++import org.apache.logging.log4j.core.LogEvent; ++import org.apache.logging.log4j.core.config.Configuration; ++import org.apache.logging.log4j.core.config.plugins.Plugin; ++import org.apache.logging.log4j.core.layout.PatternLayout; ++import org.apache.logging.log4j.core.pattern.ConverterKeys; ++import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; ++import org.apache.logging.log4j.core.pattern.PatternConverter; ++import org.apache.logging.log4j.core.pattern.PatternFormatter; ++import org.apache.logging.log4j.core.pattern.PatternParser; ++import org.apache.logging.log4j.util.PerformanceSensitive; ++ ++import java.util.List; ++ ++@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY) ++@ConverterKeys({"highlightGUIError"}) ++@PerformanceSensitive("allocation") ++public final class HighlightErrorConverter extends LogEventPatternConverter { ++ private static final String ERROR = "\u00A74\u00A7l"; // Bold Red ++ private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow ++ ++ private final List formatters; ++ ++ private HighlightErrorConverter(List formatters) { ++ super("highlightGUIError", null); ++ this.formatters = formatters; ++ } ++ ++ @Override ++ public void format(LogEvent event, StringBuilder toAppendTo) { ++ Level level = event.getLevel(); ++ if (level.isMoreSpecificThan(Level.ERROR)) { ++ format(ERROR, event, toAppendTo); ++ return; ++ } else if (level.isMoreSpecificThan(Level.WARN)) { ++ format(WARN, event, toAppendTo); ++ return; ++ } ++ for (PatternFormatter formatter : formatters) { ++ formatter.format(event, toAppendTo); ++ } ++ } ++ ++ private void format(String style, LogEvent event, StringBuilder toAppendTo) { ++ int start = toAppendTo.length(); ++ toAppendTo.append(style); ++ int end = toAppendTo.length(); ++ ++ for (PatternFormatter formatter : formatters) { ++ formatter.format(event, toAppendTo); ++ } ++ ++ if (toAppendTo.length() == end) { ++ toAppendTo.setLength(start); ++ } ++ } ++ ++ @Override ++ public boolean handlesThrowable() { ++ for (final PatternFormatter formatter : formatters) { ++ if (formatter.handlesThrowable()) { ++ return true; ++ } ++ } ++ return false; ++ } ++ ++ public static HighlightErrorConverter newInstance(Configuration config, String[] options) { ++ if (options.length != 1) { ++ LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length); ++ return null; ++ } ++ ++ if (options[0] == null) { ++ LOGGER.error("No pattern supplied on highlightGUIError"); ++ return null; ++ } ++ ++ PatternParser parser = PatternLayout.createPatternParser(config); ++ List formatters = parser.parse(options[0]); ++ return new HighlightErrorConverter(formatters); ++ } ++} +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 74ccc67e3c12dc5182602fb691ef3ddeb5b53280..52af11926a1f7973d70a1dae191d2e8138ec5c94 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -2,7 +2,16 @@ + + + +- ++ ++ ++ ++ ++ ++ ++ ++ ++ + + +