diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java index e3e1779cb..1c83a1795 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -7,7 +7,6 @@ import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.TerminalHelper; import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.store.FileNames; import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellDialects; import io.xpipe.core.store.*; @@ -46,7 +45,7 @@ public final class OpenFileSystemModel { public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef entry) { this.browserModel = browserModel; this.entry = entry; - this.name = entry.get().getName(); + this.name = DataStorage.get().getStoreBrowserDisplayName(entry.get()); this.tooltip = DataStorage.get().getId(entry.getEntry()).toString(); this.inOverview.bind(Bindings.createBooleanBinding( () -> { @@ -147,32 +146,29 @@ public final class OpenFileSystemModel { } // Handle commands typed into navigation bar - if (allowCommands && evaluatedPath != null && !FileNames.isAbsolute(evaluatedPath) + if (allowCommands + && evaluatedPath != null + && !FileNames.isAbsolute(evaluatedPath) && fileSystem.getShell().isPresent()) { var directory = currentPath.get(); var name = adjustedPath + " - " + entry.get().getName(); ThreadHelper.runFailableAsync(() -> { - if (ShellDialects.ALL.stream() - .anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) { - var cmd = fileSystem + if (ShellDialects.ALL.stream().anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) { + TerminalHelper.open(entry.getEntry(), name, fileSystem .getShell() .get() .subShell(adjustedPath) .initWith(fileSystem - .getShell() - .get() - .getShellDialect() - .getCdCommand(currentPath.get())) - .prepareTerminalOpen(name); - TerminalHelper.open(adjustedPath, cmd); + .getShell() + .get() + .getShellDialect() + .getCdCommand(currentPath.get()))); } else { - var cmd = fileSystem + TerminalHelper.open(entry.getEntry(), name, fileSystem .getShell() .get() .command(adjustedPath) - .withWorkingDirectory(directory) - .prepareTerminalOpen(name); - TerminalHelper.open(adjustedPath, cmd); + .withWorkingDirectory(directory)); } }); return Optional.ofNullable(currentPath.get()); @@ -394,10 +390,9 @@ public final class OpenFileSystemModel { BooleanScope.execute(busy, () -> { if (entry.getStore() instanceof ShellStore s) { var connection = ((ConnectionFileSystem) fileSystem).getShellControl(); - var command = s.control() - .initWith(connection.getShellDialect().getCdCommand(directory)) - .prepareTerminalOpen(directory + " - " + entry.get().getName()); - TerminalHelper.open(directory, command); + var name = directory + " - " + entry.get().getName(); + TerminalHelper.open(entry.getEntry(), name, s.control() + .initWith(connection.getShellDialect().getCdCommand(directory))); } }); }); diff --git a/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java b/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java index 049eaf310..3b7d16ffc 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java @@ -5,7 +5,6 @@ import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.ScriptHelper; import io.xpipe.app.util.TerminalHelper; -import io.xpipe.core.store.FileNames; import io.xpipe.core.process.ShellControl; import org.apache.commons.io.FilenameUtils; @@ -29,15 +28,10 @@ public abstract class MultiExecuteAction implements BranchAction { model.withShell( pc -> { for (BrowserEntry entry : entries) { - var cmd = pc.command(createCommand(pc, model, entry)) + TerminalHelper.open(model.getEntry().getEntry(), FilenameUtils.getBaseName( + entry.getRawFileEntry().getPath()), pc.command(createCommand(pc, model, entry)) .withWorkingDirectory(model.getCurrentDirectory() - .getPath()) - .prepareTerminalOpen(FileNames.getFileName( - entry.getRawFileEntry().getPath())); - TerminalHelper.open( - FilenameUtils.getBaseName( - entry.getRawFileEntry().getPath()), - cmd); + .getPath())); } }, false); diff --git a/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java index a915e1f16..b72e5b116 100644 --- a/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java @@ -15,7 +15,7 @@ public class LaunchExchangeImpl extends LaunchExchange public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { var store = getStoreEntryById(msg.getId(), false); if (store.getStore() instanceof LaunchableStore s) { - var command = s.prepareLaunchCommand(store.getName()); + var command = s.prepareLaunchCommand().prepareTerminalOpen(store.getName()); return Response.builder().command(split(command)).build(); } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java index 2cd7882c5..37c365db9 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java @@ -2,16 +2,18 @@ package io.xpipe.app.prefs; import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.storage.DataStoreColor; import io.xpipe.app.util.ApplicationHelper; import io.xpipe.app.util.MacOsPermissions; import io.xpipe.app.util.ScriptHelper; import io.xpipe.app.util.WindowsRegistry; -import io.xpipe.core.store.FileNames; -import io.xpipe.core.store.LocalStore; import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellControl; +import io.xpipe.core.store.FileNames; +import io.xpipe.core.store.LocalStore; import lombok.Getter; +import lombok.Value; import java.io.IOException; import java.nio.file.Path; @@ -22,11 +24,21 @@ import java.util.stream.Stream; public interface ExternalTerminalType extends PrefsChoiceValue { - ExternalTerminalType CMD = new SimplePathType("app.cmd", "cmd.exe") { + ExternalTerminalType CMD = new PathType("app.cmd", "cmd.exe") { @Override - protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of().add("/C").addFile(file); + public void launch(LaunchConfiguration configuration) throws Exception { + LocalStore.getShell() + .executeSimpleCommand(CommandBuilder.of() + .add("start") + .addQuoted(configuration.getTitle()) + .add("cmd", "/c") + .addFile(configuration.getScriptFile())); + } + + @Override + public boolean supportsColoredTitle() { + return false; } @Override @@ -39,7 +51,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of().add("-ExecutionPolicy", "RemoteSigned", "-NoProfile", "-Command", "cmd", "/C", "'" + file + "'"); + return CommandBuilder.of() + .add("-ExecutionPolicy", "RemoteSigned", "-NoProfile", "-Command", "cmd", "/C", "'" + file + "'"); } @Override @@ -53,10 +66,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(String name, String file) { // Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850 - return CommandBuilder.of().add("-ExecutionPolicy", "RemoteSigned", "-NoProfile", "-Command", "cmd", "/C").add(sc -> { - var script = ScriptHelper.createLocalExecScript("set \"PSModulePath=\"\r\n\"" + file + "\"\npause"); - return "'" + script + "'"; - }); + return CommandBuilder.of() + .add("-ExecutionPolicy", "RemoteSigned", "-NoProfile", "-Command", "cmd", "/C") + .add(sc -> { + var script = + ScriptHelper.createLocalExecScript("set \"PSModulePath=\"\r\n\"" + file + "\"\npause"); + return "'" + script + "'"; + }); } @Override @@ -65,15 +81,19 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType WINDOWS_TERMINAL = new SimplePathType("app.windowsTerminal", "wt.exe") { + ExternalTerminalType WINDOWS_TERMINAL = new PathType("app.windowsTerminal", "wt.exe") { @Override - protected CommandBuilder toCommand(String name, String file) { + public void launch(LaunchConfiguration configuration) throws Exception { // A weird behavior in Windows Terminal causes the trailing // backslash of a filepath to escape the closing quote in the title argument // So just remove that slash - var fixedName = FileNames.removeTrailingSlash(name); - return CommandBuilder.of().add("-w", "1", "nt", "--title").addQuoted(fixedName).addFile(file); + var fixedName = FileNames.removeTrailingSlash(configuration.getTitle()); + LocalStore.getShell() + .executeSimpleCommand(CommandBuilder.of() + .add("wt", "-w", "1", "nt", "--title") + .addQuoted(fixedName) + .addFile(configuration.getScriptFile())); } @Override @@ -82,17 +102,28 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType ALACRITTY_WINDOWS = new SimplePathType("app.alacrittyWindows", "alacritty") { + ExternalTerminalType ALACRITTY_WINDOWS = new PathType("app.alacrittyWindows", "alacritty") { @Override - protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of() - .add("-t") - .addQuoted(name) - .add("-e") - .add("cmd") - .add("/c") - .addQuoted(file.replaceAll(" ", "^$0")); + public boolean supportsColoredTitle() { + return false; + } + + @Override + public void launch(LaunchConfiguration configuration) throws Exception { + var b = CommandBuilder.of().add("alacritty"); + if (configuration.getColor() != null) { + b.add("-o") + .addQuoted("colors.primary.background='%s'" + .formatted(configuration.getColor().toHexString())); + } + LocalStore.getShell() + .executeSimpleCommand(b.add("-t") + .addQuoted(configuration.getTitle()) + .add("-e") + .add("cmd") + .add("/c") + .addQuoted(configuration.getScriptFile().replaceAll(" ", "^$0"))); } @Override @@ -100,15 +131,15 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return OsType.getLocal().equals(OsType.WINDOWS); } }; - abstract class WindowsType extends ExternalApplicationType.WindowsType - implements ExternalTerminalType { + + abstract class WindowsType extends ExternalApplicationType.WindowsType implements ExternalTerminalType { public WindowsType(String id, String executable) { super(id, executable); } @Override - public void launch(String name, String file) throws Exception { + public void launch(LaunchConfiguration configuration) throws Exception { var location = determineFromPath(); if (location.isEmpty()) { location = determineInstallation(); @@ -118,19 +149,20 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } Optional finalLocation = location; - ApplicationHelper.executeLocalApplication( - sc -> createCommand(sc, name, finalLocation.get().toString(), file), false); + execute(location.get(), configuration); } - protected abstract String createCommand(ShellControl shellControl, String name, String path, String file); + protected abstract void execute(Path file, LaunchConfiguration configuration) throws Exception; } ExternalTerminalType TABBY_WINDOWS = new WindowsType("app.tabbyWindows", "Tabby") { @Override - protected String createCommand(ShellControl shellControl, String name, String path, String file) { - return shellControl.getShellDialect().fileArgument(path) + " run " - + shellControl.getShellDialect().fileArgument(file); + protected void execute(Path file, LaunchConfiguration configuration) throws Exception { + ApplicationHelper.executeLocalApplication( + shellControl -> shellControl.getShellDialect().fileArgument(file.toString()) + " run " + + shellControl.getShellDialect().fileArgument(configuration.getScriptFile()), + true); } @Override @@ -145,34 +177,39 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; -// ExternalTerminalType HYPER_WINDOWS = new WindowsFullPathType("app.hyperWindows") { -// -// @Override -// protected String createCommand(ShellControl shellControl, String name, String path, String file) { -// return shellControl.getShellDialect().fileArgument(path) + " " -// + shellControl.getShellDialect().fileArgument(file); -// } -// -// @Override -// protected Optional determinePath() { -// Optional launcherDir; -// launcherDir = WindowsRegistry.readString( -// WindowsRegistry.HKEY_CURRENT_USER, -// "SOFTWARE\\ac619139-e2f9-5cb9-915f-69b22e7bff50", -// "InstallLocation") -// .map(p -> p + "\\Hyper.exe"); -// return launcherDir.map(Path::of); -// } -// }; + // ExternalTerminalType HYPER_WINDOWS = new WindowsFullPathType("app.hyperWindows") { + // + // @Override + // protected String createCommand(ShellControl shellControl, String name, String path, String file) { + // return shellControl.getShellDialect().fileArgument(path) + " " + // + shellControl.getShellDialect().fileArgument(file); + // } + // + // @Override + // protected Optional determinePath() { + // Optional launcherDir; + // launcherDir = WindowsRegistry.readString( + // WindowsRegistry.HKEY_CURRENT_USER, + // "SOFTWARE\\ac619139-e2f9-5cb9-915f-69b22e7bff50", + // "InstallLocation") + // .map(p -> p + "\\Hyper.exe"); + // return launcherDir.map(Path::of); + // } + // }; - ExternalTerminalType GNOME_TERMINAL = new SimplePathType("app.gnomeTerminal", "gnome-terminal") { + ExternalTerminalType GNOME_TERMINAL = new PathType("app.gnomeTerminal", "gnome-terminal") { @Override - public void launch(String name, String file) throws Exception { + public void launch(LaunchConfiguration configuration) throws Exception { try (ShellControl pc = LocalStore.getShell()) { - ApplicationHelper.isInPath(pc, executable, toTranslatedString(), null); + ApplicationHelper.checkIsInPath(pc, executable, toTranslatedString(), null); - var toExecute = executable + " " + toCommand(name, file).build(pc); + var toExecute = CommandBuilder.of() + .add(executable, "-v", "--title") + .addQuoted(configuration.getTitle()) + .add("--") + .addFile(configuration.getScriptFile()) + .build(pc); // In order to fix this bug which also affects us: // https://askubuntu.com/questions/1148475/launching-gnome-terminal-from-vscode toExecute = "GNOME_TERMINAL_SCREEN=\"\" nohup " + toExecute + " /dev/null & disown"; @@ -180,11 +217,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } } - @Override - protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of().add("-v", "--title").addQuoted(name).add("--").addFile(file); - } - @Override public boolean isSelectable() { return OsType.getLocal().equals(OsType.LINUX); @@ -211,7 +243,11 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of().add("--tab", "--title").addQuoted(name).add("--command").addFile(file); + return CommandBuilder.of() + .add("--tab", "--title") + .addQuoted(name) + .add("--command") + .addFile(file); } @Override @@ -286,11 +322,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of() - .add("-T") - .addQuoted(name) - .add("-e") - .addQuoted(file); + return CommandBuilder.of().add("-T").addQuoted(name).add("-e").addQuoted(file); } @Override @@ -303,11 +335,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of() - .add("-r") - .addQuoted(name) - .add("-e") - .addQuoted(file); + return CommandBuilder.of().add("-r").addQuoted(name).add("-e").addQuoted(file); } @Override @@ -320,11 +348,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(String name, String file) { - return CommandBuilder.of() - .add("-t") - .addQuoted(name) - .add("-e") - .addQuoted(file); + return CommandBuilder.of().add("-t").addQuoted(name).add("-e").addQuoted(file); } @Override @@ -357,32 +381,30 @@ public interface ExternalTerminalType extends PrefsChoiceValue { ExternalTerminalType ALACRITTY_MACOS = new MacOsType("app.alacrittyMacOs", "Alacritty") { @Override - public void launch(String name, String file) throws Exception { - try (ShellControl pc = new LocalStore().control().start()) { - pc.command(String.format( - """ - %s/Contents/MacOS/alacritty -t "%s" -e %s - """, - getApplicationPath().orElseThrow(), - name, - pc.getShellDialect().fileArgument(file))) - .execute(); - } + public void launch(LaunchConfiguration configuration) throws Exception { + LocalStore.getShell() + .executeSimpleCommand(CommandBuilder.of() + .add("open", "-a") + .addQuoted("Alacritty.app") + .add("-n", "--args", "-t") + .addQuoted(configuration.getTitle()) + .add("-e") + .addFile(configuration.getScriptFile())); } }; ExternalTerminalType KITTY_MACOS = new MacOsType("app.kittyMacOs", "kitty") { @Override - public void launch(String name, String file) throws Exception { + public void launch(LaunchConfiguration configuration) throws Exception { try (ShellControl pc = new LocalStore().control().start()) { pc.command(String.format( """ %s/Contents/MacOS/kitty -T "%s" %s """, getApplicationPath().orElseThrow(), - name, - pc.getShellDialect().fileArgument(file))) + configuration.getTitle(), + pc.getShellDialect().fileArgument(configuration.getScriptFile()))) .execute(); } } @@ -426,7 +448,23 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .orElse(null); } - void launch(String name, String file) throws Exception; + @Value + static class LaunchConfiguration { + + DataStoreColor color; + String title; + String scriptFile; + + public String getColorPrefix() { + return color != null ? color.getEmoji() + " " : ""; + } + } + + default boolean supportsColoredTitle() { + return true; + } + + default void launch(LaunchConfiguration configuration) throws Exception {} class MacOsTerminalType extends ExternalApplicationType.MacApplication implements ExternalTerminalType { @@ -435,9 +473,9 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } @Override - public void launch(String name, String file) throws Exception { + public void launch(LaunchConfiguration configuration) throws Exception { try (ShellControl pc = LocalStore.getShell()) { - var suffix = "\"" + file.replaceAll("\"", "\\\\\"") + "\""; + var suffix = "\"" + configuration.getScriptFile().replaceAll("\"", "\\\\\"") + "\""; pc.osascriptCommand(String.format( """ activate application "Terminal" @@ -456,7 +494,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } @Override - public void launch(String name, String file) throws Exception { + public void launch(LaunchConfiguration configuration) throws Exception { var custom = AppPrefs.get().customTerminalCommand().getValue(); if (custom == null || custom.isBlank()) { throw ErrorEvent.unreportable(new IllegalStateException("No custom terminal command specified")); @@ -464,9 +502,10 @@ public interface ExternalTerminalType extends PrefsChoiceValue { var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD"; try (var pc = LocalStore.getShell()) { - var toExecute = ApplicationHelper.replaceFileArgument(format, "CMD", file); + var toExecute = ApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile()); + // We can't be sure whether the command is blocking or not, so always make it not blocking if (pc.getOsType().equals(OsType.WINDOWS)) { - toExecute = "start \"" + name + "\" " + toExecute; + toExecute = "start \"" + configuration.getTitle() + "\" " + toExecute; } else { toExecute = "nohup " + toExecute + " /dev/null & disown"; } @@ -492,7 +531,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } @Override - public void launch(String name, String file) throws Exception { + public void launch(LaunchConfiguration configuration) throws Exception { var app = this.getApplicationPath(); if (app.isEmpty()) { throw new IllegalStateException("iTerm installation not found"); @@ -515,7 +554,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { create window with default profile command "%s" end tell """, - a, a, a, a, file.replaceAll("\"", "\\\\\""))) + a, a, a, a, configuration.getScriptFile().replaceAll("\"", "\\\\\""))) .execute(); } } @@ -528,16 +567,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } @Override - public void launch(String name, String file) throws Exception { - try (ShellControl pc = new LocalStore().control().start()) { - pc.command(String.format( - """ - %s/Contents/MacOS/Tabby run %s - """, - getApplicationPath().orElseThrow(), - pc.getShellDialect().fileArgument(file))) - .execute(); - } + public void launch(LaunchConfiguration configuration) throws Exception { + LocalStore.getShell() + .executeSimpleCommand(CommandBuilder.of() + .add("open", "-a") + .addQuoted("Tabby.app") + .add("-n", "--args", "run") + .addFile(configuration.getScriptFile())); } } @@ -548,7 +584,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } @Override - public void launch(String name, String file) throws Exception { + public void launch(LaunchConfiguration configuration) throws Exception { if (!MacOsPermissions.waitForAccessibilityPermissions()) { return; } @@ -567,7 +603,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { end tell end tell """, - file.replaceAll("\"", "\\\\\""))) + configuration.getScriptFile().replaceAll("\"", "\\\\\""))) .execute(); } } @@ -581,29 +617,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } @Getter - abstract class SimplePathType extends ExternalApplicationType.PathApplication implements ExternalTerminalType { + abstract class PathType extends ExternalApplicationType.PathApplication implements ExternalTerminalType { - public SimplePathType(String id, String executable) { + public PathType(String id, String executable) { super(id, executable); } - @Override - public void launch(String name, String file) throws Exception { - try (ShellControl pc = LocalStore.getShell()) { - ApplicationHelper.isInPath(pc, executable, toTranslatedString(), null); - - var toExecute = executable + " " + toCommand(name, file).build(pc); - if (pc.getOsType().equals(OsType.WINDOWS)) { - toExecute = "start \"" + name + "\" " + toExecute; - } else { - toExecute = "nohup " + toExecute + " /dev/null & disown"; - } - pc.executeSimpleCommand(toExecute); - } - } - - protected abstract CommandBuilder toCommand(String name, String file); - public boolean isAvailable() { try (ShellControl pc = LocalStore.getShell()) { return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand(executable)); @@ -618,4 +637,31 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return true; } } + + @Getter + abstract class SimplePathType extends PathType { + + public SimplePathType(String id, String executable) { + super(id, executable); + } + + @Override + public void launch(LaunchConfiguration configuration) throws Exception { + try (ShellControl pc = LocalStore.getShell()) { + ApplicationHelper.checkIsInPath(pc, executable, toTranslatedString(), null); + + var toExecute = executable + " " + + toCommand(configuration.getTitle(), configuration.getScriptFile()) + .build(pc); + if (pc.getOsType().equals(OsType.WINDOWS)) { + toExecute = "start \"" + configuration.getTitle() + "\" " + toExecute; + } else { + toExecute = "nohup " + toExecute + " /dev/null & disown"; + } + pc.executeSimpleCommand(toExecute); + } + } + + protected abstract CommandBuilder toCommand(String name, String file); + } } diff --git a/app/src/main/java/io/xpipe/app/prefs/TroubleshootComp.java b/app/src/main/java/io/xpipe/app/prefs/TroubleshootComp.java index 03cbda8d9..0884871b2 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TroubleshootComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/TroubleshootComp.java @@ -69,7 +69,7 @@ public class TroubleshootComp extends Comp> { sc.executeSimpleCommand( ScriptHelper.createDetachCommand(sc, "\"" + script + "\"")); } else { - TerminalHelper.open("XPipe Debug", "\"" + script + "\""); + TerminalHelper.open("XPipe Debug", LocalStore.getShell().command("\"" + script + "\"")); } } }); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java b/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java index 833f4acb2..7005bb248 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java @@ -1,27 +1,41 @@ package io.xpipe.app.storage; import com.fasterxml.jackson.annotation.JsonProperty; +import javafx.scene.paint.Color; import lombok.Getter; @Getter public enum DataStoreColor { @JsonProperty("red") - RED("red", "\uD83D\uDFE5"), + RED("red", "\uD83D\uDFE5", Color.BLUE), @JsonProperty("green") - GREEN("green", "\uD83D\uDFE9"), + GREEN("green", "\uD83D\uDFE9", Color.BLUE), @JsonProperty("yellow") - YELLOW("yellow", "\uD83D\uDFE8"), + YELLOW("yellow", "\uD83D\uDFE8", Color.BLUE), @JsonProperty("blue") - BLUE("blue", "\uD83D\uDFE6"); + BLUE("blue", "\uD83D\uDD35", Color.BLUE); private final String id; private final String emoji; + private final Color terminalColor; - DataStoreColor(String id, String emoji) { + DataStoreColor(String id, String emoji, Color terminalColor) { this.id = id; this.emoji = emoji; + this.terminalColor = terminalColor; + } + + private String format(double val) { + String in = Integer.toHexString((int) Math.round(val * 255)); + return in.length() == 1 ? "0" + in : in; + } + + public String toHexString() { + var value = terminalColor; + return "#" + (format(value.getRed()) + format(value.getGreen()) + format(value.getBlue())) + .toUpperCase(); } } diff --git a/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java b/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java index 579fe85c1..40236a4c7 100644 --- a/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java +++ b/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java @@ -40,7 +40,7 @@ public class ApplicationHelper { processControl.getShellDialect().getWhichCommand(executable)); } - public static void isInPath( + public static void checkIsInPath( ShellControl processControl, String executable, String displayName, DataStoreEntry connection) throws Exception { if (!isInPath(processControl, executable)) { diff --git a/app/src/main/java/io/xpipe/app/util/TerminalHelper.java b/app/src/main/java/io/xpipe/app/util/TerminalHelper.java index 58277cd36..8f5ae7dc3 100644 --- a/app/src/main/java/io/xpipe/app/util/TerminalHelper.java +++ b/app/src/main/java/io/xpipe/app/util/TerminalHelper.java @@ -3,27 +3,33 @@ package io.xpipe.app.util; import io.xpipe.app.core.AppI18n; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.prefs.AppPrefs; -import io.xpipe.core.process.CommandControl; +import io.xpipe.app.prefs.ExternalTerminalType; +import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.core.process.ProcessControl; import java.io.IOException; public class TerminalHelper { - public static void open(String title, CommandControl cc) throws Exception { + public static void open(String title, ProcessControl cc) throws Exception { var command = cc.prepareTerminalOpen(title); - open(title, command); + open(null, title, cc); } - public static void open(String title, String command) throws Exception { + public static void open(DataStoreEntry entry, String title, ProcessControl cc) throws Exception { var type = AppPrefs.get().terminalType().getValue(); if (type == null) { throw ErrorEvent.unreportable(new IllegalStateException(AppI18n.get("noTerminalSet"))); } - command = ScriptHelper.createLocalExecScript(command); - + var prefix = entry != null && entry.getColor() != null && type.supportsColoredTitle() + ? entry.getColor().getEmoji() + " " + : ""; + var fixedTitle = prefix + (title != null ? title : entry != null ? entry.getName() : "?"); + var file = ScriptHelper.createLocalExecScript(cc.prepareTerminalOpen(fixedTitle)); + var config = new ExternalTerminalType.LaunchConfiguration(entry != null ? entry.getColor() : null, title, file); try { - type.launch(title, command); + type.launch(config); } catch (Exception ex) { throw ErrorEvent.unreportable(new IOException( "Unable to launch terminal " + type.toTranslatedString() + ": " + ex.getMessage() diff --git a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css index 6c874874a..0d19fd1c8 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/header-bars.css @@ -13,6 +13,10 @@ -fx-spacing: 0.8em; } +.store-header-bar > .top { + -fx-font-weight: BOLD; +} + .root.nord .store-header-bar { -fx-background-radius: 0; -fx-border-radius: 0; diff --git a/core/src/main/java/io/xpipe/core/store/LaunchableStore.java b/core/src/main/java/io/xpipe/core/store/LaunchableStore.java index 539ba969c..234429453 100644 --- a/core/src/main/java/io/xpipe/core/store/LaunchableStore.java +++ b/core/src/main/java/io/xpipe/core/store/LaunchableStore.java @@ -1,10 +1,12 @@ package io.xpipe.core.store; +import io.xpipe.core.process.ProcessControl; + public interface LaunchableStore extends DataStore { default boolean canLaunch() { return true; } - String prepareLaunchCommand(String displayName) throws Exception; + ProcessControl prepareLaunchCommand() throws Exception; } diff --git a/core/src/main/java/io/xpipe/core/store/ShellStore.java b/core/src/main/java/io/xpipe/core/store/ShellStore.java index dd4503811..921793b7b 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellStore.java +++ b/core/src/main/java/io/xpipe/core/store/ShellStore.java @@ -1,6 +1,7 @@ package io.xpipe.core.store; import io.xpipe.core.process.OsType; +import io.xpipe.core.process.ProcessControl; import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellDialect; @@ -18,8 +19,8 @@ public interface ShellStore extends DataStore, InternalCacheDataStore, Launchabl } @Override - default String prepareLaunchCommand(String displayName) throws Exception { - return control().prepareTerminalOpen(displayName); + default ProcessControl prepareLaunchCommand() throws Exception { + return control(); } default ShellDialect getShellType() { diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java index 9fd1a81c4..3696f09c7 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java @@ -27,18 +27,17 @@ public class LaunchAction implements ActionProvider { public void execute() throws Exception { var storeName = entry.getName(); if (entry.getStore() instanceof ShellStore s) { - String command = ScriptStore.controlWithDefaultScripts(s.control()).prepareTerminalOpen(storeName); - TerminalHelper.open(storeName, command); + TerminalHelper.open(entry, storeName, ScriptStore.controlWithDefaultScripts(s.control())); return; } if (entry.getStore() instanceof LaunchableStore s) { - String command = s.prepareLaunchCommand(storeName); + var command = s.prepareLaunchCommand(); if (command == null) { return; } - TerminalHelper.open(storeName, command); + TerminalHelper.open(entry, storeName, command); } } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java index 3e2c7d0f5..146588325 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java @@ -48,7 +48,7 @@ public class XPipeUrlAction implements ActionProvider { public void execute() throws Exception { var storeName = entry.getName(); if (entry.getStore() instanceof LaunchableStore s) { - String command = s.prepareLaunchCommand(storeName); + var command = s.prepareLaunchCommand(); if (command == null) { return; } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java index 432c1ca53..71ee2b0e4 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java @@ -25,7 +25,7 @@ public class OpenTerminalAction implements LeafAction { if (model.getInOverview().get()) { TerminalHelper.open( model.getName(), - model.getFileSystem().getShell().orElseThrow().prepareTerminalOpen(model.getName())); + model.getFileSystem().getShell().orElseThrow()); return; }