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()