diff --git a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java index 1891a36ce..df92ca571 100644 --- a/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java +++ b/app/src/main/java/io/xpipe/app/comp/storage/store/StoreEntrySection.java @@ -83,10 +83,7 @@ public class StoreEntrySection implements StorageFilter.Filterable { return new HorizontalComp(topEntryList); } - var all = BindingsHelper.orderedContentBinding( - children, - Comparator.comparing(storeEntrySection -> - storeEntrySection.entry.lastAccessProperty().getValue())); + var all = children; var shown = BindingsHelper.filteredContentBinding( all, StoreViewState.get() diff --git a/app/src/main/java/io/xpipe/app/grid/AppInstaller.java b/app/src/main/java/io/xpipe/app/grid/AppInstaller.java index 0ae357efa..c37e63b9c 100644 --- a/app/src/main/java/io/xpipe/app/grid/AppInstaller.java +++ b/app/src/main/java/io/xpipe/app/grid/AppInstaller.java @@ -160,7 +160,7 @@ public class AppInstaller { "/qb")); var start = ShellTypes.getPlatformDefault().flatten(List.of("start", "\"\"", exec)); var command = installer + "\r\n" + start; - var script = ScriptHelper.createExecScript(shellProcessControl, command, true); + var script = ScriptHelper.createExecScript(shellProcessControl, command); shellProcessControl.executeSimpleCommand("start /min " + script); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java index 4973d6527..f65cdc12e 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java @@ -36,6 +36,21 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue { this.applicationName = applicationName; } + protected Optional getApplicationPath() { + try (ShellProcessControl pc = ShellStore.local().create().start()) { + try (var c = pc.command(String.format("osascript -e 'POSIX path of (path to application \"%s\")'", applicationName)).start()) { + var path = c.readOnlyStdout(); + if (!c.waitFor()) { + return Optional.empty(); + } + return Optional.of(Path.of(path)); + } + } catch (Exception e) { + ErrorEvent.fromThrowable(e).omit().handle(); + return Optional.empty(); + } + } + @Override public boolean isSelectable() { return OsType.getLocal().equals(OsType.MAC); @@ -43,14 +58,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue { @Override public boolean isAvailable() { - try { - return ShellStore.local() - .create() - .executeBooleanSimpleCommand(String.format("mdfind -name '%s.app'", applicationName)); - } catch (Exception e) { - ErrorEvent.fromThrowable(e).omit().handle(); - return false; - } + return getApplicationPath().isPresent(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index 5e0dad7b6..aa7c0d295 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -71,7 +71,7 @@ public interface ExternalEditorType extends PrefsChoiceValue { @Override public void launch(Path file) throws Exception { - ApplicationHelper.executeLocalApplication(List.of("open", "-a", applicationName, file.toString())); + ApplicationHelper.executeLocalApplication(List.of("open", "-a", getApplicationPath().orElseThrow().toString(), file.toString())); } } diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/CommandProcessControlImpl.java b/ext/proc/src/main/java/io/xpipe/ext/proc/CommandProcessControlImpl.java index c70b1f604..dcd24c2a8 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/CommandProcessControlImpl.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/CommandProcessControlImpl.java @@ -153,7 +153,7 @@ public class CommandProcessControlImpl extends ProcessControlImpl implements Com var baseCommand = command.apply(parent); var createExecScript = complex || baseCommand.contains("\n"); if (createExecScript) { - var script = ScriptHelper.createExecScript(parent, baseCommand, true); + var script = ScriptHelper.createExecScript(parent, baseCommand); baseCommand = "\"" + script + "\""; } diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/ExternalTerminalType.java b/ext/proc/src/main/java/io/xpipe/ext/proc/ExternalTerminalType.java index 7e78cb044..b829a079e 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/ExternalTerminalType.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/ExternalTerminalType.java @@ -131,6 +131,22 @@ public interface ExternalTerminalType extends PrefsChoiceValue { super("proc.macosTerminal", "Terminal"); } + @Override + public void launch(String name, String command) throws Exception { + try (ShellProcessControl pc = ShellStore.local().create().start()) { + var suffix = command.equals(pc.getShellType().getNormalOpenCommand()) ? "\"\"" : "\"" + command + "\""; + var cmd = "osascript -e 'tell app \"" + "Terminal" + "\" to do script " + suffix + "'"; + pc.executeSimpleCommand(cmd); + } + } + } + + static class CustomType extends ExternalApplicationType implements ExternalTerminalType { + + public CustomType() { + super("proc.custom"); + } + @Override public void launch(String name, String command) throws Exception { var custom = @@ -150,22 +166,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { pc.executeSimpleCommand(toExecute); } } - } - - static class CustomType extends ExternalApplicationType implements ExternalTerminalType { - - public CustomType() { - super("proc.custom"); - } - - @Override - public void launch(String name, String command) throws Exception { - try (ShellProcessControl pc = ShellStore.local().create().start()) { - var suffix = command.equals(pc.getShellType().getNormalOpenCommand()) ? "\"\"" : "\"" + command + "\""; - var cmd = "osascript -e 'tell app \"" + "Terminal" + "\" to do script " + suffix + "'"; - pc.executeSimpleCommand(cmd); - } - } @Override public boolean isSelectable() { @@ -194,9 +194,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue { activate set new_term to (create window with profile "Default" command "%s") end tell - end run EOF""", - command); + command.replaceAll("\"", "\\\\\"")); pc.executeSimpleCommand(cmd); } } @@ -225,7 +224,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { end tell EOF """, - command); + command.replaceAll("\"", "\\\\\"")); pc.executeSimpleCommand(cmd); } } diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/SshProcessControlImpl.java b/ext/proc/src/main/java/io/xpipe/ext/proc/SshProcessControlImpl.java index f569b3672..56206ba78 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/SshProcessControlImpl.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/SshProcessControlImpl.java @@ -71,7 +71,7 @@ public class SshProcessControlImpl extends ShellProcessControlImpl { String script = null; if (content != null) { try (var pc = start()) { - script = ScriptHelper.createExecScript(pc, content, false); + script = ScriptHelper.createExecScript(pc, content); } } diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/util/ElevationHelper.java b/ext/proc/src/main/java/io/xpipe/ext/proc/util/ElevationHelper.java index d6957bb06..a7b32fce0 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/util/ElevationHelper.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/util/ElevationHelper.java @@ -20,7 +20,7 @@ public class ElevationHelper { return c; } - var file = ScriptHelper.createAskPassScript(parent.getElevationPassword(), parent, parent.getShellType(), true); + var file = ScriptHelper.createAskPassScript(parent.getElevationPassword(), parent, parent.getShellType()); return "SUDO_ASKPASS=\"" + file + "\" sudo -k -p \"\" -A -- " + parent.getShellType().executeCommandWithShell(command); } @@ -42,7 +42,7 @@ public class ElevationHelper { scriptType = parent.getOsType().equals(OsType.WINDOWS) ? ShellTypes.CMD : ShellTypes.BASH; } - var file = ScriptHelper.createAskPassScript(parent.getElevationPassword(), parent, scriptType, true); + var file = ScriptHelper.createAskPassScript(parent.getElevationPassword(), parent, scriptType); var cmd = "SUDO_ASKPASS=\"" + file + "\" sudo -k -p \"\" -A -- " + command; return cmd; diff --git a/ext/proc/src/main/java/io/xpipe/ext/proc/util/SshToolHelper.java b/ext/proc/src/main/java/io/xpipe/ext/proc/util/SshToolHelper.java index 41a57e58c..7186cf314 100644 --- a/ext/proc/src/main/java/io/xpipe/ext/proc/util/SshToolHelper.java +++ b/ext/proc/src/main/java/io/xpipe/ext/proc/util/SshToolHelper.java @@ -48,7 +48,7 @@ public class SshToolHelper { scriptType = parent.getOsType().equals(OsType.WINDOWS) ? ShellTypes.CMD : ShellTypes.BASH; } - var file = ScriptHelper.createAskPassScript(passwordToUse, parent, scriptType, true); + var file = ScriptHelper.createAskPassScript(passwordToUse, parent, scriptType); var variables = Map.of( "DISPLAY", "localhost:0.0", "SSH_ASKPASS", file, diff --git a/extension/src/main/java/io/xpipe/extension/util/ScriptHelper.java b/extension/src/main/java/io/xpipe/extension/util/ScriptHelper.java index ef0cc99c7..4551fcd34 100644 --- a/extension/src/main/java/io/xpipe/extension/util/ScriptHelper.java +++ b/extension/src/main/java/io/xpipe/extension/util/ScriptHelper.java @@ -4,6 +4,7 @@ import io.xpipe.core.impl.FileNames; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellProcessControl; import io.xpipe.core.process.ShellType; +import io.xpipe.core.process.ShellTypes; import io.xpipe.core.store.ShellStore; import io.xpipe.core.util.SecretValue; import io.xpipe.extension.event.TrackEvent; @@ -23,10 +24,58 @@ public class ScriptHelper { @SneakyThrows public static String createLocalExecScript(String content) { try (var l = ShellStore.local().create().start()) { - return createExecScript(l, content, false); + return createExecScript(l, content); } } + private static final String ZSHI = + """ + #!/usr/bin/env zsh + + emulate -L zsh -o no_unset + + if (( ARGC == 0 )); then + print -ru2 -- 'Usage: zshi [zsh-flag]... + The same as plain `zsh [zsh-flag]...` except that an additional + gets executed after all standard Zsh startup files + have been sourced.' + return 1 + fi + + () { + local init=$1 + shift + local tmp + { + tmp=$(mktemp -d ${TMPDIR:-/tmp}/zsh.XXXXXXXXXX) || return + local rc + for rc in .zshenv .zprofile .zshrc .zlogin; do + >$tmp/$rc <<<'{ + ZDOTDIR="$_zshi_zdotdir" + if [[ -f "$ZDOTDIR/'$rc'" && -r "$ZDOTDIR/'$rc'" ]]; then + "builtin" "source" "--" "$ZDOTDIR/'$rc'" + fi + } always { + if [[ -o "no_rcs" || + -o "login" && "'$rc'" == ".zlogin" || + -o "no_login" && "'$rc'" == ".zshrc" || + -o "no_login" && -o "no_interactive" && "'$rc'" == ".zshenv" ]]; then + "builtin" "unset" "_zshi_rcs" "_zshi_zdotdir" + "builtin" "command" "rm" "-rf" "--" '${(q)tmp}' + "builtin" "eval" '${(q)init}' + else + _zshi_zdotdir=${ZDOTDIR:-~} + ZDOTDIR='${(q)tmp}' + fi + }' || return + done + _zshi_zdotdir=${ZDOTDIR:-~} ZDOTDIR=$tmp zsh "$@" + } always { + [[ -e $tmp ]] && rm -rf -- $tmp + } + } "$@" + """; + public static String constructOpenWithInitScriptCommand(ShellProcessControl processControl, List init, String toExecuteInShell) { ShellType t = processControl.getShellType(); if (init.size() == 0 && toExecuteInShell == null) { @@ -48,22 +97,29 @@ public class ScriptHelper { content += t.getExitCommand() + nl; } - var initFile = createExecScript(processControl, content, true); + var initFile = createExecScript(processControl, content); + + if (t.equals(ShellTypes.ZSH)) { + var zshiFile = createExecScript(processControl, ZSHI); + return t.getNormalOpenCommand() + " " + zshiFile + " " + initFile; + } + return t.getInitFileOpenCommand(initFile); } @SneakyThrows - public static String createExecScript(ShellProcessControl processControl, String content, boolean restart) { + public static String createExecScript(ShellProcessControl processControl, String content) { var fileName = "exec-" + getScriptId(); ShellType type = processControl.getShellType(); var temp = processControl.getTemporaryDirectory(); var file = FileNames.join(temp, fileName + "." + type.getScriptFileEnding()); - return createExecScript(processControl, file, content, restart); + return createExecScript(processControl, file, content); } @SneakyThrows private static String createExecScript( - ShellProcessControl processControl, String file, String content, boolean restart) { + ShellProcessControl processControl, String file, String content + ) { ShellType type = processControl.getShellType(); content = type.prepareScriptContent(content); @@ -81,10 +137,11 @@ public class ScriptHelper { @SneakyThrows public static String createAskPassScript( - SecretValue pass, ShellProcessControl parent, ShellType type, boolean restart) { + SecretValue pass, ShellProcessControl parent, ShellType type + ) { var content = type.getScriptEchoCommand(pass.getSecretValue()); var temp = parent.getTemporaryDirectory(); var file = FileNames.join(temp, "askpass-" + getScriptId() + "." + type.getScriptFileEnding()); - return createExecScript(parent, file, content, restart); + return createExecScript(parent, file, content); } }