From 3ce2da7f7f2353215f34085b54e8d0c0a3088943 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 1 Aug 2020 23:15:10 +0200 Subject: [PATCH] Add tab complete support --- VirtualComputer-Core/pom.xml | 10 + .../java/sznp/virtualcomputer/Commands.java | 74 +++- .../java/sznp/virtualcomputer/Computer.java | 393 +++++++++--------- .../src/main/resources/computer.commodore | 35 ++ pom.xml | 4 + 5 files changed, 318 insertions(+), 198 deletions(-) create mode 100644 VirtualComputer-Core/src/main/resources/computer.commodore diff --git a/VirtualComputer-Core/pom.xml b/VirtualComputer-Core/pom.xml index 335e630..f280abe 100644 --- a/VirtualComputer-Core/pom.xml +++ b/VirtualComputer-Core/pom.xml @@ -59,6 +59,16 @@ VirtualComputer-MSCOM 2.1-SNAPSHOT + + + com.github.PandacubeFr + commodore + patch-custom-suggests-SNAPSHOT + diff --git a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Commands.java b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Commands.java index 4df9fc5..af414ae 100644 --- a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Commands.java +++ b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Commands.java @@ -1,13 +1,31 @@ package sznp.virtualcomputer; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; +import lombok.val; +import me.lucko.commodore.Commodore; +import me.lucko.commodore.CommodoreProvider; +import me.lucko.commodore.file.CommodoreFileFormat; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; +import org.virtualbox_6_1.MouseButtonState; import org.virtualbox_6_1.VBoxException; +import sznp.virtualcomputer.util.Scancode; -public class Commands implements CommandExecutor { +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class Commands implements CommandExecutor, TabCompleter { @Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { @@ -25,6 +43,9 @@ public class Commands implements CommandExecutor { } Computer.getInstance().Start(sender, c); break; + case "list": + Computer.getInstance().List(sender); + break; case "stop": case "poweroff": case "off": @@ -82,11 +103,9 @@ public class Commands implements CommandExecutor { showusage = false; } } - } - catch (VBoxException e) { + } catch (VBoxException e) { e.printStackTrace(); - } - catch (Exception ignored) { //It will show the usage here + } catch (Exception ignored) { //It will show the usage here } } if (showusage) { @@ -131,7 +150,7 @@ public class Commands implements CommandExecutor { } try { MouseLockerPlayerListener.LockedSpeed = Float.parseFloat(args[2]); - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { sender.sendMessage("§cThe speed must be a number."); break; } @@ -143,6 +162,49 @@ public class Commands implements CommandExecutor { return true; } + private boolean tabSetup = true; + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (CommodoreProvider.isSupported() && tabSetup) { + tabSetup = false; + new Object() { + private void setup(Command command) { + val com = CommodoreProvider.getCommodore(PluginMain.Instance); + try { + val node = CommodoreFileFormat.parse(PluginMain.Instance.getResource("computer.commodore")); + CommandNode arg = RequiredArgumentBuilder.argument("index", IntegerArgumentType.integer()).build(); + replaceChildren(node.getChild("start"), arg); + replaceChildren(node.getChild("on"), arg); + arg = RequiredArgumentBuilder.argument("key", StringArgumentType.word()) + .suggests((context, builder) -> { + Arrays.stream(Scancode.values()).map(Scancode::name) + .map(name -> name.replace("sc_", "")).forEach(builder::suggest); + return builder.buildFuture(); + }).build(); + replaceChildren(node.getChild("key"), arg); + replaceChildren(node.getChild("press"), arg); + arg = RequiredArgumentBuilder.argument("button", StringArgumentType.word()) + .suggests((context, builder) -> { + Arrays.stream(MouseButtonState.values()).map(MouseButtonState::name).forEach(builder::suggest); + return builder.buildFuture(); + }).build(); + replaceChildren(node.getChild("mouse"), arg); + com.register(command, node); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void replaceChildren(CommandNode target, CommandNode node) { + target.getChildren().clear(); + target.addChild(node); + } + }.setup(command); + } + return Collections.emptyList(); + } + /** * Checks the 2nd parameter * diff --git a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java index b313721..5728c3d 100644 --- a/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java +++ b/VirtualComputer-Core/src/main/java/sznp/virtualcomputer/Computer.java @@ -2,6 +2,7 @@ package sznp.virtualcomputer; import com.google.common.collect.Lists; import lombok.Getter; +import lombok.val; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -17,220 +18,228 @@ import javax.annotation.Nullable; import java.util.Arrays; public final class Computer { - @Getter - private static Computer instance; + @Getter + private static Computer instance; - private final PluginMain plugin; - private ISession session; - private IVirtualBox vbox; - private IMachine machine; - private MachineEventHandler handler; - private IEventListener listener; - private VirtualBoxManager manager; + private final PluginMain plugin; + private ISession session; + private IVirtualBox vbox; + private IMachine machine; + private MachineEventHandler handler; + private IEventListener listener; + private VirtualBoxManager manager; - @java.beans.ConstructorProperties({"plugin"}) - public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox) { - this.plugin = plugin; - this.manager = manager; - session = manager.getSessionObject(); - this.vbox = vbox; - if (instance != null) throw new IllegalStateException("A computer already exists!"); - instance = this; - } + @java.beans.ConstructorProperties({"plugin"}) + public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox) { + this.plugin = plugin; + this.manager = manager; + session = manager.getSessionObject(); + this.vbox = vbox; + if (instance != null) throw new IllegalStateException("A computer already exists!"); + instance = this; + } - public void Start(CommandSender sender, int index) {// TODO: Add touchscreen support (#2) - if (session.getState() == SessionState.Locked) { - sender.sendMessage("§cThe machine is already running!"); - return; - } - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - if (vbox.getMachines().size() <= index) { - sendMessage(sender, "§cMachine not found!"); - return; - } - try { - sendMessage(sender, "§eStarting computer..."); - machine = vbox.getMachines().get(index); - session.setName("minecraft"); - // machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore - machine.lockMachine(session, LockType.VM); // We want the machine inside *our* process <-- Need the VM type to have console access - VBoxEventHandler.getInstance().setup(machine.getId(), sender); //TODO: Sometimes null - } catch (VBoxException e) { - if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited" - sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible"); - sendMessage(sender, "§6Make sure that the server is running as root (sudo)"); - //TODO: If we have VirtualBox open, it won't close the server's port - //TODO: "The object in question already exists." on second start - //machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); //No privileges, start the 'old' way - //session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new COMFrameBuffer(session.getConsole().getDisplay(), false))); - //sendMessage(sender, "§6Computer started with slower screen. Run as root to use a faster method."); - } else { - sendMessage(sender, "§cFailed to start computer: " + e.getMessage()); - } - } - }); - } + public void Start(CommandSender sender, int index) {// TODO: Add touchscreen support (#2) + if (session.getState() == SessionState.Locked) { + sender.sendMessage("§cThe machine is already running!"); + return; + } + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + if (vbox.getMachines().size() <= index) { + sendMessage(sender, "§cMachine not found!"); + return; + } + try { + sendMessage(sender, "§eStarting computer..."); + machine = vbox.getMachines().get(index); + session.setName("minecraft"); + // machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore + machine.lockMachine(session, LockType.VM); // We want the machine inside *our* process <-- Need the VM type to have console access + VBoxEventHandler.getInstance().setup(machine.getId(), sender); //TODO: Sometimes null + } catch (VBoxException e) { + if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited" + sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible"); + sendMessage(sender, "§6Make sure that the server is running as root (sudo)"); + //TODO: If we have VirtualBox open, it won't close the server's port + //TODO: "The object in question already exists." on second start + //machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); //No privileges, start the 'old' way + //session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new COMFrameBuffer(session.getConsole().getDisplay(), false))); + //sendMessage(sender, "§6Computer started with slower screen. Run as root to use a faster method."); + } else { + sendMessage(sender, "§cFailed to start computer: " + e.getMessage()); + } + } + }); + } - /** - * Gets called when the machine is locked after {@link #Start(CommandSender, int)} - * - * @param sender The sender which started the machine - */ - public void onLock(CommandSender sender) { - machine = session.getMachine(); // This is the Machine object we can work with - final IConsole console = session.getConsole(); - if (handler != null) - handler.disable(); - handler = new MachineEventHandler(Computer.this, sender); - listener = handler.registerTo(console.getEventSource()); - IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2 - handler.setProgress(progress); - handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way? - console.getDisplay().attachFramebuffer(0L, - COMUtils.gimmeAFramebuffer(new MCFrameBuffer(console.getDisplay()))); - } + public void List(CommandSender sender) { + val machines = vbox.getMachines(); + for (int i = 0; i < machines.size(); i++) { + val m = machines.get(i); + sender.sendMessage("[" + i + "] " + m.getName() + " - " + m.getState()); + } + } - private void sendMessage(@Nullable CommandSender sender, String message) { - if (sender != null) - sender.sendMessage(message); - plugin.getLogger().warning((sender == null ? "" : sender.getName() + ": ") + ChatColor.stripColor(message)); - } + /** + * Gets called when the machine is locked after {@link #Start(CommandSender, int)} + * + * @param sender The sender which started the machine + */ + public void onLock(CommandSender sender) { + machine = session.getMachine(); // This is the Machine object we can work with + final IConsole console = session.getConsole(); + if (handler != null) + handler.disable(); + handler = new MachineEventHandler(Computer.this, sender); + listener = handler.registerTo(console.getEventSource()); + IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2 + handler.setProgress(progress); + handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way? + console.getDisplay().attachFramebuffer(0L, + COMUtils.gimmeAFramebuffer(new MCFrameBuffer(console.getDisplay()))); + } - public void Stop(CommandSender sender) { - if (checkMachineNotRunning(sender)) { - if (session.getState().equals(SessionState.Locked)) { - onMachineStop(sender); //Needed for session reset - sendMessage(sender, "§eComputer was already off, released it."); - } - return; - } - sendMessage(sender, "§eStopping computer..."); - session.getConsole().powerDown(); - } + private void sendMessage(@Nullable CommandSender sender, String message) { + if (sender != null) + sender.sendMessage(message); + plugin.getLogger().warning((sender == null ? "" : sender.getName() + ": ") + ChatColor.stripColor(message)); + } - public void PowerButton(CommandSender sender, int index) { - sendMessage(sender, "§ePressing powerbutton..."); - Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { - @Override - public void run() { - if (session.getState() != SessionState.Locked || session.getMachine() == null) { - Start(sender, index); - } else { - session.getConsole().powerButton(); - sendMessage(sender, "§ePowerbutton pressed."); - } - } - }); - } + public void Stop(CommandSender sender) { + if (checkMachineNotRunning(sender)) { + if (session.getState().equals(SessionState.Locked)) { + onMachineStop(sender); //Needed for session reset + sendMessage(sender, "§eComputer was already off, released it."); + } + return; + } + sendMessage(sender, "§eStopping computer..."); + session.getConsole().powerDown(); + } - public void Reset(CommandSender sender) { - if (checkMachineNotRunning(sender)) - return; - sendMessage(sender, "§eResetting computer..."); - session.getConsole().reset(); - sendMessage(sender, "§eComputer reset."); - } + public void PowerButton(CommandSender sender, int index) { + sendMessage(sender, "§ePressing powerbutton..."); + Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { + @Override + public void run() { + if (session.getState() != SessionState.Locked || session.getMachine() == null) { + Start(sender, index); + } else { + session.getConsole().powerButton(); + sendMessage(sender, "§ePowerbutton pressed."); + } + } + }); + } - public void FixScreen(CommandSender sender) { - if (checkMachineNotRunning(sender)) - return; - sendMessage(sender, "§eFixing screen..."); - session.getConsole().getDisplay().setSeamlessMode(false); - session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L, true); - sendMessage(sender, "§eScreen fixed."); - } + public void Reset(CommandSender sender) { + if (checkMachineNotRunning(sender)) + return; + sendMessage(sender, "§eResetting computer..."); + session.getConsole().reset(); + sendMessage(sender, "§eComputer reset."); + } - public boolean checkMachineNotRunning(@Nullable CommandSender sender) { - if (session.getState() != SessionState.Locked || machine.getState() != MachineState.Running) { - if (sender != null) - sender.sendMessage("§cMachine isn't running."); - return true; - } - return false; - } + public void FixScreen(CommandSender sender) { + if (checkMachineNotRunning(sender)) + return; + sendMessage(sender, "§eFixing screen..."); + session.getConsole().getDisplay().setSeamlessMode(false); + session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L, true); + sendMessage(sender, "§eScreen fixed."); + } - public void PressKey(CommandSender sender, String key, String stateorduration) { - if (checkMachineNotRunning(sender)) - return; - int durationorstate; - if (stateorduration.length() == 0) - durationorstate = 0; - else if (stateorduration.equalsIgnoreCase("down")) - durationorstate = -1; - else if (stateorduration.equalsIgnoreCase("up")) - durationorstate = -2; - else - durationorstate = Short.parseShort(stateorduration); - int code = Scancode.getCode("sc_" + key.toLowerCase()); - if (code == -1) { - sender.sendMessage("§cUnknown key: " + key); - return; - } - // Release key scan code concept taken from VirtualBox source code (KeyboardImpl.cpp:putCAD()) - // +128 - if (durationorstate != -2) - session.getConsole().getKeyboard().putScancode(code); - Runnable sendrelease = () -> session.getConsole().getKeyboard().putScancodes(Lists.newArrayList(code + 128, - Scancode.sc_controlLeft.Code + 128, Scancode.sc_shiftLeft.Code + 128, Scancode.sc_altLeft.Code + 128)); - if (durationorstate == 0 || durationorstate == -2) - sendrelease.run(); - if (durationorstate > 0) { - Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, sendrelease, durationorstate); - } - } + public boolean checkMachineNotRunning(@Nullable CommandSender sender) { + if (session.getState() != SessionState.Locked || machine.getState() != MachineState.Running) { + if (sender != null) + sender.sendMessage("§cMachine isn't running."); + return true; + } + return false; + } - public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs, boolean down) throws Exception { - if (checkMachineNotRunning(sender)) - return; - int state = 0; - if (mbs.length() > 0 && down) - state = Arrays.stream(MouseButtonState.values()).filter(mousebs -> mousebs.name().equalsIgnoreCase(mbs)) - .findAny().orElseThrow(() -> new Exception("Unknown mouse button")).value(); - session.getConsole().getMouse().putMouseEvent(x, y, z, w, state); - } + public void PressKey(CommandSender sender, String key, String stateorduration) { + if (checkMachineNotRunning(sender)) + return; + int durationorstate; + if (stateorduration.length() == 0) + durationorstate = 0; + else if (stateorduration.equalsIgnoreCase("down")) + durationorstate = -1; + else if (stateorduration.equalsIgnoreCase("up")) + durationorstate = -2; + else + durationorstate = Short.parseShort(stateorduration); + int code = Scancode.getCode("sc_" + key.toLowerCase()); + if (code == -1) { + sender.sendMessage("§cUnknown key: " + key); + return; + } + // Release key scan code concept taken from VirtualBox source code (KeyboardImpl.cpp:putCAD()) + // +128 + if (durationorstate != -2) + session.getConsole().getKeyboard().putScancode(code); + Runnable sendrelease = () -> session.getConsole().getKeyboard().putScancodes(Lists.newArrayList(code + 128, + Scancode.sc_controlLeft.Code + 128, Scancode.sc_shiftLeft.Code + 128, Scancode.sc_altLeft.Code + 128)); + if (durationorstate == 0 || durationorstate == -2) + sendrelease.run(); + if (durationorstate > 0) { + Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, sendrelease, durationorstate); + } + } - public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs) throws Exception { - if (checkMachineNotRunning(sender)) - return; - UpdateMouse(sender, x, y, z, w, mbs, true); - UpdateMouse(sender, x, y, z, w, mbs, false); - } + public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs, boolean down) throws Exception { + if (checkMachineNotRunning(sender)) + return; + int state = 0; + if (mbs.length() > 0 && down) + state = Arrays.stream(MouseButtonState.values()).filter(mousebs -> mousebs.name().equalsIgnoreCase(mbs)) + .findAny().orElseThrow(() -> new Exception("Unknown mouse button")).value(); + session.getConsole().getMouse().putMouseEvent(x, y, z, w, state); + } + + public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs) throws Exception { + if (checkMachineNotRunning(sender)) + return; + UpdateMouse(sender, x, y, z, w, mbs, true); + UpdateMouse(sender, x, y, z, w, mbs, false); + } public void onMachineStart(CommandSender sender) { sendMessage(sender, "§eComputer started."); } public void onMachineStop(CommandSender sender) { - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - if (session.getState() == SessionState.Locked) { - session.unlockMachine(); //Needs to be outside of the event handler - handler = null; - machine = null; - session = manager.getSessionObject(); - sendMessage(sender, "§eComputer powered off."); //This block runs later - } - }); - GPURenderer.update(new byte[1], 0, 0, 0, 0, 640, 480); //Black screen - stopEvents(); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + if (session.getState() == SessionState.Locked) { + session.unlockMachine(); //Needs to be outside of the event handler + handler = null; + machine = null; + session = manager.getSessionObject(); + sendMessage(sender, "§eComputer powered off."); //This block runs later + } + }); + GPURenderer.update(new byte[1], 0, 0, 0, 0, 640, 480); //Black screen + stopEvents(); MouseLockerPlayerListener.computerStop(); - } + } - public void stopEvents() { + public void stopEvents() { /*if(session!=null && listener!=null) session.getConsole().getEventSource().unregisterListener(listener);*/ - if (listener != null) - handler.disable(); - listener = null; - } + if (listener != null) + handler.disable(); + listener = null; + } - public void pluginDisable(CommandSender ccs) { - stopEvents(); - if (session.getState() == SessionState.Locked) { - if (session.getMachine().getState().equals(MachineState.Running)) { - ccs.sendMessage("§aSaving machine state..."); - session.getMachine().saveState().waitForCompletion(10000); - } - session.unlockMachine(); - } - } + public void pluginDisable(CommandSender ccs) { + stopEvents(); + if (session.getState() == SessionState.Locked) { + if (session.getMachine().getState().equals(MachineState.Running)) { + ccs.sendMessage("§aSaving machine state..."); + session.getMachine().saveState().waitForCompletion(10000); + } + session.unlockMachine(); + } + } } diff --git a/VirtualComputer-Core/src/main/resources/computer.commodore b/VirtualComputer-Core/src/main/resources/computer.commodore new file mode 100644 index 0000000..a7653db --- /dev/null +++ b/VirtualComputer-Core/src/main/resources/computer.commodore @@ -0,0 +1,35 @@ +computer { + start { + index brigadier:integer; + } + on { + index brigadier:integer; + } + list; + stop; + off; + shutdown; + kill; + powerbutton; + reset; + restart; + fix; + fixscreen; + key { + key brigadier:string single_word; + } + press { + key brigadier:string single_word; + } + mouse { + button brigadier:string single_word; + } + input { + key; + mouse; + } + show { + key; + mouse; + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index d80f73b..e61e47b 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + jitpack + https://jitpack.io/ +