-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Wayland display window and icon injection
- Loading branch information
1 parent
4665bb7
commit 786ce20
Showing
6 changed files
with
219 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandDisplayWindow.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
earlydisplay/src/main/java/net/neoforged/fml/earlydisplay/wayland/WaylandIconProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Path> 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); | ||
} | ||
} |
3 changes: 2 additions & 1 deletion
3
...resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.ImmediateWindowProvider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
net.neoforged.fml.earlydisplay.DisplayWindow | ||
net.neoforged.fml.earlydisplay.DisplayWindow | ||
net.neoforged.fml.earlydisplay.wayland.WaylandDisplayWindow |
9 changes: 9 additions & 0 deletions
9
earlydisplay/src/main/resources/wayland/com.mojang.minecraft.desktop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.