Merge branch no-stderr into master

This commit is contained in:
crschnick 2024-08-09 07:38:47 +00:00
parent 65b2be5709
commit 9860b0c10f
34 changed files with 305 additions and 200 deletions

View file

@ -33,26 +33,49 @@ public class SystemStateComp extends SimpleComp {
PlatformThread.runLaterIfNeeded(() -> fi.setIconLiteral(i)); PlatformThread.runLaterIfNeeded(() -> fi.setIconLiteral(i));
}); });
var border = new FontIcon("mdi2c-circle-outline"); var bg = new FontIcon("mdi2s-square-rounded");
bg.getStyleClass().add("background-icon");
var border = new FontIcon("mdi2s-square-rounded-outline");
border.getStyleClass().add("outer-icon"); border.getStyleClass().add("outer-icon");
border.setOpacity(0.5); border.setOpacity(0.3);
var success = Styles.toDataURI( var success = Styles.toDataURI(
".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }"); """
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }
.stacked-ikonli-font-icon > .background-icon { -fx-icon-color: -color-success-9; }
"""
);
var failure = var failure =
Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }"); Styles.toDataURI(
"""
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }
.stacked-ikonli-font-icon > .background-icon { -fx-icon-color: -color-danger-9; }
"""
);
var other = var other =
Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }"); Styles.toDataURI(
"""
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }
.stacked-ikonli-font-icon > .background-icon { -fx-icon-color: -color-accent-9; }
"""
);
var pane = new StackedFontIcon(); var pane = new StackedFontIcon();
pane.getChildren().addAll(fi, border); pane.getChildren().addAll(bg, fi, border);
pane.setAlignment(Pos.CENTER); pane.setAlignment(Pos.CENTER);
var dataClass1 = var dataClass1 =
""" """
.stacked-ikonli-font-icon > .outer-icon { .stacked-ikonli-font-icon > .outer-icon {
-fx-icon-size: 22px; -fx-icon-size: 26px;
} }
.stacked-ikonli-font-icon > .background-icon {
-fx-icon-size: 26px;
}
.stacked-ikonli-font-icon > .inner-icon { .stacked-ikonli-font-icon > .inner-icon {
-fx-icon-size: 12px; -fx-icon-size: 12px;
} }

View file

@ -0,0 +1,50 @@
package io.xpipe.app.comp.base;
import atlantafx.base.theme.Styles;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import javafx.geometry.Pos;
import javafx.scene.layout.Region;
import lombok.Getter;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.javafx.StackedFontIcon;
@Getter
public class TtyWarningComp extends SimpleComp {
@Override
protected Region createSimple() {
var fi = new FontIcon("mdi2l-lightning-bolt");
fi.getStyleClass().add("inner-icon");
var border = new FontIcon("mdi2s-square-rounded-outline");
border.getStyleClass().add("outer-icon");
border.setOpacity(0.5);
var bg = new FontIcon("mdi2s-square-rounded");
bg.getStyleClass().add("background-icon");
var pane = new StackedFontIcon();
pane.getChildren().addAll(bg, fi, border);
pane.setAlignment(Pos.CENTER);
var style =
"""
.stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }
.stacked-ikonli-font-icon > .outer-icon {
-fx-icon-size: 26px;
}
.stacked-ikonli-font-icon > .background-icon {
-fx-icon-size: 26px;
-fx-icon-color: -color-danger-9;
}
.stacked-ikonli-font-icon > .inner-icon {
-fx-icon-size: 12px;
}
""";
pane.getStylesheets().add(Styles.toDataURI(style));
new TooltipAugment<>("ttyWarning", null).augment(pane);
return pane;
}
}

View file

@ -10,6 +10,7 @@ import javafx.beans.value.ObservableValue;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyCombination;
import javafx.stage.Window; import javafx.stage.Window;
import javafx.util.Duration;
public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> { public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
@ -45,6 +46,7 @@ public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
tt.setWrapText(true); tt.setWrapText(true);
tt.setMaxWidth(400); tt.setMaxWidth(400);
tt.getStyleClass().add("fancy-tooltip"); tt.getStyleClass().add("fancy-tooltip");
tt.setHideDelay(Duration.INDEFINITE);
Tooltip.install(struc.get(), tt); Tooltip.install(struc.get(), tt);
} }

View file

