Rebase fixes

This commit is contained in:
crschnick 2024-04-01 07:22:42 +00:00
parent 10ef67a6ec
commit 9b9581234d
18 changed files with 185 additions and 237 deletions

View file

@ -2,7 +2,6 @@ package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry; import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import java.util.List; import java.util.List;
@ -13,9 +12,7 @@ public abstract class ExecuteApplicationAction implements LeafAction, Applicatio
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception { public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) throws Exception {
ShellControl sc = model.getFileSystem().getShell().orElseThrow(); ShellControl sc = model.getFileSystem().getShell().orElseThrow();
for (BrowserEntry entry : entries) { for (BrowserEntry entry : entries) {
var command = detach() var command = createCommand(model, entry);
? ApplicationHelper.createDetachCommand(sc, createCommand(model, entry))
: createCommand(model, entry);
try (var cc = sc.command(command) try (var cc = sc.command(command)
.withWorkingDirectory(model.getCurrentDirectory().getPath()) .withWorkingDirectory(model.getCurrentDirectory().getPath())
.start()) { .start()) {
@ -23,19 +20,11 @@ public abstract class ExecuteApplicationAction implements LeafAction, Applicatio
} }
} }
if (detach() && refresh()) {
throw new IllegalStateException();
}
if (refresh()) { if (refresh()) {
model.refreshSync(); model.refreshSync();
} }
} }
protected boolean detach() {
return false;
}
protected boolean refresh() { protected boolean refresh() {
return false; return false;
} }

View file

@ -3,8 +3,8 @@ package io.xpipe.app.browser.action;
import io.xpipe.app.browser.BrowserEntry; import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
@ -12,7 +12,7 @@ import java.util.List;
public abstract class MultiExecuteAction implements BranchAction { public abstract class MultiExecuteAction implements BranchAction {
protected abstract String createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry); protected abstract CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry);
@Override @Override
public List<LeafAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries) { public List<LeafAction> getBranchingActions(OpenFileSystemModel model, List<BrowserEntry> entries) {
@ -56,9 +56,7 @@ public abstract class MultiExecuteAction implements BranchAction {
model.withShell( model.withShell(
pc -> { pc -> {
for (BrowserEntry entry : entries) { for (BrowserEntry entry : entries) {
var cmd = ApplicationHelper.createDetachCommand( pc.command(createCommand(pc, model, entry))
pc, createCommand(pc, model, entry));
pc.command(cmd)
.withWorkingDirectory(model.getCurrentDirectory() .withWorkingDirectory(model.getCurrentDirectory()
.getPath()) .getPath())
.execute(); .execute();
@ -71,27 +69,6 @@ public abstract class MultiExecuteAction implements BranchAction {
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) { public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return "in background"; return "in background";
} }
},
new LeafAction() {
@Override
public void execute(OpenFileSystemModel model, List<BrowserEntry> entries) {
model.withShell(
pc -> {
for (BrowserEntry entry : entries) {
pc.command(createCommand(pc, model, entry))
.withWorkingDirectory(model.getCurrentDirectory()
.getPath())
.execute();
}
},
false);
}
@Override
public String getName(OpenFileSystemModel model, List<BrowserEntry> entries) {
return "wait for completion";
}
}); });
} }
} }

View file

@ -54,7 +54,7 @@ public class ErrorEvent {
return EVENT_BASES.remove(t).description(msg); return EVENT_BASES.remove(t).description(msg);
} }
return builder().throwable(t).description(msg); return builder().throwable(t).description(msg + (t.getMessage() != null ? "\n\n" + t.getMessage() : ""));
} }
public static ErrorEventBuilder fromMessage(String msg) { public static ErrorEventBuilder fromMessage(String msg) {

View file

@ -11,7 +11,6 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.terminal.ExternalTerminalType; import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.PasswordLockSecretValue; import io.xpipe.app.util.PasswordLockSecretValue;
import io.xpipe.core.util.InPlaceSecretValue; import io.xpipe.core.util.InPlaceSecretValue;
import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.ModuleHelper;
@ -503,7 +502,7 @@ public class AppPrefs {
return null; return null;
} }
return ApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key); return ExternalApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key);
} }
@Value @Value

