From 08f9617bcd985fcb68c9215192f8c7c20e5b0aa7 Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 7 Jun 2025 04:00:29 +0000 Subject: [PATCH] Rework --- .../beacon/impl/TerminalWaitExchangeImpl.java | 2 +- .../file/BrowserFileSystemTabModel.java | 4 + .../io/xpipe/app/ext/WrapperFileSystem.java | 197 ++++++++++++++++++ .../app/prefs/ExternalApplicationType.java | 2 +- .../app/terminal/ExternalTerminalType.java | 40 +--- .../app/terminal/KonsoleTerminalType.java | 78 +++++++ .../xpipe/app/util/OptionsChoiceBuilder.java | 4 +- .../java/io/xpipe/app/vnc/TigerVncClient.java | 2 +- .../core/process/CommandConfiguration.java | 2 +- .../ext/base/action/RunScriptActionMenu.java | 5 - .../base/identity/SshIdentityStrategy.java | 14 +- .../ext/base/identity/UsernameStrategy.java | 4 +- .../ext/base/script/ScriptStoreSetup.java | 2 +- .../base/service/ServiceControlSession.java | 2 +- 14 files changed, 297 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/ext/WrapperFileSystem.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/KonsoleTerminalType.java diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java index 2765ed077..19c3e9af6 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java @@ -10,7 +10,7 @@ import com.sun.net.httpserver.HttpExchange; public class TerminalWaitExchangeImpl extends TerminalWaitExchange { @Override - public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException { + public Object handle(HttpExchange exchange, Request msg) throws BeaconServerException { TerminalLauncherManager.waitExchange(msg.getRequest()); return Response.builder().build(); } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java index dca231ed3..359e2d38c 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java @@ -8,6 +8,7 @@ import io.xpipe.app.comp.Comp; import io.xpipe.app.core.window.AppMainWindow; import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.ext.ShellStore; +import io.xpipe.app.ext.WrapperFileSystem; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStoreEntryRef; @@ -47,6 +48,7 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab progress = new SimpleObjectProperty<>(); private final ObservableList terminalRequests = FXCollections.observableArrayList(); private final BooleanProperty transferCancelled = new SimpleBooleanProperty(); + @NonNull private FileSystem fileSystem; private BrowserFileSystemSavedState savedState; private BrowserFileSystemCache cache; @@ -94,6 +96,8 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab originalFs.getShell().get().isRunning(true)); } fs.open(); // Listen to kill after init as the shell might get killed during init for certain reasons diff --git a/app/src/main/java/io/xpipe/app/ext/WrapperFileSystem.java b/app/src/main/java/io/xpipe/app/ext/WrapperFileSystem.java new file mode 100644 index 000000000..b9d194d1f --- /dev/null +++ b/app/src/main/java/io/xpipe/app/ext/WrapperFileSystem.java @@ -0,0 +1,197 @@ +package io.xpipe.app.ext; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.util.DocumentationLink; +import io.xpipe.core.process.CommandBuilder; +import io.xpipe.core.process.ShellControl; +import io.xpipe.core.process.ShellDialects; +import io.xpipe.core.store.FileEntry; +import io.xpipe.core.store.FilePath; +import io.xpipe.core.store.FileSystem; +import lombok.Getter; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +@Getter +public class WrapperFileSystem implements FileSystem { + + private final FileSystem fs; + private final Supplier check; + + public WrapperFileSystem(FileSystem fs, Supplier check) { + this.fs = fs; + this.check = check; + } + + @Override + public FileSystem createTransferOptimizedFileSystem() { + return fs; + } + + @Override + public long getFileSize(FilePath file) throws Exception { + if (!check.get()) { + return 0; + } + + return fs.getFileSize(file); + } + + @Override + public long getDirectorySize(FilePath file) throws Exception { + if (!check.get()) { + return 0; + } + + return fs.getDirectorySize(file); + } + + @Override + public Optional getShell() { + return fs.getShell(); + } + + @Override + public FileSystem open() throws Exception { + return fs.open(); + } + + @Override + public InputStream openInput(FilePath file) throws Exception { + if (!check.get()) { + return InputStream.nullInputStream(); + } + + return fs.openInput(file); + } + + @Override + public OutputStream openOutput(FilePath file, long totalBytes) throws Exception { + if (!check.get()) { + return OutputStream.nullOutputStream(); + } + + return fs.openOutput(file, totalBytes); + } + + @Override + public boolean fileExists(FilePath file) throws Exception { + if (!check.get()) { + return false; + } + + return fs.fileExists(file); + } + + @Override + public void delete(FilePath file) throws Exception { + if (!check.get()) { + return; + } + + fs.delete(file); + } + + @Override + public void copy(FilePath file, FilePath newFile) throws Exception { + if (!check.get()) { + return; + } + + fs.copy(file, newFile); + } + + @Override + public void move(FilePath file, FilePath newFile) throws Exception { + if (!check.get()) { + return; + } + + fs.move(file, newFile); + } + + @Override + public void mkdirs(FilePath file) throws Exception { + if (!check.get()) { + return; + } + + fs.mkdirs(file); + } + + @Override + public void touch(FilePath file) throws Exception { + if (!check.get()) { + return; + } + + fs.touch(file); + } + + @Override + public void symbolicLink(FilePath linkFile, FilePath targetFile) throws Exception { + if (!check.get()) { + return; + } + + fs.symbolicLink(linkFile, targetFile); + } + + @Override + public boolean directoryExists(FilePath file) throws Exception { + if (!check.get()) { + return false; + } + + return fs.directoryExists(file); + } + + @Override + public void directoryAccessible(FilePath file) throws Exception { + if (!check.get()) { + return; + } + + fs.directoryAccessible(file); + } + + @Override + public Optional getFileInfo(FilePath file) throws Exception { + if (!check.get()) { + return Optional.empty(); + } + + return fs.getFileInfo(file); + } + + @Override + public Stream listFiles(FilePath file) { + if (!check.get()) { + return Stream.empty(); + } + + return listFiles(file); + } + + @Override + public List listRoots() throws Exception { + if (!check.get()) { + return List.of(); + } + + return fs.listRoots(); + } + + @Override + public void close() throws IOException { + fs.close(); + } +} 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 3411f6a00..02f83b5a4 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java @@ -22,7 +22,7 @@ public interface ExternalApplicationType extends PrefsValue { public interface MacApplication extends ExternalApplicationType { - default CommandControl launchCommand(CommandBuilder builder, boolean args) throws Exception { + default CommandControl launchCommand(CommandBuilder builder, boolean args) { if (args) { builder.add(0, "--args"); } diff --git a/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java index ac095a064..2153f5a0e 100644 --- a/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java @@ -103,40 +103,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { ExternalTerminalType PTYXIS = new PtyxisTerminalType(); - ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole", true) { - - @Override - public String getWebsite() { - return "https://konsole.kde.org/download.html"; - } - - @Override - public TerminalOpenFormat getOpenFormat() { - return TerminalOpenFormat.NEW_WINDOW_OR_TABBED; - } - - @Override - public boolean isRecommended() { - // Tabs are only supported when single process option is enabled in konsole - return AppPrefs.get().terminalMultiplexer().getValue() != null; - } - - @Override - public boolean useColoredTitle() { - return false; - } - - @Override - protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) { - // Note for later: When debugging konsole launches, it will always open as a child process of - // IntelliJ/XPipe even though we try to detach it. - // This is not the case for production where it works as expected - return CommandBuilder.of() - .addIf(configuration.isPreferTabs(), "--new-tab") - .add("-e") - .addFile(configuration.getScriptFile()); - } - }; + ExternalTerminalType KONSOLE = new KonsoleTerminalType(); ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal", true) { @Override public String getWebsite() { @@ -461,11 +428,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return true; } - @Override - public boolean supportsUnicode() { - return true; - } - @Override protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) { return CommandBuilder.of() diff --git a/app/src/main/java/io/xpipe/app/terminal/KonsoleTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/KonsoleTerminalType.java new file mode 100644 index 000000000..988d09eff --- /dev/null +++ b/app/src/main/java/io/xpipe/app/terminal/KonsoleTerminalType.java @@ -0,0 +1,78 @@ +package io.xpipe.app.terminal; + +import io.xpipe.app.core.AppCache; +import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.core.process.CommandBuilder; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class KonsoleTerminalType extends ExternalTerminalType.SimplePathType { + + public KonsoleTerminalType() {super("app.konsole", "konsole", true);} + + @Override + public String getWebsite() { + return "https://konsole.kde.org/download.html"; + } + + @Override + public TerminalOpenFormat getOpenFormat() { + return TerminalOpenFormat.NEW_WINDOW_OR_TABBED; + } + + @Override + public boolean isRecommended() { + // Tabs are only supported when single process option is enabled in konsole + return AppPrefs.get().terminalMultiplexer().getValue() != null; + } + + @Override + public boolean useColoredTitle() { + return false; + } + + @Override + public void launch(TerminalLaunchConfiguration configuration) throws Exception { + configureSingleInstanceMode(); + super.launch(configuration); + } + + @Override + protected CommandBuilder toCommand(TerminalLaunchConfiguration configuration) { + // Note for later: When debugging konsole launches, it will always open as a child process of + // IntelliJ/XPipe even though we try to detach it. + // This is not the case for production where it works as expected + return CommandBuilder.of().addIf(configuration.isPreferTabs(), "--new-tab").add("-e").addFile(configuration.getScriptFile()); + } + + private synchronized void configureSingleInstanceMode() { + var cache = AppCache.getBoolean("konsoleInstanceOptionSet", false); + if (cache) { + return; + } + + var config = Path.of(System.getProperty("user.home"), ".config", "konsolerc"); + if (!Files.exists(config)) { + return; + } + + try { + var content = Files.readString(config); + var contains = content.contains("UseSingleInstance=true"); + if (!contains) { + var index = content.indexOf("[KonsoleWindow]"); + var augmented = index != -1 ? + content.replace("[KonsoleWindow]", "[KonsoleWindow]\nUseSingleInstance=true") : + content + "\n\n[KonsoleWindow]\nUseSingleInstance=true\n"; + Files.writeString(config, augmented); + } + + AppCache.update("konsoleInstanceOptionSet", true); + } catch (IOException e) { + ErrorEvent.fromThrowable(e).handle(); + } + } +} diff --git a/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java b/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java index da5394a24..aa5249c3e 100644 --- a/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java +++ b/app/src/main/java/io/xpipe/app/util/OptionsChoiceBuilder.java @@ -49,7 +49,7 @@ public class OptionsChoiceBuilder { cd.setAccessible(true); var defValue = cd.invoke(null); return defValue; - } catch (Exception e) { + } catch (Exception ignored) { } try { @@ -60,7 +60,7 @@ public class OptionsChoiceBuilder { m.setAccessible(true); var defValue = c.cast(m.invoke(b)); return defValue; - } catch (Exception e) { + } catch (Exception ignored) { } try { diff --git a/app/src/main/java/io/xpipe/app/vnc/TigerVncClient.java b/app/src/main/java/io/xpipe/app/vnc/TigerVncClient.java index 9cde5dd3f..1838528df 100644 --- a/app/src/main/java/io/xpipe/app/vnc/TigerVncClient.java +++ b/app/src/main/java/io/xpipe/app/vnc/TigerVncClient.java @@ -15,7 +15,7 @@ import java.util.Optional; public abstract class TigerVncClient implements ExternalVncClient { - protected CommandBuilder createBuilder(VncLaunchConfig configuration) throws Exception { + protected CommandBuilder createBuilder(VncLaunchConfig configuration) { var builder = CommandBuilder.of() .addQuoted(configuration.getHost() + ":" + configuration.getPort()); builder.addQuotedKeyValue("-ReconnectOnError", "off"); diff --git a/core/src/main/java/io/xpipe/core/process/CommandConfiguration.java b/core/src/main/java/io/xpipe/core/process/CommandConfiguration.java index 6d29eb889..013d814eb 100644 --- a/core/src/main/java/io/xpipe/core/process/CommandConfiguration.java +++ b/core/src/main/java/io/xpipe/core/process/CommandConfiguration.java @@ -4,7 +4,7 @@ public interface CommandConfiguration { String rawCommand(); - String fullCommand(ShellControl shellControl) throws Exception; + String fullCommand(ShellControl shellControl); CommandConfiguration withRawCommand(String newCommand); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java b/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java index 9edde69fd..313061b21 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/RunScriptActionMenu.java @@ -350,11 +350,6 @@ public class RunScriptActionMenu implements ActionProvider { return ShellStore.class; } - @Override - public Action createAction(DataStoreEntryRef store) { - return null; - } - @Override public List getChildren(List> batch) { if (hierarchy.isLeaf()) { diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategy.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategy.java index 3bdc8d1f7..a8308b36e 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategy.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategy.java @@ -59,7 +59,7 @@ public interface SshIdentityStrategy { public void buildCommand(CommandBuilder builder) {} @Override - public List configOptions(ShellControl parent) throws Exception { + public List configOptions(ShellControl parent) { // Don't use any agent keys to prevent too many authentication failures return List.of(new KeyValue("IdentitiesOnly", "yes"), new KeyValue("IdentityAgent", "none"), new KeyValue("IdentityFile", "none"), new KeyValue("PKCS11Provider", "none")); @@ -107,7 +107,7 @@ public interface SshIdentityStrategy { } @Override - public List configOptions(ShellControl parent) throws Exception { + public List configOptions(ShellControl parent) { return List.of(new KeyValue("IdentitiesOnly", "no"), new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"), new KeyValue("IdentityFile", "none"), new KeyValue("PKCS11Provider", "none")); } } @@ -150,7 +150,7 @@ public interface SshIdentityStrategy { } @Override - public List configOptions(ShellControl parent) throws Exception { + public List configOptions(ShellControl parent) { return List.of(new KeyValue("IdentitiesOnly", "no"), new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"), new KeyValue("IdentityFile", "none"), new KeyValue("PKCS11Provider", "none")); } @@ -203,7 +203,7 @@ public interface SshIdentityStrategy { public void buildCommand(CommandBuilder builder) {} @Override - public List configOptions(ShellControl parent) throws Exception { + public List configOptions(ShellControl parent) { return List.of(new KeyValue("IdentitiesOnly", "no"), new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"), new KeyValue("IdentityFile", "none"), new KeyValue("PKCS11Provider", "none")); } } @@ -239,7 +239,7 @@ public interface SshIdentityStrategy { } @Override - public List configOptions(ShellControl parent) throws Exception { + public List configOptions(ShellControl parent) { return List.of(new KeyValue("IdentitiesOnly", "no"), new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"), new KeyValue("IdentityFile", "none"), new KeyValue("PKCS11Provider", "none")); } } @@ -453,7 +453,7 @@ public interface SshIdentityStrategy { } @Override - public List configOptions(ShellControl parent) throws Exception { + public List configOptions(ShellControl parent) { return List.of(new KeyValue("IdentitiesOnly", "no"), new KeyValue("PKCS11Provider", file), new KeyValue("IdentityFile", "none"), new KeyValue("IdentityAgent", "none")); } } @@ -473,7 +473,7 @@ public interface SshIdentityStrategy { public void buildCommand(CommandBuilder builder) {} @Override - public List configOptions(ShellControl parent) throws Exception { + public List configOptions(ShellControl parent) { return List.of(new KeyValue("IdentitiesOnly", "no"), new KeyValue("ForwardAgent", forwardAgent ? "yes" : "no"), new KeyValue("IdentityFile", "none"), new KeyValue("PKCS11Provider", "none")); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/UsernameStrategy.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/UsernameStrategy.java index 5b7beea30..12ea5fd4b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/UsernameStrategy.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/UsernameStrategy.java @@ -25,7 +25,7 @@ public interface UsernameStrategy { } @Override - public String retrieveUsername() throws Exception { + public String retrieveUsername() { return null; } } @@ -51,7 +51,7 @@ public interface UsernameStrategy { } @Override - public String retrieveUsername() throws Exception { + public String retrieveUsername() { return getFixedUsername().orElseThrow(); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java index 0e0d3929d..ce9c71966 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java @@ -49,7 +49,7 @@ public class ScriptStoreSetup { initFlattened.forEach(s -> { pc.withInitSnippet(new ShellTerminalInitCommand() { @Override - public Optional terminalContent(ShellControl shellControl) throws Exception { + public Optional terminalContent(ShellControl shellControl) { return Optional.ofNullable(s.getStore().assembleScriptChain(shellControl)); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceControlSession.java b/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceControlSession.java index 8c9c3e638..2d6bd2b95 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceControlSession.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/service/ServiceControlSession.java @@ -49,7 +49,7 @@ public class ServiceControlSession extends Session { } @Override - public boolean checkAlive() throws Exception { + public boolean checkAlive() { return true; } }