Rework install

This commit is contained in:
crschnick
2025-08-08 19:57:08 +00:00
parent 59683e3ff0
commit 66c8e2d0d5
64 changed files with 566 additions and 739 deletions
@@ -7,7 +7,6 @@ import io.xpipe.app.util.DocumentationLink;
import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
@@ -54,7 +53,7 @@ public class AppBeaconServer {
port = BeaconConfig.getUsedPort();
propertyPort = true;
} else {
port = XPipeInstallation.getDefaultBeaconPort();
port = BeaconConfig.getDefaultBeaconPort();
propertyPort = false;
}
INSTANCE = new AppBeaconServer(port, propertyPort);
@@ -120,7 +119,7 @@ public class AppBeaconServer {
}
private void initAuthSecret() throws IOException {
var file = XPipeInstallation.getLocalBeaconAuthFile();
var file = BeaconConfig.getLocalBeaconAuthFile();
var id = UUID.randomUUID().toString();
Files.writeString(file, id);
if (OsType.getLocal() != OsType.WINDOWS) {
@@ -130,7 +129,7 @@ public class AppBeaconServer {
}
private void deleteAuthSecret() {
var file = XPipeInstallation.getLocalBeaconAuthFile();
var file = BeaconConfig.getLocalBeaconAuthFile();
try {
Files.delete(file);
} catch (IOException ignored) {
@@ -34,7 +34,7 @@ public class OpenFileWithActionProvider implements BrowserActionProvider {
@Override
public boolean isApplicable(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
return OsType.getLocal().equals(OsType.WINDOWS)
return OsType.getLocal() == OsType.WINDOWS
&& entries.size() == 1
&& entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE);
}
@@ -35,7 +35,7 @@ public class BrowserFileSystemHelper {
return path;
}
if (shell.get().getOsType().equals(OsType.WINDOWS) && path.length() == 2 && path.endsWith(":")) {
if (shell.get().getOsType() == OsType.WINDOWS && path.length() == 2 && path.endsWith(":")) {
return path + "\\";
}
@@ -47,7 +47,7 @@ public class OpenFileWithMenuProvider implements BrowserMenuLeafProvider {
@Override
public boolean isApplicable(BrowserFileSystemTabModel model, List<BrowserEntry> entries) {
return OsType.getLocal().equals(OsType.WINDOWS)
return OsType.getLocal() == OsType.WINDOWS
&& entries.size() == 1
&& entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE);
}
@@ -35,7 +35,7 @@ public class RunFileMenuProvider extends MultiExecuteMenuProvider {
}
var os = shell.get().getOsType();
if (os.equals(OsType.WINDOWS)
if (os == OsType.WINDOWS
&& Stream.of("exe", "bat", "ps1", "cmd")
.anyMatch(s -> e.getPath().toString().endsWith(s))) {
return true;
@@ -66,6 +66,6 @@ public abstract class BaseUnzipUnixMenuProvider implements BrowserMenuLeafProvid
return entries.stream()
.allMatch(entry ->
entry.getRawFileEntry().getPath().toString().endsWith(".zip"))
&& !model.getFileSystem().getShell().orElseThrow().getOsType().equals(OsType.WINDOWS);
&& model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS;
}
}
@@ -60,6 +60,6 @@ public abstract class BaseUnzipWindowsActionProvider implements BrowserMenuLeafP
return entries.stream()
.allMatch(entry ->
entry.getRawFileEntry().getPath().toString().endsWith(".zip"))
&& model.getFileSystem().getShell().orElseThrow().getOsType().equals(OsType.WINDOWS);
&& model.getFileSystem().getShell().orElseThrow().getOsType() == OsType.WINDOWS;
}
}
@@ -44,7 +44,7 @@ public class AppDesktopIntegration {
// This will initialize the toolkit on macOS and create the dock icon
// macOS does not like applications that run fully in the background, so always do it
if (OsType.getLocal().equals(OsType.MACOS) && Desktop.isDesktopSupported()) {
if (OsType.getLocal() == OsType.MACOS && Desktop.isDesktopSupported()) {
Desktop.getDesktop().setPreferencesHandler(e -> {
if (PlatformState.getCurrent() != PlatformState.RUNNING) {
return;
@@ -6,8 +6,6 @@ import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.ModuleAccess;
import io.xpipe.core.ModuleLayerLoader;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import lombok.Getter;
@@ -70,15 +68,16 @@ public class AppExtensionManager {
private void determineExtensionDirectories() throws Exception {
if (!AppProperties.get().isFullVersion()) {
var localInstallation = XPipeInstallation.getLocalDefaultInstallationBasePath(
AppProperties.get().isStaging() || AppProperties.get().isLocatePtb());
Path p = localInstallation;
var localInstallation = !AppProperties.get().isStaging() && AppProperties.get().isLocatePtb() ?
AppInstallation.ofDefault(true)
: AppInstallation.ofCurrent();
Path p = localInstallation.getBaseInstallationPath();
if (!Files.exists(p)) {
throw new IllegalStateException(
"Required local XPipe installation was not found but is required for development. See https://github.com/xpipe-io/xpipe/blob/master/CONTRIBUTING.md#development-setup");
}
var iv = getLocalInstallVersion();
var iv = getLocalInstallVersion(localInstallation);
var installVersion = AppVersion.parse(iv)
.orElseThrow(() -> new IllegalArgumentException("Invalid installation version: " + iv));
var sv = !AppProperties.get().isImage()
@@ -92,14 +91,13 @@ public class AppExtensionManager {
+ "\n\nPlease try to check out the matching release version in the repository. See https://github.com/xpipe-io/xpipe/blob/master/CONTRIBUTING.md#development-setup");
}
var extensions = XPipeInstallation.getLocalExtensionsDirectory(p);
var extensions = localInstallation.getExtensionsPath();
extensionBaseDirectories.add(extensions);
}
}
private static String getLocalInstallVersion() throws Exception {
var localInstallation = XPipeInstallation.getLocalDefaultInstallationBasePath();
var exec = localInstallation.resolve(XPipeInstallation.getDaemonExecutablePath(OsType.getLocal()));
private static String getLocalInstallVersion(AppInstallation localInstallation) throws Exception {
var exec = localInstallation.getDaemonExecutablePath();
var fc = new ProcessBuilder(exec.toString(), "version").redirectError(ProcessBuilder.Redirect.DISCARD);
var proc = fc.start();
var out = new String(proc.getInputStream().readAllBytes());
@@ -3,7 +3,6 @@ package io.xpipe.app.core;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.SupportedLocale;
import io.xpipe.core.XPipeInstallation;
import lombok.Value;
import org.apache.commons.io.FilenameUtils;
@@ -59,7 +58,7 @@ public class AppI18nData {
var translations = new HashMap<String, String>();
{
var basePath = XPipeInstallation.getLangPath().resolve("strings");
var basePath = AppInstallation.ofCurrent().getLangPath().resolve("strings");
AtomicInteger fileCounter = new AtomicInteger();
AtomicInteger lineCounter = new AtomicInteger();
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
@@ -94,7 +93,7 @@ public class AppI18nData {
var markdownDocumentations = new HashMap<String, String>();
{
var basePath = XPipeInstallation.getLangPath().resolve("texts");
var basePath = AppInstallation.ofCurrent().getLangPath().resolve("texts");
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
@@ -0,0 +1,312 @@
package io.xpipe.app.core;
import io.xpipe.core.OsType;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public abstract class AppInstallation {
private static final Windows WINDOWS = new Windows(determineCurrentInstallationBasePath(AppProperties.get().isStaging()));
private static final Linux LINUX = new Linux(determineCurrentInstallationBasePath(AppProperties.get().isStaging()));
private static final MacOs MACOS = new MacOs(determineCurrentInstallationBasePath(AppProperties.get().isStaging()));
private AppInstallation(Path base) {this.base = base;}
public static AppInstallation ofCurrent() {
return switch (OsType.getLocal()) {
case OsType.Windows ignored -> WINDOWS;
case OsType.Linux ignored -> LINUX;
case OsType.MacOs ignored -> MACOS;
default -> throw new IllegalStateException();
};
}
public static AppInstallation ofDefault() {
return ofDefault(AppProperties.get().isStaging());
}
public static AppInstallation ofDefault(boolean stage) {
var def = determineDefaultInstallationBasePath(stage);
return switch (OsType.getLocal()) {
case OsType.Windows ignored -> new Windows(def);
case OsType.Linux ignored -> new Linux(def);
case OsType.MacOs ignored -> new MacOs(def);
default -> throw new IllegalStateException();
};
}
private static Path determineDefaultInstallationBasePath(boolean stage) {
return switch (OsType.getLocal()) {
case OsType.Linux linux -> {
yield Path.of(stage ? "/opt/xpipe-ptb" : "/opt/xpipe");
}
case OsType.MacOs macOs -> {
yield Path.of(stage ? "/Applications/XPipe PTB.app" : "/Applications/XPipe.app");
}
case OsType.Windows windows -> {
var pg = AppLocations.getWindows().getProgramFiles();
var systemPath = pg.resolve(stage ? "XPipe PTB" : "XPipe");
if (Files.exists(systemPath)) {
yield systemPath;
}
var ad = AppLocations.getWindows().getLocalAppData();
yield ad.resolve(stage ? "XPipe PTB" : "XPipe");
}
};
}
private static Path determineCurrentInstallationBasePath(boolean stage) {
var command = ProcessHandle.current().info().command();
// We should always have a command associated with the current process, otherwise something went seriously wrong
if (command.isEmpty()) {
var javaHome = System.getProperty("java.home");
var javaExec = toRealPathIfPossible(Path.of(javaHome, "bin", "java"));
var path = getInstallationBasePathForJavaExecutable(javaExec);
return path;
}
// Resolve any possible links to a real path
Path path = toRealPathIfPossible(Path.of(command.get()));
// Check if the process was started using a relative path, and adapt it if necessary
if (!path.isAbsolute()) {
path = toRealPathIfPossible(Path.of(System.getProperty("user.dir")).resolve(path));
}
var name = path.getFileName().toString();
// Check if we launched the JVM via a start script instead of the native executable
if (name.endsWith("java") || name.endsWith("java.exe")) {
// If we are not an image, we are probably running in a development environment where we want to use the
// working directory
var isImage = AppProperties.get().isImage();
if (!isImage) {
return Path.of(System.getProperty("user.dir"));
}
return getInstallationBasePathForJavaExecutable(path);
} else {
return getInstallationBasePathForDaemonExecutable(path);
}
}
private static Path getInstallationBasePathForDaemonExecutable(Path executable) {
// Resolve root path of installation relative to executable in a JPackage installation
return switch (OsType.getLocal()) {
case OsType.Linux linux -> {
yield executable.getParent().getParent();
}
case OsType.MacOs macOs -> {
yield executable.getParent().getParent().getParent();
}
case OsType.Windows windows -> {
yield executable.getParent();
}
};
}
private static Path getInstallationBasePathForJavaExecutable(Path executable) {
// Resolve root path of installation relative to executable in a JPackage installation
return switch (OsType.getLocal()) {
case OsType.Linux linux -> {
yield executable.getParent().getParent().getParent().getParent();
}
case OsType.MacOs macOs -> {
yield executable
.getParent()
.getParent()
.getParent()
.getParent()
.getParent()
.getParent();
}
case OsType.Windows windows -> {
yield executable.getParent().getParent();
}
};
}
private final Path base;
public Path getBaseInstallationPath() {
return base;
}
private static Path toRealPathIfPossible(Path p) {
try {
// Under certain conditions, e.g. when running on a ramdisk, path resolution might fail.
// This is however not a big problem in that case, so we ignore it
return p.toRealPath();
} catch (IOException e) {
return p;
}
}
public abstract Path getDaemonDebugScriptPath();
public abstract Path getBundledFontsPath();
public abstract Path getLangPath();
public abstract Path getCliExecutablePath();
public abstract Path getDaemonExecutablePath();
public abstract Path getExtensionsPath();
public abstract Path getLogoPath();
public static class Windows extends AppInstallation {
private Windows(Path base) {
super(base);
}
@Override
public Path getDaemonDebugScriptPath() {
return getBaseInstallationPath().resolve("scripts", "xpiped_debug.bat");
}
@Override
public Path getBundledFontsPath() {
if (!AppProperties.get().isImage()) {
return getBaseInstallationPath().resolve("dist", "fonts");
}
return getBaseInstallationPath().resolve("fonts");
}
@Override
public Path getLangPath() {
return getBaseInstallationPath().resolve("lang");
}
@Override
public Path getCliExecutablePath() {
return getBaseInstallationPath().resolve("bin", "xpipe.exe");
}
@Override
public Path getDaemonExecutablePath() {
return getBaseInstallationPath().resolve("xpiped.exe");
}
@Override
public Path getExtensionsPath() {
return getBaseInstallationPath().resolve("extensions");
}
@Override
public Path getLogoPath() {
if (!AppProperties.get().isImage()) {
return getBaseInstallationPath().resolve("dist").resolve("logo").resolve("logo.ico");
}
return getBaseInstallationPath().resolve("logo.ico");
}
}
public static class Linux extends AppInstallation {
private Linux(Path base) {
super(base);
}
@Override
public Path getDaemonDebugScriptPath() {
return getBaseInstallationPath().resolve("scripts", "xpiped_debug.sh");
}
@Override
public Path getBundledFontsPath() {
if (!AppProperties.get().isImage()) {
return getBaseInstallationPath().resolve("dist", "fonts");
}
return getBaseInstallationPath().resolve("fonts");
}
@Override
public Path getLangPath() {
return getBaseInstallationPath().resolve("lang");
}
@Override
public Path getCliExecutablePath() {
return getBaseInstallationPath().resolve("bin", "xpipe");
}
@Override
public Path getDaemonExecutablePath() {
return getBaseInstallationPath().resolve("bin", "xpiped");
}
@Override
public Path getExtensionsPath() {
return getBaseInstallationPath().resolve("extensions");
}
@Override
public Path getLogoPath() {
if (!AppProperties.get().isImage()) {
return getBaseInstallationPath().resolve("dist").resolve("logo").resolve("logo.png");
}
return getBaseInstallationPath().resolve("logo.png");
}
}
public static class MacOs extends AppInstallation {
private MacOs(Path base) {
super(base);
}
@Override
public Path getDaemonDebugScriptPath() {
return getBaseInstallationPath().resolve("Contents", "Resources", "scripts", "xpiped_debug.sh");
}
@Override
public Path getBundledFontsPath() {
if (!AppProperties.get().isImage()) {
return getBaseInstallationPath().resolve("dist", "fonts");
}
return getBaseInstallationPath().resolve("Contents", "Resources", "fonts");
}
@Override
public Path getLangPath() {
if (!AppProperties.get().isImage()) {
return getBaseInstallationPath().resolve("lang");
}
return getBaseInstallationPath().resolve("Contents", "Resources", "lang");
}
@Override
public Path getCliExecutablePath() {
return getBaseInstallationPath().resolve("Contents", "MacOS", "xpipe");
}
@Override
public Path getDaemonExecutablePath() {
return getBaseInstallationPath().resolve("Contents", "MacOS", "xpiped");
}
@Override
public Path getExtensionsPath() {
return getBaseInstallationPath().resolve("Contents", "Resources", "extensions");
}
@Override
public Path getLogoPath() {
if (!AppProperties.get().isImage()) {
return getBaseInstallationPath().resolve("dist").resolve("logo").resolve("logo.icns");
}
return getBaseInstallationPath().resolve("Contents").resolve("Resources").resolve("xpipe.icns");
}
}
}
@@ -11,7 +11,6 @@ import io.xpipe.beacon.BeaconServer;
import io.xpipe.beacon.api.DaemonFocusExchange;
import io.xpipe.beacon.api.DaemonOpenExchange;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import java.awt.*;
import java.util.List;
@@ -82,7 +81,7 @@ public class AppInstance {
return;
}
var cli = XPipeInstallation.getLocalDefaultCliExecutable();
var cli = AppInstallation.ofCurrent().getCliExecutablePath();
ErrorEventFactory.fromThrowable(
"Unable to connect to existing running daemon instance as it did not respond."
+ " Either try to kill the process xpiped manually or use the command \""
@@ -94,7 +93,7 @@ public class AppInstance {
.handle();
}
if (OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.MACOS) {
Desktop.getDesktop().setOpenURIHandler(e -> {
try {
client.get()
@@ -0,0 +1,97 @@
package io.xpipe.app.core;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
public interface AppLocations {
Windows WINDOWS = new Windows();
Linux LINUX = new Linux();
MacOs MACOS = new MacOs();
static Windows getWindows() {
return WINDOWS;
}
static Linux getLinux() {
return LINUX;
}
static MacOs getMacOs() {
return MACOS;
}
private static Path parsePath(String path) {
if (path == null) {
return null;
}
try {
return Path.of(path);
} catch (InvalidPathException ignored) {
return null;
}
}
final class Windows implements AppLocations {
private Path userHome;
public Path getProgramFiles() {
var env = AppLocations.parsePath(System.getenv("ProgramFiles"));
if (env != null) {
return env;
}
var def = Path.of("C:\\ProgramFiles");
return def;
}
public Path getLocalAppData() {
var env = AppLocations.parsePath(System.getenv("LOCALAPPDATA"));
if (env != null) {
return env;
}
var def = getUserHome().resolve("AppData").resolve("Local");
return def;
}
public Path getUserHome() {
if (userHome != null) {
return userHome;
}
var dir = AppLocations.parsePath(System.getenv("USERPROFILE"));
if (dir == null) {
dir = AppLocations.parsePath(System.getProperty("user.home"));
}
if (dir == null) {
var username = System.getenv("USERNAME");
if (username == null) {
username = System.getProperty("user.name");
}
if (username == null) {
username = "User";
}
dir = Path.of("C:\\Users\\" + username);
}
try {
// Replace 8.3 filename
userHome = dir.toRealPath();
} catch (Exception ignored) {
userHome = dir;
}
return dir;
}
}
class Linux implements AppLocations {
}
final class MacOs implements AppLocations {
}
}
@@ -6,7 +6,6 @@ import io.xpipe.app.process.ShellDialect;
import io.xpipe.app.process.ShellDialects;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import java.util.List;
@@ -14,17 +13,17 @@ public class AppRestart {
private static String createTerminalLaunchCommand(List<String> arguments, ShellDialect dialect) {
var loc = AppProperties.get().isDevelopmentEnvironment()
? XPipeInstallation.getLocalDefaultInstallationBasePath()
: XPipeInstallation.getCurrentInstallationBasePath();
? AppInstallation.ofDefault()
: AppInstallation.ofCurrent();
var suffix = (arguments.size() > 0 ? " " + String.join(" ", arguments) : "");
if (OsType.getLocal().equals(OsType.LINUX)) {
var exec = loc.resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal()));
if (OsType.getLocal() == OsType.LINUX) {
var exec = loc.getCliExecutablePath();
return "\"" + exec + "\" open" + suffix;
} else if (OsType.getLocal().equals(OsType.MACOS)) {
var exec = loc.resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal()));
} else if (OsType.getLocal() == OsType.MACOS) {
var exec = loc.getCliExecutablePath();
return "\"" + exec + "\" open" + suffix;
} else {
var exe = loc.resolve(XPipeInstallation.getDaemonExecutablePath(OsType.getLocal()));
var exe = loc.getDaemonDebugScriptPath();
if (ShellDialects.isPowershell(dialect)) {
var escapedList =
arguments.stream().map(s -> s.replaceAll("\"", "`\"")).toList();
@@ -39,15 +38,15 @@ public class AppRestart {
private static String createBackgroundLaunchCommand(List<String> arguments, ShellDialect dialect) {
var loc = AppProperties.get().isDevelopmentEnvironment()
? XPipeInstallation.getLocalDefaultInstallationBasePath()
: XPipeInstallation.getCurrentInstallationBasePath();
? AppInstallation.ofDefault()
: AppInstallation.ofCurrent();
var suffix = (arguments.size() > 0 ? " " + String.join(" ", arguments) : "");
if (OsType.getLocal().equals(OsType.LINUX)) {
return "nohup \"" + loc + "/bin/xpiped\"" + suffix + " </dev/null >/dev/null 2>&1 & disown";
} else if (OsType.getLocal().equals(OsType.MACOS)) {
return "(sleep 1;open \"" + loc + "\" --args" + suffix + " </dev/null &>/dev/null) & disown";
if (OsType.getLocal() == OsType.LINUX) {
return "nohup \"" + loc.getDaemonExecutablePath() + "\"" + suffix + " </dev/null >/dev/null 2>&1 & disown";
} else if (OsType.getLocal() == OsType.MACOS) {
return "(sleep 1;open \"" + loc.getBaseInstallationPath() + "\" --args" + suffix + " </dev/null &>/dev/null) & disown";
} else {
var exe = loc.resolve(XPipeInstallation.getDaemonExecutablePath(OsType.getLocal()));
var exe = loc.getDaemonExecutablePath();
if (ShellDialects.isPowershell(dialect)) {
var escapedList =
arguments.stream().map(s -> s.replaceAll("\"", "`\"")).toList();
@@ -15,7 +15,7 @@ public class AppSid {
private static boolean hasSetsid;
public static void init() {
if (OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() == OsType.WINDOWS) {
return;
}
@@ -103,7 +103,7 @@ public class AppTrayIcon {
}
public void showErrorMessage(String title, String message) {
if (OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.MACOS) {
showMacAlert(title, message, "Error");
} else {
EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.ERROR));
@@ -26,7 +26,7 @@ public class AppCertutilCheck {
return;
}
if (!OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() != OsType.WINDOWS) {
return;
}
@@ -24,7 +24,7 @@ public class AppHomebrewCoreutilsCheck {
}
public static void check() {
if (!OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() != OsType.MACOS) {
return;
}
@@ -1,7 +1,7 @@
package io.xpipe.app.core.check;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import java.util.concurrent.TimeUnit;
@@ -17,7 +17,7 @@ public class AppSystemFontCheck {
}
System.setProperty(
"prism.fontdir", XPipeInstallation.getBundledFontsPath().toString());
"prism.fontdir", AppInstallation.ofCurrent().getBundledFontsPath().toString());
System.setProperty("prism.embeddedfonts", "true");
}
@@ -29,7 +29,7 @@ public class AppTempCheck {
}
public static void check() {
if (!OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() != OsType.WINDOWS) {
return;
}
@@ -1,14 +1,14 @@
package io.xpipe.app.core.check;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.process.ProcessOutputException;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
public class AppTestCommandCheck {
public static void check() throws Exception {
if (OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() == OsType.WINDOWS) {
return;
}
@@ -17,7 +17,7 @@ public class AppTestCommandCheck {
sc.getShellDialect()
.directoryExists(
sc,
XPipeInstallation.getCurrentInstallationBasePath()
AppInstallation.ofCurrent().getBaseInstallationPath()
.toString())
.execute();
} catch (ProcessOutputException ex) {
@@ -11,7 +11,7 @@ public class TrayMode extends PlatformMode {
@Override
public boolean isSupported() {
return OsType.getLocal().equals(OsType.WINDOWS)
return OsType.getLocal()== OsType.WINDOWS
&& super.isSupported()
&& Desktop.isDesktopSupported()
&& SystemTray.isSupported();
@@ -298,7 +298,7 @@ public class AppMainWindow {
}
});
if (OsType.getLocal().equals(OsType.LINUX) || OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.LINUX || OsType.getLocal() == OsType.MACOS) {
stage.getScene().addEventHandler(KeyEvent.KEY_PRESSED, event -> {
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(event)) {
OperationMode.onWindowClose();
@@ -1,6 +1,6 @@
package io.xpipe.app.ext;
import io.xpipe.core.XPipeInstallation;
import io.xpipe.app.core.AppInstallation;
public class ExtensionException extends RuntimeException {
@@ -24,7 +24,7 @@ public class ExtensionException extends RuntimeException {
public static ExtensionException corrupt(String message, Throwable cause) {
try {
var loc = XPipeInstallation.getCurrentInstallationBasePath();
var loc = AppInstallation.ofCurrent().getBaseInstallationPath();
var full =
message + ".\n\n" + "Please check whether the XPipe installation data at " + loc + " is corrupted.";
return new ExtensionException(full, cause);
@@ -80,7 +80,7 @@ public interface ExternalApplicationType extends PrefsValue {
@Override
default boolean isSelectable() {
return OsType.getLocal().equals(OsType.MACOS);
return OsType.getLocal() == OsType.MACOS;
}
}
@@ -180,7 +180,7 @@ public interface ExternalApplicationType extends PrefsValue {
@Override
default boolean isSelectable() {
return OsType.getLocal().equals(OsType.WINDOWS);
return OsType.getLocal() == OsType.WINDOWS;
}
}
}
@@ -515,13 +515,13 @@ public interface ExternalEditorType extends PrefsChoiceValue {
@SuppressWarnings("TrivialFunctionalExpressionUsage")
List<ExternalEditorType> ALL = ((Supplier<List<ExternalEditorType>>) () -> {
var all = new ArrayList<ExternalEditorType>();
if (OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() == OsType.WINDOWS) {
all.addAll(WINDOWS_EDITORS);
}
if (OsType.getLocal().equals(OsType.LINUX)) {
if (OsType.getLocal() == OsType.LINUX) {
all.addAll(LINUX_EDITORS);
}
if (OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.MACOS) {
all.addAll(MACOS_EDITORS);
}
all.addAll(CROSS_PLATFORM_EDITORS);
@@ -536,21 +536,21 @@ public interface ExternalEditorType extends PrefsChoiceValue {
return existing;
}
if (OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() == OsType.WINDOWS) {
return WINDOWS_EDITORS.stream()
.filter(PrefsChoiceValue::isAvailable)
.findFirst()
.orElse(NOTEPAD);
}
if (OsType.getLocal().equals(OsType.LINUX)) {
if (OsType.getLocal() == OsType.LINUX) {
return LINUX_EDITORS.stream()
.filter(ExternalApplicationType.PathApplication::isAvailable)
.findFirst()
.orElse(null);
}
if (OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.MACOS) {
return MACOS_EDITORS.stream()
.filter(PrefsChoiceValue::isAvailable)
.findFirst()
@@ -655,7 +655,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
@Override
public boolean isSelectable() {
return OsType.getLocal().equals(OsType.LINUX);
return OsType.getLocal() == OsType.LINUX;
}
}
@@ -4,6 +4,7 @@ import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.comp.base.TileButtonComp;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppLogs;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
@@ -18,7 +19,6 @@ import io.xpipe.app.update.AppDistributionType;
import io.xpipe.app.util.*;
import io.xpipe.core.FilePath;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import com.sun.management.HotSpotDiagnosticMXBean;
import lombok.SneakyThrows;
@@ -61,10 +61,7 @@ public class TroubleshootCategory extends AppPrefsCategory {
.addComp(
new TileButtonComp("launchDebugMode", "launchDebugModeDescription", "mdmz-refresh", e -> {
OperationMode.executeAfterShutdown(() -> {
var script = FilePath.of(
XPipeInstallation.getCurrentInstallationBasePath()
.toString(),
XPipeInstallation.getDaemonDebugScriptPath(OsType.getLocal()));
var script = AppInstallation.ofCurrent().getDaemonDebugScriptPath();
TerminalLaunch.builder()
.title("XPipe Debug")
.localScript(sc -> new ShellScript(
@@ -98,8 +95,7 @@ public class TroubleshootCategory extends AppPrefsCategory {
"openInstallationDirectoryDescription",
"mdomz-snippet_folder",
e -> {
DesktopHelper.browsePathLocal(
XPipeInstallation.getCurrentInstallationBasePath());
DesktopHelper.browsePathLocal(AppInstallation.ofCurrent().getBaseInstallationPath());
e.consume();
})
.grow(true, false),
@@ -165,7 +161,7 @@ public class TroubleshootCategory extends AppPrefsCategory {
"uninstallApplicationDescription",
"mdi2d-dump-truck",
e -> {
var file = XPipeInstallation.getCurrentInstallationBasePath()
var file = AppInstallation.ofCurrent().getBaseInstallationPath()
.resolve("Contents")
.resolve("Resources")
.resolve("scripts")
@@ -3,12 +3,13 @@ package io.xpipe.app.prefs;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.util.*;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import javafx.beans.property.SimpleObjectProperty;
@@ -44,9 +45,7 @@ public class WorkspaceCreationDialog {
var file =
switch (OsType.getLocal()) {
case OsType.Windows w -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath()
.resolve(XPipeInstallation.getDaemonExecutablePath(w))
.toString();
var exec = AppInstallation.ofCurrent().getDaemonExecutablePath().toString();
yield DesktopShortcuts.create(
exec,
"-Dio.xpipe.app.dataDir=\""
@@ -54,8 +53,7 @@ public class WorkspaceCreationDialog {
shortcutName);
}
default -> {
var exec = XPipeInstallation.getCurrentInstallationBasePath()
.resolve(XPipeInstallation.getRelativeCliExecutablePath(OsType.getLocal()))
var exec = AppInstallation.ofCurrent().getCliExecutablePath()
.toString();
yield DesktopShortcuts.create(
exec,
@@ -41,13 +41,13 @@ public interface ExternalRdpClient extends PrefsChoiceValue {
@SuppressWarnings("TrivialFunctionalExpressionUsage")
List<ExternalRdpClient> ALL = ((Supplier<List<ExternalRdpClient>>) () -> {
var all = new ArrayList<ExternalRdpClient>();
if (OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() == OsType.WINDOWS) {
all.addAll(WINDOWS_CLIENTS);
}
if (OsType.getLocal().equals(OsType.LINUX)) {
if (OsType.getLocal() == OsType.LINUX) {
all.addAll(LINUX_CLIENTS);
}
if (OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.MACOS) {
all.addAll(MACOS_CLIENTS);
}
all.add(CUSTOM);
@@ -35,7 +35,7 @@ public class CmdTerminalType
}
private CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
if (configuration.getScriptDialect() == ShellDialects.CMD) {
return CommandBuilder.of().add("/c").addFile(configuration.getScriptFile());
}
@@ -38,7 +38,7 @@ public class CustomTerminalType implements ExternalApplicationType, ExternalTerm
var toExecute = ExternalApplicationHelper.replaceVariableArgument(
format, "CMD", configuration.getScriptFile().toString());
// We can't be sure whether the command is blocking or not, so always make it not blocking
if (pc.getOsType().equals(OsType.WINDOWS)) {
if (pc.getOsType() == OsType.WINDOWS) {
toExecute = "start \"" + configuration.getCleanTitle() + "\" " + toExecute;
} else {
toExecute = "nohup " + toExecute + " </dev/null &>/dev/null & disown";
@@ -606,13 +606,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
static List<ExternalTerminalType> getTypes(OsType osType, boolean custom) {
var all = new ArrayList<ExternalTerminalType>();
if (osType == null || osType.equals(OsType.WINDOWS)) {
if (osType == null || osType == OsType.WINDOWS) {
all.addAll(WINDOWS_TERMINALS);
}
if (osType == null || osType.equals(OsType.LINUX)) {
if (osType == null || osType == OsType.LINUX) {
all.addAll(LINUX_TERMINALS);
}
if (osType == null || osType.equals(OsType.MACOS)) {
if (osType == null || osType == OsType.MACOS) {
all.addAll(MACOS_TERMINALS);
}
// Prefer recommended
@@ -626,7 +626,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
static ExternalTerminalType determineDefault(ExternalTerminalType existing) {
// Check for incompatibility with fallback shell
if (ExternalTerminalType.CMD.equals(existing)
&& !ProcessControlProvider.get().getEffectiveLocalDialect().equals(ShellDialects.CMD)) {
&& ProcessControlProvider.get().getEffectiveLocalDialect() != ShellDialects.CMD) {
return ExternalTerminalType.POWERSHELL;
}
@@ -1,5 +1,6 @@
package io.xpipe.app.terminal;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.prefs.ExternalApplicationType;
@@ -11,7 +12,7 @@ import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.ShellTemp;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.FilePath;
import io.xpipe.core.XPipeInstallation;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
@@ -38,7 +39,7 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
payload.put("type", "tab");
payload.put("logo_alpha", 0.01);
payload.put(
"logo", XPipeInstallation.getLocalDefaultInstallationIcon().toString());
"logo", AppInstallation.ofCurrent().getLogoPath().toString());
var json = JsonNodeFactory.instance.objectNode();
json.put("cmd", "launch");
@@ -42,7 +42,7 @@ public class PowerShellTerminalType implements ExternalApplicationType.PathAppli
}
protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) {
if (configuration.getScriptDialect().equals(ShellDialects.POWERSHELL)) {
if (configuration.getScriptDialect() == ShellDialects.POWERSHELL) {
return CommandBuilder.of()
.add("-ExecutionPolicy", "Bypass")
.add("-File")
@@ -66,7 +66,7 @@ public interface TabbyTerminalType extends ExternalTerminalType, TrackableTermin
@Override
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
if (configuration.getScriptDialect() == ShellDialects.CMD) {
// It also freezes with any other input than .bat files, why?
launch(CommandBuilder.of()
.add("run")
@@ -1,5 +1,6 @@
package io.xpipe.app.terminal;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEventFactory;
@@ -17,7 +18,6 @@ import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.ScriptHelper;
import io.xpipe.core.FilePath;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import lombok.*;
import lombok.experimental.NonFinal;
@@ -127,7 +127,7 @@ public class TerminalLaunchConfiguration {
var cliExecutable = TerminalProxyManager.getProxy()
.orElse(LocalShell.getShell())
.getLocalSystemAccess()
.translateFromLocalSystemPath(FilePath.of(XPipeInstallation.getLocalDefaultCliExecutable()));
.translateFromLocalSystemPath(FilePath.of(AppInstallation.ofCurrent().getCliExecutablePath()));
var scriptCommand = sc.getOsType() == OsType.MACOS || sc.getOsType() == OsType.BSD
? "script -e -q '%s' \"%s\"".formatted(logFile, command)
: "script --quiet --command '%s' \"%s\"".formatted(command, logFile);
@@ -1,6 +1,7 @@
package io.xpipe.app.terminal;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.prefs.AppPrefs;
@@ -11,7 +12,7 @@ import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.ScriptHelper;
import io.xpipe.core.FailableFunction;
import io.xpipe.core.FilePath;
import io.xpipe.core.XPipeInstallation;
import java.io.IOException;
import java.util.List;
@@ -192,7 +193,7 @@ public class TerminalLauncher {
}
private static String getTerminalRegisterCommand(UUID request) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
var exec = AppInstallation.ofCurrent().getCliExecutablePath();
return CommandBuilder.of()
.addFile(exec)
.add("terminal-register", "--request", request.toString())
@@ -1,10 +1,11 @@
package io.xpipe.app.terminal;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.process.CommandBuilder;
import io.xpipe.app.util.CommandSupport;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.XPipeInstallation;
public interface WaveTerminalType extends ExternalTerminalType, TrackableTerminalType {
@@ -65,7 +66,7 @@ public interface WaveTerminalType extends ExternalTerminalType, TrackableTermina
.formatted(
inPath
? "xpipe open"
: XPipeInstallation.getLocalDefaultCliExecutable() + " open");
: "\"" + AppInstallation.ofCurrent().getCliExecutablePath() + "\" open");
throw ErrorEventFactory.expected(new IllegalStateException(msg));
}
@@ -1,13 +1,14 @@
package io.xpipe.app.terminal;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.process.CommandBuilder;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.FilePath;
import io.xpipe.core.JacksonMapper;
import io.xpipe.core.XPipeInstallation;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
@@ -91,8 +92,8 @@ public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerm
newProfile.put("suppressApplicationTitle", true);
newProfile.put("elevate", false);
if (!AppProperties.get().isDevelopmentEnvironment()) {
var dir = XPipeInstallation.getLocalDefaultInstallationIcon();
newProfile.put("icon", dir.toString());
var logoFile = AppInstallation.ofCurrent().getLogoPath();
newProfile.put("icon", logoFile.toString());
}
profiles.add(newProfile);
JacksonMapper.getDefault().writeValue(getConfigFile().toFile(), config);
@@ -15,7 +15,7 @@ public class LocalExtensionTest extends ExtensionTest {
return;
}
var mode = OsType.getLocal().equals(OsType.WINDOWS) ? "tray" : "background";
var mode = OsType.getLocal() == OsType.WINDOWS ? "tray" : "background";
OperationMode.init(new String[] {"-Dio.xpipe.app.mode=" + mode});
}
}
@@ -7,7 +7,7 @@ import io.xpipe.app.process.ShellScript;
import io.xpipe.app.util.LocalExec;
import io.xpipe.app.util.Translatable;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import javafx.beans.value.ObservableValue;
@@ -110,9 +110,7 @@ public enum AppDistributionType implements Translatable {
private static boolean isDifferentDaemonExecutable() {
var cached = AppCache.getNonNull("daemonExecutable", String.class, () -> null);
var current = XPipeInstallation.getCurrentInstallationBasePath()
.resolve(XPipeInstallation.getDaemonExecutablePath(OsType.getLocal()))
.toString();
var current = AppInstallation.ofCurrent().getDaemonExecutablePath().toString();
if (current.equals(cached)) {
return false;
}
@@ -130,9 +128,9 @@ public enum AppDistributionType implements Translatable {
}
public static AppDistributionType determine() {
var base = XPipeInstallation.getCurrentInstallationBasePath();
if (OsType.getLocal().equals(OsType.MACOS)) {
if (!base.equals(XPipeInstallation.getLocalDefaultInstallationBasePath())) {
var base = AppInstallation.ofCurrent().getBaseInstallationPath();
if (OsType.getLocal() == OsType.MACOS) {
if (!base.equals(AppInstallation.ofDefault().getBaseInstallationPath())) {
return PORTABLE;
}
@@ -163,7 +161,7 @@ public enum AppDistributionType implements Translatable {
return WEBTOP;
}
if (OsType.getLocal().equals(OsType.WINDOWS) && !AppProperties.get().isStaging()) {
if (OsType.getLocal() == OsType.WINDOWS && !AppProperties.get().isStaging()) {
var chocoOut = LocalExec.readStdoutIfPossible("choco", "list", "xpipe");
if (chocoOut.isPresent()) {
if (chocoOut.get().contains("xpipe")
@@ -182,7 +180,7 @@ public enum AppDistributionType implements Translatable {
// }
}
if (OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.MACOS) {
var out = LocalExec.readStdoutIfPossible("/opt/homebrew/bin/brew", "list", "--casks", "--versions");
if (out.isPresent()) {
if (out.get().lines().anyMatch(s -> {
@@ -1,5 +1,6 @@
package io.xpipe.app.update;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppLogs;
import io.xpipe.app.core.AppRestart;
import io.xpipe.app.core.mode.OperationMode;
@@ -14,7 +15,7 @@ import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.FailableRunnable;
import io.xpipe.core.FilePath;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@@ -27,17 +28,17 @@ import java.nio.file.Path;
public class AppInstaller {
public static InstallerAssetType getSuitablePlatformAsset() {
if (OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() == OsType.WINDOWS) {
return new InstallerAssetType.Msi();
}
if (OsType.getLocal().equals(OsType.LINUX)) {
if (OsType.getLocal() == OsType.LINUX) {
return Files.exists(Path.of("/etc/debian_version"))
? new InstallerAssetType.Debian()
: new InstallerAssetType.Rpm();
}
if (OsType.getLocal().equals(OsType.MACOS)) {
if (OsType.getLocal() == OsType.MACOS) {
return new InstallerAssetType.Pkg();
}
@@ -69,7 +70,7 @@ public class AppInstaller {
FilePath.of(logsDir, "installer_" + file.getFileName().toString() + ".log");
var systemWide = isSystemWide();
var cmdScript =
ProcessControlProvider.get().getEffectiveLocalDialect().equals(ShellDialects.CMD)
ProcessControlProvider.get().getEffectiveLocalDialect() == ShellDialects.CMD
&& !systemWide;
var command = cmdScript
? getCmdCommand(file.toString(), logFile.toString())
@@ -101,8 +102,7 @@ public class AppInstaller {
}
private boolean isSystemWide() {
return Files.exists(
XPipeInstallation.getCurrentInstallationBasePath().resolve("system"));
return Files.exists(AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
}
private String getCmdCommand(String file, String logFile) {
@@ -2,6 +2,7 @@ package io.xpipe.app.update;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.AppRestart;
import io.xpipe.app.core.mode.OperationMode;
@@ -13,7 +14,7 @@ import io.xpipe.app.terminal.TerminalLaunch;
import io.xpipe.app.terminal.TerminalLauncher;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.XPipeInstallation;
import java.nio.file.Files;
import java.time.Instant;
@@ -120,8 +121,7 @@ public class ChocoUpdater extends UpdateHandler {
var performedUpdate = new PerformedUpdate(p.getVersion(), p.getBody(), p.getVersion());
AppCache.update("performedUpdate", performedUpdate);
OperationMode.executeAfterShutdown(() -> {
var systemWide = Files.exists(
XPipeInstallation.getCurrentInstallationBasePath().resolve("system"));
var systemWide = Files.exists(AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
var propertiesArguments = systemWide ? ", --install-arguments=\"'ALLUSERS=1'\"" : "";
TerminalLaunch.builder().title("XPipe Updater").localScript(sc -> {
var pkg = "xpipe";
@@ -2,6 +2,7 @@ package io.xpipe.app.update;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.AppRestart;
import io.xpipe.app.core.mode.OperationMode;
@@ -12,7 +13,7 @@ import io.xpipe.app.terminal.TerminalLaunch;
import io.xpipe.app.terminal.TerminalLauncher;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.XPipeInstallation;
import java.nio.file.Files;
import java.time.Instant;
@@ -121,8 +122,7 @@ public class WingetUpdater extends UpdateHandler {
AppCache.update("performedUpdate", performedUpdate);
OperationMode.executeAfterShutdown(() -> {
TerminalLaunch.builder().title("XPipe Updater").localScript(sc -> {
var systemWide = Files.exists(
XPipeInstallation.getCurrentInstallationBasePath().resolve("system"));
var systemWide = Files.exists(AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
var pkgId = "xpipe-io.xpipe";
if (systemWide) {
return ShellScript.lines(
@@ -1,10 +1,11 @@
package io.xpipe.app.util;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.process.CommandBuilder;
import io.xpipe.app.process.OsFileSystem;
import io.xpipe.core.FilePath;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -20,7 +21,7 @@ public class DesktopShortcuts {
return shortcutPath;
}
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var icon = AppInstallation.ofCurrent().getLogoPath();
var content = String.format(
"""
$TARGET="%s"
@@ -42,7 +43,7 @@ public class DesktopShortcuts {
private static Path createLinuxShortcut(String executable, String args, String name) throws Exception {
// Linux .desktop names are very restrictive
var fixedName = name.replaceAll("[^\\w _]", "");
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var icon = AppInstallation.ofCurrent().getLogoPath();
var content = String.format(
"""
[Desktop Entry]
@@ -72,7 +73,7 @@ public class DesktopShortcuts {
}
private static Path createMacOSShortcut(String executable, String args, String name) throws Exception {
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
var icon = AppInstallation.ofCurrent().getLogoPath();
var assets = icon.getParent().resolve("Assets.car");
var base = DesktopHelper.getDesktopDirectory().resolve(name + ".app");
var content = String.format(
@@ -116,15 +117,15 @@ public class DesktopShortcuts {
}
public static Path createCliOpen(String action, String name) throws Exception {
var exec = XPipeInstallation.getLocalDefaultCliExecutable();
var exec = AppInstallation.ofCurrent().getCliExecutablePath().toString();
return create(exec, "open " + action, name);
}
public static Path create(String executable, String args, String name) throws Exception {
var compat = OsFileSystem.ofLocal().makeFileSystemCompatible(name);
if (OsType.getLocal().equals(OsType.WINDOWS)) {
if (OsType.getLocal() == OsType.WINDOWS) {
return createWindowsShortcut(executable, args, compat);
} else if (OsType.getLocal().equals(OsType.LINUX)) {
} else if (OsType.getLocal() == OsType.LINUX) {
return createLinuxShortcut(executable, args, compat);
} else {
return createMacOSShortcut(executable, args, compat);
@@ -74,14 +74,14 @@ public class FileOpener {
public static void openInDefaultApplication(String localFile) {
try (var pc = LocalShell.getShell().start()) {
if (pc.getOsType().equals(OsType.WINDOWS)) {
if (pc.getOsType() == OsType.WINDOWS) {
if (pc.getShellDialect() == ShellDialects.POWERSHELL) {
pc.command(CommandBuilder.of().add("Invoke-Item").addFile(localFile))
.execute();
} else {
pc.executeSimpleCommand("start \"\" \"" + localFile + "\"");
}
} else if (pc.getOsType().equals(OsType.LINUX)) {
} else if (pc.getOsType() == OsType.LINUX) {
pc.executeSimpleCommand("xdg-open \"" + localFile + "\"");
} else {
pc.executeSimpleCommand("open \"" + localFile + "\"");
@@ -1,9 +1,9 @@
package io.xpipe.app.util;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeInstallation;
import com.sun.jna.Library;
import com.sun.jna.Native;
@@ -35,7 +35,7 @@ public class NativeBridge {
try {
System.setProperty(
"jna.library.path",
XPipeInstallation.getCurrentInstallationBasePath()
AppInstallation.ofCurrent().getBaseInstallationPath()
.resolve("Contents")
.resolve("runtime")
.resolve("Contents")
@@ -65,7 +65,7 @@ public class ScriptHelper {
// Fix for powershell as there are permission issues when executing a powershell askpass script
if (forceExecutable && ShellDialects.isPowershell(parent)) {
scriptType = parent.getOsType().equals(OsType.WINDOWS) ? ShellDialects.CMD : ShellDialects.SH;
scriptType = parent.getOsType() == OsType.WINDOWS ? ShellDialects.CMD : ShellDialects.SH;
}
return createTerminalPreparedAskpassScript(pass, parent, scriptType);
@@ -19,7 +19,7 @@ public class ShellTemp {
var temp = FileUtils.getTempDirectory().toPath().resolve("xpipe");
// On Windows and macOS, we already have user specific temp directories
// Even on macOS as root we will have a unique directory (in contrast to shell controls)
if (OsType.getLocal().equals(OsType.LINUX)) {
if (OsType.getLocal() == OsType.LINUX) {
var user = System.getenv("USER");
temp = temp.resolve(user != null ? user : "user");
@@ -39,7 +39,7 @@ public class ShellTemp {
FilePath base;
// On Windows and macOS, we already have user specific temp directories
// Even on macOS as root it is technically unique as only root will use /tmp
if (!proc.getOsType().equals(OsType.WINDOWS) && !proc.getOsType().equals(OsType.MACOS)) {
if (proc.getOsType() != OsType.WINDOWS && proc.getOsType() != OsType.MACOS) {
var temp = proc.getSystemTemporaryDirectory();
base = temp.join("xpipe");
proc.command(proc.getShellDialect().getMkdirsCommand(base.toString()))
@@ -84,7 +84,7 @@ public class ShellTemp {
}
private static boolean checkDirectoryPermissions(ShellControl proc, String dir) throws Exception {
if (proc.getOsType().equals(OsType.WINDOWS)) {
if (proc.getOsType() == OsType.WINDOWS) {
return true;
}
@@ -1,6 +1,7 @@
package io.xpipe.app.util;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.core.AppInstallation;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEventFactory;
@@ -8,7 +9,7 @@ import io.xpipe.app.process.CommandBuilder;
import io.xpipe.app.process.ShellControl;
import io.xpipe.app.process.ShellDialects;
import io.xpipe.core.FilePath;
import io.xpipe.core.XPipeInstallation;
import lombok.Getter;
import lombok.Setter;
@@ -173,7 +174,7 @@ public class SshLocalBridge {
}
private String getRemoteCommand(ShellControl sc) {
var command = "\"" + XPipeInstallation.getLocalDefaultCliExecutable() + "\" ssh-launch "
var command = "\"" + AppInstallation.ofCurrent().getCliExecutablePath() + "\" ssh-launch "
+ sc.getShellDialect().environmentVariable("SSH_ORIGINAL_COMMAND");
var p = Pattern.compile("\".+?\\\\Users\\\\([^\\\\]+)\\\\(.+)\"");
var matcher = p.matcher(command);
@@ -50,7 +50,7 @@ public class StoreStateFormat {
.format();
}
if (s.getShellDialect().equals(ShellDialects.NO_INTERACTION)) {
if (s.getShellDialect() == ShellDialects.NO_INTERACTION) {
return new StoreStateFormat(null, null, info).format();
}
-18
View File
@@ -29,26 +29,8 @@ For a full documentation, see the [OpenAPI spec](https://docs.xpipe.io/api)
The default port can be changed by passing the property `io.xpipe.beacon.port=<port>` to the daemon.
Note that if both sides do not have the same port setting, they won't be able to reach each other.
#### Custom launch command
The beacon API also supports launching the daemon automatically in case it is not started yet.
By default, it launches the daemon of the local XPipe installation.
It is possible to pass a custom launch command with the property `io.xpipe.beacon.customDaemonCommand=<cmd>`
and pass arguments to it using the property `io.xpipe.beacon.daemonArgs=<args>`.
This allows for a custom launch behaviour in a testing/development environment.
Note that the `<cmd>` value has to be a single property string, which can be prone to formatting errors
#### Verbose output
By passing the property `io.xpipe.beacon.printMessages=true`, it is possible to print debug information
about the underlying communications.
In case the `io.xpipe.beacon.printDaemonOutput` property is set, the output of the daemon can also be
printed by passing the property `io.xpipe.beacon.debugExecOutput=true`.
#### Daemon debug mode
In case the daemon is started by the beacon, it is possible to customize in which mode the daemon will start up.
By passing the property `io.xpipe.beacon.launchDebugDaemon=true`, the daemon is started in debug mode,
i.e. will log more information and enable a few other options.
By passing the property `io.xpipe.beacon.attachDebuggerToDaemon=true`, it is possible to launch a daemon
in a mode where it is waiting to attach to a debugger first prior to starting up.
@@ -2,7 +2,6 @@ package io.xpipe.beacon;
import io.xpipe.beacon.api.HandshakeExchange;
import io.xpipe.core.JacksonMapper;
import io.xpipe.core.XPipeInstallation;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;
@@ -26,7 +25,7 @@ public class BeaconClient {
public static BeaconClient establishConnection(int port, BeaconClientInformation information) throws Exception {
var client = new BeaconClient(port);
var auth = Files.readString(XPipeInstallation.getLocalBeaconAuthFile());
var auth = Files.readString(BeaconConfig.getLocalBeaconAuthFile());
HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder()
.client(information)
.auth(BeaconAuthMethod.Local.builder().authFileContent(auth).build())
@@ -1,19 +1,16 @@
package io.xpipe.beacon;
import io.xpipe.core.XPipeInstallation;
import lombok.experimental.UtilityClass;
import java.nio.file.Path;
import java.util.Optional;
@UtilityClass
public class BeaconConfig {
public static final String BEACON_PORT_PROP = "io.xpipe.beacon.port";
public static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs";
private static final String PRINT_MESSAGES_PROPERTY = "io.xpipe.beacon.printMessages";
private static final String LAUNCH_DAEMON_IN_DEBUG_PROP = "io.xpipe.beacon.launchDebugDaemon";
private static final String ATTACH_DEBUGGER_PROP = "io.xpipe.beacon.attachDebuggerToDaemon";
private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput";
private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.customDaemonCommand";
public static boolean printMessages() {
if (System.getProperty(PRINT_MESSAGES_PROPERTY) != null) {
@@ -22,27 +19,6 @@ public class BeaconConfig {
return false;
}
public static boolean launchDaemonInDebugMode() {
if (System.getProperty(LAUNCH_DAEMON_IN_DEBUG_PROP) != null) {
return Boolean.parseBoolean(System.getProperty(LAUNCH_DAEMON_IN_DEBUG_PROP));
}
return false;
}
public static boolean attachDebuggerToDaemon() {
if (System.getProperty(ATTACH_DEBUGGER_PROP) != null) {
return Boolean.parseBoolean(System.getProperty(ATTACH_DEBUGGER_PROP));
}
return false;
}
public static boolean printDaemonOutput() {
if (System.getProperty(EXEC_DEBUG_PROP) != null) {
return Boolean.parseBoolean(System.getProperty(EXEC_DEBUG_PROP));
}
return false;
}
public static int getUsedPort() {
var beaconPort = System.getenv("BEACON_PORT");
if (beaconPort != null && !beaconPort.isBlank()) {
@@ -53,22 +29,21 @@ public class BeaconConfig {
return Integer.parseInt(System.getProperty(BEACON_PORT_PROP));
}
return XPipeInstallation.getDefaultBeaconPort();
return getDefaultBeaconPort();
}
public static String getCustomDaemonCommand() {
if (System.getProperty(EXEC_PROCESS_PROP) != null) {
return System.getProperty(EXEC_PROCESS_PROP);
}
return null;
public static int getDefaultBeaconPort() {
var staging = Optional.ofNullable(System.getProperty("io.xpipe.app.staging"))
.map(Boolean::parseBoolean)
.orElse(false);
var offset = staging ? 1 : 0;
return 21721 + offset;
}
public static String getDaemonArguments() {
if (System.getProperty(DAEMON_ARGUMENTS_PROP) != null) {
return System.getProperty(DAEMON_ARGUMENTS_PROP);
}
return null;
public static Path getLocalBeaconAuthFile() {
var staging = Optional.ofNullable(System.getProperty("io.xpipe.app.staging"))
.map(Boolean::parseBoolean)
.orElse(false);
return Path.of(System.getProperty("java.io.tmpdir"), staging ? "xpipe_ptb_auth" : "xpipe_auth");
}
}
@@ -1,10 +1,6 @@
package io.xpipe.beacon;
import io.xpipe.beacon.api.DaemonStopExchange;
import io.xpipe.core.FilePath;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeDaemonMode;
import io.xpipe.core.XPipeInstallation;
import lombok.SneakyThrows;
@@ -14,11 +10,10 @@ import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
/**
* Contains basic functionality to start, communicate, and stop a remote beacon server.
*/
public class BeaconServer {
@SneakyThrows
@@ -42,109 +37,9 @@ public class BeaconServer {
}
}
private static List<String> toProcessCommand(String toExec) {
// Having the trailing space is very important to force cmd to not interpret surrounding spaces and removing
// them
return OsType.getLocal().equals(OsType.WINDOWS)
? List.of("cmd", "/c", toExec + " ")
: List.of("sh", "-c", toExec);
}
public static Process tryStartCustom() throws Exception {
var custom = BeaconConfig.getCustomDaemonCommand();
if (custom != null) {
var toExec =
custom + (BeaconConfig.getDaemonArguments() != null ? " " + BeaconConfig.getDaemonArguments() : "");
var command = toProcessCommand(toExec);
Process process = Runtime.getRuntime().exec(command.toArray(String[]::new));
printDaemonOutput(process, command);
return process;
}
return null;
}
public static Process start(String installationBase, XPipeDaemonMode mode) throws Exception {
String command;
if (!BeaconConfig.launchDaemonInDebugMode()) {
command = XPipeInstallation.createExternalAsyncLaunchCommand(
installationBase, mode, BeaconConfig.getDaemonArguments(), false);
} else {
command = XPipeInstallation.createExternalLaunchCommand(
getDaemonDebugExecutable(installationBase), BeaconConfig.getDaemonArguments(), mode);
}
var fullCommand = toProcessCommand(command);
Process process = new ProcessBuilder(fullCommand).start();
printDaemonOutput(process, fullCommand);
return process;
}
private static void printDaemonOutput(Process proc, List<String> command) {
boolean print = BeaconConfig.printDaemonOutput();
if (print) {
System.out.println("Starting daemon: " + command);
}
var out = new Thread(
null,
() -> {
try {
InputStreamReader isr = new InputStreamReader(proc.getInputStream());
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
if (print) {
System.out.println("[xpiped] " + line);
}
}
} catch (Exception ioe) {
ioe.printStackTrace();
}
},
"daemon sysout");
out.setDaemon(true);
out.start();
var err = new Thread(
null,
() -> {
try {
InputStreamReader isr = new InputStreamReader(proc.getErrorStream());
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
if (print) {
System.err.println("[xpiped] " + line);
}
}
} catch (Exception ioe) {
ioe.printStackTrace();
}
},
"daemon syserr");
err.setDaemon(true);
err.start();
}
public static boolean tryStop(BeaconClient client) throws Exception {
DaemonStopExchange.Response res =
client.performRequest(DaemonStopExchange.Request.builder().build());
return res.isSuccess();
}
public static String getDaemonDebugExecutable(String installationBase) {
var osType = OsType.getLocal();
var debug = BeaconConfig.launchDaemonInDebugMode();
if (!debug) {
throw new IllegalStateException();
} else {
if (BeaconConfig.attachDebuggerToDaemon()) {
return FilePath.of(installationBase, XPipeInstallation.getDaemonDebugAttachScriptPath(osType))
.toString();
} else {
return FilePath.of(installationBase, XPipeInstallation.getDaemonDebugScriptPath(osType))
.toString();
}
}
}
}
@@ -1,99 +0,0 @@
package io.xpipe.beacon.test;
import io.xpipe.beacon.BeaconClient;
import io.xpipe.beacon.BeaconClientInformation;
import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconServer;
import io.xpipe.core.XPipeDaemonMode;
import io.xpipe.core.XPipeInstallation;
import java.io.IOException;
public class BeaconDaemonController {
private static boolean alreadyStarted;
public static void start(XPipeDaemonMode mode) throws Exception {
if (BeaconServer.isReachable(BeaconConfig.getUsedPort())) {
alreadyStarted = true;
return;
}
var custom = false;
Process process;
if ((process = BeaconServer.tryStartCustom()) != null) {
custom = true;
} else {
var defaultBase = XPipeInstallation.getLocalDefaultInstallationBasePath();
process = BeaconServer.start(defaultBase.toString(), mode);
}
waitForStartup(process, custom);
if (!BeaconServer.isReachable(BeaconConfig.getUsedPort())) {
throw new AssertionError();
}
}
public static void stop() throws Exception {
if (alreadyStarted) {
return;
}
if (!BeaconServer.isReachable(BeaconConfig.getUsedPort())) {
return;
}
var client = BeaconClient.establishConnection(
BeaconConfig.getUsedPort(),
BeaconClientInformation.Api.builder()
.name("Beacon daemon controller")
.build());
if (!BeaconServer.tryStop(client)) {
throw new AssertionError();
}
waitForShutdown();
}
private static void waitForStartup(Process process, boolean custom) throws IOException {
for (int i = 0; i < 160; i++) {
// Breaks when using nohup & disown
// if (process != null && !custom && !process.isAlive()) {
// throw new IOException("Daemon start failed");
// }
if (process != null && custom && !process.isAlive() && process.exitValue() != 0) {
throw new IOException("Custom launch command failed");
}
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {
}
var s = BeaconClient.tryEstablishConnection(
BeaconConfig.getUsedPort(),
BeaconClientInformation.Api.builder()
.name("Beacon daemon controller")
.build());
if (s.isPresent()) {
return;
}
}
throw new IOException("Wait for daemon start up timed out");
}
private static void waitForShutdown() {
for (int i = 0; i < 40; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {
}
var r = BeaconServer.isReachable(BeaconConfig.getUsedPort());
if (!r) {
return;
}
}
}
}
@@ -1,23 +0,0 @@
package io.xpipe.beacon.test;
import io.xpipe.core.ModuleLayerLoader;
import io.xpipe.core.OsType;
import io.xpipe.core.XPipeDaemonMode;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
public class BeaconDaemonExtensionTest {
@BeforeAll
public static void setup() throws Exception {
ModuleLayerLoader.loadAll(ModuleLayer.boot(), throwable -> throwable.printStackTrace());
BeaconDaemonController.start(
OsType.getLocal().equals(OsType.WINDOWS) ? XPipeDaemonMode.TRAY : XPipeDaemonMode.BACKGROUND);
}
@AfterAll
public static void teardown() throws Exception {
BeaconDaemonController.stop();
}
}
-1
View File
@@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.Module;
open module io.xpipe.beacon {
exports io.xpipe.beacon;
exports io.xpipe.beacon.test;
exports io.xpipe.beacon.api;
requires com.fasterxml.jackson.core;
@@ -63,7 +63,7 @@ public class Deobfuscator {
var file = Files.createTempFile("xpipe_stracktrace", null);
Files.writeString(file, stackTrace);
var proc = new ProcessBuilder(
"retrace." + (OsType.getLocal().equals(OsType.WINDOWS) ? "bat" : "sh"),
"retrace." + (OsType.getLocal() == OsType.WINDOWS ? "bat" : "sh"),
System.getenv("XPIPE_MAPPING"),
file.toString())
.redirectErrorStream(true);
@@ -1,298 +0,0 @@
package io.xpipe.core;
import lombok.Getter;
import lombok.SneakyThrows;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public class XPipeInstallation {
private static final String STAGING_PROP = "io.xpipe.app.staging";
@Getter
private static final boolean staging = Optional.ofNullable(System.getProperty(STAGING_PROP))
.map(Boolean::parseBoolean)
.orElse(false);
public static int getDefaultBeaconPort() {
var offset = isStaging() ? 1 : 0;
return 21721 + offset;
}
public static Path getLocalBeaconAuthFile() {
return Path.of(System.getProperty("java.io.tmpdir"), isStaging() ? "xpipe_ptb_auth" : "xpipe_auth");
}
public static String createExternalAsyncLaunchCommand(
String installationBase, XPipeDaemonMode mode, String arguments, boolean restart) {
var suffix = (arguments != null ? " " + arguments : "");
var modeOption = mode != null ? " -Dio.xpipe.app.mode=" + mode.getDisplayName() : "";
if (OsType.getLocal().equals(OsType.LINUX)) {
return "nohup \"" + installationBase + "/bin/xpiped\"" + modeOption + suffix
+ "</dev/null >/dev/null 2>&1 & disown";
} else if (OsType.getLocal().equals(OsType.MACOS)) {
if (restart) {
return "(sleep 1;open \"" + installationBase + "\" --args" + modeOption + suffix
+ "</dev/null &>/dev/null) & disown";
} else {
return "open \"" + installationBase + "\" --args" + modeOption + suffix;
}
}
return "\"" + FilePath.of(installationBase, XPipeInstallation.getDaemonExecutablePath(OsType.getLocal())) + "\""
+ modeOption + suffix;
}
public static String createExternalLaunchCommand(String command, String arguments, XPipeDaemonMode mode) {
var suffix = (arguments != null ? " " + arguments : "");
var modeOption = mode != null ? " -Dio.xpipe.app.mode=" + mode.getDisplayName() : "";
return "\"" + command + "\"" + modeOption + suffix;
}
private static boolean isImage() {
return XPipeInstallation.class
.getProtectionDomain()
.getCodeSource()
.getLocation()
.getProtocol()
.equals("jrt");
}
@SneakyThrows
public static Path getCurrentInstallationBasePath() {
var command = ProcessHandle.current().info().command();
// We should always have a command associated with the current process, otherwise something went seriously wrong
if (command.isEmpty()) {
var javaHome = System.getProperty("java.home");
var javaExec = toRealPathIfPossible(Path.of(javaHome, "bin", "java"));
var path = getLocalInstallationBasePathForJavaExecutable(javaExec);
return path;
}
// Resolve any possible links to a real path
Path path = toRealPathIfPossible(Path.of(command.get()));
// Check if the process was started using a relative path, and adapt it if necessary
if (!path.isAbsolute()) {
path = toRealPathIfPossible(Path.of(System.getProperty("user.dir")).resolve(path));
}
var name = path.getFileName().toString();
// Check if we launched the JVM via a start script instead of the native executable
if (name.endsWith("java") || name.endsWith("java.exe")) {
// If we are not an image, we are probably running in a development environment where we want to use the
// working directory
var isImage = isImage();
if (!isImage) {
return Path.of(System.getProperty("user.dir"));
}
return getLocalInstallationBasePathForJavaExecutable(path);
} else {
return getLocalInstallationBasePathForDaemonExecutable(path);
}
}
private static Path toRealPathIfPossible(Path p) {
try {
// Under certain conditions, e.g. when running on a ramdisk, path resolution might fail.
// This is however not a big problem in that case, so we ignore it
return p.toRealPath();
} catch (IOException e) {
return p;
}
}
public static Path getLocalExtensionsDirectory(Path path) {
return OsType.getLocal().equals(OsType.MACOS)
? path.resolve("Contents").resolve("Resources").resolve("extensions")
: path.resolve("extensions");
}
private static Path getLocalInstallationBasePathForJavaExecutable(Path executable) {
// Resolve root path of installation relative to the java executable in a JPackage installation
if (OsType.getLocal().equals(OsType.MACOS)) {
return executable
.getParent()
.getParent()
.getParent()
.getParent()
.getParent()
.getParent();
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return executable.getParent().getParent().getParent().getParent();
} else {
return executable.getParent().getParent().getParent();
}
}
private static Path getLocalInstallationBasePathForDaemonExecutable(Path executable) {
// Resolve root path of installation relative to executable in a JPackage installation
if (OsType.getLocal().equals(OsType.MACOS)) {
return executable.getParent().getParent().getParent();
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return executable.getParent().getParent();
} else {
return executable.getParent();
}
}
public static Path getLocalInstallationBasePathForCLI(String cliExecutable) {
var defaultInstallation = getLocalDefaultInstallationBasePath();
// Can be empty in development mode
if (cliExecutable == null) {
return defaultInstallation;
}
if (OsType.getLocal().equals(OsType.LINUX) && cliExecutable.equals("/usr/bin/xpipe")) {
return defaultInstallation;
}
var path = Path.of(cliExecutable);
if (OsType.getLocal().equals(OsType.MACOS)) {
return path.getParent().getParent().getParent();
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return path.getParent().getParent();
} else {
return path.getParent().getParent();
}
}
public static String queryLocalInstallationVersion(String exec) throws Exception {
var process = new ProcessBuilder(exec, "version")
.redirectError(ProcessBuilder.Redirect.DISCARD)
.start();
var v = new String(process.getInputStream().readAllBytes(), StandardCharsets.US_ASCII);
process.waitFor();
return v;
}
public static String getLocalDefaultCliExecutable() {
Path path = isImage() ? getCurrentInstallationBasePath() : getLocalDefaultInstallationBasePath();
return path.resolve(getRelativeCliExecutablePath(OsType.getLocal())).toString();
}
public static Path getLocalDefaultInstallationIcon() {
Path path = getCurrentInstallationBasePath();
// Check for development environment
if (!isImage()) {
if (OsType.getLocal().equals(OsType.WINDOWS)) {
return path.resolve("dist").resolve("logo").resolve("logo.ico");
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return path.resolve("dist").resolve("logo").resolve("logo.png");
} else {
return path.resolve("dist").resolve("logo").resolve("logo.icns");
}
}
if (OsType.getLocal().equals(OsType.WINDOWS)) {
return path.resolve("logo.ico");
} else if (OsType.getLocal().equals(OsType.LINUX)) {
return path.resolve("logo.png");
} else {
return path.resolve("Contents").resolve("Resources").resolve("xpipe.icns");
}
}
public static Path getLocalDefaultInstallationBasePath() {
return getLocalDefaultInstallationBasePath(staging);
}
public static Path getLocalDefaultInstallationBasePath(boolean stage) {
Path path;
if (OsType.getLocal().equals(OsType.WINDOWS)) {
var pg = System.getenv("ProgramFiles");
var systemPath = Path.of(pg, stage ? "XPipe PTB" : "XPipe");
if (Files.exists(systemPath)) {
return systemPath;
}
var base = Path.of(System.getenv("LOCALAPPDATA"));
path = base.resolve(stage ? "XPipe PTB" : "XPipe");
} else if (OsType.getLocal().equals(OsType.LINUX)) {
path = Path.of(stage ? "/opt/xpipe-ptb" : "/opt/xpipe");
} else {
path = Path.of(stage ? "/Applications/XPipe PTB.app" : "/Applications/XPipe.app");
}
return path;
}
public static Path getLangPath() {
if (!isImage()) {
return getCurrentInstallationBasePath().resolve("lang");
}
var install = getCurrentInstallationBasePath();
var type = OsType.getLocal();
if (type.equals(OsType.WINDOWS)) {
return install.resolve("lang");
} else if (type.equals(OsType.LINUX)) {
return install.resolve("lang");
} else {
return install.resolve("Contents").resolve("Resources").resolve("lang");
}
}
public static Path getBundledFontsPath() {
if (!isImage()) {
return Path.of("dist", "fonts");
}
var install = getCurrentInstallationBasePath();
var type = OsType.getLocal();
if (type.equals(OsType.WINDOWS)) {
return install.resolve("fonts");
} else if (type.equals(OsType.LINUX)) {
return install.resolve("fonts");
} else {
return install.resolve("Contents").resolve("Resources").resolve("fonts");
}
}
public static String getDaemonDebugScriptPath(OsType.Local type) {
if (type.equals(OsType.WINDOWS)) {
return FilePath.of("scripts", "xpiped_debug.bat").toString();
} else if (type.equals(OsType.LINUX)) {
return FilePath.of("scripts", "xpiped_debug.sh").toString();
} else {
return FilePath.of("Contents", "Resources", "scripts", "xpiped_debug.sh")
.toString();
}
}
public static String getDaemonDebugAttachScriptPath(OsType.Local type) {
if (type.equals(OsType.WINDOWS)) {
return FilePath.of("scripts", "xpiped_debug_attach.bat").toString();
} else if (type.equals(OsType.LINUX)) {
return FilePath.of("scripts", "xpiped_debug_attach.sh").toString();
} else {
return FilePath.of("Contents", "Resources", "scripts", "xpiped_debug_attach.sh")
.toString();
}
}
public static String getDaemonExecutablePath(OsType.Local type) {
if (type.equals(OsType.WINDOWS)) {
return FilePath.of("xpiped.exe").toString();
} else if (type.equals(OsType.LINUX)) {
return FilePath.of("bin", "xpiped").toString();
} else {
return FilePath.of("Contents", "MacOS", "xpiped").toString();
}
}
public static String getRelativeCliExecutablePath(OsType.Local type) {
if (type.equals(OsType.WINDOWS)) {
return FilePath.of("bin", "xpipe.exe").toString();
} else if (type.equals(OsType.LINUX)) {
return FilePath.of("bin", "xpipe").toString();
} else {
return FilePath.of("Contents", "MacOS", "xpipe").toString();
}
}
}
@@ -67,7 +67,7 @@ public class SshIdentityStateManager {
event.customAction(shutdown).handle();
if (r.get()) {
if (sc.getShellDialect().equals(ShellDialects.CMD)) {
if (sc.getShellDialect() == ShellDialects.CMD) {
sc.command(
"powershell -Command \"Start-Process cmd -Wait -ArgumentList /c, sc, stop, ssh-agent -Verb runAs\"")
.executeAndCheck();
@@ -136,7 +136,7 @@ public interface SshIdentityStrategy {
@Override
public void prepareParent(ShellControl parent) throws Exception {
if (!parent.getOsType().equals(OsType.WINDOWS)) {
if (parent.getOsType() != OsType.WINDOWS) {
var out = parent.executeSimpleStringCommand("pageant -l");
if (out.isBlank()) {
throw ErrorEventFactory.expected(
@@ -157,7 +157,7 @@ public interface SshIdentityStrategy {
@Override
public void buildCommand(CommandBuilder builder) {
builder.environment("SSH_AUTH_SOCK", parent -> {
if (parent.getOsType().equals(OsType.WINDOWS)) {
if (parent.getOsType() == OsType.WINDOWS) {
return getPageantWindowsPipe(parent);
}
@@ -332,7 +332,7 @@ public interface SshIdentityStrategy {
+ " is marked to be a public key file, SSH authentication requires the private key"));
}
if ((parent.getOsType().equals(OsType.LINUX) || parent.getOsType().equals(OsType.MACOS))) {
if ((parent.getOsType() == OsType.LINUX || parent.getOsType() == OsType.MACOS)) {
// Try to preserve the same permission set
parent.command(CommandBuilder.of()
.add("test", "-w")
@@ -26,10 +26,8 @@ testing {
dependsOn(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0]).toList())
systemProperty 'io.xpipe.app.fullVersion', "true"
systemProperty 'io.xpipe.beacon.printDaemonOutput', "false"
systemProperty 'io.xpipe.app.useVirtualThreads', "false"
systemProperty "io.xpipe.beacon.port", "21723"
systemProperty "io.xpipe.beacon.launchDebugDaemon", "true"
systemProperty "io.xpipe.app.dataDir", "$projectDir/local/"
systemProperty "io.xpipe.app.logLevel", "trace"
systemProperty "io.xpipe.app.writeSysOut", "true"
@@ -35,9 +35,7 @@ testing {
" -Dio.xpipe.beacon.printMessages=true" +
" -Dio.xpipe.app.logLevel=trace"
systemProperty 'io.xpipe.beacon.printDaemonOutput', "true"
systemProperty "io.xpipe.beacon.port", "21723"
systemProperty "io.xpipe.beacon.launchDebugDaemon", "true"
}
}
}