View file

@ -0,0 +1,33 @@
package io.xpipe.app.prefs;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.CommandBuilder;
import java.util.Locale;
public class ExternalApplicationHelper {
public static String replaceFileArgument(String format, String variable, String file) {
// Support for legacy variables that were not upper case
variable = variable.toUpperCase(Locale.ROOT);
format = format.replace("$" + variable.toLowerCase(Locale.ROOT), "$" + variable.toUpperCase(Locale.ROOT));
var fileString = file.contains(" ") ? "\"" + file + "\"" : file;
// Check if the variable is already quoted
return format.replace("\"$" + variable + "\"", fileString).replace("$" + variable, fileString);
}
public static void startAsync(CommandBuilder b) throws Exception {
try (var sc = LocalShell.getShell().start()) {
var cmd = sc.getShellDialect().launchAsnyc(b);
TrackEvent.withDebug("Executing local application")
.tag("command", b.buildFull(sc))
.tag("adjusted", cmd.buildFull(sc))
.handle();
try (var c = sc.command(cmd).start()) {
c.discardOrThrow();
}
}
}
}

View file

@ -2,12 +2,11 @@ package io.xpipe.app.prefs;
import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ApplicationHelper; import io.xpipe.app.util.CommandSupport;
import io.xpipe.app.util.LocalShell; import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -95,10 +94,12 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
public abstract static class PathApplication extends ExternalApplicationType { public abstract static class PathApplication extends ExternalApplicationType {
protected final String executable; protected final String executable;
protected final boolean explicityAsync;
public PathApplication(String id, String executable) { public PathApplication(String id, String executable, boolean explicityAsync) {
super(id); super(id);
this.executable = executable; this.executable = executable;
this.explicityAsync = explicityAsync;
} }
public boolean isAvailable() { public boolean isAvailable() {
@ -110,32 +111,21 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
} }
} }
protected void launch(String title, String args) throws Exception { protected void launch(String title, CommandBuilder args) throws Exception {
try (ShellControl pc = LocalShell.getShell()) { try (ShellControl pc = LocalShell.getShell()) {
if (!ApplicationHelper.isInPath(pc, executable)) { if (!CommandSupport.isInPath(pc, executable)) {
throw ErrorEvent.expected( throw ErrorEvent.expected(
new IOException( new IOException(
"Executable " + executable "Executable " + executable
+ " not found in PATH. Either add it to the PATH and refresh the environment by restarting XPipe, or specify an absolute executable path using the custom terminal setting.")); + " not found in PATH. Either add it to the PATH and refresh the environment by restarting XPipe, or specify an absolute executable path using the custom terminal setting."));
} }
if (ShellDialects.isPowershell(pc)) { args.add(0, executable);
var cmd = CommandBuilder.of() if (explicityAsync) {
.add("Start-Process", "-FilePath") ExternalApplicationHelper.startAsync(args);
.addFile(executable)
.add("-ArgumentList")
.add(pc.getShellDialect().literalArgument(args));
pc.executeSimpleCommand(cmd);
return;
}
var toExecute = executable + " " + args;
if (pc.getOsType().equals(OsType.WINDOWS)) {
toExecute = "start \"" + title + "\" " + toExecute;
} else { } else {
toExecute = "nohup " + toExecute + " </dev/null &>/dev/null & disown"; pc.executeSimpleCommand(args);
} }
pc.executeSimpleCommand(toExecute);
} }
} }
} }

View file

