Better Cursors

This commit is contained in:
kirillsaint 2024-01-18 19:57:58 +06:00
parent c18c49d54a
commit 5713b06f6f
9 changed files with 354 additions and 35 deletions

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -0,0 +1,23 @@
#include <Xcursor/Xcursor.h>
#include <jni.h>
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]);
}

Binary file not shown.

View File

@ -0,0 +1,36 @@
#include <windows.h>
#include <winuser.h>
#include <jni.h>
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);
}

Binary file not shown.