From 32a660c14891a8275eb5ab0b2936a0fb24edcbca Mon Sep 17 00:00:00 2001 From: Ashish Agarwal Date: Sun, 12 Apr 2026 13:54:17 +0530 Subject: [PATCH 1/3] Fix GUI Cursor HOVER_TOTEM on Wayland glfwSetCursorPos() silently fails on Wayland when the cursor is in normal (non-captured) mode, such as when the inventory screen is open. This caused HOVER_TOTEM and CENTER_FIX to do nothing on Wayland. After calling glfwSetCursorPos, also update Minecraft's internal MouseHandler xpos/ypos state via a mixin accessor so the game's slot detection and rendering use the correct cursor position regardless of whether the OS actually moved the cursor. Fixes clickcrystals-development/ClickCrystals#120 --- .../clickcrystals/mixininterfaces/AccessorMouse.java | 2 ++ .../itzispyder/clickcrystals/mixins/MixinMouse.java | 8 ++++++++ .../clickcrystals/modules/modules/misc/GuiCursor.java | 8 +++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/itzispyder/clickcrystals/mixininterfaces/AccessorMouse.java b/src/main/java/io/github/itzispyder/clickcrystals/mixininterfaces/AccessorMouse.java index f7eb1138..82ef719c 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/mixininterfaces/AccessorMouse.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/mixininterfaces/AccessorMouse.java @@ -9,4 +9,6 @@ public interface AccessorMouse { void clickCrystals$middleClick(); void scroll(double amount); + + void setCursorPos(double x, double y); } diff --git a/src/main/java/io/github/itzispyder/clickcrystals/mixins/MixinMouse.java b/src/main/java/io/github/itzispyder/clickcrystals/mixins/MixinMouse.java index a7eef460..41d5ad8b 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/mixins/MixinMouse.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/mixins/MixinMouse.java @@ -18,6 +18,8 @@ public abstract class MixinMouse implements Global, AccessorMouse { @Shadow private double accumulatedDY; @Shadow private double accumulatedDX; + @Shadow private double xpos; + @Shadow private double ypos; @Shadow protected abstract void onButton(long window, MouseButtonInfo input, int action); @Shadow protected abstract void onScroll(long window, double horizontal, double vertical); @@ -75,4 +77,10 @@ public void leftClick() { public void scroll(double amount) { onScroll(mc.getWindow().handle(), 0, amount); } + + @Override + public void setCursorPos(double x, double y) { + this.xpos = x; + this.ypos = y; + } } diff --git a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java index a94323dd..a45aaca6 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java @@ -18,6 +18,7 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.inventory.ContainerInput; import net.minecraft.world.item.ItemStack; +import io.github.itzispyder.clickcrystals.mixininterfaces.AccessorMouse; import net.minecraft.world.item.Items; import org.lwjgl.glfw.GLFW; @@ -62,7 +63,12 @@ public static void setCursor(int x, int y) { int h2 = win.getGuiScaledHeight(); double ratW = (double)w2 / (double)w1; double ratH = (double)h2 / (double)h1; - GLFW.glfwSetCursorPos(win.handle(), x / ratW, y / ratH); + double rawX = x / ratW; + double rawY = y / ratH; + GLFW.glfwSetCursorPos(win.handle(), rawX, rawY); + // Update internal mouse state directly for Wayland compatibility, + // where glfwSetCursorPos silently fails in normal cursor mode + ((AccessorMouse) mc.mouseHandler).setCursorPos(rawX, rawY); } public static double getCursorX(double x) { From dda06612740eb8e6143298c3fe71f29797c29edd Mon Sep 17 00:00:00 2001 From: I-No-oNe <145749961+I-No-oNe@users.noreply.github.com> Date: Sun, 12 Apr 2026 13:31:19 +0300 Subject: [PATCH 2/3] fixed https://github.com/clickcrystals-development/ClickCrystals/issues/120 and improved gui-curser --- .../modules/modules/misc/GuiCursor.java | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java index a45aaca6..743e2e93 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java @@ -13,15 +13,19 @@ import io.github.itzispyder.clickcrystals.modules.settings.SettingSection; import io.github.itzispyder.clickcrystals.util.minecraft.HotbarUtils; import io.github.itzispyder.clickcrystals.util.minecraft.InvUtils; +import io.github.itzispyder.clickcrystals.mixininterfaces.AccessorMouse; import net.minecraft.client.gui.screens.inventory.InventoryScreen; import net.minecraft.network.protocol.game.ServerboundContainerClickPacket; import net.minecraft.world.InteractionHand; import net.minecraft.world.inventory.ContainerInput; import net.minecraft.world.item.ItemStack; -import io.github.itzispyder.clickcrystals.mixininterfaces.AccessorMouse; import net.minecraft.world.item.Items; import org.lwjgl.glfw.GLFW; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + public class GuiCursor extends Module implements Listener { private final SettingSection scGeneral = getGeneralSection(); @@ -37,7 +41,17 @@ public class GuiCursor extends Module implements Listener { .def(false) .build() ); - private boolean listeningForNextDraw, shiftKeyDown; + public final ModuleSetting closestTotemSlot = scGeneral.add(createBoolSetting() + .name("closest-totem-slot") + .description("Picks the totem slot closest to the center of the inventory instead of the first one found.") + .def(true) + .build() + ); + + private final List totemSlots = new ArrayList<>(); + private final List allSlots = new ArrayList<>(); + private int collectState = -1; // -1 idle, 0 waiting, 1 collecting + private boolean shiftKeyDown; public GuiCursor() { super("gui-cursor", Categories.MISC, "What to do with your cursor when you open inventory gui"); @@ -46,13 +60,17 @@ public GuiCursor() { @Override protected void onEnable() { system.addListener(this); - this.listeningForNextDraw = false; + this.collectState = -1; + this.totemSlots.clear(); + this.allSlots.clear(); } @Override protected void onDisable() { system.removeListener(this); - this.listeningForNextDraw = false; + this.collectState = -1; + this.totemSlots.clear(); + this.allSlots.clear(); } public static void setCursor(int x, int y) { @@ -61,8 +79,8 @@ public static void setCursor(int x, int y) { int w2 = win.getGuiScaledWidth(); int h1 = win.getScreenHeight(); int h2 = win.getGuiScaledHeight(); - double ratW = (double)w2 / (double)w1; - double ratH = (double)h2 / (double)h1; + double ratW = (double) w2 / (double) w1; + double ratH = (double) h2 / (double) h1; double rawX = x / ratW; double rawY = y / ratH; GLFW.glfwSetCursorPos(win.handle(), rawX, rawY); @@ -75,7 +93,7 @@ public static double getCursorX(double x) { Window win = mc.getWindow(); int w1 = win.getScreenWidth(); int w2 = win.getGuiScaledWidth(); - double ratW = (double)w2 / (double)w1; + double ratW = (double) w2 / (double) w1; return x * ratW; } @@ -83,7 +101,7 @@ public static double getCursorY(double y) { Window win = mc.getWindow(); int h1 = win.getScreenHeight(); int h2 = win.getGuiScaledHeight(); - double ratH = (double)h2 / (double)h1; + double ratH = (double) h2 / (double) h1; return y * ratH; } @@ -93,23 +111,59 @@ public void centerFix() { } public void hoverTotem() { - listeningForNextDraw = true; - system.scheduler.runDelayedTask(() -> listeningForNextDraw = false, 50); + totemSlots.clear(); + allSlots.clear(); + collectState = 0; + system.scheduler.runDelayedTask(() -> { + pickAndMoveCursor(); + totemSlots.clear(); + allSlots.clear(); + collectState = -1; + }, 50); + } + + private void pickAndMoveCursor() { + if (totemSlots.isEmpty()) return; + + if (!closestTotemSlot.getVal() || allSlots.isEmpty()) { + setCursor(totemSlots.getFirst()[0], totemSlots.getFirst()[1]); + return; + } + + // average all slot centers to get the inv center without touching protected fields + double cx = allSlots.stream().mapToInt(s -> s[0]).average().orElse(0); + double cy = allSlots.stream().mapToInt(s -> s[1]).average().orElse(0); + + int[] chosen = totemSlots.stream() + .min(Comparator.comparingDouble(a -> Math.hypot(a[0] - cx, a[1] - cy))) + .orElse(totemSlots.getFirst()); + + setCursor(chosen[0], chosen[1]); } @EventHandler private void onKey(KeyPressEvent e) { - if (e.getKeycode() == GLFW.GLFW_KEY_LEFT_SHIFT) { + if (e.getKeycode() == GLFW.GLFW_KEY_LEFT_SHIFT) shiftKeyDown = e.getAction().isDown(); - } } @EventHandler private void renderInventoryItem(RenderInventorySlotEvent e) { - if (e.getItem().is(Items.TOTEM_OF_UNDYING) && listeningForNextDraw) { - setCursor(e.getX() + 8, e.getY() + 8); - listeningForNextDraw = false; + if (collectState == -1) return; + + int sx = e.getX() + 8; + int sy = e.getY() + 8; + + if (collectState == 0) { + totemSlots.clear(); + allSlots.clear(); + collectState = 1; } + + allSlots.add(new int[]{sx, sy}); + + if (e.getItem().is(Items.TOTEM_OF_UNDYING)) + totemSlots.add(new int[]{sx, sy}); } @EventHandler @@ -143,8 +197,7 @@ private void onClick(PacketSendEvent e) { if (mainEmpty) { system.scheduler.runDelayedTask(this::hoverTotem, 50); } - } - else if (slotValid) { + } else if (slotValid) { e.cancel(); InvUtils.quickMove(slot); InvUtils.inv().tick(); @@ -157,4 +210,4 @@ public enum Mode { CENTER_FIX, HOVER_TOTEM } -} +} \ No newline at end of file From ed11c9933187611f0808fcae286de0fac0fc31ac Mon Sep 17 00:00:00 2001 From: I-No-oNe <145749961+I-No-oNe@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:22:44 +0300 Subject: [PATCH 3/3] changed `List` to `List` --- .../modules/modules/misc/GuiCursor.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java index 743e2e93..61caf16d 100644 --- a/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java +++ b/src/main/java/io/github/itzispyder/clickcrystals/modules/modules/misc/GuiCursor.java @@ -13,7 +13,7 @@ import io.github.itzispyder.clickcrystals.modules.settings.SettingSection; import io.github.itzispyder.clickcrystals.util.minecraft.HotbarUtils; import io.github.itzispyder.clickcrystals.util.minecraft.InvUtils; -import io.github.itzispyder.clickcrystals.mixininterfaces.AccessorMouse; +import io.github.itzispyder.clickcrystals.mixininterfaces.AccessorMouseHandler; import net.minecraft.client.gui.screens.inventory.InventoryScreen; import net.minecraft.network.protocol.game.ServerboundContainerClickPacket; import net.minecraft.world.InteractionHand; @@ -22,6 +22,7 @@ import net.minecraft.world.item.Items; import org.lwjgl.glfw.GLFW; +import java.awt.Point; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -43,13 +44,13 @@ public class GuiCursor extends Module implements Listener { ); public final ModuleSetting closestTotemSlot = scGeneral.add(createBoolSetting() .name("closest-totem-slot") - .description("Picks the totem slot closest to the center of the inventory instead of the first one found.") + .description("Picks the totem slot closest to the center of the inventory instead of the first one found. Requires cursor-action to be HOVER_TOTEM.") .def(true) .build() ); - private final List totemSlots = new ArrayList<>(); - private final List allSlots = new ArrayList<>(); + private final List totemSlots = new ArrayList<>(); + private final List allSlots = new ArrayList<>(); private int collectState = -1; // -1 idle, 0 waiting, 1 collecting private boolean shiftKeyDown; @@ -86,7 +87,7 @@ public static void setCursor(int x, int y) { GLFW.glfwSetCursorPos(win.handle(), rawX, rawY); // Update internal mouse state directly for Wayland compatibility, // where glfwSetCursorPos silently fails in normal cursor mode - ((AccessorMouse) mc.mouseHandler).setCursorPos(rawX, rawY); + ((AccessorMouseHandler) mc.mouseHandler).setCursorPos(rawX, rawY); } public static double getCursorX(double x) { @@ -125,20 +126,24 @@ public void hoverTotem() { private void pickAndMoveCursor() { if (totemSlots.isEmpty()) return; - if (!closestTotemSlot.getVal() || allSlots.isEmpty()) { - setCursor(totemSlots.getFirst()[0], totemSlots.getFirst()[1]); + boolean useClosest = cursorAction.getVal() == Mode.HOVER_TOTEM + && closestTotemSlot.getVal() + && !allSlots.isEmpty(); + + if (!useClosest) { + Point first = totemSlots.getFirst(); + setCursor(first.x, first.y); return; } - // average all slot centers to get the inv center without touching protected fields - double cx = allSlots.stream().mapToInt(s -> s[0]).average().orElse(0); - double cy = allSlots.stream().mapToInt(s -> s[1]).average().orElse(0); + double cx = allSlots.stream().mapToInt(p -> p.x).average().orElse(0); + double cy = allSlots.stream().mapToInt(p -> p.y).average().orElse(0); - int[] chosen = totemSlots.stream() - .min(Comparator.comparingDouble(a -> Math.hypot(a[0] - cx, a[1] - cy))) + Point chosen = totemSlots.stream() + .min(Comparator.comparingDouble(p -> Math.hypot(p.x - cx, p.y - cy))) .orElse(totemSlots.getFirst()); - setCursor(chosen[0], chosen[1]); + setCursor(chosen.x, chosen.y); } @EventHandler @@ -160,10 +165,10 @@ private void renderInventoryItem(RenderInventorySlotEvent e) { collectState = 1; } - allSlots.add(new int[]{sx, sy}); + allSlots.add(new Point(sx, sy)); if (e.getItem().is(Items.TOTEM_OF_UNDYING)) - totemSlots.add(new int[]{sx, sy}); + totemSlots.add(new Point(sx, sy)); } @EventHandler @@ -210,4 +215,4 @@ public enum Mode { CENTER_FIX, HOVER_TOTEM } -} \ No newline at end of file +}