Add tab complete support

This commit is contained in:
Norbi Peti 2020-08-01 23:15:10 +02:00
parent de13d9876d
commit 3ce2da7f7f
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 318 additions and 198 deletions

View file

@ -59,6 +59,16 @@
<artifactId>VirtualComputer-MSCOM</artifactId> <artifactId>VirtualComputer-MSCOM</artifactId>
<version>2.1-SNAPSHOT</version> <version>2.1-SNAPSHOT</version>
</dependency> </dependency>
<!-- <dependency>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.8</version>
</dependency> -->
<dependency>
<groupId>com.github.PandacubeFr</groupId>
<artifactId>commodore</artifactId>
<version>patch-custom-suggests-SNAPSHOT</version>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>
<profile> <profile>

View file

@ -1,13 +1,31 @@
package sznp.virtualcomputer; 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.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.virtualbox_6_1.MouseButtonState;
import org.virtualbox_6_1.VBoxException; 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 @Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 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); Computer.getInstance().Start(sender, c);
break; break;
case "list":
Computer.getInstance().List(sender);
break;
case "stop": case "stop":
case "poweroff": case "poweroff":
case "off": case "off":
@ -82,11 +103,9 @@ public class Commands implements CommandExecutor {
showusage = false; showusage = false;
} }
} }
} } catch (VBoxException e) {
catch (VBoxException e) {
e.printStackTrace(); e.printStackTrace();
} } catch (Exception ignored) { //It will show the usage here
catch (Exception ignored) { //It will show the usage here
} }
} }
if (showusage) { if (showusage) {
@ -131,7 +150,7 @@ public class Commands implements CommandExecutor {
} }
try { try {
MouseLockerPlayerListener.LockedSpeed = Float.parseFloat(args[2]); MouseLockerPlayerListener.LockedSpeed = Float.parseFloat(args[2]);
} catch(NumberFormatException e) { } catch (NumberFormatException e) {
sender.sendMessage("§cThe speed must be a number."); sender.sendMessage("§cThe speed must be a number.");
break; break;
} }
@ -143,6 +162,49 @@ public class Commands implements CommandExecutor {
return true; return true;
} }
private boolean tabSetup = true;
@Override
public List<String> 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<Object> 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<Object> target, CommandNode<Object> node) {
target.getChildren().clear();
target.addChild(node);
}
}.setup(command);
}
return Collections.emptyList();
}
/** /**
* Checks the 2nd parameter * Checks the 2nd parameter
* *

View file

@ -2,6 +2,7 @@ package sznp.virtualcomputer;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import lombok.Getter; import lombok.Getter;
import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -17,220 +18,228 @@ import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
public final class Computer { public final class Computer {
@Getter @Getter
private static Computer instance; private static Computer instance;
private final PluginMain plugin; private final PluginMain plugin;
private ISession session; private ISession session;
private IVirtualBox vbox; private IVirtualBox vbox;
private IMachine machine; private IMachine machine;
private MachineEventHandler handler; private MachineEventHandler handler;
private IEventListener listener; private IEventListener listener;
private VirtualBoxManager manager; private VirtualBoxManager manager;
@java.beans.ConstructorProperties({"plugin"}) @java.beans.ConstructorProperties({"plugin"})
public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox) { public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox) {
this.plugin = plugin; this.plugin = plugin;
this.manager = manager; this.manager = manager;
session = manager.getSessionObject(); session = manager.getSessionObject();
this.vbox = vbox; this.vbox = vbox;
if (instance != null) throw new IllegalStateException("A computer already exists!"); if (instance != null) throw new IllegalStateException("A computer already exists!");
instance = this; instance = this;
} }
public void Start(CommandSender sender, int index) {// TODO: Add touchscreen support (#2) public void Start(CommandSender sender, int index) {// TODO: Add touchscreen support (#2)
if (session.getState() == SessionState.Locked) { if (session.getState() == SessionState.Locked) {
sender.sendMessage("§cThe machine is already running!"); sender.sendMessage("§cThe machine is already running!");
return; return;
} }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
if (vbox.getMachines().size() <= index) { if (vbox.getMachines().size() <= index) {
sendMessage(sender, "§cMachine not found!"); sendMessage(sender, "§cMachine not found!");
return; return;
} }
try { try {
sendMessage(sender, "§eStarting computer..."); sendMessage(sender, "§eStarting computer...");
machine = vbox.getMachines().get(index); machine = vbox.getMachines().get(index);
session.setName("minecraft"); session.setName("minecraft");
// machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); - This creates a *process*, we don't want that anymore // 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 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 VBoxEventHandler.getInstance().setup(machine.getId(), sender); //TODO: Sometimes null
} catch (VBoxException e) { } catch (VBoxException e) {
if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited" if (e.getResultCode() == 0x80070005) { //lockMachine: "The object functionality is limited"
sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible"); sendMessage(sender, "§6Cannot start computer, the machine may be inaccessible");
sendMessage(sender, "§6Make sure that the server is running as root (sudo)"); 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: If we have VirtualBox open, it won't close the server's port
//TODO: "The object in question already exists." on second start //TODO: "The object in question already exists." on second start
//machine.launchVMProcess(session, "headless", "").waitForCompletion(10000); //No privileges, start the 'old' way //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))); //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."); //sendMessage(sender, "§6Computer started with slower screen. Run as root to use a faster method.");
} else { } else {
sendMessage(sender, "§cFailed to start computer: " + e.getMessage()); sendMessage(sender, "§cFailed to start computer: " + e.getMessage());
} }
} }
}); });
} }
/** public void List(CommandSender sender) {
* Gets called when the machine is locked after {@link #Start(CommandSender, int)} val machines = vbox.getMachines();
* for (int i = 0; i < machines.size(); i++) {
* @param sender The sender which started the machine val m = machines.get(i);
*/ sender.sendMessage("[" + i + "] " + m.getName() + " - " + m.getState());
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())));
}
private void sendMessage(@Nullable CommandSender sender, String message) { /**
if (sender != null) * Gets called when the machine is locked after {@link #Start(CommandSender, int)}
sender.sendMessage(message); *
plugin.getLogger().warning((sender == null ? "" : sender.getName() + ": ") + ChatColor.stripColor(message)); * @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) { private void sendMessage(@Nullable CommandSender sender, String message) {
if (checkMachineNotRunning(sender)) { if (sender != null)
if (session.getState().equals(SessionState.Locked)) { sender.sendMessage(message);
onMachineStop(sender); //Needed for session reset plugin.getLogger().warning((sender == null ? "" : sender.getName() + ": ") + ChatColor.stripColor(message));
sendMessage(sender, "§eComputer was already off, released it."); }
}
return;
}
sendMessage(sender, "§eStopping computer...");
session.getConsole().powerDown();
}
public void PowerButton(CommandSender sender, int index) { public void Stop(CommandSender sender) {
sendMessage(sender, "§ePressing powerbutton..."); if (checkMachineNotRunning(sender)) {
Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { if (session.getState().equals(SessionState.Locked)) {
@Override onMachineStop(sender); //Needed for session reset
public void run() { sendMessage(sender, "§eComputer was already off, released it.");
if (session.getState() != SessionState.Locked || session.getMachine() == null) { }
Start(sender, index); return;
} else { }
session.getConsole().powerButton(); sendMessage(sender, "§eStopping computer...");
sendMessage(sender, "§ePowerbutton pressed."); session.getConsole().powerDown();
} }
}
});
}
public void Reset(CommandSender sender) { public void PowerButton(CommandSender sender, int index) {
if (checkMachineNotRunning(sender)) sendMessage(sender, "§ePressing powerbutton...");
return; Bukkit.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() {
sendMessage(sender, "§eResetting computer..."); @Override
session.getConsole().reset(); public void run() {
sendMessage(sender, "§eComputer reset."); if (session.getState() != SessionState.Locked || session.getMachine() == null) {
} Start(sender, index);
} else {
session.getConsole().powerButton();
sendMessage(sender, "§ePowerbutton pressed.");
}
}
});
}
public void FixScreen(CommandSender sender) { public void Reset(CommandSender sender) {
if (checkMachineNotRunning(sender)) if (checkMachineNotRunning(sender))
return; return;
sendMessage(sender, "§eFixing screen..."); sendMessage(sender, "§eResetting computer...");
session.getConsole().getDisplay().setSeamlessMode(false); session.getConsole().reset();
session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L, true); sendMessage(sender, "§eComputer reset.");
sendMessage(sender, "§eScreen fixed."); }
}
public boolean checkMachineNotRunning(@Nullable CommandSender sender) { public void FixScreen(CommandSender sender) {
if (session.getState() != SessionState.Locked || machine.getState() != MachineState.Running) { if (checkMachineNotRunning(sender))
if (sender != null) return;
sender.sendMessage("§cMachine isn't running."); sendMessage(sender, "§eFixing screen...");
return true; session.getConsole().getDisplay().setSeamlessMode(false);
} session.getConsole().getDisplay().setVideoModeHint(0L, true, false, 0, 0, 640L, 480L, 32L, true);
return false; sendMessage(sender, "§eScreen fixed.");
} }
public void PressKey(CommandSender sender, String key, String stateorduration) { public boolean checkMachineNotRunning(@Nullable CommandSender sender) {
if (checkMachineNotRunning(sender)) if (session.getState() != SessionState.Locked || machine.getState() != MachineState.Running) {
return; if (sender != null)
int durationorstate; sender.sendMessage("§cMachine isn't running.");
if (stateorduration.length() == 0) return true;
durationorstate = 0; }
else if (stateorduration.equalsIgnoreCase("down")) return false;
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, boolean down) throws Exception { public void PressKey(CommandSender sender, String key, String stateorduration) {
if (checkMachineNotRunning(sender)) if (checkMachineNotRunning(sender))
return; return;
int state = 0; int durationorstate;
if (mbs.length() > 0 && down) if (stateorduration.length() == 0)
state = Arrays.stream(MouseButtonState.values()).filter(mousebs -> mousebs.name().equalsIgnoreCase(mbs)) durationorstate = 0;
.findAny().orElseThrow(() -> new Exception("Unknown mouse button")).value(); else if (stateorduration.equalsIgnoreCase("down"))
session.getConsole().getMouse().putMouseEvent(x, y, z, w, state); 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 { public void UpdateMouse(CommandSender sender, int x, int y, int z, int w, String mbs, boolean down) throws Exception {
if (checkMachineNotRunning(sender)) if (checkMachineNotRunning(sender))
return; return;
UpdateMouse(sender, x, y, z, w, mbs, true); int state = 0;
UpdateMouse(sender, x, y, z, w, mbs, false); 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) { public void onMachineStart(CommandSender sender) {
sendMessage(sender, "§eComputer started."); sendMessage(sender, "§eComputer started.");
} }
public void onMachineStop(CommandSender sender) { public void onMachineStop(CommandSender sender) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
if (session.getState() == SessionState.Locked) { if (session.getState() == SessionState.Locked) {
session.unlockMachine(); //Needs to be outside of the event handler session.unlockMachine(); //Needs to be outside of the event handler
handler = null; handler = null;
machine = null; machine = null;
session = manager.getSessionObject(); session = manager.getSessionObject();
sendMessage(sender, "§eComputer powered off."); //This block runs later sendMessage(sender, "§eComputer powered off."); //This block runs later
} }
}); });
GPURenderer.update(new byte[1], 0, 0, 0, 0, 640, 480); //Black screen GPURenderer.update(new byte[1], 0, 0, 0, 0, 640, 480); //Black screen
stopEvents(); stopEvents();
MouseLockerPlayerListener.computerStop(); MouseLockerPlayerListener.computerStop();
} }
public void stopEvents() { public void stopEvents() {
/*if(session!=null && listener!=null) /*if(session!=null && listener!=null)
session.getConsole().getEventSource().unregisterListener(listener);*/ session.getConsole().getEventSource().unregisterListener(listener);*/
if (listener != null) if (listener != null)
handler.disable(); handler.disable();
listener = null; listener = null;
} }
public void pluginDisable(CommandSender ccs) { public void pluginDisable(CommandSender ccs) {
stopEvents(); stopEvents();
if (session.getState() == SessionState.Locked) { if (session.getState() == SessionState.Locked) {
if (session.getMachine().getState().equals(MachineState.Running)) { if (session.getMachine().getState().equals(MachineState.Running)) {
ccs.sendMessage("§aSaving machine state..."); ccs.sendMessage("§aSaving machine state...");
session.getMachine().saveState().waitForCompletion(10000); session.getMachine().saveState().waitForCompletion(10000);
} }
session.unlockMachine(); session.unlockMachine();
} }
} }
} }

View file

@ -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;
}
}

View file

@ -33,6 +33,10 @@
<id>spigot-repo</id> <id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository> </repository>
<repository>
<id>jitpack</id>
<url>https://jitpack.io/</url>
</repository>
</repositories> </repositories>
<dependencies> <dependencies>