@ -2,7 +2,7 @@ package io.xpipe.app.prefs;
import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ApplicationHelper; import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.WindowsRegistry; import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
@ -106,9 +106,8 @@ public interface ExternalEditorType extends PrefsChoiceValue {
var format = var format =
customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE"; customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE";
ApplicationHelper.executeLocalApplication( ExternalApplicationHelper.startAsync(
CommandBuilder.of().add(ApplicationHelper.replaceFileArgument(format, "FILE", file.toString())), CommandBuilder.of().add(ExternalApplicationHelper.replaceFileArgument(format, "FILE", file.toString())));
true);
} }
@Override @Override
@ -200,33 +199,28 @@ public interface ExternalEditorType extends PrefsChoiceValue {
throw new IOException("Application " + applicationName + ".app not found"); throw new IOException("Application " + applicationName + ".app not found");
} }
ApplicationHelper.executeLocalApplication( ExternalApplicationHelper.startAsync(
CommandBuilder.of() CommandBuilder.of()
.add("open", "-a") .add("open", "-a")
.addFile(execFile.orElseThrow().toString()) .addFile(execFile.orElseThrow().toString())
.addFile(file.toString()), .addFile(file.toString()));
false);
} }
} }
class GenericPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType { class GenericPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType {
private final boolean detach; public GenericPathType(String id, String command, boolean explicityAsync) {
super(id, command, explicityAsync);
public GenericPathType(String id, String command, boolean detach) {
super(id, command);
this.detach = detach;
} }
@Override @Override
public void launch(Path file) throws Exception { public void launch(Path file) throws Exception {
ApplicationHelper.executeLocalApplication( var builder = CommandBuilder.of().addFile(executable).addFile(file.toString());
CommandBuilder.of().add(executable).addFile(file.toString()), detach); if (explicityAsync) {
} ExternalApplicationHelper.startAsync(builder);
} else {
@Override LocalShell.getShell().executeSimpleCommand(builder);
public boolean isSelectable() { }
return true;
} }
} }
@ -248,7 +242,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
public WindowsType(String id, String executable, boolean detach) { public WindowsType(String id, String executable, boolean detach) {
super(id, executable); super(id, executable);
this.detach = detach; this.detach = true;
} }
@Override @Override
@ -262,9 +256,12 @@ public interface ExternalEditorType extends PrefsChoiceValue {
} }
} }
Optional<Path> finalLocation = location; var builder = CommandBuilder.of().addFile(location.get().toString()).addFile(file.toString());
ApplicationHelper.executeLocalApplication( if (detach) {
CommandBuilder.of().addFile(finalLocation.get().toString()).addFile(file.toString()), detach); ExternalApplicationHelper.startAsync(builder);
} else {
LocalShell.getShell().executeSimpleCommand(builder);
}
} }
} }
} }

View file

