diff --git a/src/main/java/me/eigenraven/lwjgl3ify/core/Config.java b/src/main/java/me/eigenraven/lwjgl3ify/core/Config.java index 2fe86d18..378a531c 100644 --- a/src/main/java/me/eigenraven/lwjgl3ify/core/Config.java +++ b/src/main/java/me/eigenraven/lwjgl3ify/core/Config.java @@ -27,6 +27,8 @@ public class Config { public static boolean WINDOW_START_MAXIMIZED = false, WINDOW_START_FOCUSED = true, WINDOW_START_ICONIFIED = false; public static boolean WINDOW_CENTERED = true; public static boolean WINDOW_DECORATED = true; + public static boolean WINDOW_BORDERLESS_REPLACES_FULLSCREEN = false; + public static boolean WINDOW_BORDERLESS_WINDOWS_COMPATIBILITY = true; public static boolean OPENGL_DEBUG_CONTEXT = false; public static boolean OPENGL_SRGB_CONTEXT = false; public static boolean OPENGL_DOUBLEBUFFER = true; @@ -120,6 +122,16 @@ public static void reloadConfigObject() { WINDOW_START_ICONIFIED = config .getBoolean("iconified", CATEGORY_WINDOW, WINDOW_START_ICONIFIED, "Start iconified?"); WINDOW_CENTERED = config.getBoolean("centered", CATEGORY_WINDOW, WINDOW_CENTERED, "Start centered?"); + WINDOW_BORDERLESS_REPLACES_FULLSCREEN = config.getBoolean( + "borderless", + CATEGORY_WINDOW, + WINDOW_BORDERLESS_REPLACES_FULLSCREEN, + "Should exclusive fullscreen mode replaced with borderless fullscreen mode"); + WINDOW_BORDERLESS_WINDOWS_COMPATIBILITY = config.getBoolean( + "borderlessWindowsCompatibility", + CATEGORY_WINDOW, + WINDOW_BORDERLESS_WINDOWS_COMPATIBILITY, + "Windows-only - should borderless window have height increased by 1 to solve flickering on un-focusing"); WINDOW_DECORATED = config.getBoolean( "decorated", CATEGORY_WINDOW, diff --git a/src/main/java/me/eigenraven/lwjgl3ify/core/Lwjgl3ifyCoremod.java b/src/main/java/me/eigenraven/lwjgl3ify/core/Lwjgl3ifyCoremod.java index 225a0f7d..f8262175 100644 --- a/src/main/java/me/eigenraven/lwjgl3ify/core/Lwjgl3ifyCoremod.java +++ b/src/main/java/me/eigenraven/lwjgl3ify/core/Lwjgl3ifyCoremod.java @@ -86,6 +86,9 @@ public List getMixins(Set loadedCoreMods) { // Improved KeyBinding handling to handle dead keys mixins.add("game.MixinMinecraftKeyBinding"); + // Adds the borderless mode + mixins.add("game.MixinBorderlessWindow"); + // STB replacements for vanilla functions if (Config.MIXIN_STBI_TEXTURE_LOADING) { LOGGER.info("Enabling STB texture loading mixin"); diff --git a/src/main/java/me/eigenraven/lwjgl3ify/mixins/game/MixinBorderlessWindow.java b/src/main/java/me/eigenraven/lwjgl3ify/mixins/game/MixinBorderlessWindow.java new file mode 100644 index 00000000..6c4b174c --- /dev/null +++ b/src/main/java/me/eigenraven/lwjgl3ify/mixins/game/MixinBorderlessWindow.java @@ -0,0 +1,26 @@ +package me.eigenraven.lwjgl3ify.mixins.game; + +import static org.lwjgl.glfw.GLFW.*; + +import net.minecraft.client.Minecraft; + +import org.lwjglx.opengl.Display; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import me.eigenraven.lwjgl3ify.core.Config; + +@Mixin(Minecraft.class) +public class MixinBorderlessWindow { + + @Inject(method = "Lnet/minecraft/client/Minecraft;toggleFullscreen()V", at = @At("HEAD"), cancellable = true) + public void toggleFullscreen(CallbackInfo ci) { + if (Config.WINDOW_BORDERLESS_REPLACES_FULLSCREEN) { + ci.cancel(); + + Display.toggleBorderless(); + } + } +} diff --git a/src/main/java/org/lwjglx/opengl/Display.java b/src/main/java/org/lwjglx/opengl/Display.java index 98aa0188..a045c55e 100644 --- a/src/main/java/org/lwjglx/opengl/Display.java +++ b/src/main/java/org/lwjglx/opengl/Display.java @@ -9,8 +9,14 @@ import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; +import java.util.Optional; +import net.minecraft.client.Minecraft; + +import org.lwjgl.PointerBuffer; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWCharCallback; import org.lwjgl.glfw.GLFWCursorPosCallback; @@ -33,6 +39,9 @@ import org.lwjglx.input.KeyCodes; import org.lwjglx.input.Keyboard; import org.lwjglx.input.Mouse; +import org.lwjglx.util.Rectangle; + +import com.github.bsideup.jabel.Desugar; import me.eigenraven.lwjgl3ify.Lwjgl3ify; import me.eigenraven.lwjgl3ify.api.InputEvents; @@ -49,6 +58,7 @@ public class Display { private static boolean displayDirty = false; private static boolean displayResizable = false; private static boolean startFullscreen = false; + private static boolean borderlessInsteadOfFullscreen = true; private static DisplayMode mode = new DisplayMode(854, 480); private static DisplayMode desktopDisplayMode = new DisplayMode(854, 480); @@ -113,7 +123,6 @@ public class Display { * * @param pixelFormat Describes the minimum specifications the context must fulfill. * @param sharedDrawable The Drawable to share context with. (optional, may be null) - * * @throws org.lwjglx.LWJGLException */ public static void create(PixelFormat pixelFormat, Drawable sharedDrawable) { @@ -187,7 +196,7 @@ public static void create(PixelFormat pixelFormat, ContextAttribs attribs, long glfwWindowHintString(GLFW_COCOA_FRAME_NAME, Config.COCOA_FRAME_NAME); glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); // request a non-hidpi framebuffer on Retina displays - // on MacOS + // on MacOS if (Config.WINDOW_CENTERED) { glfwWindowHint(GLFW_POSITION_X, (monitorWidth - mode.getWidth()) / 2); @@ -240,7 +249,7 @@ public void invoke(long window, int key, int scancode, int action, int mods) { } } if (key > GLFW_KEY_SPACE && key <= GLFW_KEY_GRAVE_ACCENT) { // Handle keys have a char. Exclude space to - // avoid extra input when switching IME + // avoid extra input when switching IME /* * AltGr and LAlt require special consideration. @@ -285,7 +294,7 @@ public void invoke(long window, int key, int scancode, int action, int mods) { Keyboard.addGlfwKeyEvent(window, key, scancode, action, mods, (char) (key & 0x1f)); cancelNextChar = true; // Cancel char event from ctrl key since its already handled here } else if (action > 0) { // Delay press and repeat key event to actual char input. There is ALWAYS a - // char after them + // char after them ingredientKeyEvent = new Keyboard.KeyEvent( KeyCodes.glfwToLwjgl(key), '\0', @@ -677,6 +686,59 @@ public static void setDisplayModeAndFullscreen(DisplayMode mode) { private static int savedX[] = new int[1], savedY[] = new int[1]; private static int savedW[] = new int[1], savedH[] = new int[1]; + public static PositionedGLFWVidMode getTargetFullscreenMonitor() { + int x = savedX[0] + (savedW[0] / 2); + int y = savedY[0] + (savedH[0] / 2); + + PointerBuffer monitors = glfwGetMonitors(); + assert monitors != null; + ArrayList monitorInfos = new ArrayList<>(monitors.limit()); + for (int i = 0; i < monitors.limit(); i++) { + long monitor = monitors.get(i); + + PositionedGLFWVidMode monitorInfo = getPositionedMonitorInfo(monitor); + monitorInfos.add(monitorInfo); + + if (monitorInfo.bounds.contains(x, y)) { + return monitorInfo; + } + } + + // If the center of the screen doesn't contains in any monitors, try to look by intersect area + Rectangle windowBounds = new Rectangle(savedX[0], savedY[0], savedW[0], savedH[0]); + Optional targetMonitor = monitorInfos.stream() + .filter( + o -> !o.bounds.intersection(windowBounds, null) + .isEmpty()) + .max( + Comparator.comparingInt( + o -> o.bounds.intersection(windowBounds, null) + .getArea())); + + return targetMonitor.orElse(getPositionedMonitorInfo(glfwGetPrimaryMonitor())); + } + + private static PositionedGLFWVidMode getPositionedMonitorInfo(long monitorId) { + IntBuffer posX = BufferUtils.createIntBuffer(1); + IntBuffer posY = BufferUtils.createIntBuffer(1); + + glfwGetMonitorPos(monitorId, posX, posY); + int x = posX.get(0); + int y = posY.get(0); + + GLFWVidMode vidmode = glfwGetVideoMode(monitorId); + assert vidmode != null; + return new PositionedGLFWVidMode( + x, + y, + new Rectangle(x, y, vidmode.width(), vidmode.height()), + monitorId, + vidmode); + } + + @Desugar + public record PositionedGLFWVidMode(int x, int y, Rectangle bounds, long monitorId, GLFWVidMode vidMode) {} + public static void setFullscreen(boolean fullscreen) { final long window = getWindow(); if (window == 0) { @@ -687,17 +749,100 @@ public static void setFullscreen(boolean fullscreen) { if (currentState == fullscreen) { return; } + + glfwSetWindowSizeLimits(window, 0, 0, GLFW_DONT_CARE, GLFW_DONT_CARE); if (fullscreen) { glfwGetWindowPos(window, savedX, savedY); glfwGetWindowSize(window, savedW, savedH); - long monitorId = glfwGetPrimaryMonitor(); - final GLFWVidMode vidMode = glfwGetVideoMode(monitorId); - glfwSetWindowMonitor(window, monitorId, 0, 0, vidMode.width(), vidMode.height(), vidMode.refreshRate()); + PositionedGLFWVidMode monitorInfo = getTargetFullscreenMonitor(); + GLFWVidMode vidMode = monitorInfo.vidMode; + glfwSetWindowMonitor( + window, + monitorInfo.monitorId, + 0, + 0, + vidMode.width(), + vidMode.height(), + vidMode.refreshRate()); + Minecraft.getMinecraft() + .resize(vidMode.width(), vidMode.height()); } else { + glfwSetWindowSize(window, savedW[0], savedH[0]); glfwSetWindowMonitor(window, NULL, savedX[0], savedY[0], savedW[0], savedH[0], 0); } } + public static void toggleBorderless() { + setBorderless(!isBorderless()); + } + + public static void setBorderless(boolean toBorderless) { + final long window = getWindow(); + if (window == NULL) { + return; + } + if (toBorderless) { + glfwGetWindowPos(window, savedX, savedY); + glfwGetWindowSize(window, savedW, savedH); + PositionedGLFWVidMode monitorInfo = getTargetFullscreenMonitor(); + GLFWVidMode vidMode = monitorInfo.vidMode; + + int height = vidMode.height(); + + // Fix bothered from + // https://github.com/Kir-Antipov/cubes-without-borders/blob/b38306bf17d3f0936475a3a28c4ee2be4e881a62/src/main/java/dev/kir/cubeswithoutborders/mixin/WindowMixin.java#L130 + // There's a bug that causes a fullscreen window to flicker when it loses focus. + // As far as I know, this is relevant for Windows and X11 desktops. + // Fuck X11 - it's a perpetually broken piece of legacy. + // However, we do need to implement a fix for Windows desktops, as they + // are not going anywhere in the foreseeable future (sadly enough). + // This "fix" involves not bringing a window into a "proper" fullscreen mode, + // but rather stretching it 1 pixel beyond the screen's supported resolution. + if (Config.WINDOW_BORDERLESS_WINDOWS_COMPATIBILITY && System.getProperty("os.name") + .toLowerCase() + .contains("win")) { + height = height + 1; + } + + glfwSetWindowSizeLimits(window, 0, 0, vidMode.width(), height); + glfwSetWindowSize(window, vidMode.width(), height); + glfwSetWindowMonitor( + window, + NULL, + monitorInfo.x, + monitorInfo.y, + vidMode.width(), + height, + vidMode.refreshRate()); + } else { + glfwSetWindowSizeLimits(window, 0, 0, GLFW_DONT_CARE, GLFW_DONT_CARE); + glfwSetWindowSize(window, savedW[0], savedH[0]); + glfwSetWindowMonitor(window, NULL, savedX[0], savedY[0], savedW[0], savedH[0], 0); + } + } + + public static boolean isBorderless() { + long window = Display.getWindow(); + long windowMonitor = glfwGetWindowMonitor(Display.getWindow()); + if (Display.getWindow() != 0 && windowMonitor == NULL) { + IntBuffer windowX = BufferUtils.createIntBuffer(1); + IntBuffer windowY = BufferUtils.createIntBuffer(1); + IntBuffer windowWidth = BufferUtils.createIntBuffer(1); + IntBuffer windowHeight = BufferUtils.createIntBuffer(1); + + glfwGetWindowPos(window, windowX, windowY); + glfwGetWindowSize(window, windowWidth, windowHeight); + + Display.PositionedGLFWVidMode monitorInfo = Display.getTargetFullscreenMonitor(); + GLFWVidMode vidMode = monitorInfo.vidMode(); + + return windowX.get(0) == monitorInfo.x() && windowY.get(0) == monitorInfo.y() + && windowWidth.get(0) == vidMode.width() + && (windowHeight.get(0) >= vidMode.height()); + } + return false; + } + public static boolean isFullscreen() { if (getWindow() != 0) { return glfwGetWindowMonitor(getWindow()) != NULL; diff --git a/src/main/java/org/lwjglx/util/Rectangle.java b/src/main/java/org/lwjglx/util/Rectangle.java index 6cac829a..c5500c5f 100644 --- a/src/main/java/org/lwjglx/util/Rectangle.java +++ b/src/main/java/org/lwjglx/util/Rectangle.java @@ -108,6 +108,13 @@ public void setBounds(ReadableRectangle r) { height = r.getHeight(); } + /* + * Returns the area of this rectangle + */ + public int getArea() { + return width * height; + } + /* * (Overrides) * @see com.shavenpuppy.jglib.ReadableRectangle#getBounds(com.shavenpuppy.jglib.Rectangle)