diff --git a/addon.gradle b/addon.gradle
index 76b1bdee62..67e58c78ff 100644
--- a/addon.gradle
+++ b/addon.gradle
@@ -11,3 +11,10 @@ tasks.named("processResources", ProcessResources).configure {
filter(ReplaceTokens, tokens: [version: pVersion])
}
}
+
+sourceSets {
+ lwjgl3 {
+ compileClasspath += sourceSets.main.compileClasspath
+ compileClasspath += sourceSets.main.output
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index e57a16f9f1..9597fbeaa8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,3 +3,15 @@
plugins {
id 'com.gtnewhorizons.gtnhconvention'
}
+
+// https://github.com/GTNewHorizons/NewHorizonsCoreMod/blob/master/build.gradle.kts
+def lwjgl3Version = project.minecraft.lwjgl3Version.get()
+dependencies {
+ lwjgl3CompileOnly("org.lwjgl:lwjgl-sdl:${lwjgl3Version}") { transitive = false }
+ lwjgl3CompileOnly("org.lwjgl:lwjgl:${lwjgl3Version}") { transitive = false }
+ lwjgl3CompileOnly("com.github.GTNewHorizons:lwjgl3ify:3.0.3:dev") { transitive = false }
+}
+
+jar {
+ from sourceSets.lwjgl3.output
+}
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index 996b710cf6..70fac78cda 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -7,7 +7,7 @@ dependencies {
api("com.github.GTNewHorizons:AE2FluidCraft-Rework:1.5.39-gtnh:dev")
compileOnly("com.github.GTNewHorizons:Angelica:1.0.0-beta68a:api") {transitive = false}
- api("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-betea-777-GTNH:dev")
+ api("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-777-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Avaritiaddons:1.9.3-GTNH:dev") {transitive = false}
compileOnly("com.github.GTNewHorizons:BloodMagic:1.8.8:dev") {transitive = false}
compileOnly("com.github.GTNewHorizons:BuildCraft:7.1.48:dev") {transitive = false}
diff --git a/src/lwjgl3/java/li/cil/oc/integration/lwjgl3ify/FileUploadSupport.java b/src/lwjgl3/java/li/cil/oc/integration/lwjgl3ify/FileUploadSupport.java
new file mode 100644
index 0000000000..a0ded9f9a5
--- /dev/null
+++ b/src/lwjgl3/java/li/cil/oc/integration/lwjgl3ify/FileUploadSupport.java
@@ -0,0 +1,38 @@
+package li.cil.oc.integration.lwjgl3ify;
+
+import li.cil.oc.client.gui.traits.InputBuffer;
+import li.cil.oc.integration.lwjgl3ify.IFileUpload;
+import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+import org.lwjgl.sdl.SDLEvents;
+import org.lwjgl.sdl.SDL_Event;
+import org.lwjgl.sdl.SDL_EventFilter;
+import org.lwjgl.system.MemoryUtil;
+
+import static org.lwjgl.sdl.SDLEvents.SDL_EVENT_DROP_FILE;
+
+@Lwjgl3Aware
+class FileUploadSupport implements IFileUpload {
+ @Lwjgl3Aware
+ class DropFileFilter extends SDL_EventFilter {
+ @Override
+ public boolean invoke(long userdata, long eventPtr) {
+ SDL_Event event = SDL_Event.create(eventPtr);
+ if (event.type() == SDL_EVENT_DROP_FILE) {
+ GuiScreen screen = Minecraft.getMinecraft().currentScreen;
+ if (screen instanceof InputBuffer) {
+ String file = MemoryUtil.memUTF8Safe(event.drop().data());
+ if (file != null) ((InputBuffer) screen).handleDropFile(file);
+ }
+ }
+ return true;
+ }
+ }
+
+ DropFileFilter filter = new DropFileFilter();
+ @Override
+ public void init() {
+ SDLEvents.SDL_AddEventWatch(filter, MemoryUtil.NULL);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/li/cil/oc/api/internal/TextBuffer.java b/src/main/java/li/cil/oc/api/internal/TextBuffer.java
index 4221196407..b30aebcc3a 100644
--- a/src/main/java/li/cil/oc/api/internal/TextBuffer.java
+++ b/src/main/java/li/cil/oc/api/internal/TextBuffer.java
@@ -581,6 +581,17 @@ public interface TextBuffer extends ManagedEnvironment, Persistable {
*/
void clipboard(String value, EntityPlayer player);
+ /**
+ * Signals a file drop event for the buffer.
+ *
+ * This method is intended to be called on the client side only.
+ *
+ * @param fileName the name of file that was dropped.
+ * @param fileContent the context of the file being transferred.
+ * @param player the player that dropped the file. Pass null on the client side.
+ */
+ void dropFile(String fileName, String fileContent, EntityPlayer player);
+
/**
* Signals a mouse button down event for the buffer.
*
diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/95_drop_file.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/95_drop_file.lua
new file mode 100644
index 0000000000..6ffba4f52f
--- /dev/null
+++ b/src/main/resources/assets/opencomputers/loot/openos/boot/95_drop_file.lua
@@ -0,0 +1,27 @@
+local event = require("event")
+local fs = require("filesystem")
+local shell = require("shell")
+
+local function onDropFile(ev, _, filename, context)
+ local path = shell.resolve(filename)
+ local file_parentpath = fs.path(path)
+ fs.makeDirectory(file_parentpath)
+
+ if fs.exists(path) then
+ if not os.remove(path) then
+ io.stderr:write("file already exists")
+ return
+ end
+ end
+
+ local f, reason = io.open(filename, "w")
+ if not f then
+ io.stderr:write("Failed opening file for writing: " .. reason)
+ return
+ end
+
+ f:write(context)
+ f:close()
+end
+
+event.listen("drop_file", onDropFile)
diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala
index ccf46a956c..1ffd855d7d 100644
--- a/src/main/scala/li/cil/oc/client/PacketHandler.scala
+++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala
@@ -48,6 +48,7 @@ object PacketHandler extends CommonPacketHandler {
case PacketType.ChargerState => onChargerState(p)
case PacketType.ClientLog => onClientLog(p)
case PacketType.Clipboard => onClipboard(p)
+ case PacketType.DropFile => onDropFile(p)
case PacketType.ColorChange => onColorChange(p)
case PacketType.ComputerState => onComputerState(p)
case PacketType.ComputerUserList => onComputerUserList(p)
@@ -140,6 +141,9 @@ object PacketHandler extends CommonPacketHandler {
GuiScreen.setClipboardString(p.readUTF())
}
+ def onDropFile(p: PacketParser): Unit = {
+ }
+
def onColorChange(p: PacketParser) =
p.readTileEntity[Colored]() match {
case Some(t) =>
diff --git a/src/main/scala/li/cil/oc/client/PacketSender.scala b/src/main/scala/li/cil/oc/client/PacketSender.scala
index 1b4b7f0eb8..2fd7b58477 100644
--- a/src/main/scala/li/cil/oc/client/PacketSender.scala
+++ b/src/main/scala/li/cil/oc/client/PacketSender.scala
@@ -91,6 +91,24 @@ object PacketSender {
}
}
+ def sendDropFile(address: String, name: String, context: String): Unit = {
+ val length = name.length + context.length
+ if (length > 64 * 1024) {
+ val player = Minecraft.getMinecraft.thePlayer
+ val handler = Minecraft.getMinecraft.getSoundHandler
+ handler.playSound(new PositionedSoundRecord(new ResourceLocation("note.harp"), 1, 1, player.posX.toFloat, player.posY.toFloat, player.posZ.toFloat))
+ }
+ else {
+ val pb = new CompressedPacketBuilder(PacketType.DropFile)
+
+ pb.writeUTF(address)
+ pb.writeUTF(name)
+ pb.writeUTF(context)
+
+ pb.sendToServer()
+ }
+ }
+
def sendMouseClick(address: String, x: Double, y: Double, drag: Boolean, button: Int) {
val pb = new SimplePacketBuilder(PacketType.MouseClickOrDrag)
diff --git a/src/main/scala/li/cil/oc/client/gui/traits/InputBuffer.scala b/src/main/scala/li/cil/oc/client/gui/traits/InputBuffer.scala
index 75f142aad1..aff35a5329 100644
--- a/src/main/scala/li/cil/oc/client/gui/traits/InputBuffer.scala
+++ b/src/main/scala/li/cil/oc/client/gui/traits/InputBuffer.scala
@@ -1,8 +1,8 @@
package li.cil.oc.client.gui.traits
-import li.cil.oc.api
-import li.cil.oc.client.KeyBindings
-import li.cil.oc.client.Textures
+import li.cil.oc.{OpenComputers, api}
+import li.cil.oc.client.{KeyBindings, Textures}
+import li.cil.oc.common.EventHandler
import li.cil.oc.integration.util.NEI
import li.cil.oc.util.RenderState
import net.minecraft.client.Minecraft
@@ -12,7 +12,13 @@ import net.minecraft.client.renderer.Tessellator
import org.lwjgl.input.Keyboard
import org.lwjgl.opengl.GL11
+import java.io.File
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.util.concurrent.Executors
+import scala.collection.JavaConverters.asScalaIteratorConverter
import scala.collection.mutable
+import scala.concurrent.{ExecutionContext, Future}
trait InputBuffer extends DisplayBuffer {
protected def buffer: api.internal.TextBuffer
@@ -117,4 +123,41 @@ trait InputBuffer extends DisplayBuffer {
code == Keyboard.KEY_LMETA ||
code == Keyboard.KEY_RMETA
}
+
+ private val fileIoContext: ExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(2))
+ private case class FileResult(relativePath: String, file: File)
+
+ private def getFiles(path: String): List[FileResult] = {
+ val rootFile = new File(path)
+ if (rootFile.isDirectory) {
+ val basePath = rootFile.getAbsoluteFile.getParentFile.toPath
+ val stream = Files.walk(rootFile.toPath)
+ try {
+ stream.iterator().asScala.filter(Files.isRegularFile(_)).map { path =>
+ val relative = basePath.relativize(path.toAbsolutePath).toString
+ FileResult(relative, path.toFile)
+ }.toList
+ } finally {
+ stream.close()
+ }
+ } else {
+ List(FileResult(rootFile.getName, rootFile))
+ }
+ }
+
+ def handleDropFile(filePath: String): Unit = {
+ Future {
+ getFiles(filePath).foreach {
+ case FileResult(path, file) =>
+ if (file.length() < 64 * 1024){
+ val content = new String(Files.readAllBytes(file.toPath), StandardCharsets.UTF_8)
+ EventHandler.scheduleClient(() => {
+ buffer.dropFile(path, content, null)
+ })
+ }
+ }
+ }(fileIoContext).failed.foreach{ e =>
+ OpenComputers.log.warn("Failed to handle drop file.", e)
+ }(fileIoContext)
+ }
}
diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala
index 8e05f946dd..02c5cceb58 100644
--- a/src/main/scala/li/cil/oc/common/PacketType.scala
+++ b/src/main/scala/li/cil/oc/common/PacketType.scala
@@ -83,6 +83,7 @@ object PacketType extends Enumeration {
KeyDown,
KeyUp,
Clipboard,
+ DropFile,
MouseClickOrDrag,
MouseScroll,
MouseUp,
diff --git a/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala b/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala
index 59c542f8e0..4e0d0887bd 100644
--- a/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala
+++ b/src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala
@@ -60,6 +60,7 @@ class GpuTextBuffer(val owner: String, val id: Int, val data: li.cil.oc.util.Tex
override def keyDown(character: Char, code: Int, player: EntityPlayer): Unit = {}
override def keyUp(character: Char, code: Int, player: EntityPlayer): Unit = {}
override def clipboard(value: String, player: EntityPlayer): Unit = {}
+ override def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit = {}
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
diff --git a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala
index a45531435f..caf43f8fb2 100644
--- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala
+++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala
@@ -381,6 +381,9 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi
override def clipboard(value: String, player: EntityPlayer): Unit =
proxy.clipboard(value, player)
+ override def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit =
+ proxy.dropFile(fileName, fileContent, player)
+
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit =
proxy.mouseDown(x, y, button, player)
@@ -589,6 +592,8 @@ object TextBuffer {
def clipboard(value: String, player: EntityPlayer): Unit
+ def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit
+
def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit
def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer): Unit
@@ -682,6 +687,11 @@ object TextBuffer {
ClientPacketSender.sendClipboard(nodeAddress, value)
}
+ override def dropFile(fileName: String, fileContent: String, player: EntityPlayer) {
+ debug(s"{type = dropFile}")
+ ClientPacketSender.sendDropFile(nodeAddress, fileName, fileContent)
+ }
+
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer) {
debug(s"{type = mouseDown, x = $x, y = $y, button = $button}")
ClientPacketSender.sendMouseClick(nodeAddress, x, y, drag = false, button)
@@ -819,6 +829,10 @@ object TextBuffer {
sendToKeyboards("keyboard.clipboard", player, value)
}
+ override def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit = {
+ owner.node.sendToReachable("computer.checked_signal", player, "drop_file", fileName, fileContent)
+ }
+
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer) {
sendMouseEvent(player, "touch", x, y, button)
}
diff --git a/src/main/scala/li/cil/oc/integration/Mods.scala b/src/main/scala/li/cil/oc/integration/Mods.scala
index 886a80787e..ac540e4aa7 100644
--- a/src/main/scala/li/cil/oc/integration/Mods.scala
+++ b/src/main/scala/li/cil/oc/integration/Mods.scala
@@ -51,6 +51,7 @@ object Mods {
val IndustrialCraft2 = new SimpleMod(IDs.IndustrialCraft2)
val IndustrialCraft2Classic = new SimpleMod(IDs.IndustrialCraft2Classic)
val IngameWiki = new SimpleMod(IDs.IngameWiki, version = "@[1.1.3,)")
+ val lwjgl3ify = new SimpleMod(IDs.lwjgl3ify, version = "@[3.0.0,)")
val Mekanism = new SimpleMod(IDs.Mekanism)
val MekanismGas = new SimpleMod(IDs.MekanismGas)
val Minecraft = new SimpleMod(IDs.Minecraft)
@@ -114,6 +115,7 @@ object Mods {
integration.gc.ModGalacticraft,
integration.gregtech.ModGregtech,
integration.ic2.ModIndustrialCraft2,
+ integration.lwjgl3ify.ModLwjgl3ify,
integration.mekanism.ModMekanism,
integration.mekanism.gas.ModMekanismGas,
integration.mfr.ModMineFactoryReloaded,
@@ -204,6 +206,7 @@ object Mods {
final val IndustrialCraft2Classic = "IC2-Classic"
final val IndustrialCraft2Spmod = "IC2-Classic-Spmod"
final val IngameWiki = "IGWMod"
+ final val lwjgl3ify = "lwjgl3ify"
final val Mekanism = "Mekanism"
final val MekanismGas = "MekanismAPI|gas"
final val Minecraft = "Minecraft"
diff --git a/src/main/scala/li/cil/oc/integration/lwjgl3ify/IFileUpload.java b/src/main/scala/li/cil/oc/integration/lwjgl3ify/IFileUpload.java
new file mode 100644
index 0000000000..4ecb89ac54
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/lwjgl3ify/IFileUpload.java
@@ -0,0 +1,5 @@
+package li.cil.oc.integration.lwjgl3ify;
+
+public interface IFileUpload {
+ void init();
+}
diff --git a/src/main/scala/li/cil/oc/integration/lwjgl3ify/ModLwjgl3ify.scala b/src/main/scala/li/cil/oc/integration/lwjgl3ify/ModLwjgl3ify.scala
new file mode 100644
index 0000000000..90d7011c00
--- /dev/null
+++ b/src/main/scala/li/cil/oc/integration/lwjgl3ify/ModLwjgl3ify.scala
@@ -0,0 +1,18 @@
+package li.cil.oc.integration.lwjgl3ify
+
+import li.cil.oc.integration.{ModProxy, Mods}
+
+object ModLwjgl3ify extends ModProxy {
+ override def getMod: Mods.SimpleMod = Mods.lwjgl3ify
+
+ override def initialize(): Unit = {
+ try {
+ val clazz = Class.forName("li.cil.oc.integration.lwjgl3ify.FileUploadSupport")
+ val support = clazz.getDeclaredConstructor().newInstance().asInstanceOf[IFileUpload]
+
+ support.init()
+ } catch {
+ case _: Throwable =>
+ }
+ }
+}
diff --git a/src/main/scala/li/cil/oc/server/PacketHandler.scala b/src/main/scala/li/cil/oc/server/PacketHandler.scala
index 16a45d2485..9ff88c4c26 100644
--- a/src/main/scala/li/cil/oc/server/PacketHandler.scala
+++ b/src/main/scala/li/cil/oc/server/PacketHandler.scala
@@ -50,6 +50,7 @@ object PacketHandler extends CommonPacketHandler {
case PacketType.KeyDown => onKeyDown(p)
case PacketType.KeyUp => onKeyUp(p)
case PacketType.Clipboard => onClipboard(p)
+ case PacketType.DropFile => onDropFile(p)
case PacketType.MouseClickOrDrag => onMouseClick(p)
case PacketType.MouseScroll => onMouseScroll(p)
case PacketType.DatabaseSetSlot => onDatabaseSetSlot(p)
@@ -201,6 +202,16 @@ object PacketHandler extends CommonPacketHandler {
}
}
+ def onDropFile(p: PacketParser): Unit = {
+ val address = p.readUTF()
+ val fileName = p.readUTF()
+ val fileContent = p.readUTF()
+ ComponentTracker.get(p.player.worldObj, address) match {
+ case Some(buffer: api.internal.TextBuffer) => buffer.dropFile(fileName, fileContent, p.player.asInstanceOf[EntityPlayer])
+ case _ => // Invalid Packet
+ }
+ }
+
def onMouseClick(p: PacketParser) {
val address = p.readUTF()
val x = p.readFloat()