Black screen, event fixes, error detection
Only getting map fields once to speed things up Rendering black screen on first render, which also helps initialize Aparapi Disabling event handlers on stop (can't unregister) Removed and moved unlockMachine() calls from event handlers Detecting failed machine start Added note about sudo
This commit is contained in:
parent
c8ea718787
commit
e26489d864
8 changed files with 85 additions and 40 deletions
|
@ -2,13 +2,13 @@ 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;
|
||||||
import org.virtualbox_6_0.*;
|
import org.virtualbox_6_0.*;
|
||||||
import sznp.virtualcomputer.events.MachineEventHandler;
|
import sznp.virtualcomputer.events.MachineEventHandler;
|
||||||
import sznp.virtualcomputer.events.VBoxEventHandler;
|
import sznp.virtualcomputer.events.VBoxEventHandler;
|
||||||
|
import sznp.virtualcomputer.renderer.GPURendererInternal;
|
||||||
import sznp.virtualcomputer.renderer.MCFrameBuffer;
|
import sznp.virtualcomputer.renderer.MCFrameBuffer;
|
||||||
import sznp.virtualcomputer.util.Scancode;
|
import sznp.virtualcomputer.util.Scancode;
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ public final class Computer {
|
||||||
private ISession session;
|
private ISession session;
|
||||||
private IVirtualBox vbox;
|
private IVirtualBox vbox;
|
||||||
private IMachine machine;
|
private IMachine machine;
|
||||||
|
private MachineEventHandler handler;
|
||||||
|
private IEventListener listener;
|
||||||
|
|
||||||
@java.beans.ConstructorProperties({"plugin"})
|
@java.beans.ConstructorProperties({"plugin"})
|
||||||
public Computer(PluginMain plugin, ISession session, IVirtualBox vbox) {
|
public Computer(PluginMain plugin, ISession session, IVirtualBox vbox) {
|
||||||
|
@ -49,13 +51,13 @@ public final class Computer {
|
||||||
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);
|
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)");
|
||||||
//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: Can't detect if machine fails to start because of hardening issues
|
//TODO: "The object in question already exists." on second start
|
||||||
//TODO: This error also occurs if the machine has failed to start at least once (always reassign the machine?)
|
|
||||||
//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 MCFrameBuffer(session.getConsole().getDisplay(), false)));
|
//session.getConsole().getDisplay().attachFramebuffer(0L, new IFramebuffer(new MCFrameBuffer(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.");
|
||||||
|
@ -74,8 +76,8 @@ public final class Computer {
|
||||||
public void onLock(CommandSender sender) {
|
public void onLock(CommandSender sender) {
|
||||||
machine = session.getMachine(); // This is the Machine object we can work with
|
machine = session.getMachine(); // This is the Machine object we can work with
|
||||||
final IConsole console = session.getConsole();
|
final IConsole console = session.getConsole();
|
||||||
val handler = new MachineEventHandler(Computer.this);
|
handler = new MachineEventHandler(Computer.this);
|
||||||
handler.registerTo(console.getEventSource());
|
listener = handler.registerTo(console.getEventSource());
|
||||||
IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
|
IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
|
||||||
handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way?
|
handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way?
|
||||||
console.getDisplay().attachFramebuffer(0L,
|
console.getDisplay().attachFramebuffer(0L,
|
||||||
|
@ -99,7 +101,6 @@ public final class Computer {
|
||||||
}
|
}
|
||||||
sendMessage(sender, "§eStopping computer...");
|
sendMessage(sender, "§eStopping computer...");
|
||||||
session.getConsole().powerDown().waitForCompletion(2000);
|
session.getConsole().powerDown().waitForCompletion(2000);
|
||||||
session.unlockMachine();
|
|
||||||
sendMessage(sender, "§eComputer stopped.");
|
sendMessage(sender, "§eComputer stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +193,26 @@ public final class Computer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMachineStop() {
|
public void onMachineStop() {
|
||||||
session.unlockMachine();
|
System.out.println("Unlocking machine...");
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
|
if (session.getState() == SessionState.Locked) {
|
||||||
|
System.out.println("Unlocking...");
|
||||||
|
session.unlockMachine(); //Needs to be outside of the event handler
|
||||||
|
System.out.println("Machine unlocked.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
System.out.println("Setting pixels...");
|
||||||
|
GPURendererInternal.setPixels(new byte[1], 0, 0); //Black screen
|
||||||
|
System.out.println("Stopping events...");
|
||||||
|
stopEvents();
|
||||||
plugin.getLogger().info("Computer powered off.");
|
plugin.getLogger().info("Computer powered off.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stopEvents() {
|
||||||
|
/*if(session!=null && listener!=null)
|
||||||
|
session.getConsole().getEventSource().unregisterListener(listener);*/
|
||||||
|
if (listener != null)
|
||||||
|
handler.disable();
|
||||||
|
listener = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ public class PluginMain extends JavaPlugin {
|
||||||
private IMachine machine;
|
private IMachine machine;
|
||||||
private BukkitTask screenupdatetask;
|
private BukkitTask screenupdatetask;
|
||||||
private BukkitTask mousetask;
|
private BukkitTask mousetask;
|
||||||
|
private IEventListener listener;
|
||||||
|
private IEventSource source;
|
||||||
|
|
||||||
public static PluginMain Instance;
|
public static PluginMain Instance;
|
||||||
//public static ByteBuffer allpixels = ByteBuffer.allocate(640 * 480 * 4); // It's set on each change
|
//public static ByteBuffer allpixels = ByteBuffer.allocate(640 * 480 * 4); // It's set on each change
|
||||||
|
@ -70,7 +72,7 @@ public class PluginMain extends JavaPlugin {
|
||||||
VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom");
|
VBoxLib vbl = LibraryLoader.create(VBoxLib.class).load("vboxjxpcom");
|
||||||
vbl.RTR3InitExe(0, "", 0);
|
vbl.RTR3InitExe(0, "", 0);
|
||||||
vbox = manager.getVBox();
|
vbox = manager.getVBox();
|
||||||
new VBoxEventHandler().registerTo(vbox.getEventSource());
|
listener = new VBoxEventHandler().registerTo(source = vbox.getEventSource());
|
||||||
session = manager.getSessionObject();
|
session = manager.getSessionObject();
|
||||||
new Computer(this, session, vbox); //Saves itself
|
new Computer(this, session, vbox); //Saves itself
|
||||||
ccs.sendMessage("§bLoading Screen...");
|
ccs.sendMessage("§bLoading Screen...");
|
||||||
|
@ -109,6 +111,14 @@ public class PluginMain extends JavaPlugin {
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
ConsoleCommandSender ccs = getServer().getConsoleSender();
|
ConsoleCommandSender ccs = getServer().getConsoleSender();
|
||||||
mousetask.cancel();
|
mousetask.cancel();
|
||||||
|
/*try {
|
||||||
|
source.unregisterListener(listener);
|
||||||
|
} catch (VBoxException e) { //"Listener was never registered"
|
||||||
|
e.printStackTrace(); - VBox claims the listener was never registered (can double register as well)
|
||||||
|
}*/
|
||||||
|
((VBoxEventHandler) listener.getTypedWrapped()).disable(); //The save progress wait locks with the event
|
||||||
|
if (Computer.getInstance() != null)
|
||||||
|
Computer.getInstance().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...");
|
||||||
|
|
|
@ -22,6 +22,7 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi
|
||||||
* The events to listen for. It will only look for these handlers.
|
* The events to listen for. It will only look for these handlers.
|
||||||
*/
|
*/
|
||||||
private final Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap;
|
private final Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap;
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
protected EventHandlerBase(Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap) {
|
protected EventHandlerBase(Map<VBoxEventType, Class<? extends org.virtualbox_6_0.IEvent>> eventMap) {
|
||||||
//this.eventMap = eventMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().value(), Map.Entry::getValue));
|
//this.eventMap = eventMap.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().value(), Map.Entry::getValue));
|
||||||
|
@ -31,6 +32,8 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi
|
||||||
@Override
|
@Override
|
||||||
public final void handleEvent(IEvent iEvent) {
|
public final void handleEvent(IEvent iEvent) {
|
||||||
//val cl=eventMap.get((int)iEvent.getType()); - We can afford to search through the events for this handler
|
//val cl=eventMap.get((int)iEvent.getType()); - We can afford to search through the events for this handler
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
val kv = eventMap.entrySet().stream().filter(e -> e.getKey().value() == iEvent.getType()).findAny();
|
val kv = eventMap.entrySet().stream().filter(e -> e.getKey().value() == iEvent.getType()).findAny();
|
||||||
if (!kv.isPresent()) return; //Event not supported
|
if (!kv.isPresent()) return; //Event not supported
|
||||||
val cl = kv.get().getValue();
|
val cl = kv.get().getValue();
|
||||||
|
@ -50,7 +53,11 @@ public abstract class EventHandlerBase extends COMObjectBase implements IEventLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends EventHandlerBase> void registerTo(IEventSource source) {
|
public <T extends EventHandlerBase> org.virtualbox_6_0.IEventListener registerTo(IEventSource source) {
|
||||||
Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet()));
|
return Utils.registerListener(source, this, new ArrayList<>(eventMap.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disable() {
|
||||||
|
enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import sznp.virtualcomputer.Computer;
|
||||||
|
|
||||||
public class MachineEventHandler extends EventHandlerBase {
|
public class MachineEventHandler extends EventHandlerBase {
|
||||||
private final Computer computer;
|
private final Computer computer;
|
||||||
|
private boolean starting = false;
|
||||||
|
|
||||||
public MachineEventHandler(Computer computer) {
|
public MachineEventHandler(Computer computer) {
|
||||||
super(ImmutableMap.of(VBoxEventType.OnStateChanged, IStateChangedEvent.class,
|
super(ImmutableMap.of(VBoxEventType.OnStateChanged, IStateChangedEvent.class,
|
||||||
|
@ -18,13 +19,26 @@ public class MachineEventHandler extends EventHandlerBase {
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void handleStateChange(IStateChangedEvent event) { //https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#a80b08f71210afe16038e904a656ed9eb
|
public void handleStateChange(IStateChangedEvent event) { //https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#a80b08f71210afe16038e904a656ed9eb
|
||||||
|
System.out.println("State event: " + event.getState());
|
||||||
switch (event.getState()) {
|
switch (event.getState()) {
|
||||||
case Stuck:
|
case Stuck:
|
||||||
computer.Stop(null);
|
computer.Stop(null);
|
||||||
break;
|
break;
|
||||||
case PoweredOff:
|
case PoweredOff:
|
||||||
case Saved:
|
case Saved:
|
||||||
|
if (starting) {
|
||||||
|
System.out.println("Failed to start computer! See the VM's log for more details.");
|
||||||
|
starting = false; //TODO: Sender
|
||||||
|
}
|
||||||
computer.onMachineStop();
|
computer.onMachineStop();
|
||||||
|
break;
|
||||||
|
case Starting:
|
||||||
|
starting = true;
|
||||||
|
break;
|
||||||
|
case Running:
|
||||||
|
System.out.println("Computer is running.");
|
||||||
|
starting = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,7 @@ public class VBoxEventHandler extends EventHandlerBase {
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onSessionStateChange(ISessionStateChangedEvent event) {
|
public void onSessionStateChange(ISessionStateChangedEvent event) {
|
||||||
System.out.println("Session change event: " + event);
|
|
||||||
System.out.println("ID1: " + event.getMachineId() + " - ID2: " + machineID);
|
|
||||||
if (!event.getMachineId().equals(machineID)) return;
|
if (!event.getMachineId().equals(machineID)) return;
|
||||||
System.out.println("State: " + event.getState());
|
|
||||||
if (event.getState() == SessionState.Locked) //Need to check here, because we can't access the console yet
|
if (event.getState() == SessionState.Locked) //Need to check here, because we can't access the console yet
|
||||||
Computer.getInstance().onLock(sender);
|
Computer.getInstance().onLock(sender);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package sznp.virtualcomputer.renderer;
|
package sznp.virtualcomputer.renderer;
|
||||||
|
|
||||||
import lombok.val;
|
|
||||||
import net.minecraft.server.v1_12_R1.WorldMap;
|
import net.minecraft.server.v1_12_R1.WorldMap;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
|
|
||||||
import org.bukkit.craftbukkit.v1_12_R1.map.RenderData;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.map.MapCanvas;
|
import org.bukkit.map.MapCanvas;
|
||||||
import org.bukkit.map.MapPalette;
|
import org.bukkit.map.MapPalette;
|
||||||
|
@ -14,23 +11,20 @@ import sznp.virtualcomputer.util.Timing;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class GPURenderer extends MapRenderer implements IRenderer {
|
public class GPURenderer extends MapRenderer implements IRenderer {
|
||||||
private byte[] buffer;
|
private byte[] buffer;
|
||||||
private GPURendererInternal kernel;
|
private GPURendererInternal kernel;
|
||||||
|
private WorldMap wmap;
|
||||||
//Store at central location after conversion
|
//Store at central location after conversion
|
||||||
private static int[] colors_;
|
private static int[] colors_;
|
||||||
|
|
||||||
public GPURenderer(short id, World world, int mapx, int mapy) throws Exception {
|
public GPURenderer(short id, World world, int mapx, int mapy) throws Exception {
|
||||||
MapView map = IRenderer.prepare(id, world);
|
MapView map = IRenderer.prepare(id, world);
|
||||||
if (map == null) return; //Testing
|
if (map == null) return; //Testing
|
||||||
Field field = map.getClass().getDeclaredField("renderCache");
|
|
||||||
field.setAccessible(true);
|
|
||||||
@SuppressWarnings("unchecked") val renderCache = (Map<CraftPlayer, RenderData>) field.get(map);
|
|
||||||
|
|
||||||
if (colors_ == null) {
|
if (colors_ == null) {
|
||||||
field = MapPalette.class.getDeclaredField("colors");
|
Field field = MapPalette.class.getDeclaredField("colors");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
Color[] cs = (Color[]) field.get(null);
|
Color[] cs = (Color[]) field.get(null);
|
||||||
colors_ = new int[cs.length];
|
colors_ = new int[cs.length];
|
||||||
|
@ -38,15 +32,11 @@ public class GPURenderer extends MapRenderer implements IRenderer {
|
||||||
colors_[i] = cs[i].getRGB();
|
colors_[i] = cs[i].getRGB();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Field field = map.getClass().getDeclaredField("worldMap");
|
||||||
|
field.setAccessible(true);
|
||||||
|
wmap = (WorldMap) field.get(map);
|
||||||
kernel = new GPURendererInternal(mapx, mapy, colors_);
|
kernel = new GPURendererInternal(mapx, mapy, colors_);
|
||||||
|
|
||||||
RenderData render = renderCache.get(null);
|
|
||||||
|
|
||||||
if (render == null)
|
|
||||||
renderCache.put(null, render = new RenderData());
|
|
||||||
|
|
||||||
this.buffer = render.buffer;
|
|
||||||
|
|
||||||
//System.setProperty("com.codegen.config.enable.NEW", "true");
|
//System.setProperty("com.codegen.config.enable.NEW", "true");
|
||||||
|
|
||||||
map.addRenderer(this);
|
map.addRenderer(this);
|
||||||
|
@ -57,13 +47,12 @@ public class GPURenderer extends MapRenderer implements IRenderer {
|
||||||
Timing t = new Timing();
|
Timing t = new Timing();
|
||||||
try {
|
try {
|
||||||
if (kernel.isRendered()) return; //TODO: Stop rendering after computer is stopped
|
if (kernel.isRendered()) return; //TODO: Stop rendering after computer is stopped
|
||||||
|
if (buffer == null) { //The buffer remains the same, as the canvas remains the same
|
||||||
Field field = canvas.getClass().getDeclaredField("buffer");
|
Field field = canvas.getClass().getDeclaredField("buffer");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
buffer = (byte[]) field.get(canvas);
|
buffer = (byte[]) field.get(canvas);
|
||||||
|
}
|
||||||
kernel.render(buffer);
|
kernel.render(buffer);
|
||||||
field = map.getClass().getDeclaredField("worldMap");
|
|
||||||
field.setAccessible(true);
|
|
||||||
WorldMap wmap = (WorldMap) field.get(map);
|
|
||||||
wmap.flagDirty(0, 0);
|
wmap.flagDirty(0, 0);
|
||||||
wmap.flagDirty(127, 127); // Send the whole image - TODO: Only send changes
|
wmap.flagDirty(127, 127); // Send the whole image - TODO: Only send changes
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class GPURendererInternal extends Kernel {
|
||||||
private byte[] buffer; //References the map buffer
|
private byte[] buffer; //References the map buffer
|
||||||
private Range range;
|
private Range range;
|
||||||
@Getter
|
@Getter
|
||||||
private boolean rendered = true;
|
private boolean rendered;
|
||||||
private static ArrayList<GPURendererInternal> renderers = new ArrayList<>();
|
private static ArrayList<GPURendererInternal> renderers = new ArrayList<>();
|
||||||
|
|
||||||
//public static byte[] test=new byte[1]; - LAMBDAS
|
//public static byte[] test=new byte[1]; - LAMBDAS
|
||||||
|
@ -29,6 +29,12 @@ public class GPURendererInternal extends Kernel {
|
||||||
this.mapy = mapy;
|
this.mapy = mapy;
|
||||||
this.colors = colors;
|
this.colors = colors;
|
||||||
range = Range.create2D(128, 128);
|
range = Range.create2D(128, 128);
|
||||||
|
|
||||||
|
//Do an intial draw of a black screen with Aparapi so it doesn't lag at start
|
||||||
|
pixels = new byte[1];
|
||||||
|
width = height = 0;
|
||||||
|
rendered = false;
|
||||||
|
|
||||||
renderers.add(this);
|
renderers.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,10 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
//public static void registerListener(IEventSource source, IEventListener listener, VBoxEventType... types) {
|
//public static void registerListener(IEventSource source, IEventListener listener, VBoxEventType... types) {
|
||||||
public static void registerListener(IEventSource source, IEventListener listener, List<VBoxEventType> types) {
|
public static org.virtualbox_6_0.IEventListener registerListener(IEventSource source, IEventListener listener, List<VBoxEventType> types) {
|
||||||
source.registerListener(new org.virtualbox_6_0.IEventListener(listener), types, true);
|
val ret = new org.virtualbox_6_0.IEventListener(listener);
|
||||||
|
source.registerListener(ret, types, true);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
Loading…
Reference in a new issue