diff --git a/.gitignore b/.gitignore index 12f8644..9746858 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ build # other eclipse run +runs # Files from Forge MDK forge*changelog.txt diff --git a/README.md b/README.md index 2e768a9..9962f0d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,20 @@ -# MC-122477 Fix -[![CurseForge](https://cf.way2muchnoise.eu/versions/432448.svg)](https://www.curseforge.com/minecraft/mc-mods/mc122477fix) +# Linux T Prefix on Opening Chat ![GitHub License](https://img.shields.io/github/license/RecursiveG/Mc122477Fix) [![Mojira issue MC-122477](https://img.shields.io/jira/issue/MC-122477?baseUrl=https%3A%2F%2Fbugs.mojang.com)](https://bugs.mojang.com/browse/MC-122477) Fixes the extra `t` or `/` issue on some Linux desktop environments when opening the chat window. +This is the NeoForge port of [RecursiveG's MC-122477 Fix](https://github.com/RecursiveG/Mc122477Fix). The original project and this port are licensed under the MIT License. + +## NeoForge 1.21.1 port + +This branch ports the client-side fix to Minecraft 1.21.1 using NeoForge 21.1 and Java 21. +Build it with `JAVA_HOME=/path/to/java-21 ./gradlew build`; the distributable JAR is written to `build/libs`. + ## Downloads -- [CurseForge](https://www.curseforge.com/minecraft/mc-mods/mc122477fix) -- [Modrinth](https://modrinth.com/mod/mc122477fix) -- [GitHub releases](https://github.com/RecursiveG/Mc122477Fix/releases) +- [NeoForge port releases](https://github.com/bomxacalaka/Mc122477Fix/releases) +- [Original project on CurseForge](https://www.curseforge.com/minecraft/mc-mods/mc122477fix) +- [Original project on Modrinth](https://modrinth.com/mod/mc122477fix) ## What is this bug? Thank you to `__null` on Mojira for their help in discovering the source of the bug. MC-122477 arises from an issue originating in GLFW (see [GLFW/glfw#1794](https://github.com/glfw/GLFW/issues/1794)). The Minecraft client polls for GLFW events twice per frame. A key press event is an event created when the player presses a key in the game. It is used to process game input like WASD. A char type event is also an event created when the player presses a key and is used to process text input. Key press events are always processed before char type events. In a normal environment, a key press and char type event would be polled at the same time. However, on some Linux desktop environments, the key press event and char type event are received on *separate* polls, allowing for the possibility of a game tick to occur between receiving the key press and char type events. Because of this possible extra game tick in between the two events, the key press event can be processed to open the chat on the game tick, and then the char type event is processed after the chat has already opened, causing an extra character to be typed. This doesn't happen on Windows, Mac OS, and the remaining Linux desktop environments because it is not possible for the chat to already be open when the char type event for opening chat (e.g. the character `t`) is processed. @@ -18,5 +24,5 @@ Thank you to `__null` on Mojira for their help in discovering the source of the ### Fabric The Fabric version of the mod injects Mixin callbacks into `Keyboard#onKey` and `Keyboard#onChar` to listen for key press and char type events from [GLFW](https://github.com/glfw/GLFW). A Mixin is also injected into `MinecraftClient#tick` on 1.14 and `RenderSystem#flipFrame` on 1.15+ to listen for GLFW event polls. The mod then keeps track of how many polls have been processed since the game started in a poll counter. The mod stores this poll count separately when a key press event is detected for the chat open key or command key. The first char type event that is received within 5 polls after the poll of the original key press is then canceled, and the stored field is reset for the next time. This fixes the bug because it stops the char type event from ever being able to be processed and is only executed when the chat is first opened. -### Forge -The Forge version of the mod uses built-in Forge events to listen for the opening of the chat screen and then stores the timestamp at which it was opened. If a char type event is detected within 50 milliseconds of the chat screen originally opening, the char type event is canceled. This has problems because slower hardware might have the char type event processed sometime after 50 milliseconds has already occurred since the opening of the chat. This rudimentary version is similar to how the original Fabric version worked until 1.1+. However, this method is still effective at catching most of the extra char type events from being processed. +### Forge / NeoForge +The Forge-family version uses built-in client events to detect the chat or creative inventory screen opening. Key and character events arriving during the first two rendered frames are canceled, preventing the delayed opening character from reaching the newly opened text field. diff --git a/build.gradle b/build.gradle index 1c61f43..20bd94f 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.gradle.userdev' version '7.0.97' + id 'net.neoforged.gradle.userdev' version '7.1.38' } version = mod_version @@ -14,11 +14,11 @@ repositories { } base { - archivesName = mod_id + archivesName = 'linux-t-prefix-on-opening-chat' } -// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. -java.toolchain.languageVersion = JavaLanguageVersion.of(17) +// Minecraft 1.21.1 requires Java 21. +java.toolchain.languageVersion = JavaLanguageVersion.of(21) //minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') //minecraft.accessTransformers.entry public net.minecraft.client.Minecraft textureManager # textureManager @@ -132,7 +132,7 @@ tasks.withType(ProcessResources).configureEach { ] inputs.properties replaceProperties - filesMatching(['META-INF/mods.toml']) { + filesMatching(['META-INF/neoforge.mods.toml']) { expand replaceProperties } } @@ -162,4 +162,3 @@ idea { downloadJavadoc = true } } - diff --git a/gradle.properties b/gradle.properties index 6fc2b55..47f2b22 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,24 +3,20 @@ org.gradle.daemon=false org.gradle.debug=false -#read more on this at https://github.com/neoforged/NeoGradle/blob/NG_7.0/README.md#apply-parchment-mappings -# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started -neogradle.subsystems.parchment.minecraftVersion=1.20.3 -neogradle.subsystems.parchment.mappingsVersion=2023.12.31 # Environment Properties # You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge # The Minecraft version must agree with the Neo version to get a valid artifact -minecraft_version=1.20.4 +minecraft_version=1.21.1 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.20.4,1.21) +minecraft_version_range=[1.21.1,1.21.2) # The Neo version must agree with the Minecraft version to get a valid artifact -neo_version=20.4.147-beta +neo_version=21.1.241 # The Neo version range can use any version of Neo as bounds -neo_version_range=[20.4,) +neo_version_range=[21.1,21.2) # The loader version range can only use the major version of FML as bounds -loader_version_range=[2,) +loader_version_range=[4,) ## Mod Properties @@ -28,16 +24,16 @@ loader_version_range=[2,) # Must match the String constant located in the main mod class annotated with @Mod. mod_id=mc122477fix # The human-readable display name for the mod. -mod_name=MC-122477 Fix +mod_name=Linux T Prefix on Opening Chat # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=1.1.2 +mod_version=1.1.3-mc1.21.1-neoforge # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html mod_group_id=me.recursiveg # The authors of the mod. This is a simple text string that is used for display purposes in the mod list. -mod_authors=RecursiveG +mod_authors=RecursiveG; NeoForge ports by bomxacalaka # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. -mod_description="Fix MC-122477, which causes double-typing 't' or '/' on Linux." +mod_description=Fixes the extra 't' or '/' typed when opening Minecraft chat on Linux. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22c..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/me/recursiveg/Mc122477Fix.java b/src/main/java/me/recursiveg/Mc122477Fix.java index e75f0d0..c8e2d7d 100644 --- a/src/main/java/me/recursiveg/Mc122477Fix.java +++ b/src/main/java/me/recursiveg/Mc122477Fix.java @@ -2,45 +2,54 @@ import net.minecraft.client.gui.screens.ChatScreen; import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen; +import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.client.event.RenderFrameEvent; import net.neoforged.neoforge.client.event.ScreenEvent; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.TickEvent; -@Mod("mc122477fix") -public class Mc122477Fix { +@Mod(value = Mc122477Fix.MOD_ID, dist = Dist.CLIENT) +public final class Mc122477Fix { + public static final String MOD_ID = "mc122477fix"; + public Mc122477Fix() { - NeoForge.EVENT_BUS.register(this); } - long renderTicksSinceScreenOpen = 0; + @EventBusSubscriber(modid = MOD_ID, value = Dist.CLIENT) + public static final class ClientEvents { + private static long framesSinceScreenOpen = Long.MAX_VALUE; - @SubscribeEvent - public void onScreenOpen(ScreenEvent.Opening ev) { - if (ev.getNewScreen() instanceof ChatScreen || ev.getNewScreen() instanceof CreativeModeInventoryScreen) { - renderTicksSinceScreenOpen = 0; + private ClientEvents() { } - } - @SubscribeEvent - public void onKeyPressed(ScreenEvent.KeyPressed.Pre ev) { - if (renderTicksSinceScreenOpen < 2) { - ev.setCanceled(true); + @SubscribeEvent + public static void onScreenOpen(ScreenEvent.Opening event) { + if (event.getNewScreen() instanceof ChatScreen + || event.getNewScreen() instanceof CreativeModeInventoryScreen) { + framesSinceScreenOpen = 0; + } } - } - @SubscribeEvent - public void onCharTyped(ScreenEvent.CharacterTyped.Pre ev) { - if (renderTicksSinceScreenOpen < 2) { - ev.setCanceled(true); + @SubscribeEvent + public static void onKeyPressed(ScreenEvent.KeyPressed.Pre event) { + if (framesSinceScreenOpen < 2) { + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onCharTyped(ScreenEvent.CharacterTyped.Pre event) { + if (framesSinceScreenOpen < 2) { + event.setCanceled(true); + } } - } - @SubscribeEvent - public void onPostClientTick(TickEvent.RenderTickEvent ev) { - if (ev.phase == TickEvent.Phase.END) { - renderTicksSinceScreenOpen++; + @SubscribeEvent + public static void onPostRenderFrame(RenderFrameEvent.Post event) { + if (framesSinceScreenOpen < 2) { + framesSinceScreenOpen++; + } } } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml deleted file mode 100644 index 587cef0..0000000 --- a/src/main/resources/META-INF/mods.toml +++ /dev/null @@ -1,100 +0,0 @@ -# This is an example mods.toml file. It contains the data relating to the loading mods. -# There are several mandatory fields (#mandatory), and many more that are optional (#optional). -# The overall format is standard TOML format, v0.5.0. -# Note that there are a couple of TOML lists in this file. -# Find more information on toml format here: https://github.com/toml-lang/toml -# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml -modLoader="javafml" #mandatory - -# A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. -loaderVersion="${loader_version_range}" #mandatory - -# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. -# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. -license="${mod_license}" - -# A URL to refer people to when problems occur with this mod -#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional - -# A list of mods - how many allowed here is determined by the individual mod loader -[[mods]] #mandatory - -# The modid of the mod -modId="${mod_id}" #mandatory - -# The version number of the mod -version="${mod_version}" #mandatory - -# A display name for the mod -displayName="${mod_name}" #mandatory - -# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ -#updateJSONURL="https://change.me.example.invalid/updates.json" #optional - -# A URL for the "homepage" for this mod, displayed in the mod UI -#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional - -# A file name (in the root of the mod JAR) containing a logo for display -#logoFile="examplemod.png" #optional - -# A text field displayed in the mod UI -#credits="" #optional - -# A text field displayed in the mod UI -authors="${mod_authors}" #optional - -# Display Test controls the display for your mod in the server connection screen -# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. -# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. -# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. -# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. -# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. -#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) - -# The description text for the mod (multi line!) (#mandatory) -description='''${mod_description}''' - -# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. -#[[mixins]] -#config="${mod_id}.mixins.json" - -# The [[accessTransformers]] block allows you to declare where your AT file is. -# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg -#[[accessTransformers]] -#file="META-INF/accesstransformer.cfg" - -# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json - -# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. -[[dependencies.${mod_id}]] #optional - # the modid of the dependency - modId="neoforge" #mandatory - # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). - # 'required' requires the mod to exist, 'optional' does not - # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning - type="required" #mandatory - # Optional field describing why the dependency is required or why it is incompatible - # reason="..." - # The version range of the dependency - versionRange="${neo_version_range}" #mandatory - # An ordering relationship for the dependency. - # BEFORE - This mod is loaded BEFORE the dependency - # AFTER - This mod is loaded AFTER the dependency - ordering="NONE" - # Side this dependency is applied on - BOTH, CLIENT, or SERVER - side="BOTH" - -# Here's another dependency -[[dependencies.${mod_id}]] - modId="minecraft" - type="required" - # This version range declares a minimum of the current minecraft version up to but not including the next major version - versionRange="${minecraft_version_range}" - ordering="NONE" - side="BOTH" - -# Features are specific properties of the game environment, that you may want to declare you require. This example declares -# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't -# stop your mod loading on the server for example. -#[features.${mod_id}] -#openGLVersion="[3.2,)" diff --git a/src/main/resources/META-INF/neoforge.mods.toml b/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..241c460 --- /dev/null +++ b/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,26 @@ +modLoader="javafml" +loaderVersion="${loader_version_range}" +license="${mod_license}" +issueTrackerURL="https://github.com/bomxacalaka/Mc122477Fix/issues" + +[[mods]] +modId="${mod_id}" +version="${mod_version}" +displayName="${mod_name}" +displayURL="https://github.com/bomxacalaka/Mc122477Fix" +authors="${mod_authors}" +description='''${mod_description}''' + +[[dependencies.${mod_id}]] +modId="neoforge" +type="required" +versionRange="${neo_version_range}" +ordering="NONE" +side="CLIENT" + +[[dependencies.${mod_id}]] +modId="minecraft" +type="required" +versionRange="${minecraft_version_range}" +ordering="NONE" +side="CLIENT"