More shell rework

This commit is contained in:
crschnick 2024-08-10 09:38:55 +00:00
parent 99971caba5
commit 1caa6cad6b
11 changed files with 41 additions and 138 deletions

View file

@ -36,6 +36,7 @@ public class ShellStartExchangeImpl extends ShellStartExchange {
.osType(control.getOsType())
.osName(control.getOsName())
.temp(control.getSystemTemporaryDirectory())
.ttyState(control.getTtyState())
.build();
}
}

View file

@ -40,7 +40,7 @@ public class StoreCreationMenu {
menu.getItems()
.add(category("addTunnel", "mdi2v-vector-polyline-plus", DataStoreCreationCategory.TUNNEL, null));
// menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, "cmd"));
menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", DataStoreCreationCategory.COMMAND, "cmd"));
menu.getItems().add(category("addSerial", "mdi2s-serial-port", DataStoreCreationCategory.SERIAL, "serial"));

View file

@ -6,6 +6,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState;
import javafx.beans.value.ObservableValue;
public class DataStoreFormatter {
@ -41,7 +42,8 @@ public class DataStoreFormatter {
return s.getShellDialect().getDisplayName();
}
return s.isRunning() ? formattedOsName(s.getOsName()) : "Connection failed";
var prefix = s.getTtyState() != ShellTtyState.NONE ? "[PTY] " : "";
return s.isRunning() ? prefix + formattedOsName(s.getOsName()) : "Connection failed";
}
return "?";

View file

@ -1673,6 +1673,7 @@ These errors will be returned with the HTTP return code 500.
"shellDialect": 0,
"osType": "string",
"osName": "string",
"ttyState": "string",
"temp": "string"
}
```
@ -2969,6 +2970,7 @@ undefined
"shellDialect": 0,
"osType": "string",
"osName": "string",
"ttyState": "string",
"temp": "string"
}
@ -2981,6 +2983,7 @@ undefined
|shellDialect|integer|true|none|The shell dialect|
|osType|string|true|none|The general type of operating system|
|osName|string|true|none|The display name of the operating system|
|ttyState|string|false|none|Whether a tty/pty has been allocated for the connection. If allocated, input and output will be unreliable. It is not recommended to use a shell connection then.|
|temp|string|true|none|The location of the temporary directory|
<h2 id="tocS_ShellStopRequest">ShellStopRequest</h2>

View file

@ -3,6 +3,7 @@ package io.xpipe.beacon.api;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.store.FilePath;
import lombok.Builder;
@ -40,6 +41,9 @@ public class ShellStartExchange extends BeaconInterface<ShellStartExchange.Reque
@NonNull
String osName;
@NonNull
ShellTtyState ttyState;
@NonNull
FilePath temp;
}

View file

@ -4,8 +4,6 @@ import io.xpipe.core.store.FileNames;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
public interface OsType {
@ -36,12 +34,6 @@ public interface OsType {
String getName();
String getTempDirectory(ShellControl pc) throws Exception;
Map<String, String> getProperties(ShellControl pc) throws Exception;
String determineOperatingSystemName(ShellControl pc) throws Exception;
sealed interface Local extends OsType permits OsType.Windows, OsType.Linux, OsType.MacOs {
String getId();
@ -87,51 +79,6 @@ public interface OsType {
return "Windows";
}
@Override
public String getTempDirectory(ShellControl pc) throws Exception {
var def = pc.executeSimpleStringCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("TEMP"));
if (!def.isBlank() && pc.getShellDialect().directoryExists(pc, def).executeAndCheck()) {
return def;
}
var fallback = pc.executeSimpleStringCommand(
pc.getShellDialect().getPrintEnvironmentVariableCommand("LOCALAPPDATA"));
if (!fallback.isBlank()
&& pc.getShellDialect().directoryExists(pc, fallback).executeAndCheck()) {
return fallback;
}
return def;
}
@Override
public Map<String, String> getProperties(ShellControl pc) throws Exception {
try (CommandControl c = pc.command("systeminfo").start()) {
var text = c.readStdoutOrThrow();
return PropertiesFormatsParser.parse(text, ":");
}
}
@Override
public String determineOperatingSystemName(ShellControl pc) {
try {
return pc.executeSimpleStringCommand("wmic os get Caption")
.lines()
.skip(1)
.collect(Collectors.joining())
.trim()
+ " "
+ pc.executeSimpleStringCommand("wmic os get Version")
.lines()
.skip(1)
.collect(Collectors.joining())
.trim();
} catch (Throwable t) {
// Just in case this fails somehow
return "Windows";
}
}
@Override
public String getId() {
return "windows";
@ -168,32 +115,6 @@ public interface OsType {
return "Linux";
}
@Override
public String getTempDirectory(ShellControl pc) {
return "/tmp/";
}
@Override
public Map<String, String> getProperties(ShellControl pc) {
return null;
}
@Override
public String determineOperatingSystemName(ShellControl pc) throws Exception {
String type = "Unknown";
var uname = pc.command("uname -o").readStdoutIfPossible();
if (uname.isPresent()) {
type = uname.get();
}
String version = "?";
var unameR = pc.command("uname -r").readStdoutIfPossible();
if (unameR.isPresent()) {
version = unameR.get();
}
return type + " " + version;
}
}
final class Linux extends Unix implements OsType, Local, Any {
@ -203,20 +124,6 @@ public interface OsType {
return "linux";
}
@Override
public String determineOperatingSystemName(ShellControl pc) throws Exception {
var rel = pc.command("lsb_release -a").readStdoutIfPossible();
if (rel.isPresent()) {
return PropertiesFormatsParser.parse(rel.get(), ":").getOrDefault("Description", "Unknown");
}
var cat = pc.command("cat /etc/*release").readStdoutIfPossible();
if (cat.isPresent()) {
return PropertiesFormatsParser.parse(cat.get(), "=").getOrDefault("PRETTY_NAME", "Unknown");
}
return super.determineOperatingSystemName(pc);
}
}
final class Solaris extends Unix implements Any {}
@ -265,38 +172,5 @@ public interface OsType {
return "Mac";
}
@Override
public String getTempDirectory(ShellControl pc) throws Exception {
var found = pc.executeSimpleStringCommand(pc.getShellDialect().getPrintVariableCommand("TMPDIR"));
// This variable is not defined for root users, so manually fix it. Why? ...
if (found.isBlank()) {
return "/tmp";
}
return found;
}
@Override
public Map<String, String> getProperties(ShellControl pc) throws Exception {
try (CommandControl c = pc.command("sw_vers").start()) {
var text = c.readStdoutOrThrow();
return PropertiesFormatsParser.parse(text, ":");
}
}
@Override
public String determineOperatingSystemName(ShellControl pc) throws Exception {
var properties = getProperties(pc);
var name = pc.executeSimpleStringCommand(
"awk '/SOFTWARE LICENSE AGREEMENT FOR macOS/' '/System/Library/CoreServices/Setup "
+ "Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | "
+ "awk -F 'macOS ' '{print $NF}' | awk '{print substr($0, 0, length($0)-1)}'");
// For preleases and others
if (name.isBlank()) {
name = "?";
}
return properties.get("ProductName") + " " + name + " " + properties.get("ProductVersion");
}
}
}

View file

@ -9,6 +9,7 @@ import io.xpipe.core.util.StreamCharset;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
@ -120,6 +121,8 @@ public interface ShellDialect {
String getPrintStartEchoCommand(String prefix);
Optional<String> executeRobustBootstrapOutputCommand(ShellControl shellControl, String original) throws Exception;
String getPrintExitCodeCommand(String id, String prefix, String suffix);
int assignMissingExitCode();

View file

@ -7,19 +7,23 @@ import lombok.Getter;
public enum ShellTtyState {
@JsonProperty("none")
NONE(true, false, false),
NONE(true, false, false, true, true),
@JsonProperty("merged")
MERGED_STDERR(false, false, false),
MERGED_STDERR(false, false, false, false, true),
@JsonProperty("pty")
PTY_ALLOCATED(false, true, true);
PTY_ALLOCATED(false, true, true, false, false);
private final boolean hasSeparateStreams;
private final boolean hasAnsiEscapes;
private final boolean echoesAllInput;
private final boolean supportsInput;
private final boolean preservesOutput;
ShellTtyState(boolean hasSeparateStreams, boolean hasAnsiEscapes, boolean echoesAllInput) {
ShellTtyState(boolean hasSeparateStreams, boolean hasAnsiEscapes, boolean echoesAllInput, boolean supportsInput, boolean preservesOutput) {
this.hasSeparateStreams = hasSeparateStreams;
this.hasAnsiEscapes = hasAnsiEscapes;
this.echoesAllInput = echoesAllInput;
this.supportsInput = supportsInput;
this.preservesOutput = preservesOutput;
}
}

View file

@ -1,3 +1,11 @@
## TTYs and PTYs
Up until now, if you added a connection that always allocated pty, XPipe would complain about a missing stderr.
In XPipe 11, there has been a ground up rework of the shell initialization code which will in theory allow for better handling of these cases.
They are not fully supported yet and have some issues, but should work better.
The main concern here is to verify that the existing normal shell implementation still works as before and there were no bugs introduced by this rework.
## Profiles
You can now create multiple user profiles in the settings menu.

View file

@ -4,7 +4,6 @@ import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.base.OsLogoComp;
import io.xpipe.app.comp.base.SystemStateComp;
import io.xpipe.app.comp.base.TtyWarningComp;
import io.xpipe.app.comp.store.StoreEntryComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.ext.ActionProvider;
@ -13,6 +12,7 @@ import io.xpipe.app.ext.DataStoreUsageCategory;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState;
@ -20,6 +20,7 @@ import io.xpipe.core.store.ShellStore;
import io.xpipe.ext.base.script.ScriptStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue;
public interface ShellStoreProvider extends DataStoreProvider {
@ -32,11 +33,6 @@ public interface ShellStoreProvider extends DataStoreProvider {
w.getPersistentState()));
}
@Override
default StoreEntryComp customEntryComp(StoreSection s, boolean preferLarge) {
return StoreEntryComp.create(s, createTtyWarning(s.getWrapper()), preferLarge);
}
@Override
default ActionProvider.Action launchAction(DataStoreEntry entry) {
return new ActionProvider.Action() {
@ -66,4 +62,9 @@ public interface ShellStoreProvider extends DataStoreProvider {
default DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.SHELL;
}
@Override
default ObservableValue<String> informationString(StoreSection section) {
return DataStoreFormatter.shellInformation(section.getWrapper());
}
}

View file

@ -627,6 +627,9 @@ components:
osName:
type: string
description: The display name of the operating system
ttyState:
type: string
description: Whether a tty/pty has been allocated for the connection. If allocated, input and output will be unreliable. It is not recommended to use a shell connection then.
temp:
type: string
description: The location of the temporary directory