diff --git a/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java b/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java index 573f0d8c5..126f46f4e 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java +++ b/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java @@ -130,7 +130,7 @@ public abstract class Charsetter { public NewLine inferNewLine(byte[] content) { Map count = new HashMap<>(); for (var nl : NewLine.values()) { - var nlBytes = nl.getNewLine().getBytes(StandardCharsets.UTF_8); + var nlBytes = nl.getNewLineString().getBytes(StandardCharsets.UTF_8); count.put(nl, count(content, nlBytes)); } diff --git a/core/src/main/java/io/xpipe/core/charsetter/NewLine.java b/core/src/main/java/io/xpipe/core/charsetter/NewLine.java index b2ca78d74..ef4f6bf66 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/NewLine.java +++ b/core/src/main/java/io/xpipe/core/charsetter/NewLine.java @@ -20,7 +20,7 @@ public enum NewLine { public static NewLine platform() { return Arrays.stream(values()) - .filter(n -> n.getNewLine().equals(System.getProperty("line.separator"))) + .filter(n -> n.getNewLineString().equals(System.getProperty("line.separator"))) .findFirst() .orElseThrow(); } @@ -32,7 +32,7 @@ public enum NewLine { .orElseThrow(); } - public String getNewLine() { + public String getNewLineString() { return newLine; } diff --git a/core/src/main/java/io/xpipe/core/impl/TextWriteConnection.java b/core/src/main/java/io/xpipe/core/impl/TextWriteConnection.java index eab77a6b8..5c81e0126 100644 --- a/core/src/main/java/io/xpipe/core/impl/TextWriteConnection.java +++ b/core/src/main/java/io/xpipe/core/impl/TextWriteConnection.java @@ -14,6 +14,6 @@ public class TextWriteConnection extends StreamWriteConnection implements io.xpi @Override public void writeLine(String line) throws Exception { writer.write(line); - writer.write(source.getNewLine().getNewLine()); + writer.write(source.getNewLine().getNewLineString()); } } diff --git a/core/src/main/java/io/xpipe/core/store/CommandProcessControl.java b/core/src/main/java/io/xpipe/core/store/CommandProcessControl.java index e396991d4..d5aa6f2ac 100644 --- a/core/src/main/java/io/xpipe/core/store/CommandProcessControl.java +++ b/core/src/main/java/io/xpipe/core/store/CommandProcessControl.java @@ -2,8 +2,6 @@ package io.xpipe.core.store; import java.io.*; import java.nio.charset.Charset; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; public interface CommandProcessControl extends ProcessControl { @@ -45,7 +43,7 @@ public interface CommandProcessControl extends ProcessControl { CommandProcessControl start() throws Exception; @Override - CommandProcessControl exitTimeout(int timeout); + CommandProcessControl exitTimeout(Integer timeout); String readOnlyStdout() throws Exception; @@ -53,6 +51,8 @@ public interface CommandProcessControl extends ProcessControl { readOrThrow(); } + public String readOrThrow() throws Exception; + public default boolean startAndCheckExit() { try (var pc = start()) { return pc.discardAndCheckExit(); @@ -70,52 +70,6 @@ public interface CommandProcessControl extends ProcessControl { } } - public default Optional readStderrIfPresent() throws Exception { - discardOut(); - var bytes = getStderr().readAllBytes(); - var string = new String(bytes, getCharset()); - var ec = waitFor(); - return ec ? Optional.of(string) : Optional.empty(); - } - - public default String readOrThrow() throws Exception { - AtomicReference readError = new AtomicReference<>(""); - var errorThread = new Thread(() -> { - try { - - readError.set(new String(getStderr().readAllBytes(), getCharset())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - errorThread.setDaemon(true); - errorThread.start(); - - AtomicReference read = new AtomicReference<>(""); - var t = new Thread(() -> { - try { - read.set(readLine()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - t.setDaemon(true); - t.start(); - - var ec = waitFor(); - if (!ec) { - throw new ProcessOutputException("Command timed out"); - } - - var exitCode = getExitCode(); - if (exitCode == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) { - return read.get().trim(); - } else { - throw new ProcessOutputException( - "Command returned with " + ec + ": " + readError.get().trim()); - } - } - Thread discardOut(); Thread discardErr(); diff --git a/core/src/main/java/io/xpipe/core/store/MachineStore.java b/core/src/main/java/io/xpipe/core/store/MachineStore.java index 34adcf791..f6b8274a9 100644 --- a/core/src/main/java/io/xpipe/core/store/MachineStore.java +++ b/core/src/main/java/io/xpipe/core/store/MachineStore.java @@ -1,19 +1,26 @@ package io.xpipe.core.store; +import io.xpipe.core.util.SupportedOs; + import java.io.InputStream; import java.io.OutputStream; public interface MachineStore extends FileSystemStore, ShellStore { + @Override + default void validate() throws Exception { + try (ShellProcessControl pc = create().start()) { + } + } + public default boolean isLocal() { return false; } public default String queryMachineName() throws Exception { - try (CommandProcessControl pc = create().commandListFunction(shellProcessControl -> - shellProcessControl.getShellType().getOperatingSystemNameCommand()) - .start()) { - return pc.readOrThrow().trim(); + try (var pc = create().start()) { + var operatingSystem = SupportedOs.determine(pc); + return operatingSystem.determineOperatingSystemName(pc); } } diff --git a/core/src/main/java/io/xpipe/core/store/ProcessControl.java b/core/src/main/java/io/xpipe/core/store/ProcessControl.java index aaea19571..210239de1 100644 --- a/core/src/main/java/io/xpipe/core/store/ProcessControl.java +++ b/core/src/main/java/io/xpipe/core/store/ProcessControl.java @@ -1,10 +1,10 @@ package io.xpipe.core.store; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; -import java.util.UUID; public interface ProcessControl extends AutoCloseable { @@ -12,41 +12,15 @@ public interface ProcessControl extends AutoCloseable { ShellType getShellType(); - String readResultLine(String input, boolean captureOutput) throws IOException; - void writeLine(String line) throws IOException; - void writeLine(String line, boolean captureOutput) throws IOException; - void typeLine(String line); - public default String readOutput() throws IOException { - var id = UUID.randomUUID(); - writeLine("echo " + id, false); - String lines = ""; - while (true) { - var newLine = readLine(); - if (newLine.contains(id.toString())) { - if (getShellType().echoesInput()) { - readLine(); - } - - break; - } - - lines = lines + newLine + "\n"; - } - return lines; - } - @Override void close() throws IOException; + void kill() throws Exception; - String readLine() throws IOException; - - void kill() throws IOException; - - ProcessControl exitTimeout(int timeout); + ProcessControl exitTimeout(Integer timeout); ProcessControl start() throws Exception; @@ -59,4 +33,7 @@ public interface ProcessControl extends AutoCloseable { InputStream getStderr(); Charset getCharset(); + + BufferedReader getStdoutReader(); + BufferedReader getStderrReader(); } diff --git a/core/src/main/java/io/xpipe/core/store/PropertiesFormatsParser.java b/core/src/main/java/io/xpipe/core/store/PropertiesFormatsParser.java new file mode 100644 index 000000000..661294a24 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/store/PropertiesFormatsParser.java @@ -0,0 +1,51 @@ +package io.xpipe.core.store; + +import lombok.SneakyThrows; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.util.LinkedHashMap; +import java.util.Map; + +public class PropertiesFormatsParser { + + @SneakyThrows + public static Map parse(String text, String split) { + var map = new LinkedHashMap(); + + var reader = new BufferedReader(new StringReader(text)); + String line; + + String currentKey = null; + String currentValue = ""; + while ((line = reader.readLine()) != null) { + if (line.startsWith("\s") || line.startsWith("\t")) { + currentValue += line; + continue; + } + + if (!line.contains(split)) { + continue; + } + + var keyName = line.substring(0, line.indexOf(split)).strip(); + var value = line.substring(line.indexOf(split) + 1).strip(); + if (value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1, value.length() - 1); + } + + if (currentKey != null) { + map.put(currentKey, currentValue); + } + + currentKey = keyName; + currentValue = value; + } + + if (currentKey != null) { + map.put(currentKey, currentValue); + } + + return map; + } +} diff --git a/core/src/main/java/io/xpipe/core/store/ShellProcessControl.java b/core/src/main/java/io/xpipe/core/store/ShellProcessControl.java index 194ea09cd..5dc31d5b8 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellProcessControl.java +++ b/core/src/main/java/io/xpipe/core/store/ShellProcessControl.java @@ -6,15 +6,20 @@ import lombok.NonNull; import java.io.IOException; import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; public interface ShellProcessControl extends ProcessControl { - ShellProcessControl elevation(SecretValue value); + ShellProcessControl elevated(Predicate elevationFunction); + + ShellProcessControl elevation(SecretValue value); + + ShellProcessControl startTimeout(Integer timeout); SecretValue getElevationPassword(); - default ShellProcessControl shell(@NonNull ShellType type){ + default ShellProcessControl shell(@NonNull ShellType type) { return shell(type.openCommand()); } @@ -23,15 +28,15 @@ public interface ShellProcessControl extends ProcessControl { command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "))); } - default ShellProcessControl shell(@NonNull String command){ + default ShellProcessControl shell(@NonNull String command) { return shell(processControl -> command); } - ShellProcessControl shell(@NonNull Function command); + ShellProcessControl shell(@NonNull Function command); - void executeCommand(String command) throws IOException; + void executeCommand(String command) throws Exception; - default void executeCommand(List command) throws IOException { + default void executeCommand(List command) throws Exception { executeCommand( command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "))); } @@ -40,8 +45,9 @@ public interface ShellProcessControl extends ProcessControl { ShellProcessControl start() throws Exception; default CommandProcessControl commandListFunction(Function> command) { - return commandFunction(shellProcessControl -> - command.apply(shellProcessControl).stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" "))); + return commandFunction(shellProcessControl -> command.apply(shellProcessControl).stream() + .map(s -> s.contains(" ") ? "\"" + s + "\"" : s) + .collect(Collectors.joining(" "))); } CommandProcessControl commandFunction(Function command); @@ -54,5 +60,4 @@ public interface ShellProcessControl extends ProcessControl { } void exit() throws IOException; - } diff --git a/core/src/main/java/io/xpipe/core/store/ShellStore.java b/core/src/main/java/io/xpipe/core/store/ShellStore.java index 1f57cf33b..c4ad696ec 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellStore.java +++ b/core/src/main/java/io/xpipe/core/store/ShellStore.java @@ -8,6 +8,10 @@ public interface ShellStore extends DataStore { return new LocalStore(); } + static boolean isLocal(ShellStore s) { + return s instanceof LocalStore; + } + ShellProcessControl create(); public default ShellType determineType() throws Exception { diff --git a/core/src/main/java/io/xpipe/core/store/ShellType.java b/core/src/main/java/io/xpipe/core/store/ShellType.java index 4e07c9acd..c45da28a9 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellType.java +++ b/core/src/main/java/io/xpipe/core/store/ShellType.java @@ -3,20 +3,13 @@ package io.xpipe.core.store; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.xpipe.core.charsetter.NewLine; -import java.io.IOException; import java.nio.charset.Charset; import java.util.List; -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - property = "type" -) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface ShellType { - void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException; - - default void init(ProcessControl proc) throws IOException { - } + void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception; default String getExitCommand() { return "exit"; @@ -28,9 +21,14 @@ public interface ShellType { return ";"; } - String getEchoCommand(String s, boolean newLine); + default String getAndConcatenationOperator() { + return "&&"; + } + + String getEchoCommand(String s, boolean toErrorStream); List openCommand(); + String switchTo(String cmd); List createMkdirsCommand(String dirs); @@ -41,7 +39,7 @@ public interface ShellType { List createFileExistsCommand(String file); - Charset determineCharset(ProcessControl control) throws Exception; + Charset determineCharset(ShellProcessControl control) throws Exception; NewLine getNewLine(); @@ -49,7 +47,5 @@ public interface ShellType { String getDisplayName(); - List getOperatingSystemNameCommand(); - boolean echoesInput(); } diff --git a/core/src/main/java/io/xpipe/core/store/ShellTypes.java b/core/src/main/java/io/xpipe/core/store/ShellTypes.java index a902773ea..e6aa54aef 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellTypes.java +++ b/core/src/main/java/io/xpipe/core/store/ShellTypes.java @@ -4,7 +4,8 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import io.xpipe.core.charsetter.NewLine; import lombok.Value; -import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; @@ -16,42 +17,6 @@ public class ShellTypes { public static final ShellType CMD = new Cmd(); public static final ShellType SH = new Sh(); - public static ShellType determine(ProcessControl proc) throws Exception { - proc.writeLine("echo -NoEnumerate \"a\"", false); - String line; - while (true) { - line = proc.readLine(); - if (line.equals("-NoEnumerate a")) { - return SH; - } - - if (line.contains("echo -NoEnumerate \"a\"")) { - break; - } - } - - var o = proc.readLine(); - - if (o.equals("a")) { - return POWERSHELL; - } else if (o.equals("-NoEnumerate \"a\"")) { - return CMD; - } else { - return SH; - } - } - - public static ShellType[] getAvailable(ShellStore store) throws Exception { - try (ProcessControl proc = store.create().start()) { - var type = determine(proc); - if (type == SH) { - return getLinuxShells(); - } else { - return getWindowsShells(); - } - } - } - public static ShellType getRecommendedDefault() { if (System.getProperty("os.name").startsWith("Windows")) { return POWERSHELL; @@ -81,8 +46,8 @@ public class ShellTypes { public static class Cmd implements ShellType { @Override - public String getEchoCommand(String s, boolean newLine) { - return newLine ? "echo " + s : "echo | set /p dummyName=" + s; + public String getEchoCommand(String s, boolean toErrorStream) { + return toErrorStream ? "(echo " + s + ")1>&2" : "echo " + s; } @Override @@ -91,27 +56,20 @@ public class ShellTypes { } @Override - public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException { - control.executeCommand("net session >NUL 2>NUL"); - control.executeCommand("echo %errorLevel%"); - var exitCode = Integer.parseInt(control.readLine()); - if (exitCode != 0) { - throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation."); + public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception { + try (CommandProcessControl c = control.command("net session >NUL 2>NUL")) { + var exitCode = c.getExitCode(); + if (exitCode != 0) { + throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation."); + } } control.executeCommand(command); } - @Override - public void init(ProcessControl proc) throws IOException { - proc.readLine(); - proc.readLine(); - proc.readLine(); - } - @Override public String getExitCodeVariable() { - return "%errorlevel%"; + return "!errorlevel!"; } @Override @@ -121,12 +79,12 @@ public class ShellTypes { @Override public List openCommand() { - return List.of("cmd"); + return List.of("cmd", "/V:on"); } @Override public String switchTo(String cmd) { - return "cmd.exe /c " + cmd; + return "cmd.exe /V:on /c " + cmd; } @Override @@ -150,9 +108,18 @@ public class ShellTypes { } @Override - public Charset determineCharset(ProcessControl control) throws Exception { - var output = control.readResultLine("chcp", true); - var matcher = Pattern.compile("\\d+").matcher(output); + public Charset determineCharset(ShellProcessControl control) throws Exception { + control.writeLine("chcp"); + + var r = new BufferedReader(new InputStreamReader(control.getStdout(), StandardCharsets.US_ASCII)); + // Read echo of command + r.readLine(); + // Read actual output + var line = r.readLine(); + // Read additional empty line + r.readLine(); + + var matcher = Pattern.compile("\\d+").matcher(line); matcher.find(); return Charset.forName("ibm" + matcher.group()); } @@ -167,11 +134,6 @@ public class ShellTypes { return "cmd"; } - @Override - public List getOperatingSystemNameCommand() { - return List.of("Get-ComputerInfo"); - } - @Override public boolean echoesInput() { return true; @@ -188,11 +150,13 @@ public class ShellTypes { } @Override - public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException { - control.executeCommand("([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)"); - var exitCode = Integer.parseInt(control.readLine()); - if (exitCode != 0) { - throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation."); + public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception { + try (CommandProcessControl c = control.command( + "([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)") + .start()) { + if (c.startAndCheckExit()) { + throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation."); + } } control.executeCommand(command); @@ -204,8 +168,8 @@ public class ShellTypes { } @Override - public String getEchoCommand(String s, boolean newLine) { - return newLine ? "echo " + s : String.format("Write-Host \"%s\" -NoNewLine", s); + public String getEchoCommand(String s, boolean toErrorStream) { + return String.format("%s \"%s\"", toErrorStream ? "Write-Error" : "Write-Output", s); } @Override @@ -220,7 +184,7 @@ public class ShellTypes { @Override public List createMkdirsCommand(String dirs) { - return List.of("New-Item", "-Path", "D:\\temp\\Test Folder", "-ItemType", "Directory"); + return List.of("New-Item", "-Path", dirs, "-ItemType", "Directory"); } @Override @@ -239,11 +203,13 @@ public class ShellTypes { } @Override - public Charset determineCharset(ProcessControl control) throws Exception { - var output = control.readResultLine("chcp", true); - var matcher = Pattern.compile("\\d+").matcher(output); - matcher.find(); - return Charset.forName("ibm" + matcher.group()); + public Charset determineCharset(ShellProcessControl control) throws Exception { + try (CommandProcessControl c = control.command("chcp").start()) { + var output = c.readOrThrow().strip(); + var matcher = Pattern.compile("\\d+").matcher(output); + matcher.find(); + return Charset.forName("ibm" + matcher.group()); + } } @Override @@ -260,11 +226,6 @@ public class ShellTypes { public String getDisplayName() { return "PowerShell"; } - - @Override - public List getOperatingSystemNameCommand() { - return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\""); - } } @JsonTypeName("sh") @@ -272,12 +233,24 @@ public class ShellTypes { public static class Sh implements ShellType { @Override - public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException { - if (control.getElevationPassword().getSecretValue() == null) { - throw new IllegalStateException("No password for sudo has been set"); + public String getExitCommand() { + return "exit 0"; + } + + @Override + public String getConcatenationOperator() { + return ";"; + } + + @Override + public void elevate(ShellProcessControl control, String command, String displayCommand) throws Exception { + if (control.getElevationPassword() == null) { + control.executeCommand("SUDO_ASKPASS=/bin/false; sudo -p \"\" -S " + command); + return; } - control.executeCommand("sudo -S " + switchTo(command)); + control.executeCommand("sudo -p \"\" -S " + command); + // Thread.sleep(200); control.writeLine(control.getElevationPassword().getSecretValue()); } @@ -287,13 +260,13 @@ public class ShellTypes { } @Override - public String getEchoCommand(String s, boolean newLine) { - return newLine ? "echo " + s : "echo -n " + s; + public String getEchoCommand(String s, boolean toErrorStream) { + return "echo " + s + (toErrorStream ? " 1>&2" : ""); } @Override public List openCommand() { - return List.of("sh"); + return List.of("sh", "-i", "-l"); } @Override @@ -322,7 +295,7 @@ public class ShellTypes { } @Override - public Charset determineCharset(ProcessControl st) throws Exception { + public Charset determineCharset(ShellProcessControl st) throws Exception { return StandardCharsets.UTF_8; } @@ -341,11 +314,6 @@ public class ShellTypes { return "/bin/sh"; } - @Override - public List getOperatingSystemNameCommand() { - return List.of("uname", "-o"); - } - @Override public boolean echoesInput() { return false; diff --git a/core/src/main/java/io/xpipe/core/util/SupportedOs.java b/core/src/main/java/io/xpipe/core/util/SupportedOs.java new file mode 100644 index 000000000..33f6ce9bd --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/SupportedOs.java @@ -0,0 +1,153 @@ +package io.xpipe.core.util; + +import io.xpipe.core.store.*; +import lombok.SneakyThrows; + +import java.nio.file.Path; +import java.util.Map; +import java.util.UUID; + +public interface SupportedOs { + + Windows WINDOWS = new Windows(); + Linux LINUX = new Linux(); + Mac MAC = new Mac(); + + static SupportedOs determine(ShellProcessControl pc) throws Exception { + try (CommandProcessControl c = pc.command(pc.getShellType().createFileExistsCommand("C:\\pagefile.sys")).start()) { + if (c.discardAndCheckExit()) { + return WINDOWS; + } + } + + return LINUX; + } + + Map getProperties(ShellProcessControl pc) throws Exception; + + String determineOperatingSystemName(ShellProcessControl pc) throws Exception; + + @SneakyThrows + public static SupportedOs getLocal() { + try (ShellProcessControl pc = ShellStore.local().create().start()) { + return determine(pc); + } + } + + Path getBaseInstallPath(); + + UUID getSystemUUID(ShellProcessControl pc) throws Exception; + + static class Windows implements SupportedOs { + + @Override + public Map getProperties(ShellProcessControl pc) throws Exception { + try (CommandProcessControl c = + pc.shell(ShellTypes.CMD).command("systeminfo").start()) { + var text = c.readOrThrow(); + return PropertiesFormatsParser.parse(text, ":"); + } + } + + @Override + public String determineOperatingSystemName(ShellProcessControl pc) throws Exception { + var properties = getProperties(pc); + return properties.get("OS Name") + " " + + properties.get("OS Version").split(" ")[0]; + } + + @Override + public Path getBaseInstallPath() { + return Path.of(System.getenv("LOCALAPPDATA"), "X-Pipe"); + } + + @Override + public UUID getSystemUUID(ShellProcessControl pc) throws Exception { + try (CommandProcessControl c = pc.command( + "reg query \"Computer\\\\HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\" /v MachineGuid")) { + var output = c.readOnlyStdout(); + return null; + } + } + } + + static class Linux implements SupportedOs { + + @Override + public Map getProperties(ShellProcessControl pc) throws Exception { + return null; + } + + @Override + public String determineOperatingSystemName(ShellProcessControl pc) throws Exception { + try (CommandProcessControl c = + pc.shell(ShellTypes.SH).command("lsb_release -a").start()) { + var text = c.readOnlyStdout(); + if (c.getExitCode() == 0) { + return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", null); + } + } + + try (CommandProcessControl c = + pc.shell(ShellTypes.SH).command("cat /etc/*release").start()) { + var text = c.readOnlyStdout(); + if (c.getExitCode() == 0) { + return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", null); + } + } + + String type = "Unknown"; + try (CommandProcessControl c = + pc.shell(ShellTypes.SH).command("uname -o").start()) { + var text = c.readOnlyStdout(); + if (c.getExitCode() == 0) { + type = text.strip(); + } + } + + String version = "?"; + try (CommandProcessControl c = + pc.shell(ShellTypes.SH).command("uname -r").start()) { + var text = c.readOnlyStdout(); + if (c.getExitCode() == 0) { + version = text.strip(); + } + } + + return type + " " + version; + } + + @Override + public Path getBaseInstallPath() { + return Path.of("/opt/xpipe"); + } + + @Override + public UUID getSystemUUID(ShellProcessControl pc) throws Exception { + return null; + } + } + + static class Mac implements SupportedOs { + + @Override + public Map getProperties(ShellProcessControl pc) throws Exception { + return null; + } + + @Override + public String determineOperatingSystemName(ShellProcessControl pc) throws Exception { + return null; + } + + @Override + public Path getBaseInstallPath() { + return Path.of(System.getProperty("user.home"), "Application Support", "X-Pipe"); + } + + @Override + public UUID getSystemUUID(ShellProcessControl pc) throws Exception { + return null; + } + } +} diff --git a/extension/src/main/java/io/xpipe/extension/comp/TextFieldComp.java b/extension/src/main/java/io/xpipe/extension/comp/TextFieldComp.java index 3c7cbc361..399712cd2 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/TextFieldComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/TextFieldComp.java @@ -4,6 +4,7 @@ import io.xpipe.fxcomps.Comp; import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.util.PlatformThread; +import io.xpipe.fxcomps.util.SimpleChangeListener; import javafx.beans.property.Property; import javafx.beans.property.SimpleStringProperty; import javafx.event.EventHandler; @@ -26,7 +27,9 @@ public class TextFieldComp extends Comp> { this.currentValue = new SimpleStringProperty(value.getValue()); this.lazy = lazy; if (!lazy) { - value.bind(currentValue); + SimpleChangeListener.apply(currentValue, val -> { + value.setValue(val); + }); } } diff --git a/extension/src/main/java/io/xpipe/extension/event/EventHandler.java b/extension/src/main/java/io/xpipe/extension/event/EventHandler.java index 507660d79..4e1ee8ffc 100644 --- a/extension/src/main/java/io/xpipe/extension/event/EventHandler.java +++ b/extension/src/main/java/io/xpipe/extension/event/EventHandler.java @@ -5,7 +5,7 @@ import java.util.ServiceLoader; public abstract class EventHandler { - private static final EventHandler DEFAULT = new EventHandler() { + public static final EventHandler DEFAULT = new EventHandler() { @Override public List snapshotEvents() { return List.of(); diff --git a/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java b/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java index 6117216a3..a1db4da89 100644 --- a/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java +++ b/extension/src/main/java/io/xpipe/extension/event/TrackEvent.java @@ -35,6 +35,10 @@ public class TrackEvent { return builder().type("info").message(message); } + public static TrackEventBuilder withWarn(String category, String message) { + return builder().category(category).type("warn").message(message); + } + public static TrackEventBuilder withWarn(String message) { return builder().type("warn").message(message); } diff --git a/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java index 095bf70b1..9c786cc95 100644 --- a/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java +++ b/extension/src/main/java/io/xpipe/extension/util/DynamicOptionsBuilder.java @@ -183,7 +183,7 @@ public class DynamicOptionsBuilder { public DynamicOptionsBuilder addComp(ObservableValue name, Comp comp, Property prop) { entries.add(new DynamicOptionsComp.Entry(name, comp)); - props.add(prop); + if (prop != null) props.add(prop); return this; } diff --git a/extension/src/main/java/io/xpipe/extension/util/SupportedOs.java b/extension/src/main/java/io/xpipe/extension/util/SupportedOs.java deleted file mode 100644 index f929c037c..000000000 --- a/extension/src/main/java/io/xpipe/extension/util/SupportedOs.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.xpipe.extension.util; - -import org.apache.commons.lang3.SystemUtils; - -import java.nio.file.Path; -import java.util.UUID; - -public interface SupportedOs { - - Windows WINDOWS = new Windows(); - Linux LINUX = new Linux(); - Mac MAC = new Mac(); - - public static SupportedOs get() { - if (SystemUtils.IS_OS_WINDOWS) { - return WINDOWS; - } else if (SystemUtils.IS_OS_LINUX) { - return LINUX; - } else if (SystemUtils.IS_OS_MAC) { - return MAC; - } else { - throw new UnsupportedOperationException("Unsupported operating system"); - } - } - - Path getBaseInstallPath(); - - UUID getSystemUUID(); - - static class Windows implements SupportedOs { - - @Override - public Path getBaseInstallPath() { - return Path.of(System.getenv("LOCALAPPDATA"), "X-Pipe"); - } - - @Override - public UUID getSystemUUID() { - var s = WindowsRegistry.readRegistry( - "Computer\\HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "MachineGuid") - .orElse(null); - if (s == null) { - return null; - } - - return UUID.fromString(s); - } - } - - static class Linux implements SupportedOs { - - @Override - public Path getBaseInstallPath() { - return Path.of("/opt/xpipe"); - } - - @Override - public UUID getSystemUUID() { - return null; - } - } - - static class Mac implements SupportedOs { - - @Override - public Path getBaseInstallPath() { - return Path.of(System.getProperty("user.home"), "Application Support", "X-Pipe"); - } - - @Override - public UUID getSystemUUID() { - return null; - } - } -}