mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-21 15:10:23 +00:00
Merge branch no-stderr into master
This commit is contained in:
parent
65b2be5709
commit
9860b0c10f
34 changed files with 305 additions and 200 deletions
|
@ -33,26 +33,49 @@ public class SystemStateComp extends SimpleComp {
|
|||
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.setOpacity(0.5);
|
||||
border.setOpacity(0.3);
|
||||
|
||||
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 =
|
||||
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 =
|
||||
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();
|
||||
pane.getChildren().addAll(fi, border);
|
||||
pane.getChildren().addAll(bg, fi, border);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
|
||||
var dataClass1 =
|
||||
"""
|
||||
.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 {
|
||||
-fx-icon-size: 12px;
|
||||
}
|
||||
|
|
50
app/src/main/java/io/xpipe/app/comp/base/TtyWarningComp.java
Normal file
50
app/src/main/java/io/xpipe/app/comp/base/TtyWarningComp.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import javafx.beans.value.ObservableValue;
|
|||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
|
||||
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.setMaxWidth(400);
|
||||
tt.getStyleClass().add("fancy-tooltip");
|
||||
tt.setHideDelay(Duration.INDEFINITE);
|
||||
Tooltip.install(struc.get(), tt);
|
||||
}
|
||||
|
||||
|
|
|
@ -109,6 +109,9 @@ public class AppPrefs {
|
|||
map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class);
|
||||
private final ObservableBooleanValue developerDisableGuiRestrictionsEffective =
|
||||
bindDeveloperTrue(developerDisableGuiRestrictions);
|
||||
final BooleanProperty developerForceSshTty =
|
||||
map(new SimpleBooleanProperty(false), "developerForceSshTty", Boolean.class);
|
||||
|
||||
final ObjectProperty<SupportedLocale> language =
|
||||
map(new SimpleObjectProperty<>(SupportedLocale.getEnglish()), "language", SupportedLocale.class);
|
||||
|
||||
|
@ -435,6 +438,10 @@ public class AppPrefs {
|
|||
return developerDisableGuiRestrictionsEffective;
|
||||
}
|
||||
|
||||
public ObservableBooleanValue developerForceSshTty() {
|
||||
return bindDeveloperTrue(developerForceSshTty);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T map(T o, String name, Class<?> clazz) {
|
||||
mapping.add(new Mapping<>(name, (Property<T>) o, (Class<T>) clazz));
|
||||
|
|
|
@ -61,6 +61,8 @@ public class DeveloperCategory extends AppPrefsCategory {
|
|||
.sub(new OptionsBuilder()
|
||||
.nameAndDescription("developerDisableUpdateVersionCheck")
|
||||
.addToggle(prefs.developerDisableUpdateVersionCheck)
|
||||
.nameAndDescription("developerForceSshTty")
|
||||
.addToggle(prefs.developerForceSshTty)
|
||||
.nameAndDescription("developerDisableGuiRestrictions")
|
||||
.addToggle(prefs.developerDisableGuiRestrictions)
|
||||
.nameAndDescription("shellCommandTest")
|
||||
|
|
|
@ -114,14 +114,12 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
|
|||
|
||||
protected Optional<Path> determineFromPath() {
|
||||
// Try to locate if it is in the Path
|
||||
try (var cc = LocalShell.getShell()
|
||||
.command(CommandBuilder.ofFunction(
|
||||
var1 -> var1.getShellDialect().getWhichCommand(executable)))
|
||||
try (var sc = LocalShell.getShell()
|
||||
.start()) {
|
||||
var out = cc.readStdoutDiscardErr();
|
||||
var exit = cc.getExitCode();
|
||||
if (exit == 0) {
|
||||
var first = out.lines().findFirst();
|
||||
var out = sc.command(CommandBuilder.ofFunction(
|
||||
var1 -> var1.getShellDialect().getWhichCommand(executable))).readStdoutIfPossible();
|
||||
if (out.isPresent()) {
|
||||
var first = out.get().lines().findFirst();
|
||||
if (first.isPresent()) {
|
||||
return first.map(String::trim).map(Path::of);
|
||||
}
|
||||
|
|
|
@ -99,16 +99,13 @@ public enum XPipeDistributionType {
|
|||
// In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the
|
||||
// production behavior
|
||||
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||
try (var chocoOut =
|
||||
sc.command("choco search --local-only -r xpipe").start()) {
|
||||
var out = chocoOut.readStdoutDiscardErr();
|
||||
if (chocoOut.getExitCode() == 0) {
|
||||
var split = out.split("\\|");
|
||||
if (split.length == 2) {
|
||||
var version = split[1];
|
||||
if (AppProperties.get().getVersion().equals(version)) {
|
||||
return CHOCO;
|
||||
}
|
||||
var out = sc.command("choco search --local-only -r xpipe").readStdoutIfPossible();
|
||||
if (out.isPresent()) {
|
||||
var split = out.get().split("\\|");
|
||||
if (split.length == 2) {
|
||||
var version = split[1];
|
||||
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
|
||||
// production behavior
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
try (var brewOut = sc.command("brew list --casks --versions").start()) {
|
||||
var out = brewOut.readStdoutDiscardErr();
|
||||
if (brewOut.getExitCode() == 0) {
|
||||
if (out.lines().anyMatch(s -> {
|
||||
var split = s.split(" ");
|
||||
return split.length == 2
|
||||
&& split[0].equals("xpipe")
|
||||
&& split[1].equals(AppProperties.get().getVersion());
|
||||
})) {
|
||||
return HOMEBREW;
|
||||
}
|
||||
var out = sc.command("brew list --casks --versions").readStdoutIfPossible();
|
||||
if (out.isPresent()) {
|
||||
if (out.get().lines().anyMatch(s -> {
|
||||
var split = s.split(" ");
|
||||
return split.length == 2
|
||||
&& split[0].equals("xpipe")
|
||||
&& split[1].equals(AppProperties.get().getVersion());
|
||||
})) {
|
||||
return HOMEBREW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,10 @@ public class DesktopHelper {
|
|||
return Path.of(LocalShell.getLocalPowershell()
|
||||
.executeSimpleStringCommand("[Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)"));
|
||||
} else if (OsType.getLocal() == OsType.LINUX) {
|
||||
try (var cmd = LocalShell.getShell().command("xdg-user-dir DESKTOP").start()) {
|
||||
var read = cmd.readStdoutDiscardErr();
|
||||
var exit = cmd.getExitCode();
|
||||
if (exit == 0) {
|
||||
return Path.of(read);
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var out = sc.command("xdg-user-dir DESKTOP").readStdoutIfPossible();
|
||||
if (out.isPresent()) {
|
||||
return Path.of(out.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,12 +33,10 @@ public class DesktopHelper {
|
|||
.executeSimpleStringCommand(
|
||||
"(New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path"));
|
||||
} else if (OsType.getLocal() == OsType.LINUX) {
|
||||
try (var cmd =
|
||||
LocalShell.getShell().command("xdg-user-dir DOWNLOAD").start()) {
|
||||
var read = cmd.readStdoutDiscardErr();
|
||||
var exit = cmd.getExitCode();
|
||||
if (exit == 0) {
|
||||
return Path.of(read);
|
||||
try (var sc = LocalShell.getShell().start()) {
|
||||
var out = sc.command("xdg-user-dir DOWNLOAD").readStdoutIfPossible();
|
||||
if (out.isPresent()) {
|
||||
return Path.of(out.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,23 +145,20 @@ public abstract class WindowsRegistry {
|
|||
.add("/v")
|
||||
.addQuoted(valueName);
|
||||
|
||||
String output;
|
||||
try (var c = shellControl.command(command).start()) {
|
||||
output = c.readStdoutDiscardErr();
|
||||
if (c.getExitCode() != 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
var output = shellControl.command(command).readStdoutIfPossible();
|
||||
if (output.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Output has the following format:
|
||||
// \n<Version information>\n\n<key>\t<registry type>\t<value>
|
||||
if (output.contains("\t")) {
|
||||
String[] parsed = output.split("\t");
|
||||
if (output.get().contains("\t")) {
|
||||
String[] parsed = output.get().split("\t");
|
||||
return Optional.of(parsed[parsed.length - 1]);
|
||||
}
|
||||
|
||||
if (output.contains(" ")) {
|
||||
String[] parsed = output.split(" ");
|
||||
if (output.get().contains(" ")) {
|
||||
String[] parsed = output.get().split(" ");
|
||||
return Optional.of(parsed[parsed.length - 1]);
|
||||
}
|
||||
|
||||
|
@ -176,14 +173,7 @@ public abstract class WindowsRegistry {
|
|||
.add("/v")
|
||||
.addQuoted(valueName)
|
||||
.add("/s");
|
||||
try (var c = shellControl.command(command).start()) {
|
||||
var output = c.readStdoutDiscardErr();
|
||||
if (c.getExitCode() != 0) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(output);
|
||||
}
|
||||
}
|
||||
return shellControl.command(command).readStdoutIfPossible();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -196,22 +186,17 @@ public abstract class WindowsRegistry {
|
|||
.add("/s")
|
||||
.add("/e")
|
||||
.add("/d");
|
||||
try (var c = shellControl.command(command).start()) {
|
||||
var output = c.readStdoutDiscardErr();
|
||||
if (c.getExitCode() != 0) {
|
||||
return shellControl.command(command).readStdoutIfPossible().flatMap(output -> {
|
||||
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();
|
||||
} 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
## Welcome
|
||||
|
||||
Thank you for using XPipe!
|
||||
Welcome to XPipe!
|
||||
|
||||
You can view the development status, report issues, and more at the following places:
|
||||
|
||||
- [GitHub Repository](https://github.com/xpipe-io/xpipe/)
|
||||
- [Discord Server](https://discord.gg/8y89vS8cRb)
|
||||
- [Slack Server](https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg)
|
||||
- [Email me](mailto://crschnick@xpipe.io)
|
||||
|
||||
Note that the XPipe project currently is a one-man show, but I still try to respond to everything in time.
|
||||
- [Email us](mailto://hello@xpipe.io)
|
||||
|
|
|
@ -128,3 +128,7 @@
|
|||
.root:dark .loading-comp {
|
||||
-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; }
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface CommandControl extends ProcessControl {
|
||||
|
@ -70,32 +64,14 @@ public interface CommandControl extends ProcessControl {
|
|||
|
||||
CommandControl elevated(ElevationFunction function);
|
||||
|
||||
void withStdoutOrThrow(FailableConsumer<InputStreamReader, Exception> c);
|
||||
|
||||
String[] readStdoutAndStderr() throws Exception;
|
||||
|
||||
String readStdoutDiscardErr() throws Exception;
|
||||
|
||||
String readStderrDiscardStdout() throws Exception;
|
||||
|
||||
void discardOrThrow() throws Exception;
|
||||
|
||||
void accumulateStdout(Consumer<String> con);
|
||||
|
||||
void accumulateStderr(Consumer<String> con);
|
||||
|
||||
byte[] readRawBytesOrThrow() 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;
|
||||
|
||||
default boolean discardAndCheckExit() throws ProcessOutputException {
|
||||
|
@ -113,10 +89,6 @@ public interface CommandControl extends ProcessControl {
|
|||
}
|
||||
}
|
||||
|
||||
void discardOut();
|
||||
|
||||
void discardErr();
|
||||
|
||||
enum TerminalExitMode {
|
||||
KEEP_OPEN,
|
||||
CLOSE
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
public interface CommandFeedbackPredicate {
|
||||
|
||||
boolean test(CommandBuilder command);
|
||||
}
|
|
@ -181,19 +181,15 @@ public interface OsType {
|
|||
@Override
|
||||
public String determineOperatingSystemName(ShellControl pc) throws Exception {
|
||||
String type = "Unknown";
|
||||
try (CommandControl c = pc.command("uname -o").start()) {
|
||||
var text = c.readStdoutDiscardErr();
|
||||
if (c.getExitCode() == 0) {
|
||||
type = text.strip();
|
||||
}
|
||||
var uname = pc.command("uname -o").readStdoutIfPossible();
|
||||
if (uname.isPresent()) {
|
||||
type = uname.get();
|
||||
}
|
||||
|
||||
String version = "?";
|
||||
try (CommandControl c = pc.command("uname -r").start()) {
|
||||
var text = c.readStdoutDiscardErr();
|
||||
if (c.getExitCode() == 0) {
|
||||
version = text.strip();
|
||||
}
|
||||
var unameR = pc.command("uname -r").readStdoutIfPossible();
|
||||
if (unameR.isPresent()) {
|
||||
version = unameR.get();
|
||||
}
|
||||
|
||||
return type + " " + version;
|
||||
|
@ -209,18 +205,14 @@ public interface OsType {
|
|||
|
||||
@Override
|
||||
public String determineOperatingSystemName(ShellControl pc) throws Exception {
|
||||
try (CommandControl c = pc.command("lsb_release -a").start()) {
|
||||
var text = c.readStdoutDiscardErr();
|
||||
if (c.getExitCode() == 0) {
|
||||
return PropertiesFormatsParser.parse(text, ":").getOrDefault("Description", "Unknown");
|
||||
}
|
||||
var rel = pc.command("lsb_release -a").readStdoutIfPossible();
|
||||
if (rel.isPresent()) {
|
||||
return PropertiesFormatsParser.parse(rel.get(), ":").getOrDefault("Description", "Unknown");
|
||||
}
|
||||
|
||||
try (CommandControl c = pc.command("cat /etc/*release").start()) {
|
||||
var text = c.readStdoutDiscardErr();
|
||||
if (c.getExitCode() == 0) {
|
||||
return PropertiesFormatsParser.parse(text, "=").getOrDefault("PRETTY_NAME", "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);
|
||||
|
|
|
@ -18,6 +18,8 @@ import java.util.function.Function;
|
|||
|
||||
public interface ShellControl extends ProcessControl {
|
||||
|
||||
ShellTtyState getTtyState();
|
||||
|
||||
void setNonInteractive();
|
||||
|
||||
boolean isInteractive();
|
||||
|
@ -65,6 +67,7 @@ public interface ShellControl extends ProcessControl {
|
|||
var s = store.getState().toBuilder()
|
||||
.osType(shellControl.getOsType())
|
||||
.shellDialect(shellControl.getOriginalShellDialect())
|
||||
.ttyState(shellControl.getTtyState())
|
||||
.running(true)
|
||||
.osName(shellControl.getOsName())
|
||||
.build();
|
||||
|
@ -113,25 +116,12 @@ public interface ShellControl extends ProcessControl {
|
|||
script));
|
||||
}
|
||||
|
||||
default byte[] executeSimpleRawBytesCommand(String command) throws Exception {
|
||||
try (CommandControl c = command(command).start()) {
|
||||
return c.readRawBytesOrThrow();
|
||||
}
|
||||
}
|
||||
|
||||
default String executeSimpleStringCommand(String command) throws Exception {
|
||||
try (CommandControl c = command(command).start()) {
|
||||
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 {
|
||||
try (CommandControl c = command(command).start()) {
|
||||
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);
|
||||
|
||||
ShellSecurityPolicy getEffectiveSecurityPolicy();
|
||||
|
|
|
@ -184,5 +184,5 @@ public interface ShellDialect {
|
|||
|
||||
String getDisplayName();
|
||||
|
||||
boolean doesEchoInput();
|
||||
boolean doesEchoInputByDefault();
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package io.xpipe.core.process;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class ShellProperties {
|
||||
|
||||
ShellDialect dialect;
|
||||
boolean ansiEscapes;
|
||||
}
|
|
@ -19,6 +19,7 @@ public class ShellStoreState extends DataStoreState implements OsNameState {
|
|||
OsType.Any osType;
|
||||
String osName;
|
||||
ShellDialect shellDialect;
|
||||
ShellTtyState ttyState;
|
||||
Boolean running;
|
||||
|
||||
public boolean isRunning() {
|
||||
|
@ -39,6 +40,7 @@ public class ShellStoreState extends DataStoreState implements OsNameState {
|
|||
b.osType(useNewer(osType, shellStoreState.getOsType()))
|
||||
.osName(useNewer(osName, shellStoreState.getOsName()))
|
||||
.shellDialect(useNewer(shellDialect, shellStoreState.getShellDialect()))
|
||||
.ttyState(useNewer(ttyState, shellStoreState.getTtyState()))
|
||||
.running(useNewer(running, shellStoreState.getRunning()));
|
||||
}
|
||||
}
|
||||
|
|
25
core/src/main/java/io/xpipe/core/process/ShellTtyState.java
Normal file
25
core/src/main/java/io/xpipe/core/process/ShellTtyState.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -10,13 +10,11 @@ import io.xpipe.core.process.ShellControl;
|
|||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class SampleStoreAction implements ActionProvider {
|
||||
|
||||
|
@ -83,23 +81,14 @@ public class SampleStoreAction implements ActionProvider {
|
|||
sc.executeSimpleStringCommand(sc.getShellDialect().getEchoCommand("hello!", false));
|
||||
|
||||
// You can also implement custom handling for more complex commands
|
||||
try (CommandControl cc = sc.command("ls").start()) {
|
||||
// Discard stderr
|
||||
cc.discardErr();
|
||||
|
||||
// Read the stdout lines as a stream
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(cc.getStdout(), cc.getCharset()));
|
||||
// We don't have to close this stream here, that will be automatically done by the command control
|
||||
// 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
|
||||
}
|
||||
}
|
||||
var lsOut = sc.command("ls").readStdoutOrThrow();
|
||||
// Read the stdout lines as a stream
|
||||
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
|
||||
// after the try-with block
|
||||
reader.lines().filter(s -> !s.isBlank()).forEach(s -> {
|
||||
System.out.println(s);
|
||||
});
|
||||
|
||||
// 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
|
||||
|
|
|
@ -16,12 +16,10 @@ public abstract class ToFileCommandAction implements LeafAction, ApplicationPath
|
|||
ShellControl sc = model.getFileSystem().getShell().orElseThrow();
|
||||
for (BrowserEntry entry : entries) {
|
||||
var command = createCommand(model, entry);
|
||||
try (var cc = sc.command(command)
|
||||
var out = sc.command(command)
|
||||
.withWorkingDirectory(model.getCurrentDirectory().getPath())
|
||||
.start()) {
|
||||
cc.discardErr();
|
||||
FileOpener.openCommandOutput(entry.getFileName(), entry, cc);
|
||||
}
|
||||
.readStdoutOrThrow();
|
||||
FileOpener.openReadOnlyString(out);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -500,3 +500,6 @@ workspaceNameDescription=Visningsnavnet på arbejdsområdet
|
|||
workspacePath=Sti til arbejdsområde
|
||||
workspacePathDescription=Placeringen af arbejdsområdets datakatalog
|
||||
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.
|
||||
|
|
|
@ -494,3 +494,6 @@ workspaceNameDescription=Der Anzeigename des Arbeitsbereichs
|
|||
workspacePath=Pfad zum Arbeitsbereich
|
||||
workspacePathDescription=Der Ort des Datenverzeichnisses des Arbeitsbereichs
|
||||
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.
|
||||
|
|
|
@ -498,3 +498,6 @@ workspaceNameDescription=The display name of the workspace
|
|||
workspacePath=Workspace path
|
||||
workspacePathDescription=The location of the workspace data directory
|
||||
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.
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=El nombre para mostrar del espacio de trabajo
|
|||
workspacePath=Ruta del espacio de trabajo
|
||||
workspacePathDescription=La ubicación del directorio de datos del espacio 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.
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=Le nom d'affichage de 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
|
||||
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.
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=Il nome di visualizzazione dell'area di lavoro
|
|||
workspacePath=Percorso dello spazio di lavoro
|
||||
workspacePathDescription=La posizione della directory dei dati dell'area 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.
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=ワークスペースの表示名
|
|||
workspacePath=ワークスペースのパス
|
||||
workspacePathDescription=ワークスペースのデータディレクトリの場所
|
||||
workspaceCreationAlertTitle=ワークスペースの作成
|
||||
developerForceSshTty=強制SSH TTY
|
||||
developerForceSshTtyDescription=すべてのSSHコネクションにptyを割り当て、stderrとptyがない場合のサポートをテストする。
|
||||
ttyWarning=接続が強制的にpty/ttyを割り当て、個別のstderrストリームを提供しない。\n\nこれはいくつかの問題を引き起こす可能性がある。\n\n可能であれば、接続コマンドで pty を割り当てないようにすることを検討してほしい。
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=De weergavenaam van de werkruimte
|
|||
workspacePath=Werkruimte pad
|
||||
workspacePathDescription=De locatie van de gegevensmap van de werkruimte
|
||||
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.
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=O nome de apresentação 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
|
||||
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.
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=Отображаемое имя рабочей обла
|
|||
workspacePath=Путь к рабочему пространству
|
||||
workspacePathDescription=Расположение каталога данных рабочей области
|
||||
workspaceCreationAlertTitle=Создание рабочего пространства
|
||||
developerForceSshTty=Принудительный SSH TTY
|
||||
developerForceSshTtyDescription=Заставь все SSH-соединения выделять pty, чтобы проверить поддержку отсутствующего stderr и pty.
|
||||
ttyWarning=Соединение принудительно выделило pty/tty и не предоставляет отдельный поток stderr.\n\nЭто может привести к нескольким проблемам.\n\nЕсли можешь, попробуй сделать так, чтобы команда подключения не выделяла pty.
|
||||
|
|
|
@ -482,3 +482,6 @@ workspaceNameDescription=Çalışma alanının görünen adı
|
|||
workspacePath=Çalışma alanı yolu
|
||||
workspacePathDescription=Çalışma alanı veri dizininin konumu
|
||||
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.
|
||||
|
|
|
@ -481,3 +481,6 @@ workspaceNameDescription=工作区的显示名称
|
|||
workspacePath=工作区路径
|
||||
workspacePathDescription=工作区数据目录的位置
|
||||
workspaceCreationAlertTitle=创建工作区
|
||||
developerForceSshTty=强制 SSH TTY
|
||||
developerForceSshTtyDescription=让所有 SSH 连接都分配一个 pty,以测试对缺失的 stderr 和 pty 的支持。
|
||||
ttyWarning=连接强行分配了 pty/tty,且未提供单独的 stderr 流。\n\n这可能会导致一些问题。\n\n如果可以,请考虑让连接命令不分配 pty。
|
||||
|
|
Loading…
Reference in a new issue