diff --git a/build.gradle b/build.gradle
index 8cf5cab6..713fd6ba 100644
--- a/build.gradle
+++ b/build.gradle
@@ -485,9 +485,6 @@ project(':cleanroom') {
installer "io.netty:netty-codec-dns:$props.netty_version"
installer "io.netty:netty-resolver-dns:$props.netty_version"
- // installer 'io.netty:netty-all:4.1.93.Final'
-
- // TODO: Unpin in 1.18.1 or when Mojang bumps the Log4J version
installer 'org.apache.logging.log4j:log4j-api:2.24.1'
installer 'org.apache.logging.log4j:log4j-core:2.24.1'
installer 'org.apache.logging.log4j:log4j-slf4j2-impl:2.24.1'
@@ -504,6 +501,8 @@ project(':cleanroom') {
installer 'jakarta.activation:jakarta.activation-api:2.1.3'
installer 'org.glassfish.corba:glassfish-corba-omgapi:4.2.5'
installer 'org.openjdk.nashorn:nashorn-core:15.4'
+ installer 'ca.weblite:java-objc-bridge:1.2'
+
// Mixin
installer 'com.cleanroommc:sponge-mixin:0.20.7+mixin.0.8.7'
diff --git a/src/main/java/com/cleanroommc/client/IMEHandler.java b/src/main/java/com/cleanroommc/client/IMEHandler.java
index 1740582e..4752efcb 100644
--- a/src/main/java/com/cleanroommc/client/IMEHandler.java
+++ b/src/main/java/com/cleanroommc/client/IMEHandler.java
@@ -1,5 +1,6 @@
package com.cleanroommc.client;
+import com.cleanroommc.client.ime.CocoaIMEHandler;
import com.cleanroommc.client.ime.WindowsIMEHandler;
import net.minecraftforge.fml.common.FMLLog;
import org.lwjgl.glfw.GLFW;
@@ -11,7 +12,8 @@ public class IMEHandler {
static {
switch (GLFW.glfwGetPlatform()) {
case GLFW.GLFW_PLATFORM_WIN32 -> instance = new WindowsIMEHandler();
- default -> FMLLog.log.warn("Unsupported platform: " + GLFW.glfwGetPlatform());
+ case GLFW.GLFW_PLATFORM_COCOA -> instance = new CocoaIMEHandler();
+ default -> FMLLog.log.warn("Unsupported platform: {}", GLFW.glfwGetPlatform());
}
}
public static void setIME(boolean active) {
diff --git a/src/main/java/com/cleanroommc/client/ime/CocoaIMEHandler.java b/src/main/java/com/cleanroommc/client/ime/CocoaIMEHandler.java
index 03491f0b..1dfa61e0 100644
--- a/src/main/java/com/cleanroommc/client/ime/CocoaIMEHandler.java
+++ b/src/main/java/com/cleanroommc/client/ime/CocoaIMEHandler.java
@@ -1,13 +1,72 @@
package com.cleanroommc.client.ime;
+import ca.weblite.objc.Runtime;
+import ca.weblite.objc.RuntimeUtils;
+import com.sun.jna.Callback;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
import com.sun.jna.Pointer;
import java.util.function.Consumer;
+/**
+ * Copied from IMBlocker
+ */
public class CocoaIMEHandler implements Consumer {
+ private static boolean active = false;
+ static private final Pointer viewClass = Runtime.INSTANCE.objc_getClass("GLFWContentView");
+ static private Pointer view = null;
+ static private final InterpretKeyEventsCallback Imp;
+ static private final InterpretKeyEventsCallback NewImp;
+
+ static {
+ // see https://github.com/glfw/glfw/blob/b4c3ef9d0fdf46845f3e81e5d989dab06e71e6c1/src/cocoa_window.m#L571
+ // Replacing the method dynamically to determine whether to send text based on state
+ // see reference for objc_runtime's dynamic manipulation at https://developer.apple.com/documentation/objectivec/objective-c_runtime
+ var selector = RuntimeUtils.sel("interpretKeyEvents:");
+ var method = Runtime.INSTANCE.class_getInstanceMethod(viewClass, selector);
+ Imp = ObjC.INSTANCE.method_getImplementation(method);
+ NewImp = (self, sel, eventArray) -> {
+ if (view == null) view = self;
+ if (!active) {
+ var textInputContext = RuntimeUtils.cls("NSTextInputContext");
+ var current = RuntimeUtils.msgPointer(textInputContext, "currentInputContext");
+ RuntimeUtils.msg(current, "discardMarkedText");
+ return;
+ }
+ Imp.invoke(self, sel, eventArray);
+ };
+ ObjC.INSTANCE.class_replaceMethod(viewClass, selector, NewImp, "v@:@");
+ }
+ /**
+ * The underlying native type is IMP, which should be a function pointer to the implementation of interpretKeyEvents:
+ * @see Documentation for IMP
+ * @see Documentation for interpretKeyEvents:
+ */
+ private interface InterpretKeyEventsCallback extends Callback {
+ /**
+ * @param self "this" pointer for NSObject
+ * @param selector selector for interpretKeyEvents:
+ * @param eventArray an array of NSEvent objects
+ */
+ void invoke(Pointer self, Pointer selector, Pointer eventArray);
+ }
+
+ /**
+ * @see Apple Developer Documentation for objc_runtime:
+ */
+ private interface ObjC extends Library {
+ ObjC INSTANCE = Native.load("objc.A", ObjC.class);
+
+ void class_replaceMethod(Pointer cls, Pointer selector, InterpretKeyEventsCallback imp, String types);
+
+ InterpretKeyEventsCallback method_getImplementation(Pointer selector);
+ }
@Override
public void accept(Boolean aBoolean) {
-
+ if (active != aBoolean) {
+ active = aBoolean;
+ }
}
}
diff --git a/src/main/java/com/cleanroommc/client/ime/WindowsIMEHandler.java b/src/main/java/com/cleanroommc/client/ime/WindowsIMEHandler.java
index e8f04d68..bcc23258 100644
--- a/src/main/java/com/cleanroommc/client/ime/WindowsIMEHandler.java
+++ b/src/main/java/com/cleanroommc/client/ime/WindowsIMEHandler.java
@@ -6,8 +6,10 @@
import com.sun.jna.platform.win32.WinNT;
import java.util.function.Consumer;
-import java.util.function.Supplier;
+/**
+ * Copied from IMBlocker
+ */
public class WindowsIMEHandler implements Consumer {
private static native WinNT.HANDLE ImmGetContext(WinDef.HWND hwnd);