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/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); } }