diff --git a/src/main/java/net/silentclient/client/utils/MouseCursorHandler.java b/src/main/java/net/silentclient/client/utils/MouseCursorHandler.java index 9251e75..55f44b9 100644 --- a/src/main/java/net/silentclient/client/utils/MouseCursorHandler.java +++ b/src/main/java/net/silentclient/client/utils/MouseCursorHandler.java @@ -1,5 +1,6 @@ package net.silentclient.client.utils; +import net.silentclient.client.utils.cursors.SystemCursors; import org.apache.logging.log4j.LogManager; import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; @@ -69,56 +70,75 @@ public class MouseCursorHandler { } public boolean enableCursor(final CursorType cursorType) { - if (this.customCursorDisabled) { - return false; - } - if (this.currentCursor.equals(cursorType)) { + if(SystemCursors.isSupported()) { + SystemCursors.setCursor(cursorType.getCursor()); return true; - } - if (cursorType.equals(CursorType.NORMAL)) { - this.disableCursor(); - return true; - } - try { - final Cursor nativeCursor = this.cursors.get(cursorType); - if (nativeCursor != null) { - Mouse.setNativeCursor(nativeCursor); - this.currentCursor = cursorType; + } else { + if (this.customCursorDisabled) { + return false; + } + if (this.currentCursor.equals(cursorType)) { + return true; + } + if (cursorType.equals(CursorType.NORMAL)) { + this.disableCursor(); return true; } - } - catch (final Exception ex) { - LogManager.getLogger().catching((Throwable)ex); - } - return false; - } - - public void disableCursor() { - if (this.customCursorDisabled) { - return; - } - if (this.currentCursor != CursorType.NORMAL) { try { - Mouse.setNativeCursor((Cursor)null); - this.currentCursor = CursorType.NORMAL; + final Cursor nativeCursor = this.cursors.get(cursorType); + if (nativeCursor != null) { + Mouse.setNativeCursor(nativeCursor); + this.currentCursor = cursorType; + return true; + } } catch (final Exception ex) { LogManager.getLogger().catching((Throwable)ex); } + return false; + } + } + + public void disableCursor() { + if(SystemCursors.isSupported()) { + SystemCursors.setCursor(SystemCursors.ARROW); + } else { + if (this.customCursorDisabled) { + return; + } + if (this.currentCursor != CursorType.NORMAL) { + try { + Mouse.setNativeCursor((Cursor)null); + this.currentCursor = CursorType.NORMAL; + } + catch (final Exception ex) { + LogManager.getLogger().catching((Throwable)ex); + } + } } } public CursorType getCurrentCursor() { - return currentCursor; + return CursorType.NORMAL; } public enum CursorType { - NORMAL, - NESW_RESIZE, - NWSE_RESIZE, - MOVE, - EDIT_TEXT, - POINTER; + NORMAL(SystemCursors.ARROW), + NESW_RESIZE(SystemCursors.RESIZE_NESW), + NWSE_RESIZE(SystemCursors.RESIZE_NWSE), + MOVE(SystemCursors.CROSSHAIR), + EDIT_TEXT(SystemCursors.IBEAM), + POINTER(SystemCursors.POINTING_HAND); + + private final byte cursor; + + CursorType(byte cursor) { + this.cursor = cursor; + } + + public byte getCursor() { + return cursor; + } } } diff --git a/src/main/java/net/silentclient/client/utils/cursors/SystemCursors.java b/src/main/java/net/silentclient/client/utils/cursors/SystemCursors.java new file mode 100644 index 0000000..1b34197 --- /dev/null +++ b/src/main/java/net/silentclient/client/utils/cursors/SystemCursors.java @@ -0,0 +1,56 @@ +package net.silentclient.client.utils.cursors; + +import org.apache.logging.log4j.*; +import org.lwjgl.LWJGLUtil; +import org.lwjgl.input.Mouse; + +public class SystemCursors { + public static final byte ARROW = 0; + public static final byte IBEAM = 1; + public static final byte CROSSHAIR = 2; + public static final byte POINTING_HAND = 3; + public static final byte RESIZE_EW = 4; + public static final byte RESIZE_NS = 5; + public static final byte RESIZE_NWSE = 6; + public static final byte RESIZE_NESW = 7; + public static final byte ALL_CURSOR = 8; + public static final byte NOT_ALLOWED = 9; + public static final byte SIZE = size(); + private static final Logger LOGGER = LogManager.getLogger(); + private static boolean supported = true; + + static void markUnsupported() { + supported = false; + } + + public static void setCursor(byte cursor) { + if (!supported) + return; + + try { + if (cursor == ARROW) { + Mouse.setNativeCursor(null); + return; + } + + if (cursor < 0 || cursor >= SIZE) + throw new IllegalArgumentException(Byte.toString(cursor)); + + if (LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_LINUX || LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_MACOSX) + X11SystemCursors.setCursor(cursor); + else if (LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_WINDOWS) + Win32SystemCursors.setCursor(cursor); + } catch (Throwable error) { + LOGGER.error("Error occured; not trying again", error); + markUnsupported(); + } + } + + public static boolean isSupported() { + return supported; + } + + private static byte size() { + return NOT_ALLOWED + 1; + } +} diff --git a/src/main/java/net/silentclient/client/utils/cursors/Util.java b/src/main/java/net/silentclient/client/utils/cursors/Util.java new file mode 100644 index 0000000..41c0723 --- /dev/null +++ b/src/main/java/net/silentclient/client/utils/cursors/Util.java @@ -0,0 +1,77 @@ +package net.silentclient.client.utils.cursors; + +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.InputImplementation; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +public class Util { + private static final Logger LOGGER = LogManager.getLogger(); + private static MethodHandle getInputImplementationMethod; + private static MethodHandle getDisplayImplementationMethod; + + static { + try { + Method getInputImplementation = Mouse.class.getDeclaredMethod("getImplementation"); + getInputImplementation.setAccessible(true); + getInputImplementationMethod = MethodHandles.lookup().unreflect(getInputImplementation); + + Method getDisplayImplementation = Display.class.getDeclaredMethod("getImplementation"); + getDisplayImplementation.setAccessible(true); + getDisplayImplementationMethod = MethodHandles.lookup().unreflect(getDisplayImplementation); + } catch (Throwable error) { + LOGGER.error("Could not perform reflection", error); + } + } + + public static RuntimeException sneakyThrow(Throwable error) { + if (error instanceof Error) + throw (Error) error; + if (error instanceof RuntimeException) + throw (RuntimeException) error; + if (error instanceof IOException) + throw new UncheckedIOException((IOException) error); + + throw new IllegalStateException(error); + } + + public static boolean foundInputImplementationMethod() { + return getInputImplementationMethod != null; + } + + public static InputImplementation getInputImplementation() { + try { + return (InputImplementation) getInputImplementationMethod.invokeExact(); + } catch (Throwable error) { + throw sneakyThrow(error); + } + } + + public static Object getDisplayImplementation() { + try { + return getDisplayImplementationMethod.invoke(); + } catch (Throwable error) { + throw sneakyThrow(error); + } + } + + public static void loadLibrary(String name) throws IOException { + String resourceName = System.mapLibraryName(name); + File file = File.createTempFile(resourceName, ""); + file.deleteOnExit(); + try (InputStream in = Util.class.getResourceAsStream('/' + resourceName)) { + FileUtils.copyInputStreamToFile(in, file); + } + System.load(file.getAbsolutePath()); + } +} diff --git a/src/main/java/net/silentclient/client/utils/cursors/Win32SystemCursors.java b/src/main/java/net/silentclient/client/utils/cursors/Win32SystemCursors.java new file mode 100644 index 0000000..41eb6b4 --- /dev/null +++ b/src/main/java/net/silentclient/client/utils/cursors/Win32SystemCursors.java @@ -0,0 +1,54 @@ +package net.silentclient.client.utils.cursors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +public class Win32SystemCursors { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Long[] CACHE = new Long[SystemCursors.SIZE]; + private static MethodHandle getHwndMethod; + + static { + if (Util.foundInputImplementationMethod()) { + try { + Class windowsDisplay = Class.forName("org.lwjgl.opengl.WindowsDisplay"); + Method getHwnd = windowsDisplay.getDeclaredMethod("getHwnd"); + getHwnd.setAccessible(true); + getHwndMethod = MethodHandles.lookup().unreflect(getHwnd); + + Util.loadLibrary("lwjglLegacyCursorsWin32"); + } catch (Throwable error) { + LOGGER.error("Could not perform reflection/load natives", error); + SystemCursors.markUnsupported(); + } + } else + SystemCursors.markUnsupported(); + } + + public static void setCursor(byte cursor) { + nSetCursor(getHwnd(), getDefaultCursorHandle(cursor)); + } + + private static long getDefaultCursorHandle(byte cursor) { + if (CACHE[cursor] != null) + return CACHE[cursor]; + + return CACHE[cursor] = nGetDefaultCursorHandle(cursor); + } + + private static long getHwnd() { + try { + return (long) getHwndMethod.invoke(Util.getDisplayImplementation()); + } catch (Throwable error) { + throw Util.sneakyThrow(error); + } + } + + private static native long nGetDefaultCursorHandle(byte cursor); + + private static native void nSetCursor(long hwnd, long cursor); +} diff --git a/src/main/java/net/silentclient/client/utils/cursors/X11SystemCursors.java b/src/main/java/net/silentclient/client/utils/cursors/X11SystemCursors.java new file mode 100644 index 0000000..1a2078d --- /dev/null +++ b/src/main/java/net/silentclient/client/utils/cursors/X11SystemCursors.java @@ -0,0 +1,53 @@ +package net.silentclient.client.utils.cursors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.LWJGLException; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +public class X11SystemCursors { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Long[] CACHE = new Long[SystemCursors.SIZE]; + private static MethodHandle getDisplayMethod; + + static { + if (Util.foundInputImplementationMethod()) { + try { + Class linuxDisplay = Class.forName("org.lwjgl.opengl.LinuxDisplay"); + Method getDisplay = linuxDisplay.getDeclaredMethod("getDisplay"); + getDisplay.setAccessible(true); + getDisplayMethod = MethodHandles.lookup().unreflect(getDisplay); + + Util.loadLibrary("lwjglLegacyCursorsX11"); + } catch (Throwable error) { + LOGGER.error("Could not perform reflection/load natives", error); + SystemCursors.markUnsupported(); + } + } else + SystemCursors.markUnsupported(); + } + + public static void setCursor(byte cursor) throws LWJGLException { + Util.getInputImplementation().setNativeCursor(getDefaultCursorHandle(cursor)); + } + + private static long getDefaultCursorHandle(byte cursor) { + if (CACHE[cursor] != null) + return CACHE[cursor]; + + return CACHE[cursor] = nGetDefaultCursorHandle(getDisplay(), cursor); + } + + private static long getDisplay() { + try { + return (long) getDisplayMethod.invokeExact(); + } catch (Throwable error) { + throw Util.sneakyThrow(error); + } + } + + private static native long nGetDefaultCursorHandle(long display, byte cursor); +} diff --git a/src/main/resources/liblwjglLegacyCursorsX11.c b/src/main/resources/liblwjglLegacyCursorsX11.c new file mode 100644 index 0000000..4756d74 --- /dev/null +++ b/src/main/resources/liblwjglLegacyCursorsX11.c @@ -0,0 +1,23 @@ +#include +#include + +const char *LOOKUP[] = { + "default", + "text", + "crosshair", + "pointer", + "ew-resize", + "ns-resize", + "nwse-resize", + "nesw-resize", + "all-scroll", + "not-allowed" +}; + +JNIEXPORT jlong JNICALL Java_io_github_solclient_client_util_cursors_X11SystemCursors_nGetDefaultCursorHandle( + JNIEnv *env, jclass unused, jlong display, jbyte cursor) { + if (cursor < 0 || cursor >= sizeof(LOOKUP) / sizeof(*LOOKUP)) + cursor = 0; + + return XcursorLibraryLoadCursor((Display *) display, LOOKUP[cursor]); +} diff --git a/src/main/resources/liblwjglLegacyCursorsX11.so b/src/main/resources/liblwjglLegacyCursorsX11.so new file mode 100644 index 0000000..a78eacc Binary files /dev/null and b/src/main/resources/liblwjglLegacyCursorsX11.so differ diff --git a/src/main/resources/lwjglLegacyCursorsWin32.c b/src/main/resources/lwjglLegacyCursorsWin32.c new file mode 100644 index 0000000..4a77e66 --- /dev/null +++ b/src/main/resources/lwjglLegacyCursorsWin32.c @@ -0,0 +1,36 @@ +#include +#include +#include + +JNIEXPORT jlong JNICALL Java_io_github_solclient_client_util_cursors_Win32SystemCursors_nGetDefaultCursorHandle( + JNIEnv *env, jclass unused, jbyte cursor) { + switch (cursor) { + case 0: + default: + return (jlong) LoadCursor(NULL, IDC_ARROW); + case 1: + return (jlong) LoadCursor(NULL, IDC_IBEAM); + case 2: + return (jlong) LoadCursor(NULL, IDC_CROSS); + case 3: + return (jlong) LoadCursor(NULL, IDC_HAND); + case 4: + return (jlong) LoadCursor(NULL, IDC_SIZEWE); + case 5: + return (jlong) LoadCursor(NULL, IDC_SIZENS); + case 6: + return (jlong) LoadCursor(NULL, IDC_SIZENWSE); + case 7: + return (jlong) LoadCursor(NULL, IDC_SIZENESW); + case 8: + return (jlong) LoadCursor(NULL, IDC_SIZEALL); + case 9: + return (jlong) LoadCursor(NULL, IDC_NO); + } +} + +JNIEXPORT void JNICALL Java_io_github_solclient_client_util_cursors_Win32SystemCursors_nSetCursor( + JNIEnv *env, jclass unused, jlong hwnd, jlong cursor) { + SetClassLongPtr((HWND) hwnd, GCLP_HCURSOR, NULL); + SetCursor((HCURSOR) cursor); +} diff --git a/src/main/resources/lwjglLegacyCursorsWin32.dll b/src/main/resources/lwjglLegacyCursorsWin32.dll new file mode 100644 index 0000000..57efb03 Binary files /dev/null and b/src/main/resources/lwjglLegacyCursorsWin32.dll differ