From 972c2db109717327497e783a424933564619af07 Mon Sep 17 00:00:00 2001 From: hinyb <40139991+hinyb@users.noreply.github.com> Date: Thu, 30 Jul 2026 06:06:40 +0800 Subject: [PATCH] fix(clipboard): make clipbboard signal batching opt-in for backward compatibility --- src/main/resources/application.conf | 14 +++++++++++++- src/main/scala/li/cil/oc/Settings.scala | 11 ++++++++++- src/main/scala/li/cil/oc/client/PacketSender.scala | 8 +------- .../scala/li/cil/oc/server/PacketHandler.scala | 8 +++----- .../li/cil/oc/server/component/Keyboard.scala | 13 +++++++------ 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 074c3a4fc0..73d919519a 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1257,10 +1257,22 @@ opencomputers { # Note: also applies to the motion sensor. inputUsername: true + # Enables batching mode for clipboard pasting. + # When set to true, clipboard signals are sent in fixed-size chunks + # rather than line-by-line. + # WARNING: Enabling this changes the 'clipboard' signal behavior and + # may break existing Lua scripts that expect per-line signals. + enableClipboardBatching: false + + # The maximum total length of pasted clipboard content. + # Note: Only applied if enableClipboardBatching = false. + maxClipboardSize: 65536 + # The maximum length of each clipboard signal. Clipboard contents are # split into chunks of this size, independent of line breaks, so one # signal can contain multiple short lines. - maxClipboard: 256 + # Note: Only applied if enableClipboardBatching = true. + clipboardBatchSize: 256 # The TTL (Time-To-Live) upon creation of a network packet. When a packet # passes through a Relay, its TTL is decremented. If a Relay receives a diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 1f4fa92da1..9cd46b1216 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -343,7 +343,16 @@ class Settings(val config: Config) { val maxScreenWidth = config.getInt("misc.maxScreenWidth") max 1 val maxScreenHeight = config.getInt("misc.maxScreenHeight") max 1 val inputUsername = config.getBoolean("misc.inputUsername") - val maxClipboard = config.getInt("misc.maxClipboard") max 1 + val enableClipboardBatching = config.getBoolean("misc.enableClipboardBatching") + val maxClipboardSize = config.getInt("misc.maxClipboardSize") + val clipboardBatchSize = config.getInt("misc.clipboardBatchSize") max 1 + def maxClipboardLength: Int = { + if (enableClipboardBatching) { + val value = clipboardBatchSize.toLong * maxSignalQueueSize.toLong + if (value > Int.MaxValue) Int.MaxValue else value.toInt + } + else maxClipboardSize + } val initialNetworkPacketTTL = config.getInt("misc.initialNetworkPacketTTL") max 5 val maxNetworkPacketSize = config.getInt("misc.maxNetworkPacketSize") max 0 // Need at least 4 for nanomachine protocol. Because I can! diff --git a/src/main/scala/li/cil/oc/client/PacketSender.scala b/src/main/scala/li/cil/oc/client/PacketSender.scala index 204d3f17da..552b377245 100644 --- a/src/main/scala/li/cil/oc/client/PacketSender.scala +++ b/src/main/scala/li/cil/oc/client/PacketSender.scala @@ -14,12 +14,6 @@ import net.minecraft.util.ResourceLocation import net.minecraftforge.common.util.ForgeDirection object PacketSender { - // The server can queue this many clipboard chunks. Keep the client-side - // whole-paste limit in line with that capacity instead of imposing the - // unrelated historical 64 KiB limit. - private def maxClipboardLength: Long = - Settings.get.maxClipboard.toLong * Settings.get.maxSignalQueueSize - // Timestamp after which the next clipboard message may be sent. Used to // avoid spamming large packets on key repeat. protected var clipboardCooldown = 0L @@ -78,7 +72,7 @@ object PacketSender { def sendClipboard(address: String, value: String) { if (value != null && !value.isEmpty) { - if (value.length.toLong > maxClipboardLength || System.currentTimeMillis() < clipboardCooldown) { + if (value.length > Settings.get.maxClipboardLength || System.currentTimeMillis() < clipboardCooldown) { 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)) diff --git a/src/main/scala/li/cil/oc/server/PacketHandler.scala b/src/main/scala/li/cil/oc/server/PacketHandler.scala index f363bbc29c..a730814ab9 100644 --- a/src/main/scala/li/cil/oc/server/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/server/PacketHandler.scala @@ -2,9 +2,7 @@ package li.cil.oc.server import cpw.mods.fml.common.eventhandler.SubscribeEvent import cpw.mods.fml.common.network.FMLNetworkEvent.ServerCustomPacketEvent -import li.cil.oc.Localization -import li.cil.oc.OpenComputers -import li.cil.oc.api +import li.cil.oc.{Localization, OpenComputers, Settings, api} import li.cil.oc.api.internal.Server import li.cil.oc.api.machine.Machine import li.cil.oc.common.Achievement @@ -211,7 +209,7 @@ object PacketHandler extends CommonPacketHandler { def onClipboard(p: PacketParser): Unit = { val address = p.readUTF() val copy = p.readUTF() - if (copy.length > maxClientTextLength) return // Oversized; likely a forged client. + if (copy.length > Settings.get.maxClipboardLength) return // Oversized; likely a forged client. ComponentTracker.get(p.player.worldObj, address) match { case Some(buffer: api.internal.TextBuffer) => buffer.clipboard(copy, p.player.asInstanceOf[EntityPlayer]) case _ => // Invalid Packet @@ -393,4 +391,4 @@ object PacketHandler extends CommonPacketHandler { case _ => // Invalid packet. } } -} \ No newline at end of file +} diff --git a/src/main/scala/li/cil/oc/server/component/Keyboard.scala b/src/main/scala/li/cil/oc/server/component/Keyboard.scala index eb70c0118e..cc9547c8c4 100644 --- a/src/main/scala/li/cil/oc/server/component/Keyboard.scala +++ b/src/main/scala/li/cil/oc/server/component/Keyboard.scala @@ -89,11 +89,8 @@ class Keyboard(val host: EnvironmentHost) extends prefab.ManagedEnvironment with } case Array(p: EntityPlayer, value: String) if message.name == "keyboard.clipboard" => if (isUseableByPlayer(p)) { - // linesWithSeparators is used here deliberately: unlike lines it - // retains the newline characters. The helper then fills parts by - // character capacity, crossing line boundaries when possible and - // splitting individual long lines when necessary. - for (part <- clipboardParts(value)) { + val it = if (Settings.get.enableClipboardBatching) clipboardParts(value) else value.linesWithSeparators + for (part <- it) { if (Settings.get.inputUsername) { signal(p, "clipboard", part, p.getCommandSenderName) } @@ -106,8 +103,12 @@ class Keyboard(val host: EnvironmentHost) extends prefab.ManagedEnvironment with } } + // linesWithSeparators is used here deliberately: unlike lines it + // retains the newline characters. The helper then fills parts by + // character capacity, crossing line boundaries when possible and + // splitting individual long lines when necessary. private def clipboardParts(value: String): Iterator[String] = { - val limit = Settings.get.maxClipboard max 1 + val limit = Settings.get.clipboardBatchSize max 1 val parts = mutable.ArrayBuffer.empty[String] val current = new mutable.StringBuilder