Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for XDG directory standards #108

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,13 @@ project.properties
.settings/
.classpath
dependency-reduced-pom.xml
*.exe
*.exe
linux-jdk/
linux-aarch64-jdk/
native-linux-x86_64/
native-linux-aarch64/
linux-jdk/
*.AppImage
*.AppImage-patched
OpenJDK11U-jre_*_linux_hotspot_*.tar.gz
packr_runelite-*.jar
73 changes: 53 additions & 20 deletions src/main/java/net/runelite/launcher/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,56 @@
@Slf4j
public class Launcher
{
private static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs");
private static final File REPO_DIR = new File(RUNELITE_DIR, "repository2");
public static final File CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log");
private static final File LEGACY_RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");

private static final File RUNELITE_DIR;
private static final File REPO_DIR;
public static final File LOGS_DIR;
public static final File CRASH_FILES;

private static final String USER_AGENT = "RuneLite/" + LauncherProperties.getVersion();
private static final String APP_NAME = "runelite";

static
{
// if ~/.runelite is found, use it instead of xdg defaults
// to bypass this check, either rename/move/delete the directory, or set RUNELITE_USE_XDG=1 in your environment
if (LEGACY_RUNELITE_DIR.exists() && System.getProperty("RUNELITE_USE_XDG", null) == null)
{
RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
REPO_DIR = new File(RUNELITE_DIR, "repository2");

LOGS_DIR = new File(RUNELITE_DIR, "logs");
CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log");
}
else
{
switch (OS.getOS())
{
case Linux:
case MacOS:
RUNELITE_DIR = new File(OS.getXDG("data", APP_NAME));
REPO_DIR = new File(OS.getXDG("data", APP_NAME), "repository2");

LOGS_DIR = new File(OS.getXDG("state", APP_NAME));
CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log");
break;
case Windows:
case Other:
default:
RUNELITE_DIR = new File(System.getProperty("user.home"), "." + APP_NAME); // ~/.runelite
REPO_DIR = new File(RUNELITE_DIR, "repository2");

LOGS_DIR = new File(RUNELITE_DIR, "logs");
CRASH_FILES = new File(LOGS_DIR, "jvm_crash_pid_%p.log");
break;
}
}
}

public static void main(String[] args)
{

OptionParser parser = new OptionParser(false);
parser.allowsUnrecognizedOptions();
parser.accepts("postinstall", "Perform post-install tasks");
Expand All @@ -114,7 +156,7 @@ public static void main(String[] args)
parser.accepts("scale", "Custom scale factor for Java 2D").withRequiredArg();
parser.accepts("help", "Show this text (use --clientargs --help for client help)").forHelp();

if (OS.getOs() == OS.OSType.MacOS)
if (OS.equals("mac"))
{
// Parse macos PSN, eg: -psn_0_352342
parser.accepts("p").withRequiredArg();
Expand All @@ -124,7 +166,7 @@ public static void main(String[] args)
final ArgumentAcceptingOptionSpec<HardwareAccelerationMode> mode = parser.accepts("mode")
.withRequiredArg()
.ofType(HardwareAccelerationMode.class)
.defaultsTo(HardwareAccelerationMode.defaultMode(OS.getOs()));
.defaultsTo(HardwareAccelerationMode.defaultMode(OS.getOS()));

final OptionSet options;
final HardwareAccelerationMode hardwareAccelerationMode;
Expand Down Expand Up @@ -190,12 +232,12 @@ public static void main(String[] args)
}

log.info("Setting hardware acceleration to {}", hardwareAccelerationMode);
jvmProps.addAll(hardwareAccelerationMode.toParams(OS.getOs()));
jvmProps.addAll(hardwareAccelerationMode.toParams(OS.getOS()));

// As of JDK-8243269 (11.0.8) and JDK-8235363 (14), AWT makes macOS dark mode support opt-in so interfaces
// with hardcoded foreground/background colours don't get broken by system settings. Considering the native
// Aqua we draw consists a window border and an about box, it's safe to say we can opt in.
if (OS.getOs() == OS.OSType.MacOS)
if (OS.equals("mac"))
{
jvmProps.add("-Dapple.awt.application.appearance=system");
}
Expand All @@ -208,7 +250,7 @@ public static void main(String[] args)
jvmProps.add("-Drunelite.insecure-skip-tls-verification=true");
}

if (OS.getOs() == OS.OSType.Windows && !options.has("use-jre-truststore"))
if (OS.equals("windows") && !options.has("use-jre-truststore"))
{
// Use the Windows Trusted Root Certificate Authorities instead of the bundled cacerts.
// Corporations, schools, antivirus, and malware commonly install root certificates onto
Expand Down Expand Up @@ -323,18 +365,9 @@ public static void main(String[] args)
return true;
}

final String os = System.getProperty("os.name");
final String arch = System.getProperty("os.arch");
for (Platform platform : a.getPlatform())
{
if (platform.getName() == null)
{
continue;
}

OS.OSType platformOs = OS.parseOs(platform.getName());
if ((platformOs == OS.OSType.Other ? platform.getName().equals(os) : platformOs == OS.getOs())
&& (platform.getArch() == null || platform.getArch().equals(arch)))
if (OS.isCompatible(platform.getName(), platform.getArch()))
{
return true;
}
Expand Down Expand Up @@ -842,7 +875,7 @@ private static void postInstall(List<String> jvmParams)

private static void initDllBlacklist()
{
if (OS.getOs() != OS.OSType.Windows)
if (!OS.equals("windows"))
{
return;
}
Expand Down
209 changes: 201 additions & 8 deletions src/main/java/net/runelite/launcher/OS.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,144 @@
*/
package net.runelite.launcher;

import java.nio.file.Path;
import java.nio.file.Paths;

import javax.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;

/*
*/

@Slf4j
public class OS
{
/*
* XDG prefixed variables are OS-defaults
* non-prefixed variables are the running environment variables
* the minuscule variables are the runelite-specific variables
* */
private static final Path CONFIG_HOME;
private static final Path DATA_HOME;
private static final Path CACHE_HOME;
private static final Path STATE_HOME;
private static final Path RUNTIME_DIR;
private static final Path PICTURES_DIR;

public enum OSType
{
Windows, MacOS, Linux, Other
}

private static final OSType DETECTED_OS;
private static final String DETECTED_ARCH;

private static final String placeholder = "%{project_name}"; // used on Windows
static
{
final String os = System
.getProperty("os.name", "generic")
.toLowerCase();
DETECTED_OS = parseOs(os);
String os = System.getProperty("os.name", "generic").toLowerCase();
DETECTED_ARCH = System.getProperty("os.arch", "unknown");

Path XDG_CONFIG_HOME;
Path XDG_DATA_HOME;
Path XDG_CACHE_HOME;
Path XDG_STATE_HOME;
Path XDG_RUNTIME_DIR;
Path XDG_PICTURES_DIR;

if (os.contains("mac") || os.contains("darwin"))
{
XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), "Library", "Preferences");
XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), "Library", "Application Support");
XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), "Library", "Caches");
// STATE is a newcomer to the standard. it was split apart from the cache directory... but not on MacOS (yet, at least)
XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), "Library", "Caches");
XDG_RUNTIME_DIR = Paths.get(System.getProperty("user.home"), "Library", "Caches");

XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), "Pictures");

DETECTED_OS = OSType.MacOS;
}
else if (os.contains("win"))
{
XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Roaming", placeholder, "Config");
XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Local", placeholder, "Data" );
XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Local", placeholder, "Cache");
XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), "AppData", "Local", placeholder, "Cache");
XDG_RUNTIME_DIR = Paths.get(System.getProperty("user.home"), "AppData", "Roaming", placeholder);

XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), "Pictures", placeholder);

DETECTED_OS = OSType.Windows;
}
else if (os.contains("linux"))
{
XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), ".config");
XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), ".local", "share");
XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), ".cache");
XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), ".local", "state");
// the default runtime directory on systemd is /run/user/1000, but this depends on the init. other
// init systems should make sure they've exported the runtime directory to the environment
XDG_RUNTIME_DIR = Paths.get("run", "user", System.getProperty("UID"));

XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), "Pictures");

DETECTED_OS = OSType.Linux;
}
else
{
XDG_CONFIG_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "config");
XDG_DATA_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "data");
XDG_CACHE_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "cache");
XDG_STATE_HOME = Paths.get(System.getProperty("user.home"), ".runelite", "state");
XDG_RUNTIME_DIR = Paths.get(System.getProperty("user.home"), ".runelite", "runtime");

XDG_PICTURES_DIR = Paths.get(System.getProperty("user.home"), ".runelite", "Pictures");