@ -3,12 +3,10 @@ package io.xpipe.app.terminal;
import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.ExternalApplicationHelper;
import io.xpipe.app.prefs.ExternalApplicationType; import io.xpipe.app.prefs.ExternalApplicationType;
import io.xpipe.app.storage.DataStoreColor; import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.ApplicationHelper; import io.xpipe.app.util.*;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.MacOsPermissions;
import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.*; import io.xpipe.core.process.*;
import io.xpipe.core.store.FilePath; import io.xpipe.core.store.FilePath;
import lombok.Getter; import lombok.Getter;
@ -23,7 +21,7 @@ import java.util.function.Supplier;
public interface ExternalTerminalType extends PrefsChoiceValue { public interface ExternalTerminalType extends PrefsChoiceValue {
ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe") { ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -45,7 +43,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
} }
}; };
ExternalTerminalType POWERSHELL = new SimplePathType("app.powershell", "powershell") { ExternalTerminalType POWERSHELL = new SimplePathType("app.powershell", "powershell", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -73,14 +71,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
var base64 = Base64.getEncoder() var base64 = Base64.getEncoder()
.encodeToString(configuration .encodeToString(configuration
.getDialectLaunchCommand() .getDialectLaunchCommand()
.buildCommandBase(sc) .buildBase(sc)
.getBytes(StandardCharsets.UTF_16LE)); .getBytes(StandardCharsets.UTF_16LE));
return "\"" + base64 + "\""; return "\"" + base64 + "\"";
}); });
} }
}; };
ExternalTerminalType PWSH = new SimplePathType("app.pwsh", "pwsh") { ExternalTerminalType PWSH = new SimplePathType("app.pwsh", "pwsh", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -100,14 +98,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.add(sc -> { .add(sc -> {
// Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850 // Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850
var c = "$env:PSModulePath=\"\";" var c = "$env:PSModulePath=\"\";"
+ configuration.getDialectLaunchCommand().buildCommandBase(sc); + configuration.getDialectLaunchCommand().buildBase(sc);
var base64 = Base64.getEncoder().encodeToString(c.getBytes(StandardCharsets.UTF_16LE)); var base64 = Base64.getEncoder().encodeToString(c.getBytes(StandardCharsets.UTF_16LE));
return "\"" + base64 + "\""; return "\"" + base64 + "\"";
}); });
} }
}; };
ExternalTerminalType ALACRITTY_WINDOWS = new SimplePathType("app.alacritty", "alacritty") { ExternalTerminalType ALACRITTY_WINDOWS = new SimplePathType("app.alacritty", "alacritty", false) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -127,12 +125,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addQuoted("colors.primary.background='%s'" .addQuoted("colors.primary.background='%s'"
.formatted(configuration.getColor().toHexString())); .formatted(configuration.getColor().toHexString()));
} }
// Alacritty is bugged and will not accept arguments with spaces even if they are correctly passed/escaped
// So this will not work when the script file has spaces
return b.add("-t") return b.add("-t")
.addQuoted(configuration.getCleanTitle()) .addQuoted(configuration.getCleanTitle())
.add("-e") .add("-e")
.add("cmd") .add(configuration.getDialectLaunchCommand());
.add("/c")
.addQuoted(configuration.getScriptFile().toString().replaceAll(" ", "^$0"));
} }
}; };
ExternalTerminalType TABBY_WINDOWS = new WindowsType("app.tabby", "Tabby") { ExternalTerminalType TABBY_WINDOWS = new WindowsType("app.tabby", "Tabby") {
@ -145,11 +144,20 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override @Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception { protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
// Tabby has a very weird handling of output, even detaching with start does not prevent it from printing // Tabby has a very weird handling of output, even detaching with start does not prevent it from printing
if (configuration.getScriptDialect().equals(ShellDialects.CMD)) {
// It also freezes with any other input than .bat files, why?
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of()
.addFile(file.toString())
.add("run")
.addFile(configuration.getScriptFile())
.discardOutput());
}
LocalShell.getShell() LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of() .executeSimpleCommand(CommandBuilder.of()
.addFile(file.toString()) .addFile(file.toString())
.add("run") .add("run")
.addFile(configuration.getScriptFile()) .add(configuration.getDialectLaunchCommand())
.discardOutput()); .discardOutput());
} }
@ -183,9 +191,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override @Override
protected void execute(Path file, LaunchConfiguration configuration) throws Exception { protected void execute(Path file, LaunchConfiguration configuration) throws Exception {
ApplicationHelper.executeLocalApplication( LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().addFile(file.toString()).add("start").add(configuration.getDialectLaunchCommand()));
CommandBuilder.of().addFile(file.toString()).add("start").addFile(configuration.getScriptFile()),
true);
} }
@Override @Override
@ -199,7 +205,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return launcherDir.map(Path::of); return launcherDir.map(Path::of);
} }
}; };
ExternalTerminalType WEZ_LINUX = new SimplePathType("app.wezterm", "wezterm-gui") { ExternalTerminalType WEZ_LINUX = new SimplePathType("app.wezterm", "wezterm-gui", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -211,7 +217,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return CommandBuilder.of().add("start").addFile(configuration.getScriptFile()); return CommandBuilder.of().add("start").addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal") { ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -221,7 +227,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override @Override
public void launch(LaunchConfiguration configuration) throws Exception { public void launch(LaunchConfiguration configuration) throws Exception {
try (ShellControl pc = LocalShell.getShell()) { try (ShellControl pc = LocalShell.getShell()) {
ApplicationHelper.checkIsInPath( CommandSupport.isInPathOrThrow(
pc, executable, toTranslatedString().getValue(), null); pc, executable, toTranslatedString().getValue(), null);
var toExecute = CommandBuilder.of() var toExecute = CommandBuilder.of()
@ -229,15 +235,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addQuoted(configuration.getColoredTitle()) .addQuoted(configuration.getColoredTitle())
.add("--") .add("--")
.addFile(configuration.getScriptFile()) .addFile(configuration.getScriptFile())
.buildString(pc); // In order to fix this bug which also affects us:
// In order to fix this bug which also affects us: // https://askubuntu.com/questions/1148475/launching-gnome-terminal-from-vscode
// https://askubuntu.com/questions/1148475/launching-gnome-terminal-from-vscode .envrironment("GNOME_TERMINAL_SCREEN", sc -> "");
toExecute = "GNOME_TERMINAL_SCREEN=\"\" nohup " + toExecute + " </dev/null &>/dev/null & disown";
pc.executeSimpleCommand(toExecute);
} }
} }
}; };
ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole") { ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -257,7 +261,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return CommandBuilder.of().add("--new-tab", "-e").addFile(configuration.getScriptFile()); return CommandBuilder.of().add("--new-tab", "-e").addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal") { ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -273,7 +277,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()); .addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType ELEMENTARY = new SimplePathType("app.elementaryTerminal", "io.elementary.terminal") { ExternalTerminalType ELEMENTARY = new SimplePathType("app.elementaryTerminal", "io.elementary.terminal", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -285,7 +289,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return CommandBuilder.of().add("--new-tab").add("-e").addFile(configuration.getColoredTitle()); return CommandBuilder.of().add("--new-tab").add("-e").addFile(configuration.getColoredTitle());
} }
}; };
ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix") { ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -301,7 +305,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()); .addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator") { ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -318,7 +322,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.add("--new-tab"); .add("--new-tab");
} }
}; };
ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology") { ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -335,7 +339,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()); .addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term") { ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -351,7 +355,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()); .addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake") { ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -368,7 +372,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()); .addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType ALACRITTY_LINUX = new SimplePathType("app.alacritty", "alacritty") { ExternalTerminalType ALACRITTY_LINUX = new SimplePathType("app.alacritty", "alacritty", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -389,7 +393,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()); .addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda") { ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -401,7 +405,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return CommandBuilder.of().add("-c").addFile(configuration.getScriptFile()); return CommandBuilder.of().add("-c").addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType XTERM = new SimplePathType("app.xterm", "xterm") { ExternalTerminalType XTERM = new SimplePathType("app.xterm", "xterm", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -417,7 +421,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addFile(configuration.getScriptFile()); .addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType DEEPIN_TERMINAL = new SimplePathType("app.deepinTerminal", "deepin-terminal") { ExternalTerminalType DEEPIN_TERMINAL = new SimplePathType("app.deepinTerminal", "deepin-terminal", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -429,7 +433,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return CommandBuilder.of().add("-C").addFile(configuration.getScriptFile()); return CommandBuilder.of().add("-C").addFile(configuration.getScriptFile());
} }
}; };
ExternalTerminalType Q_TERMINAL = new SimplePathType("app.qTerminal", "qterminal") { ExternalTerminalType Q_TERMINAL = new SimplePathType("app.qTerminal", "qterminal", true) {
@Override @Override
public boolean supportsTabs() { public boolean supportsTabs() {
@ -576,10 +580,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.resolve("wezterm-gui") .resolve("wezterm-gui")
.toString()) .toString())
.add("start") .add("start")
.addFile(configuration.getScriptFile()) .add(configuration.getDialectLaunchCommand());
.buildString(LocalShell.getShell()); ExternalApplicationHelper.startAsync(c);
c = ApplicationHelper.createDetachCommand(LocalShell.getShell(), c);
LocalShell.getShell().executeSimpleCommand(c);
} }
}; };
ExternalTerminalType KITTY_MACOS = new MacOsType("app.kitty", "kitty") { ExternalTerminalType KITTY_MACOS = new MacOsType("app.kitty", "kitty") {
@ -768,7 +770,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD"; var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
try (var pc = LocalShell.getShell()) { try (var pc = LocalShell.getShell()) {
var toExecute = ApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile().toString()); var toExecute = ExternalApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile().toString());
// We can't be sure whether the command is blocking or not, so always make it not blocking // We can't be sure whether the command is blocking or not, so always make it not blocking
if (pc.getOsType().equals(OsType.WINDOWS)) { if (pc.getOsType().equals(OsType.WINDOWS)) {
toExecute = "start \"" + configuration.getCleanTitle() + "\" " + toExecute; toExecute = "start \"" + configuration.getCleanTitle() + "\" " + toExecute;
@ -795,21 +797,21 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Getter @Getter
abstract class PathCheckType extends ExternalApplicationType.PathApplication implements ExternalTerminalType { abstract class PathCheckType extends ExternalApplicationType.PathApplication implements ExternalTerminalType {
public PathCheckType(String id, String executable) { public PathCheckType(String id, String executable, boolean explicitAsync) {
super(id, executable); super(id, executable, explicitAsync);
} }
} }
@Getter @Getter
abstract class SimplePathType extends PathCheckType { abstract class SimplePathType extends PathCheckType {
public SimplePathType(String id, String executable) { public SimplePathType(String id, String executable, boolean explicitAsync) {
super(id, executable); super(id, executable, explicitAsync);
} }
@Override @Override
public void launch(LaunchConfiguration configuration) throws Exception { public void launch(LaunchConfiguration configuration) throws Exception {
var args = toCommand(configuration).buildCommandBase(LocalShell.getShell()); var args = toCommand(configuration);
launch(configuration.getColoredTitle(), args); launch(configuration.getColoredTitle(), args);
} }

View file

@ -1,7 +1,7 @@
package io.xpipe.app.terminal; package io.xpipe.app.terminal;
import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import io.xpipe.app.util.ApplicationHelper; import io.xpipe.app.util.CommandSupport;
import io.xpipe.app.util.LocalShell; import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.ShellTemp; import io.xpipe.app.util.ShellTemp;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
@ -44,8 +44,8 @@ public class KittyTerminalType {
private static boolean prepare() throws Exception { private static boolean prepare() throws Exception {
var socket = getSocket(); var socket = getSocket();
try (var sc = LocalShell.getShell().start()) { try (var sc = LocalShell.getShell().start()) {
ApplicationHelper.checkIsInPath(sc, "kitty", "Kitty", null); CommandSupport.isInPathOrThrow(sc, "kitty", "Kitty", null);
ApplicationHelper.checkIsInPath(sc, "socat", "socat", null); CommandSupport.isInPathOrThrow(sc, "socat", "socat", null);
if (sc.executeSimpleBooleanCommand("test -w " + sc.getShellDialect().fileArgument(socket))) { if (sc.executeSimpleBooleanCommand("test -w " + sc.getShellDialect().fileArgument(socket))) {
return false; return false;

View file

@ -11,7 +11,7 @@ import java.nio.file.Path;
public class WindowsTerminalType { public class WindowsTerminalType {
public static final ExternalTerminalType WINDOWS_TERMINAL = public static final ExternalTerminalType WINDOWS_TERMINAL =
new ExternalTerminalType.SimplePathType("app.windowsTerminal", "wt.exe") { new ExternalTerminalType.SimplePathType("app.windowsTerminal", "wt.exe", false) {
@Override @Override
protected CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception { protected CommandBuilder toCommand(LaunchConfiguration configuration) throws Exception {

View file

@ -1,86 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.util.FailableSupplier;
import java.io.IOException;
import java.util.Locale;
public class ApplicationHelper {
public static String replaceFileArgument(String format, String variable, String file) {
// Support for legacy variables that were not upper case
variable = variable.toUpperCase(Locale.ROOT);
format = format.replace("$" + variable.toLowerCase(Locale.ROOT), "$" + variable.toUpperCase(Locale.ROOT));
var fileString = file.contains(" ") ? "\"" + file + "\"" : file;
// Check if the variable is already quoted
return format.replace("\"$" + variable + "\"", fileString).replace("$" + variable, fileString);
}
public static void executeLocalApplication(CommandBuilder b, boolean detach) throws Exception {
try (var sc = LocalShell.getShell().start()) {
var cmd = detach ? createDetachCommand(sc, b.buildString(sc)) : b.buildString(sc);
TrackEvent.withDebug("Executing local application")
.tag("command", cmd)
.handle();
try (var c = sc.command(cmd).start()) {
c.discardOrThrow();
}
}
}
public static String createDetachCommand(ShellControl pc, String command) {
if (ShellDialects.isPowershell(pc)) {
var script = ScriptHelper.createExecScript(pc, command);
return String.format(
"Start-Process -FilePath %s -ArgumentList \"-NoProfile\", \"-File\", %s",
pc.getShellDialect().getExecutableName(),
pc.getShellDialect().fileArgument(script.toString()));
}
if (pc.getOsType().equals(OsType.WINDOWS)) {
return "start \"\" " + command;
} else {
return "nohup " + command + " </dev/null &>/dev/null & disown";
}
}
public static boolean isInPath(ShellControl processControl, String executable) throws Exception {
return processControl.executeSimpleBooleanCommand(
processControl.getShellDialect().getWhichCommand(executable));
}
public static boolean isInPathSilent(ShellControl processControl, String executable) {
try {
return processControl.executeSimpleBooleanCommand(
processControl.getShellDialect().getWhichCommand(executable));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
}
public static void checkIsInPath(
ShellControl processControl, String executable, String displayName, DataStoreEntry connection)
throws Exception {
if (!isInPath(processControl, executable)) {
throw ErrorEvent.expected(new IOException(displayName + " executable " + executable + " not found in PATH"
+ (connection != null ? " on system " + connection.getName() : "")));
}
}
public static void isSupported(FailableSupplier<Boolean> supplier, String displayName, DataStoreEntry connection)
throws Exception {
if (!supplier.get()) {
throw ErrorEvent.expected(new IOException(displayName + " is not supported"
+ (connection != null ? " on system " + connection.getName() : "")));
}
}
}

View file

@ -0,0 +1,42 @@
package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.FailableSupplier;
import java.io.IOException;
public class CommandSupport {
public static boolean isInPath(ShellControl processControl, String executable) throws Exception {
return processControl.executeSimpleBooleanCommand(
processControl.getShellDialect().getWhichCommand(executable));
}
public static boolean isInPathSilent(ShellControl processControl, String executable) {
try {
return processControl.executeSimpleBooleanCommand(
processControl.getShellDialect().getWhichCommand(executable));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
}
public static void isInPathOrThrow(
ShellControl processControl, String executable, String displayName, DataStoreEntry connection)
throws Exception {
if (!isInPath(processControl, executable)) {
throw ErrorEvent.expected(new IOException(displayName + " executable " + executable + " not found in PATH"
+ (connection != null ? " on system " + connection.getName() : "")));
}
}
public static void isSupported(FailableSupplier<Boolean> supplier, String displayName, DataStoreEntry connection)
throws Exception {
if (!supplier.get()) {
throw ErrorEvent.expected(new IOException(displayName + " is not supported"
+ (connection != null ? " on system " + connection.getName() : "")));
}
}
}

View file

@ -46,7 +46,7 @@ public class ShellControlCache {
public boolean isApplicationInPath(String app) { public boolean isApplicationInPath(String app) {
if (!installedApplications.containsKey(app)) { if (!installedApplications.containsKey(app)) {
try { try {
var b = ApplicationHelper.isInPath(shellControl, app); var b = CommandSupport.isInPath(shellControl, app);
installedApplications.put(app, b); installedApplications.put(app, b);
} catch (Exception e) { } catch (Exception e) {
installedApplications.put(app, false); installedApplications.put(app, false);

View file

@ -61,9 +61,7 @@ public class TerminalLauncher {
latch.await(); latch.await();
} catch (Exception ex) { } catch (Exception ex) {
throw ErrorEvent.expected(new IOException( throw ErrorEvent.expected(new IOException(
"Unable to launch terminal " + type.toTranslatedString().getValue() + ": " + ex.getMessage() "Unable to launch terminal " + type.toTranslatedString().getValue() + ": " + ex.getMessage(), ex));
+ ".\nMaybe try to use a different terminal in the settings.",
ex));
} }
} }
} }

View file

@ -210,7 +210,11 @@ public class CommandBuilder {
return this; return this;
} }
public String buildCommandBase(ShellControl sc) throws Exception { public String buildBase(ShellControl sc) throws Exception {
return String.join(" ", buildBaseParts(sc));
}
public List<String> buildBaseParts(ShellControl sc) throws Exception {
countDown = CountDown.of(); countDown = CountDown.of();
uuid = UUID.randomUUID(); uuid = UUID.randomUUID();
@ -227,11 +231,11 @@ public class CommandBuilder {
list.add(evaluate); list.add(evaluate);
} }
return String.join(" ", list); return list;
} }
public String buildString(ShellControl sc) throws Exception { public String buildFull(ShellControl sc) throws Exception {
var s = buildCommandBase(sc); var s = buildBase(sc);
LinkedHashMap<String, String> map = new LinkedHashMap<>(); LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (var e : environmentVariables.entrySet()) { for (var e : environmentVariables.entrySet()) {
var v = e.getValue().evaluate(sc); var v = e.getValue().evaluate(sc);
@ -239,7 +243,7 @@ public class CommandBuilder {
map.put(e.getKey(), v); map.put(e.getKey(), v);
} }
} }
return sc.getShellDialect().addInlineVariablesToCommand(map, s); return sc.getShellDialect().assembleCommand(s, map);
} }
public CommandControl build(ShellControl sc) { public CommandControl build(ShellControl sc) {

View file

@ -16,6 +16,8 @@ import java.util.stream.Stream;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface ShellDialect { public interface ShellDialect {
CommandBuilder launchAsnyc(CommandBuilder cmd);
default String getLicenseFeatureId() { default String getLicenseFeatureId() {
return null; return null;
} }
@ -80,7 +82,7 @@ public interface ShellDialect {
String getScriptFileEnding(); String getScriptFileEnding();
String addInlineVariablesToCommand(Map<String, String> variables, String command); String assembleCommand(String command, Map<String, String> variables);
Stream<FileSystem.FileEntry> listFiles(FileSystem fs, ShellControl control, String dir) throws Exception; Stream<FileSystem.FileEntry> listFiles(FileSystem fs, ShellControl control, String dir) throws Exception;

View file

@ -5,6 +5,7 @@ import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.BrowserActionFormatter; import io.xpipe.app.browser.action.BrowserActionFormatter;
import io.xpipe.app.browser.action.MultiExecuteAction; import io.xpipe.app.browser.action.MultiExecuteAction;
import io.xpipe.app.browser.icon.BrowserIconFileType; import io.xpipe.app.browser.icon.BrowserIconFileType;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import java.util.List; import java.util.List;
@ -27,8 +28,8 @@ public class JarAction extends MultiExecuteAction implements JavaAction, FileTyp
} }
@Override @Override
protected String createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) { protected CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) {
return "java -jar " + entry.getOptionallyQuotedFileName(); return CommandBuilder.of().add("java", "-jar").addFile(entry.getRawFileEntry().getPath());
} }
@Override @Override

View file

@ -3,6 +3,7 @@ package io.xpipe.ext.base.browser;
import io.xpipe.app.browser.BrowserEntry; import io.xpipe.app.browser.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.MultiExecuteAction; import io.xpipe.app.browser.action.MultiExecuteAction;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
@ -69,8 +70,7 @@ public class RunAction extends MultiExecuteAction {
return entries.stream().allMatch(entry -> isExecutable(entry.getRawFileEntry())); return entries.stream().allMatch(entry -> isExecutable(entry.getRawFileEntry()));
} }
@Override protected CommandBuilder createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) {
protected String createCommand(ShellControl sc, OpenFileSystemModel model, BrowserEntry entry) { return CommandBuilder.of().add(sc.getShellDialect().runScriptCommand(sc, entry.getRawFileEntry().getPath()));
return sc.getShellDialect().runScriptCommand(sc, entry.getRawFileEntry().getPath());
} }
} }