@ -109,6 +109,9 @@ public class AppPrefs {
map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class); map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class);
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective = private final ObservableBooleanValue developerDisableGuiRestrictionsEffective =
bindDeveloperTrue(developerDisableGuiRestrictions); bindDeveloperTrue(developerDisableGuiRestrictions);
final BooleanProperty developerForceSshTty =
map(new SimpleBooleanProperty(false), "developerForceSshTty", Boolean.class);
final ObjectProperty<SupportedLocale> language = final ObjectProperty<SupportedLocale> language =
map(new SimpleObjectProperty<>(SupportedLocale.getEnglish()), "language", SupportedLocale.class); map(new SimpleObjectProperty<>(SupportedLocale.getEnglish()), "language", SupportedLocale.class);
@ -435,6 +438,10 @@ public class AppPrefs {
return developerDisableGuiRestrictionsEffective; return developerDisableGuiRestrictionsEffective;
} }
public ObservableBooleanValue developerForceSshTty() {
return bindDeveloperTrue(developerForceSshTty);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> T map(T o, String name, Class<?> clazz) { private <T> T map(T o, String name, Class<?> clazz) {
mapping.add(new Mapping<>(name, (Property<T>) o, (Class<T>) clazz)); mapping.add(new Mapping<>(name, (Property<T>) o, (Class<T>) clazz));

View file

@ -61,6 +61,8 @@ public class DeveloperCategory extends AppPrefsCategory {
.sub(new OptionsBuilder() .sub(new OptionsBuilder()
.nameAndDescription("developerDisableUpdateVersionCheck") .nameAndDescription("developerDisableUpdateVersionCheck")
.addToggle(prefs.developerDisableUpdateVersionCheck) .addToggle(prefs.developerDisableUpdateVersionCheck)
.nameAndDescription("developerForceSshTty")
.addToggle(prefs.developerForceSshTty)
.nameAndDescription("developerDisableGuiRestrictions") .nameAndDescription("developerDisableGuiRestrictions")
.addToggle(prefs.developerDisableGuiRestrictions) .addToggle(prefs.developerDisableGuiRestrictions)
.nameAndDescription("shellCommandTest") .nameAndDescription("shellCommandTest")

View file

@ -114,14 +114,12 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
protected Optional<Path> determineFromPath() { protected Optional<Path> determineFromPath() {
// Try to locate if it is in the Path // Try to locate if it is in the Path
try (var cc = LocalShell.getShell() try (var sc = LocalShell.getShell()
.command(CommandBuilder.ofFunction(
var1 -> var1.getShellDialect().getWhichCommand(executable)))
.start()) { .start()) {
var out = cc.readStdoutDiscardErr(); var out = sc.command(CommandBuilder.ofFunction(
var exit = cc.getExitCode(); var1 -> var1.getShellDialect().getWhichCommand(executable))).readStdoutIfPossible();
if (exit == 0) { if (out.isPresent()) {
var first = out.lines().findFirst(); var first = out.get().lines().findFirst();
if (first.isPresent()) { if (first.isPresent()) {
return first.map(String::trim).map(Path::of); return first.map(String::trim).map(Path::of);
} }

View file

@ -99,16 +99,13 @@ public enum XPipeDistributionType {
// In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the // In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the
// production behavior // production behavior
if (OsType.getLocal().equals(OsType.WINDOWS)) { if (OsType.getLocal().equals(OsType.WINDOWS)) {
try (var chocoOut = var out = sc.command("choco search --local-only -r xpipe").readStdoutIfPossible();
sc.command("choco search --local-only -r xpipe").start()) { if (out.isPresent()) {
var out = chocoOut.readStdoutDiscardErr(); var split = out.get().split("\\|");
if (chocoOut.getExitCode() == 0) { if (split.length == 2) {
var split = out.split("\\|"); var version = split[1];
if (split.length == 2) { if (AppProperties.get().getVersion().equals(version)) {
var version = split[1]; return CHOCO;
if (AppProperties.get().getVersion().equals(version)) {
return CHOCO;
}
} }
} }
} }
@ -117,17 +114,15 @@ public enum XPipeDistributionType {
// In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the // In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the
// production behavior // production behavior
if (OsType.getLocal().equals(OsType.MACOS)) { if (OsType.getLocal().equals(OsType.MACOS)) {
try (var brewOut = sc.command("brew list --casks --versions").start()) { var out = sc.command("brew list --casks --versions").readStdoutIfPossible();
var out = brewOut.readStdoutDiscardErr(); if (out.isPresent()) {
if (brewOut.getExitCode() == 0) { if (out.get().lines().anyMatch(s -> {
if (out.lines().anyMatch(s -> { var split = s.split(" ");
var split = s.split(" "); return split.length == 2
return split.length == 2 && split[0].equals("xpipe")
&& split[0].equals("xpipe") && split[1].equals(AppProperties.get().getVersion());
&& split[1].equals(AppProperties.get().getVersion()); })) {
})) { return HOMEBREW;
return HOMEBREW;
}
} }
} }
} }

View file

@ -16,11 +16,10 @@ public class DesktopHelper {
return Path.of(LocalShell.getLocalPowershell() return Path.of(LocalShell.getLocalPowershell()
.executeSimpleStringCommand("[Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)")); .executeSimpleStringCommand("[Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)"));
} else if (OsType.getLocal() == OsType.LINUX) { } else if (OsType.getLocal() == OsType.LINUX) {
try (var cmd = LocalShell.getShell().command("xdg-user-dir DESKTOP").start()) { try (var sc = LocalShell.getShell().start()) {
var read = cmd.readStdoutDiscardErr(); var out = sc.command("xdg-user-dir DESKTOP").readStdoutIfPossible();
var exit = cmd.getExitCode(); if (out.isPresent()) {
if (exit == 0) { return Path.of(out.get());
return Path.of(read);
} }
} }
} }
@ -34,12 +33,10 @@ public class DesktopHelper {
.executeSimpleStringCommand( .executeSimpleStringCommand(
"(New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path")); "(New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path"));
} else if (OsType.getLocal() == OsType.LINUX) { } else if (OsType.getLocal() == OsType.LINUX) {
try (var cmd = try (var sc = LocalShell.getShell().start()) {
LocalShell.getShell().command("xdg-user-dir DOWNLOAD").start()) { var out = sc.command("xdg-user-dir DOWNLOAD").readStdoutIfPossible();
var read = cmd.readStdoutDiscardErr(); if (out.isPresent()) {
var exit = cmd.getExitCode(); return Path.of(out.get());
if (exit == 0) {
return Path.of(read);
} }
} }
} }

View file

@ -145,23 +145,20 @@ public abstract class WindowsRegistry {
.add("/v") .add("/v")
.addQuoted(valueName); .addQuoted(valueName);
String output; var output = shellControl.command(command).readStdoutIfPossible();
try (var c = shellControl.command(command).start()) { if (output.isEmpty()) {
output = c.readStdoutDiscardErr(); return Optional.empty();
if (c.getExitCode() != 0) {
return Optional.empty();
}
} }
// Output has the following format: // Output has the following format:
// \n<Version information>\n\n<key>\t<registry type>\t<value> // \n<Version information>\n\n<key>\t<registry type>\t<value>
if (output.contains("\t")) { if (output.get().contains("\t")) {
String[] parsed = output.split("\t"); String[] parsed = output.get().split("\t");
return Optional.of(parsed[parsed.length - 1]); return Optional.of(parsed[parsed.length - 1]);
} }
if (output.contains(" ")) { if (output.get().contains(" ")) {
String[] parsed = output.split(" "); String[] parsed = output.get().split(" ");
return Optional.of(parsed[parsed.length - 1]); return Optional.of(parsed[parsed.length - 1]);
} }
@ -176,14 +173,7 @@ public abstract class WindowsRegistry {
.add("/v") .add("/v")
.addQuoted(valueName) .addQuoted(valueName)
.add("/s"); .add("/s");
try (var c = shellControl.command(command).start()) { return shellControl.command(command).readStdoutIfPossible();
var output = c.readStdoutDiscardErr();
if (c.getExitCode() != 0) {
return Optional.empty();
} else {
return Optional.of(output);
}
}
} }
@Override @Override
@ -196,22 +186,17 @@ public abstract class WindowsRegistry {
.add("/s") .add("/s")
.add("/e") .add("/e")
.add("/d"); .add("/d");
try (var c = shellControl.command(command).start()) { return shellControl.command(command).readStdoutIfPossible().flatMap(output -> {
var output = c.readStdoutDiscardErr(); return output.lines().findFirst().flatMap(s -> {
if (c.getExitCode() != 0) { if (s.startsWith("HKEY_CURRENT_USER\\")) {
return Optional.of(new Key(HKEY_CURRENT_USER, s.replace("HKEY_CURRENT_USER\\", "")));
}
if (s.startsWith("HKEY_LOCAL_MACHINE\\")) {
return Optional.of(new Key(HKEY_LOCAL_MACHINE, s.replace("HKEY_LOCAL_MACHINE\\", "")));
}
return Optional.empty(); return Optional.empty();
} else { });
return output.lines().findFirst().flatMap(s -> { });
if (s.startsWith("HKEY_CURRENT_USER\\")) {
return Optional.of(new Key(HKEY_CURRENT_USER, s.replace("HKEY_CURRENT_USER\\", "")));
}
if (s.startsWith("HKEY_LOCAL_MACHINE\\")) {
return Optional.of(new Key(HKEY_LOCAL_MACHINE, s.replace("HKEY_LOCAL_MACHINE\\", "")));
}
return Optional.empty();
});
}
}
} }
} }
} }

View file

@ -1,11 +1,10 @@
## Welcome ## Welcome
Thank you for using XPipe! Welcome to XPipe!
You can view the development status, report issues, and more at the following places: You can view the development status, report issues, and more at the following places:
- [GitHub Repository](https://github.com/xpipe-io/xpipe/) - [GitHub Repository](https://github.com/xpipe-io/xpipe/)
- [Discord Server](https://discord.gg/8y89vS8cRb) - [Discord Server](https://discord.gg/8y89vS8cRb)
- [Slack Server](https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg) - [Slack Server](https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg)
- [Email me](mailto://crschnick@xpipe.io) - [Email us](mailto://hello@xpipe.io)
Note that the XPipe project currently is a one-man show, but I still try to respond to everything in time.

View file

@ -128,3 +128,7 @@
.root:dark .loading-comp { .root:dark .loading-comp {
-fx-background-color: rgba(0, 0, 0, 0.5); -fx-background-color: rgba(0, 0, 0, 0.5);
} }
.root:light .stacked-ikonli-font-icon > .background-icon { -fx-opacity: 0; }
.stacked-ikonli-font-icon > .background-icon { -fx-opacity: 0.2; }

View file

@ -1,16 +1,10 @@
package io.xpipe.core.process; package io.xpipe.core.process;
import io.xpipe.core.util.FailableConsumer;
import com.fasterxml.jackson.databind.JsonNode;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.time.Duration; import java.time.Duration;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
public interface CommandControl extends ProcessControl { public interface CommandControl extends ProcessControl {
@ -70,32 +64,14 @@ public interface CommandControl extends ProcessControl {
CommandControl elevated(ElevationFunction function); CommandControl elevated(ElevationFunction function);
void withStdoutOrThrow(FailableConsumer<InputStreamReader, Exception> c);
String[] readStdoutAndStderr() throws Exception; String[] readStdoutAndStderr() throws Exception;
String readStdoutDiscardErr() throws Exception;
String readStderrDiscardStdout() throws Exception;
void discardOrThrow() throws Exception; void discardOrThrow() throws Exception;
void accumulateStdout(Consumer<String> con);
void accumulateStderr(Consumer<String> con);
byte[] readRawBytesOrThrow() throws Exception; byte[] readRawBytesOrThrow() throws Exception;
String readStdoutOrThrow() throws Exception; String readStdoutOrThrow() throws Exception;
JsonNode readStdoutJsonOrThrow() throws Exception;
String readStderrOrThrow() throws Exception;
String readStdoutAndWait() throws Exception;
String readStderrAndWait() throws Exception;
Optional<String> readStdoutIfPossible() throws Exception; Optional<String> readStdoutIfPossible() throws Exception;
default boolean discardAndCheckExit() throws ProcessOutputException { default boolean discardAndCheckExit() throws ProcessOutputException {
@ -113,10 +89,6 @@ public interface CommandControl extends ProcessControl {
} }
} }
void discardOut();
void discardErr();
enum TerminalExitMode { enum TerminalExitMode {
KEEP_OPEN, KEEP_OPEN,
CLOSE CLOSE

View file

@ -1,6 +0,0 @@
package io.xpipe.core.process;
public interface CommandFeedbackPredicate {
boolean test(CommandBuilder command);
}

View file

@ -181,19 +181,15 @@ public interface OsType {
@Override @Override
public String determineOperatingSystemName(ShellControl pc) throws Exception { public String determineOperatingSystemName(ShellControl pc) throws Exception {
String type = "Unknown"; String type = "Unknown";
try (CommandControl c = pc.command("uname -o").start()) { var uname = pc.command("uname -o").readStdoutIfPossible();
var text = c.readStdoutDiscardErr(); if (uname.isPresent()) {
if (c.getExitCode() == 0) { type = uname.get();
type = text.strip();
}
} }
String version = "?"; String version = "?";
try (CommandControl c = pc.command("uname -r").start()) { var unameR = pc.command("uname -r").readStdoutIfPossible();
var text = c.readStdoutDiscardErr(); if (unameR.isPresent()) {
if (c.getExitCode() == 0) { version = unameR.get();
version = text.strip();
}
} }
return type + " " + version; return type + " " + version;
@ -209,18 +205,14 @@ public interface OsType {
@Override @Override
public String determineOperatingSystemName(ShellControl pc) throws Exception { public String determineOperatingSystemName(ShellControl pc) throws Exception {
try (CommandControl c = pc.command("lsb_release -a").start()) { var rel = pc.command("lsb_release -a").readStdoutIfPossible();
var text = c.readStdoutDiscardErr(); if (rel.isPresent()) {
if (c.getExitCode() == 0) { return PropertiesFormatsParser.parse(rel.get(), ":").getOrDefault("Description", "Unknown");
return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", "Unknown");
}
} }
try (CommandControl c = pc.command("cat /etc/*release").start()) { var cat = pc.command("cat /etc/*release").readStdoutIfPossible();
var text = c.readStdoutDiscardErr(); if (cat.isPresent()) {
if (c.getExitCode() == 0) { return PropertiesFormatsParser.parse(cat.get(), "=").getOrDefault("PRETTY_NAME", "Unknown");
return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", "Unknown");
}
} }
return super.determineOperatingSystemName(pc); return super.determineOperatingSystemName(pc);

View file

@ -18,6 +18,8 @@ import java.util.function.Function;
public interface ShellControl extends ProcessControl { public interface ShellControl extends ProcessControl {
ShellTtyState getTtyState();
void setNonInteractive(); void setNonInteractive();
boolean isInteractive(); boolean isInteractive();
@ -65,6 +67,7 @@ public interface ShellControl extends ProcessControl {
var s = store.getState().toBuilder() var s = store.getState().toBuilder()
.osType(shellControl.getOsType()) .osType(shellControl.getOsType())
.shellDialect(shellControl.getOriginalShellDialect()) .shellDialect(shellControl.getOriginalShellDialect())
.ttyState(shellControl.getTtyState())
.running(true) .running(true)
.osName(shellControl.getOsName()) .osName(shellControl.getOsName())
.build(); .build();
@ -113,25 +116,12 @@ public interface ShellControl extends ProcessControl {
script)); script));
} }
default byte[] executeSimpleRawBytesCommand(String command) throws Exception {
try (CommandControl c = command(command).start()) {
return c.readRawBytesOrThrow();
}
}
default String executeSimpleStringCommand(String command) throws Exception { default String executeSimpleStringCommand(String command) throws Exception {
try (CommandControl c = command(command).start()) { try (CommandControl c = command(command).start()) {
return c.readStdoutOrThrow(); return c.readStdoutOrThrow();
} }
} }
default Optional<String> executeSimpleStringCommandAndCheck(String command) throws Exception {
try (CommandControl c = command(command).start()) {
var out = c.readStdoutDiscardErr();
return c.getExitCode() == 0 ? Optional.of(out) : Optional.empty();
}
}
default boolean executeSimpleBooleanCommand(String command) throws Exception { default boolean executeSimpleBooleanCommand(String command) throws Exception {
try (CommandControl c = command(command).start()) { try (CommandControl c = command(command).start()) {
return c.discardAndCheckExit(); return c.discardAndCheckExit();
@ -150,20 +140,6 @@ public interface ShellControl extends ProcessControl {
} }
} }
default void executeSimpleCommand(String command, String failMessage) throws Exception {
try (CommandControl c = command(command).start()) {
c.discardOrThrow();
} catch (ProcessOutputException out) {
throw ProcessOutputException.withPrefix(failMessage, out);
}
}
default String executeSimpleStringCommand(ShellDialect type, String command) throws Exception {
try (var sub = subShell(type).start()) {
return sub.executeSimpleStringCommand(command);
}
}
ShellControl withSecurityPolicy(ShellSecurityPolicy policy); ShellControl withSecurityPolicy(ShellSecurityPolicy policy);
ShellSecurityPolicy getEffectiveSecurityPolicy(); ShellSecurityPolicy getEffectiveSecurityPolicy();

View file

@ -184,5 +184,5 @@ public interface ShellDialect {
String getDisplayName(); String getDisplayName();
boolean doesEchoInput(); boolean doesEchoInputByDefault();
} }

View file

@ -1,10 +0,0 @@
package io.xpipe.core.process;
import lombok.Value;
@Value
public class ShellProperties {
ShellDialect dialect;
boolean ansiEscapes;
}

View file

@ -19,6 +19,7 @@ public class ShellStoreState extends DataStoreState implements OsNameState {
OsType.Any osType; OsType.Any osType;
String osName; String osName;
ShellDialect shellDialect; ShellDialect shellDialect;
ShellTtyState ttyState;
Boolean running; Boolean running;
public boolean isRunning() { public boolean isRunning() {
@ -39,6 +40,7 @@ public class ShellStoreState extends DataStoreState implements OsNameState {
b.osType(useNewer(osType, shellStoreState.getOsType())) b.osType(useNewer(osType, shellStoreState.getOsType()))
.osName(useNewer(osName, shellStoreState.getOsName())) .osName(useNewer(osName, shellStoreState.getOsName()))
.shellDialect(useNewer(shellDialect, shellStoreState.getShellDialect())) .shellDialect(useNewer(shellDialect, shellStoreState.getShellDialect()))
.ttyState(useNewer(ttyState, shellStoreState.getTtyState()))
.running(useNewer(running, shellStoreState.getRunning())); .running(useNewer(running, shellStoreState.getRunning()));
} }
} }

View file

@ -0,0 +1,25 @@
package io.xpipe.core.process;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
@Getter
public enum ShellTtyState {
@JsonProperty("none")
NONE(true, false, false),
@JsonProperty("merged")
MERGED_STDERR(false, false, false),
@JsonProperty("pty")
PTY_ALLOCATED(false, true, true);
private final boolean hasSeparateStreams;
private final boolean hasAnsiEscapes;
private final boolean echoesAllInput;
ShellTtyState(boolean hasSeparateStreams, boolean hasAnsiEscapes, boolean echoesAllInput) {
this.hasSeparateStreams = hasSeparateStreams;
this.hasAnsiEscapes = hasAnsiEscapes;
this.echoesAllInput = echoesAllInput;
}
}

View file

@ -10,13 +10,11 @@ import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.store.LocalStore; import io.xpipe.core.store.LocalStore;
import io.xpipe.core.store.ShellStore; import io.xpipe.core.store.ShellStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import lombok.Value; import lombok.Value;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.StringReader;
public class SampleStoreAction implements ActionProvider { public class SampleStoreAction implements ActionProvider {
@ -83,23 +81,14 @@ public class SampleStoreAction implements ActionProvider {
sc.executeSimpleStringCommand(sc.getShellDialect().getEchoCommand("hello!", false)); sc.executeSimpleStringCommand(sc.getShellDialect().getEchoCommand("hello!", false));
// You can also implement custom handling for more complex commands // You can also implement custom handling for more complex commands
try (CommandControl cc = sc.command("ls").start()) { var lsOut = sc.command("ls").readStdoutOrThrow();
// Discard stderr // Read the stdout lines as a stream
cc.discardErr(); BufferedReader reader = new BufferedReader(new StringReader(lsOut));
// We don't have to close this stream here, that will be automatically done by the command control
// Read the stdout lines as a stream // after the try-with block
BufferedReader reader = new BufferedReader(new InputStreamReader(cc.getStdout(), cc.getCharset())); reader.lines().filter(s -> !s.isBlank()).forEach(s -> {
// We don't have to close this stream here, that will be automatically done by the command control System.out.println(s);
// after the try-with block });
reader.lines().filter(s -> !s.isBlank()).forEach(s -> {
System.out.println(s);
});
// Waits for command completion and returns exit code
if (cc.getExitCode() != 0) {
// Handle failure
}
}
// Commands can also be more complex and span multiple lines. // Commands can also be more complex and span multiple lines.
// In this case, XPipe will internally write a command to a script file and then execute the script // In this case, XPipe will internally write a command to a script file and then execute the script

View file

@ -16,12 +16,10 @@ public abstract class ToFileCommandAction implements LeafAction, ApplicationPath
ShellControl sc = model.getFileSystem().getShell().orElseThrow(); ShellControl sc = model.getFileSystem().getShell().orElseThrow();
for (BrowserEntry entry : entries) { for (BrowserEntry entry : entries) {
var command = createCommand(model, entry); var command = createCommand(model, entry);
try (var cc = sc.command(command) var out = sc.command(command)
.withWorkingDirectory(model.getCurrentDirectory().getPath()) .withWorkingDirectory(model.getCurrentDirectory().getPath())
.start()) { .readStdoutOrThrow();
cc.discardErr(); FileOpener.openReadOnlyString(out);
FileOpener.openCommandOutput(entry.getFileName(), entry, cc);
}
} }
} }

View file

@ -0,0 +1,69 @@
package io.xpipe.ext.base.store;
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;
import io.xpipe.app.ext.DataStoreProvider;
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.TerminalLauncher;
import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.process.ShellTtyState;
import io.xpipe.core.store.ShellStore;
import io.xpipe.ext.base.script.ScriptStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
public interface ShellStoreProvider extends DataStoreProvider {
default Comp<?> createTtyWarning(StoreEntryWrapper w) {
return new TtyWarningComp().hide(Bindings.createObjectBinding(
() -> {
ShellStoreState state = (ShellStoreState) w.getPersistentState().getValue();
return state.getTtyState() == ShellTtyState.NONE;
},
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() {
@Override
public void execute() throws Exception {
ShellStore store = entry.getStore().asNeeded();
TerminalLauncher.open(entry, DataStorage.get().getStoreEntryDisplayName(entry), null, ScriptStore.controlWithDefaultScripts(store.control()));
}
};
}
@Override
default ActionProvider.Action browserAction(BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) {
return new ActionProvider.Action() {
@Override
public void execute() throws Exception {
sessionModel.openFileSystemAsync(store.ref(), null, busy);
}
};
}
default Comp<?> stateDisplay(StoreEntryWrapper w) {
return new OsLogoComp(w, SystemStateComp.State.shellState(w));
}
@Override
default DataStoreUsageCategory getUsageCategory() {
return DataStoreUsageCategory.SHELL;
}
}

View file

@ -500,3 +500,6 @@ workspaceNameDescription=Visningsnavnet på arbejdsområdet
workspacePath=Sti til arbejdsområde workspacePath=Sti til arbejdsområde
workspacePathDescription=Placeringen af arbejdsområdets datakatalog workspacePathDescription=Placeringen af arbejdsområdets datakatalog
workspaceCreationAlertTitle=Oprettelse af arbejdsområde workspaceCreationAlertTitle=Oprettelse af arbejdsområde
developerForceSshTty=Fremtving SSH TTY
developerForceSshTtyDescription=Få alle SSH-forbindelser til at tildele en pty for at teste understøttelsen af en manglende stderr og en pty.
ttyWarning=Forbindelsen har tvangstildelt en pty/tty og giver ikke en separat stderr-strøm.\n\nDet kan føre til et par problemer.\n\nHvis du kan, så prøv at få forbindelseskommandoen til ikke at tildele en pty.

View file

@ -494,3 +494,6 @@ workspaceNameDescription=Der Anzeigename des Arbeitsbereichs
workspacePath=Pfad zum Arbeitsbereich workspacePath=Pfad zum Arbeitsbereich
workspacePathDescription=Der Ort des Datenverzeichnisses des Arbeitsbereichs workspacePathDescription=Der Ort des Datenverzeichnisses des Arbeitsbereichs
workspaceCreationAlertTitle=Arbeitsbereich erstellen workspaceCreationAlertTitle=Arbeitsbereich erstellen
developerForceSshTty=SSH TTY erzwingen
developerForceSshTtyDescription=Lass alle SSH-Verbindungen ein pty zuweisen, um die Unterstützung für einen fehlenden stderr und ein pty zu testen.
ttyWarning=Die Verbindung hat zwangsweise ein pty/tty zugewiesen und stellt keinen separaten stderr-Stream zur Verfügung.\n\nDas kann zu einigen Problemen führen.\n\nWenn du kannst, solltest du dafür sorgen, dass der Verbindungsbefehl kein pty zuweist.

View file

@ -498,3 +498,6 @@ workspaceNameDescription=The display name of the workspace
workspacePath=Workspace path workspacePath=Workspace path
workspacePathDescription=The location of the workspace data directory workspacePathDescription=The location of the workspace data directory
workspaceCreationAlertTitle=Workspace creation workspaceCreationAlertTitle=Workspace creation
developerForceSshTty=Force SSH TTY
developerForceSshTtyDescription=Make all SSH connections allocate a pty to test the support for a missing stderr and a pty.
ttyWarning=The connection has forcefully allocated a pty/tty and does not provide a separate stderr stream.\n\nThis might lead to a few problems.\n\nIf you can, look into making the connection command not allocate a pty.

View file

@ -481,3 +481,6 @@ workspaceNameDescription=El nombre para mostrar del espacio de trabajo
workspacePath=Ruta del espacio de trabajo workspacePath=Ruta del espacio de trabajo
workspacePathDescription=La ubicación del directorio de datos del espacio de trabajo workspacePathDescription=La ubicación del directorio de datos del espacio de trabajo
workspaceCreationAlertTitle=Creación de espacios de trabajo workspaceCreationAlertTitle=Creación de espacios de trabajo
developerForceSshTty=Forzar SSH TTY
developerForceSshTtyDescription=Haz que todas las conexiones SSH asignen una pty para probar la compatibilidad con una stderr y una pty ausentes.
ttyWarning=La conexión ha asignado forzosamente un pty/tty y no proporciona un flujo stderr separado.\n\nEsto puede provocar algunos problemas.\n\nSi puedes, intenta que el comando de conexión no asigne una pty.

View file

@ -481,3 +481,6 @@ workspaceNameDescription=Le nom d'affichage de l'espace de travail
workspacePath=Chemin d'accès à l'espace de travail workspacePath=Chemin d'accès à l'espace de travail
workspacePathDescription=L'emplacement du répertoire de données de l'espace de travail workspacePathDescription=L'emplacement du répertoire de données de l'espace de travail
workspaceCreationAlertTitle=Création d'un espace de travail workspaceCreationAlertTitle=Création d'un espace de travail
developerForceSshTty=Force SSH TTY
developerForceSshTtyDescription=Fais en sorte que toutes les connexions SSH allouent un pty pour tester la prise en charge d'un stderr et d'un pty manquants.
ttyWarning=La connexion a alloué de force un pty/tty et ne fournit pas de flux stderr séparé.\n\nCela peut entraîner quelques problèmes.\n\nSi tu le peux, essaie de faire en sorte que la commande de connexion n'alloue pas de pty.

View file

@ -481,3 +481,6 @@ workspaceNameDescription=Il nome di visualizzazione dell'area di lavoro
workspacePath=Percorso dello spazio di lavoro workspacePath=Percorso dello spazio di lavoro
workspacePathDescription=La posizione della directory dei dati dell'area di lavoro workspacePathDescription=La posizione della directory dei dati dell'area di lavoro
workspaceCreationAlertTitle=Creazione di uno spazio di lavoro workspaceCreationAlertTitle=Creazione di uno spazio di lavoro
developerForceSshTty=Forza SSH TTY
developerForceSshTtyDescription=Fai in modo che tutte le connessioni SSH allocino una pty per testare il supporto di una stderr e di una pty mancanti.
ttyWarning=La connessione ha allocato forzatamente una pty/tty e non fornisce un flusso stderr separato.\n\nQuesto potrebbe causare alcuni problemi.\n\nSe puoi, cerca di fare in modo che il comando di connessione non allarghi una pty.

View file

@ -481,3 +481,6 @@ workspaceNameDescription=ワークスペースの表示名
workspacePath=ワークスペースのパス workspacePath=ワークスペースのパス
workspacePathDescription=ワークスペースのデータディレクトリの場所 workspacePathDescription=ワークスペースのデータディレクトリの場所
workspaceCreationAlertTitle=ワークスペースの作成 workspaceCreationAlertTitle=ワークスペースの作成
developerForceSshTty=強制SSH TTY
developerForceSshTtyDescription=すべてのSSHコネクションにptyを割り当て、stderrとptyがない場合のサポートをテストする。
ttyWarning=接続が強制的にpty/ttyを割り当て、個別のstderrストリームを提供しない。\n\nこれはいくつかの問題を引き起こす可能性がある。\n\n可能であれば、接続コマンドで pty を割り当てないようにすることを検討してほしい。

View file

@ -481,3 +481,6 @@ workspaceNameDescription=De weergavenaam van de werkruimte
workspacePath=Werkruimte pad workspacePath=Werkruimte pad
workspacePathDescription=De locatie van de gegevensmap van de werkruimte workspacePathDescription=De locatie van de gegevensmap van de werkruimte
workspaceCreationAlertTitle=Werkruimte maken workspaceCreationAlertTitle=Werkruimte maken
developerForceSshTty=SSH TTY afdwingen
developerForceSshTtyDescription=Laat alle SSH-verbindingen een pty toewijzen om de ondersteuning voor een ontbrekende stderr en een pty te testen.
ttyWarning=De verbinding heeft geforceerd een pty/tty toegewezen en biedt geen aparte stderr stream.\n\nDit kan tot een paar problemen leiden.\n\nAls je kunt, kijk dan of je het connection commando geen pty kunt laten toewijzen.

View file

@ -481,3 +481,6 @@ workspaceNameDescription=O nome de apresentação do espaço de trabalho
workspacePath=Caminho do espaço de trabalho workspacePath=Caminho do espaço de trabalho
workspacePathDescription=A localização do diretório de dados do espaço de trabalho workspacePathDescription=A localização do diretório de dados do espaço de trabalho
workspaceCreationAlertTitle=Criação de espaço de trabalho workspaceCreationAlertTitle=Criação de espaço de trabalho
developerForceSshTty=Força o SSH TTY
developerForceSshTtyDescription=Faz com que todas as ligações SSH atribuam um pty para testar o suporte para um stderr e um pty em falta.
ttyWarning=A ligação atribuiu à força um pty/tty e não fornece um fluxo stderr separado.\n\nIsto pode levar a alguns problemas.\n\nSe puderes, tenta fazer com que o comando de ligação não atribua um pty.

View file

@ -481,3 +481,6 @@ workspaceNameDescription=Отображаемое имя рабочей обла
workspacePath=Путь к рабочему пространству workspacePath=Путь к рабочему пространству
workspacePathDescription=Расположение каталога данных рабочей области workspacePathDescription=Расположение каталога данных рабочей области
workspaceCreationAlertTitle=Создание рабочего пространства workspaceCreationAlertTitle=Создание рабочего пространства
developerForceSshTty=Принудительный SSH TTY
developerForceSshTtyDescription=Заставь все SSH-соединения выделять pty, чтобы проверить поддержку отсутствующего stderr и pty.
ttyWarning=Соединение принудительно выделило pty/tty и не предоставляет отдельный поток stderr.\n\nЭто может привести к нескольким проблемам.\n\nЕсли можешь, попробуй сделать так, чтобы команда подключения не выделяла pty.

View file

@ -482,3 +482,6 @@ workspaceNameDescription=Çalışma alanının görünen adı
workspacePath=Çalışma alanı yolu workspacePath=Çalışma alanı yolu
workspacePathDescription=Çalışma alanı veri dizininin konumu workspacePathDescription=Çalışma alanı veri dizininin konumu
workspaceCreationAlertTitle=Çalışma alanı oluşturma workspaceCreationAlertTitle=Çalışma alanı oluşturma
developerForceSshTty=SSH TTY'yi Zorla
developerForceSshTtyDescription=Eksik bir stderr ve bir pty desteğini test etmek için tüm SSH bağlantılarının bir pty ayırmasını sağlayın.
ttyWarning=Bağlantı zorla bir pty/tty ayırmış ve ayrı bir stderr akışı sağlamıyor.\n\nBu durum birkaç soruna yol açabilir.\n\nEğer yapabiliyorsanız, bağlantı komutunun bir pty tahsis etmemesini sağlayın.

View file

@ -481,3 +481,6 @@ workspaceNameDescription=工作区的显示名称
workspacePath=工作区路径 workspacePath=工作区路径
workspacePathDescription=工作区数据目录的位置 workspacePathDescription=工作区数据目录的位置
workspaceCreationAlertTitle=创建工作区 workspaceCreationAlertTitle=创建工作区
developerForceSshTty=强制 SSH TTY
developerForceSshTtyDescription=让所有 SSH 连接都分配一个 pty以测试对缺失的 stderr 和 pty 的支持。
ttyWarning=连接强行分配了 pty/tty且未提供单独的 stderr 流。\n\n这可能会导致一些问题。\n\n如果可以请考虑让连接命令不分配 pty。