From 786ce207d6fcccee0a4b637d505a54a78a948efd Mon Sep 17 00:00:00 2001 From: wired-tomato Date: Mon, 22 Jul 2024 16:42:18 -0400 Subject: [PATCH 1/3] Wayland display window and icon injection --- .../fml/earlydisplay/DisplayWindow.java | 30 +++-- .../wayland/WaylandDisplayWindow.java | 65 +++++++++ .../wayland/WaylandIconProvider.java | 126 ++++++++++++++++++ ...rgespi.earlywindow.ImmediateWindowProvider | 3 +- .../wayland/com.mojang.minecraft.desktop | 9 ++ .../wayland/neoforged_inject_icon.png | Bin 0 -> 1787 bytes 6 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java create mode 100644 earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java create mode 100644 earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop create mode 100644 earlydisplay/src/main/resources/wayland/neoforged_inject_icon.png diff --git a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java index c0dd38f7f..dc2462473 100644 --- a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java +++ b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java @@ -331,6 +331,22 @@ private void crashElegantly(String errorDetails) { System.exit(1); } + protected void glfwWindowHints(String mcVersion) { + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + if (mcVersion != null) { + // this emulates what we would get without early progress window + // as vanilla never sets these, so GLFW uses the first window title + // set them explicitly to avoid it using "FML early loading progress" as the class + String vanillaWindowTitle = "Minecraft* " + mcVersion; + glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle); + glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle); + } + } + /** * Called to initialize the window when preparing for the Render Thread. * @@ -360,19 +376,7 @@ public void initWindow(@Nullable String mcVersion) { handleLastGLFWError((error, description) -> LOGGER.error(String.format("Suppressing Last GLFW error: [0x%X]%s", error, description))); // Set window hints for the new window we're gonna create. - glfwDefaultWindowHints(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); - glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - if (mcVersion != null) { - // this emulates what we would get without early progress window - // as vanilla never sets these, so GLFW uses the first window title - // set them explicitly to avoid it using "FML early loading progress" as the class - String vanillaWindowTitle = "Minecraft* " + mcVersion; - glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle); - glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle); - } + glfwWindowHints(mcVersion); long primaryMonitor = glfwGetPrimaryMonitor(); if (primaryMonitor == 0) { diff --git a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java new file mode 100644 index 000000000..53eb34f73 --- /dev/null +++ b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.fml.earlydisplay.wayland; + +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.system.MemoryUtil.memAllocInt; + +import net.neoforged.fml.earlydisplay.DisplayWindow; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Loading Window that is opened Immediately after Forge starts. + * It is called from the ModDirTransformerDiscoverer, the soonest method that ModLauncher calls into Forge code. + * In this way, we can be sure that this will not run before any transformer or injection. + * + * The window itself is spun off into a secondary thread, and is handed off to the main game by Forge. + * + * Because it is created so early, this thread will "absorb" the context from OpenGL. + * Therefore, it is of utmost importance that the Context is made Current for the main thread before handoff, + * otherwise OS X will crash out. + * + * Based on the prior ClientVisualization, with some personal touches. + */ +public class WaylandDisplayWindow extends DisplayWindow { + private static final Logger LOGGER = LoggerFactory.getLogger("WAYLANDEARLYDISPLAY"); + private static final int GLFW_WAYLAND_APP_ID = 0x26001; + + @Override + public String name() { + return "fmlwaylandearlywindow"; + } + + @Override + public void initWindow(@Nullable String mcVersion) { + if (glfwPlatformSupported(GLFW_PLATFORM_WAYLAND)) { + glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND); + } + + super.initWindow(mcVersion); + } + + @Override + protected void glfwWindowHints(String mcVersion) { + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_FALSE); + var major = memAllocInt(1); + var minor = memAllocInt(1); + var rev = memAllocInt(1); + glfwGetVersion(major, minor, rev); + if (major.get(0) >= 3 && minor.get(0) >= 4) { + WaylandIconProvider.injectIcon(mcVersion); + glfwWindowHintString(GLFW_WAYLAND_APP_ID, WaylandIconProvider.APP_ID); + } + } +} diff --git a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java new file mode 100644 index 000000000..4cd2411c9 --- /dev/null +++ b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.fml.earlydisplay.wayland; + +import com.google.common.base.Charsets; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import javax.imageio.ImageIO; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WaylandIconProvider { + private static final Logger LOGGER = LoggerFactory.getLogger("WAYLANDICONPROVIDER"); + public static String APP_ID = "com.mojang.minecraft"; + public static String ICON_NAME = "neoforged_inject_icon.png"; + public static String DESKTOP_FILE_NAME = APP_ID + ".desktop"; + private static final List injects = new ArrayList<>(); + + public static void injectIcon(@Nullable String mcVersion) { + Runtime.getRuntime().addShutdownHook(new Thread(WaylandIconProvider::uninjectIcon)); + + try (var iconRes = WaylandIconProvider.class.getResourceAsStream("/wayland/" + ICON_NAME); + var desktopRes = WaylandIconProvider.class.getResourceAsStream("/wayland/" + DESKTOP_FILE_NAME)) { + if (iconRes == null) throw new IOException("Failed to find icon resource"); + if (desktopRes == null) throw new IOException("Failed to find desktop resource"); + + var iconByteStream = new ByteArrayOutputStream(); + iconRes.transferTo(iconByteStream); + var image = ImageIO.read(new ByteArrayInputStream(iconByteStream.toByteArray())); + var iconPath = getIconFileLocation(image.getWidth(), image.getHeight()); + if (iconPath == null) throw new IOException("Failed to resolve mc icon path"); + injectFile(iconPath, iconByteStream.toByteArray()); + + var desktopString = String.format(new String(desktopRes.readAllBytes(), Charsets.UTF_8), mcVersion, ICON_NAME.substring(0, ICON_NAME.lastIndexOf("."))); + var desktopPath = getDesktopFileLocation(); + if (desktopPath == null) throw new IOException("Failed to resolve desktop icon path"); + injectFile(desktopPath, desktopString.getBytes(Charsets.UTF_8)); + + updateIconSystem(); + } catch (IOException | NullPointerException e) { + LOGGER.error("Failed to inject icon", e); + } + } + + public static void uninjectIcon() { + injects.forEach((path) -> { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + LOGGER.error("Failed to delete file", e); + } + }); + } + + private static void injectFile(Path target, byte[] data) { + try { + Files.createDirectories(target.getParent()); + Files.write(target, data); + injects.add(target); + } catch (IOException e) { + LOGGER.error("Failed to inject file", e); + } + } + + private static void updateIconSystem() { + var procBuilder = new ProcessBuilder("xdg-icon-resource", "forceupdate"); + try { + procBuilder.start(); + } catch (IOException e) { + LOGGER.error("Failed to update icon system", e); + } + } + + @Nullable + private static Path getIconFileLocation(int width, int height) { + var userDataLocation = getUserDataLocation(); + if (userDataLocation == null) return null; + + return getUserDataLocation() + .resolve("icons/hicolor") + .resolve(width + "x" + height) + .resolve("apps") + .resolve(ICON_NAME); + } + + @Nullable + private static Path getDesktopFileLocation() { + var userDataLocation = getUserDataLocation(); + if (userDataLocation == null) return null; + + return getUserDataLocation() + .resolve("applications") + .resolve(DESKTOP_FILE_NAME); + } + + @Nullable + private static Path getHome() { + var home = System.getenv().getOrDefault("HOME", System.getProperty("user.home")); + if (home == null || home.isEmpty()) { + LOGGER.error("Failed to resolve user home"); + return null; + } + + return Paths.get(home); + } + + @Nullable + private static Path getUserDataLocation() { + var xdgDataHome = System.getenv("XDG_DATA_HOME"); + if (xdgDataHome == null || xdgDataHome.isEmpty()) { + var home = getHome(); + return home != null ? getHome().resolve(".local/share/") : null; + } + return Paths.get(xdgDataHome); + } +} diff --git a/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider b/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider index 6f3868fae..9a60d6357 100644 --- a/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider +++ b/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider @@ -1 +1,2 @@ -net.neoforged.fml.earlydisplay.DisplayWindow \ No newline at end of file +net.neoforged.fml.earlydisplay.DisplayWindow +net.neoforged.fml.earlydisplay.wayland.WaylandDisplayWindow \ No newline at end of file diff --git a/earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop b/earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop new file mode 100644 index 000000000..0ec0f4aea --- /dev/null +++ b/earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Minecraft %s +Comment=Minecraft (NeoForge FML) +Icon=%s +Exec=true +Hidden=true +Terminal=false +Type=Application +Categories=Game; \ No newline at end of file diff --git a/earlydisplay/src/main/resources/wayland/neoforged_inject_icon.png b/earlydisplay/src/main/resources/wayland/neoforged_inject_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..082c94b12f15b63340cec365c7787e76b405f849 GIT binary patch literal 1787 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xj)7d$oILO^e!PC{*%+S)z zOxH-ykbyyCV(G;D+RcFy$L()-q|I{LxnSBc(NBxT%=NZOw07s7D%iaDoAcY_w}0nl z?hMTfpR(iQ>!$q;(NCi^l~lLK2%E3D{y8selI`;6HuFE*KCiTTZkfQlbjp@EbB>oj ze%wFyu510UX!S!$_Odx87v%y=O3f@J7cHy3lW6$ZcYoySZ#(PPW~wgJPRJCI3lGs% zw^6m(JT2Jwo>*S+q=}*rgCE-Y>S@$uzq-h^f5w~@AEuv-(s;m}-MvBkWB20y5uRC@ ziItY`O0?GhxpTaxQ+e71WoFNa{P}VE+KF?UvtAX? z`*n~1QL17-gMb=|XxkNWzh!zm-^|=)@cGZ_^*wL){JWK{Y~3IKGdCsu)b!Woy>Iq> zd^TIzT7J*xZzs-uVO^fO!{7-o!yy*3&^58Y4A+fnU%xvYpH^1)@7bXf=dRo@fAb<< z**aM6{>-1dm)SjE8C`ezfB*Zwb+Pi~cxcxP_8rrL)%)K2z0!pQ+qqvS&P`c6*LiXE?B(b6{(OHBi8FI%R6=uY{k*AwSEmODSmFZYv?-}z*7 z;g?TG&e-ea&6U4-^XK2|6X#ByYV97jt^69$e>pOHjW!v7we+ujA6$QZ-pczYpS|3C zy*BcMG(!s~i758F>crWJuaZv9YkxO?uYt?y^5=a0@9v&Zov(S1LBWSal#_drzrZ;6 z{TlsWg6lrLh@Rj7=1%SVzrfV0H^<(5Q}MH_>h5Jf>d7z};*y+|`Am89>~>r2EPr+6 z Date: Tue, 23 Jul 2024 07:12:58 -0400 Subject: [PATCH 2/3] Revision #1 --- .../fml/earlydisplay/DisplayWindow.java | 49 ++++--- .../wayland/WaylandDisplayWindow.java | 65 --------- .../wayland/WaylandIconProvider.java | 126 ------------------ ...rgespi.earlywindow.ImmediateWindowProvider | 3 +- .../wayland/com.mojang.minecraft.desktop | 9 -- .../wayland/neoforged_inject_icon.png | Bin 1787 -> 0 bytes 6 files changed, 33 insertions(+), 219 deletions(-) delete mode 100644 earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java delete mode 100644 earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java delete mode 100644 earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop delete mode 100644 earlydisplay/src/main/resources/wayland/neoforged_inject_icon.png diff --git a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java index dc2462473..449b595a0 100644 --- a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java +++ b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java @@ -8,6 +8,7 @@ import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL.createCapabilities; import static org.lwjgl.opengl.GL32C.*; +import static org.lwjgl.system.MemoryUtil.memAllocInt; import java.awt.Desktop; import java.io.IOException; @@ -75,6 +76,7 @@ public class DisplayWindow implements ImmediateWindowProvider { private static final int[][] GL_VERSIONS = new int[][] { { 4, 6 }, { 4, 5 }, { 4, 4 }, { 4, 3 }, { 4, 2 }, { 4, 1 }, { 4, 0 }, { 3, 3 }, { 3, 2 } }; private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY"); + private static final int GLFW_WAYLAND_APP_ID = 0x26001; private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true); private ColourScheme colourScheme; @@ -103,6 +105,7 @@ public class DisplayWindow implements ImmediateWindowProvider { private final Semaphore renderLock = new Semaphore(1); private boolean maximized; + private boolean waylandEnabled; private String glVersion; private SimpleFont font; private Runnable repaintTick = () -> {}; @@ -124,6 +127,7 @@ public Runnable initialize(String[] arguments) { .withRequiredArg().ofType(Integer.class) .defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT)); var maximizedopt = parser.accepts("earlywindow.maximized"); + var waylandopt = parser.accepts("earlywindow.wayland"); parser.allowsUnrecognizedOptions(); var parsed = parser.parse(arguments); winWidth = parsed.valueOf(widthopt); @@ -145,6 +149,7 @@ public Runnable initialize(String[] arguments) { } } this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED); + this.waylandEnabled = parsed.has(waylandopt); var forgeVersion = parsed.valueOf(forgeversionopt); StartupNotificationManager.modLoaderConsumer().ifPresent(c -> c.accept("NeoForge loading " + forgeVersion)); @@ -331,22 +336,6 @@ private void crashElegantly(String errorDetails) { System.exit(1); } - protected void glfwWindowHints(String mcVersion) { - glfwDefaultWindowHints(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); - glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - if (mcVersion != null) { - // this emulates what we would get without early progress window - // as vanilla never sets these, so GLFW uses the first window title - // set them explicitly to avoid it using "FML early loading progress" as the class - String vanillaWindowTitle = "Minecraft* " + mcVersion; - glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle); - glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle); - } - } - /** * Called to initialize the window when preparing for the Render Thread. * @@ -360,6 +349,11 @@ protected void glfwWindowHints(String mcVersion) { * @return The selected GL profile as an integer pair */ public void initWindow(@Nullable String mcVersion) { + // Set the platform to wayland if enabled & supported + if (waylandEnabled && glfwPlatformSupported(GLFW_PLATFORM_WAYLAND)) { + glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND); + } + // Initialize GLFW with a time guard, in case something goes wrong long glfwInitBegin = System.nanoTime(); if (!glfwInit()) { @@ -376,7 +370,28 @@ public void initWindow(@Nullable String mcVersion) { handleLastGLFWError((error, description) -> LOGGER.error(String.format("Suppressing Last GLFW error: [0x%X]%s", error, description))); // Set window hints for the new window we're gonna create. - glfwWindowHints(mcVersion); + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + + if (waylandEnabled && (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND)) { + var major = memAllocInt(1); + var minor = memAllocInt(1); + var rev = memAllocInt(1); + glfwGetVersion(major, minor, rev); + if (major.get(0) >= 3 && minor.get(0) >= 4) { + glfwWindowHintString(GLFW_WAYLAND_APP_ID, "com.mojang.minecraft"); + } + } else if (mcVersion != null) { + // this emulates what we would get without early progress window + // as vanilla never sets these, so GLFW uses the first window title + // set them explicitly to avoid it using "FML early loading progress" as the class + String vanillaWindowTitle = "Minecraft* " + mcVersion; + glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle); + glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle); + } long primaryMonitor = glfwGetPrimaryMonitor(); if (primaryMonitor == 0) { diff --git a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java deleted file mode 100644 index 53eb34f73..000000000 --- a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.fml.earlydisplay.wayland; - -import static org.lwjgl.glfw.GLFW.*; -import static org.lwjgl.system.MemoryUtil.memAllocInt; - -import net.neoforged.fml.earlydisplay.DisplayWindow; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The Loading Window that is opened Immediately after Forge starts. - * It is called from the ModDirTransformerDiscoverer, the soonest method that ModLauncher calls into Forge code. - * In this way, we can be sure that this will not run before any transformer or injection. - * - * The window itself is spun off into a secondary thread, and is handed off to the main game by Forge. - * - * Because it is created so early, this thread will "absorb" the context from OpenGL. - * Therefore, it is of utmost importance that the Context is made Current for the main thread before handoff, - * otherwise OS X will crash out. - * - * Based on the prior ClientVisualization, with some personal touches. - */ -public class WaylandDisplayWindow extends DisplayWindow { - private static final Logger LOGGER = LoggerFactory.getLogger("WAYLANDEARLYDISPLAY"); - private static final int GLFW_WAYLAND_APP_ID = 0x26001; - - @Override - public String name() { - return "fmlwaylandearlywindow"; - } - - @Override - public void initWindow(@Nullable String mcVersion) { - if (glfwPlatformSupported(GLFW_PLATFORM_WAYLAND)) { - glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND); - } - - super.initWindow(mcVersion); - } - - @Override - protected void glfwWindowHints(String mcVersion) { - glfwDefaultWindowHints(); - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); - glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API); - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_FALSE); - var major = memAllocInt(1); - var minor = memAllocInt(1); - var rev = memAllocInt(1); - glfwGetVersion(major, minor, rev); - if (major.get(0) >= 3 && minor.get(0) >= 4) { - WaylandIconProvider.injectIcon(mcVersion); - glfwWindowHintString(GLFW_WAYLAND_APP_ID, WaylandIconProvider.APP_ID); - } - } -} diff --git a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java deleted file mode 100644 index 4cd2411c9..000000000 --- a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.fml.earlydisplay.wayland; - -import com.google.common.base.Charsets; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import javax.imageio.ImageIO; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class WaylandIconProvider { - private static final Logger LOGGER = LoggerFactory.getLogger("WAYLANDICONPROVIDER"); - public static String APP_ID = "com.mojang.minecraft"; - public static String ICON_NAME = "neoforged_inject_icon.png"; - public static String DESKTOP_FILE_NAME = APP_ID + ".desktop"; - private static final List injects = new ArrayList<>(); - - public static void injectIcon(@Nullable String mcVersion) { - Runtime.getRuntime().addShutdownHook(new Thread(WaylandIconProvider::uninjectIcon)); - - try (var iconRes = WaylandIconProvider.class.getResourceAsStream("/wayland/" + ICON_NAME); - var desktopRes = WaylandIconProvider.class.getResourceAsStream("/wayland/" + DESKTOP_FILE_NAME)) { - if (iconRes == null) throw new IOException("Failed to find icon resource"); - if (desktopRes == null) throw new IOException("Failed to find desktop resource"); - - var iconByteStream = new ByteArrayOutputStream(); - iconRes.transferTo(iconByteStream); - var image = ImageIO.read(new ByteArrayInputStream(iconByteStream.toByteArray())); - var iconPath = getIconFileLocation(image.getWidth(), image.getHeight()); - if (iconPath == null) throw new IOException("Failed to resolve mc icon path"); - injectFile(iconPath, iconByteStream.toByteArray()); - - var desktopString = String.format(new String(desktopRes.readAllBytes(), Charsets.UTF_8), mcVersion, ICON_NAME.substring(0, ICON_NAME.lastIndexOf("."))); - var desktopPath = getDesktopFileLocation(); - if (desktopPath == null) throw new IOException("Failed to resolve desktop icon path"); - injectFile(desktopPath, desktopString.getBytes(Charsets.UTF_8)); - - updateIconSystem(); - } catch (IOException | NullPointerException e) { - LOGGER.error("Failed to inject icon", e); - } - } - - public static void uninjectIcon() { - injects.forEach((path) -> { - try { - Files.deleteIfExists(path); - } catch (IOException e) { - LOGGER.error("Failed to delete file", e); - } - }); - } - - private static void injectFile(Path target, byte[] data) { - try { - Files.createDirectories(target.getParent()); - Files.write(target, data); - injects.add(target); - } catch (IOException e) { - LOGGER.error("Failed to inject file", e); - } - } - - private static void updateIconSystem() { - var procBuilder = new ProcessBuilder("xdg-icon-resource", "forceupdate"); - try { - procBuilder.start(); - } catch (IOException e) { - LOGGER.error("Failed to update icon system", e); - } - } - - @Nullable - private static Path getIconFileLocation(int width, int height) { - var userDataLocation = getUserDataLocation(); - if (userDataLocation == null) return null; - - return getUserDataLocation() - .resolve("icons/hicolor") - .resolve(width + "x" + height) - .resolve("apps") - .resolve(ICON_NAME); - } - - @Nullable - private static Path getDesktopFileLocation() { - var userDataLocation = getUserDataLocation(); - if (userDataLocation == null) return null; - - return getUserDataLocation() - .resolve("applications") - .resolve(DESKTOP_FILE_NAME); - } - - @Nullable - private static Path getHome() { - var home = System.getenv().getOrDefault("HOME", System.getProperty("user.home")); - if (home == null || home.isEmpty()) { - LOGGER.error("Failed to resolve user home"); - return null; - } - - return Paths.get(home); - } - - @Nullable - private static Path getUserDataLocation() { - var xdgDataHome = System.getenv("XDG_DATA_HOME"); - if (xdgDataHome == null || xdgDataHome.isEmpty()) { - var home = getHome(); - return home != null ? getHome().resolve(".local/share/") : null; - } - return Paths.get(xdgDataHome); - } -} diff --git a/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider b/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider index 9a60d6357..6f3868fae 100644 --- a/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider +++ b/earlydisplay/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider @@ -1,2 +1 @@ -net.neoforged.fml.earlydisplay.DisplayWindow -net.neoforged.fml.earlydisplay.wayland.WaylandDisplayWindow \ No newline at end of file +net.neoforged.fml.earlydisplay.DisplayWindow \ No newline at end of file diff --git a/earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop b/earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop deleted file mode 100644 index 0ec0f4aea..000000000 --- a/earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Name=Minecraft %s -Comment=Minecraft (NeoForge FML) -Icon=%s -Exec=true -Hidden=true -Terminal=false -Type=Application -Categories=Game; \ No newline at end of file diff --git a/earlydisplay/src/main/resources/wayland/neoforged_inject_icon.png b/earlydisplay/src/main/resources/wayland/neoforged_inject_icon.png deleted file mode 100644 index 082c94b12f15b63340cec365c7787e76b405f849..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1787 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xj)7d$oILO^e!PC{*%+S)z zOxH-ykbyyCV(G;D+RcFy$L()-q|I{LxnSBc(NBxT%=NZOw07s7D%iaDoAcY_w}0nl z?hMTfpR(iQ>!$q;(NCi^l~lLK2%E3D{y8selI`;6HuFE*KCiTTZkfQlbjp@EbB>oj ze%wFyu510UX!S!$_Odx87v%y=O3f@J7cHy3lW6$ZcYoySZ#(PPW~wgJPRJCI3lGs% zw^6m(JT2Jwo>*S+q=}*rgCE-Y>S@$uzq-h^f5w~@AEuv-(s;m}-MvBkWB20y5uRC@ ziItY`O0?GhxpTaxQ+e71WoFNa{P}VE+KF?UvtAX? z`*n~1QL17-gMb=|XxkNWzh!zm-^|=)@cGZ_^*wL){JWK{Y~3IKGdCsu)b!Woy>Iq> zd^TIzT7J*xZzs-uVO^fO!{7-o!yy*3&^58Y4A+fnU%xvYpH^1)@7bXf=dRo@fAb<< z**aM6{>-1dm)SjE8C`ezfB*Zwb+Pi~cxcxP_8rrL)%)K2z0!pQ+qqvS&P`c6*LiXE?B(b6{(OHBi8FI%R6=uY{k*AwSEmODSmFZYv?-}z*7 z;g?TG&e-ea&6U4-^XK2|6X#ByYV97jt^69$e>pOHjW!v7we+ujA6$QZ-pczYpS|3C zy*BcMG(!s~i758F>crWJuaZv9YkxO?uYt?y^5=a0@9v&Zov(S1LBWSal#_drzrZ;6 z{TlsWg6lrLh@Rj7=1%SVzrfV0H^<(5Q}MH_>h5Jf>d7z};*y+|`Am89>~>r2EPr+6 z Date: Tue, 12 Nov 2024 17:40:51 -0500 Subject: [PATCH 3/3] Reintroduce GLFW_FOCUS_ON_SHOW window hint to solve KDE Plasma incorrectly reporting the window as not responding --- .../java/net/neoforged/fml/earlydisplay/DisplayWindow.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java index 449b595a0..9847cbdad 100644 --- a/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java +++ b/earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/DisplayWindow.java @@ -381,8 +381,9 @@ public void initWindow(@Nullable String mcVersion) { var minor = memAllocInt(1); var rev = memAllocInt(1); glfwGetVersion(major, minor, rev); - if (major.get(0) >= 3 && minor.get(0) >= 4) { - glfwWindowHintString(GLFW_WAYLAND_APP_ID, "com.mojang.minecraft"); + if (major.get(0) >= 3 && minor.get(0) >= 3) { + glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_FALSE); + if (minor.get(0) >= 4) glfwWindowHintString(GLFW_WAYLAND_APP_ID, "com.mojang.minecraft"); } } else if (mcVersion != null) { // this emulates what we would get without early progress window