DETECTED_OS = OSType.Other;
}

// note: system variables don't have a placeholder for the appname.
switch (DETECTED_OS)
{
case Linux:
case MacOS:
PICTURES_DIR = Paths.get(System.getProperty("XDG_PICTURES_DIR", XDG_PICTURES_DIR.toString()));
CONFIG_HOME = Paths.get(System.getProperty("XDG_CONFIG_HOME", XDG_CONFIG_HOME.toString()));
DATA_HOME = Paths.get(System.getProperty("XDG_DATA_HOME", XDG_DATA_HOME.toString()));
CACHE_HOME = Paths.get(System.getProperty("XDG_CACHE_HOME", XDG_CACHE_HOME.toString()));
STATE_HOME = Paths.get(System.getProperty("XDG_STATE_HOME", XDG_STATE_HOME.toString()));
RUNTIME_DIR = Paths.get(System.getProperty("XDG_RUNTIME_DIR", XDG_RUNTIME_DIR.toString()));
break;
case Windows:
case Other:
default:
CONFIG_HOME = XDG_CONFIG_HOME;
DATA_HOME = XDG_DATA_HOME;
CACHE_HOME = XDG_CACHE_HOME;
STATE_HOME = XDG_STATE_HOME;
RUNTIME_DIR = XDG_RUNTIME_DIR;
PICTURES_DIR = XDG_PICTURES_DIR;
break;
}

log.debug("Detect OS: {}", DETECTED_OS);
}

static OSType parseOs(@Nonnull String os)

public static OSType getOs(@Nonnull String os)
{
return getOS(os);
}

public static OSType getOs()
{
os = os.toLowerCase();
if ((os.contains("mac")) || (os.contains("darwin")))
return getOS();
}

public static OSType getOS(@Nonnull String os)
{
if (os.contains("mac") || os.contains("darwin"))
{
return OSType.MacOS;
}
Expand All @@ -67,8 +179,89 @@ else if (os.contains("linux"))
}
}

public static OSType getOs()
public static OSType getOS()
{
return DETECTED_OS;
}
public static String getArch()
{
return DETECTED_ARCH;
}

public static boolean equals(String os)
{
return DETECTED_OS.equals(getOS(os.toLowerCase()));
}

public static String getXDG(@Nonnull String home, String appName) throws IllegalArgumentException
{
if (appName == null)
{
appName = "";
}

String config_home;
String data_home;
String cache_home;
String state_home;
String pictures_dir;
String runtime_dir;

if (OS.equals("windows"))
{
config_home = CONFIG_HOME.toString().replace(placeholder, appName);
data_home = DATA_HOME.toString().replace(placeholder, appName);
cache_home = CACHE_HOME.toString().replace(placeholder, appName);
state_home = STATE_HOME.toString().replace(placeholder, appName);
runtime_dir = RUNTIME_DIR.toString().replace(placeholder, appName);
}
else
{
config_home = Paths.get(CONFIG_HOME.toString(), appName).toString();
data_home = Paths.get(DATA_HOME.toString(), appName).toString();
cache_home = Paths.get(CACHE_HOME.toString(), appName).toString();
state_home = Paths.get(STATE_HOME.toString(), appName).toString();
runtime_dir = Paths.get(RUNTIME_DIR.toString(), appName).toString();
}
pictures_dir = Paths.get(PICTURES_DIR.toString(), appName).toString();

switch (home.toLowerCase())
{
case "config":
return config_home;
case "data":
return data_home;
case "cache":
return cache_home;
case "state":
return state_home;
case "runtime":
return state_home;
case "pictures":
case "screenshots":
return pictures_dir;
default:
throw new IllegalArgumentException("XDG paths must be config, data, cache or state");
}
}

public static boolean isCompatible(String platformName, String platformArch)
{
if (platformName == null)
{
return false;
}

OSType platformOS = OS.getOS(platformName);

// we should document why we are using the platform name when we don't find a match for the platform os
if (platformOS == OSType.Other ? platformName.equals(DETECTED_OS) : platformOS == DETECTED_OS)
{
if (platformArch == null || platformArch.equals(DETECTED_ARCH))
{
return true;
}
}
return false;
}
}