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 f3818bc7c..32f41fbb4 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 @@ -44,6 +44,8 @@ import java.util.stream.Stream; @Getter public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab { + private static boolean wasTerminalDocked; + private final Property filter = new SimpleStringProperty(); private final BrowserFileListModel fileList; private final ReadOnlyObjectWrapper currentPath = new ReadOnlyObjectWrapper<>(); @@ -580,19 +582,24 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab struc.setGraphic(new FontIcon("mdi2h-hand-back-left-outline"))) .apply(struc -> struc.setWrapText(true)) diff --git a/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java b/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java index 9f40edcb0..16515d30e 100644 --- a/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java @@ -8,6 +8,11 @@ import javafx.beans.value.ObservableValue; public interface CountGroupStoreProvider extends DataStoreProvider { + @Override + default boolean includeInConnectionCount() { + return false; + } + @Override default ObservableValue informationString(StoreSection section) { return Bindings.createStringBinding( diff --git a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java index 1eaeef369..944f0cfd9 100644 --- a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java +++ b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java @@ -74,9 +74,11 @@ public class SentryErrorHandler implements ErrorHandler { ObjectInputStream ois = new ObjectInputStream(bais); var copy = (Throwable) ois.readObject(); - var msgField = Throwable.class.getDeclaredField("detailMessage"); - msgField.setAccessible(true); - msgField.set(copy, null); + if (!(copy instanceof NullPointerException) && !(copy instanceof IndexOutOfBoundsException)) { + var msgField = Throwable.class.getDeclaredField("detailMessage"); + msgField.setAccessible(true); + msgField.set(copy, null); + } if (copy instanceof FileSystemException) { var fileField = FileSystemException.class.getDeclaredField("file"); diff --git a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java index 5e7847176..e42691ff6 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -178,12 +178,6 @@ public class TerminalCategory extends AppPrefsCategory { return new OptionsBuilder() .addTitle("terminalConfiguration") .sub(terminalChoice(true)) - .hide(Bindings.createBooleanBinding( - () -> { - return !TerminalDockHubManager.isSupported(); - }, - prefs.terminalType, - prefs.terminalMultiplexer)) .sub(terminalPrompt()) .sub(terminalProxy()) .sub(terminalMultiplexer()) diff --git a/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java b/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java index 3d169b14b..e47a904de 100644 --- a/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java +++ b/app/src/main/java/io/xpipe/app/secret/SecretInPlaceStrategy.java @@ -61,7 +61,7 @@ public class SecretInPlaceStrategy implements SecretRetrievalStrategy { return new SecretQuery() { @Override public SecretQueryResult query(String prompt) { - return new SecretQueryResult(value, SecretQueryState.NORMAL); + return value != null ? new SecretQueryResult(value, SecretQueryState.NORMAL) : new SecretQueryResult(null, SecretQueryState.RETRIEVAL_FAILURE); } @Override diff --git a/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java b/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java index 9531c136a..b1494db03 100644 --- a/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java +++ b/app/src/main/java/io/xpipe/app/secret/SecretQueryResult.java @@ -9,4 +9,9 @@ public class SecretQueryResult { SecretValue secret; SecretQueryState state; + + public SecretQueryResult(SecretValue secret, SecretQueryState state) { + this.secret = secret; + this.state = state; + } } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java index dce7f986a..2211c87fd 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockBrowserComp.java @@ -5,6 +5,7 @@ import io.xpipe.app.comp.base.LoadingIconComp; import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.window.AppMainWindow; +import io.xpipe.app.platform.NativeWinWindowControl; import io.xpipe.app.platform.PlatformThread; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.GlobalTimer; @@ -153,14 +154,22 @@ public class TerminalDockBrowserComp extends SimpleRegionBuilder { return; } - var bounds = region.localToScreen(region.getBoundsInLocal()); + var bounds = region.localToScene(region.getBoundsInLocal()); var p = region.getPadding(); var sx = region.getScene().getWindow().getOutputScaleX(); var sy = region.getScene().getWindow().getOutputScaleY(); + + var scene = region.getScene(); + var windowRect = NativeWinWindowControl.MAIN_WINDOW.getBounds(); + var x = windowRect.getX() + ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx); + var y = windowRect.getY() + ((bounds.getMinY() + p.getTop() + scene.getY()) * sy); + var w = (bounds.getWidth() * sx) - p.getRight() - p.getLeft(); + var h = (bounds.getHeight() * sy) - p.getBottom() - p.getTop(); + model.resizeView( - (int) Math.ceil(bounds.getMinX() * sx + p.getLeft()), - (int) Math.ceil(bounds.getMinY() * sy + p.getTop()), - (int) Math.floor(bounds.getWidth() * sx - p.getRight() - p.getLeft()), - (int) Math.floor(bounds.getHeight() * sy - p.getBottom() - p.getTop())); + (int) Math.round(x), + (int) Math.round(y), + (int) Math.round(w), + (int) Math.round(h)); } } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java index 1ccaa6451..c207f8411 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubComp.java @@ -3,6 +3,8 @@ package io.xpipe.app.terminal; import io.xpipe.app.comp.SimpleRegionBuilder; import io.xpipe.app.core.window.AppMainWindow; +import io.xpipe.app.platform.NativeWinWindowControl; +import io.xpipe.app.util.GlobalTimer; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -13,6 +15,7 @@ import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.stage.WindowEvent; +import java.time.Duration; import java.util.concurrent.atomic.AtomicReference; public class TerminalDockHubComp extends SimpleRegionBuilder { @@ -46,6 +49,16 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { update(stack); } }; + var scale = new ChangeListener() { + @Override + public void changed(ObservableValue observable, Number oldValue, Number newValue) { + GlobalTimer.delay(() -> { + Platform.runLater(() -> { + update(stack); + }); + }, Duration.ofMillis(100)); + } + }; var update = new ChangeListener() { @Override public void changed(ObservableValue observable, Number oldValue, Number newValue) { @@ -87,6 +100,7 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { s.iconifiedProperty().removeListener(iconified); s.removeEventFilter(WindowEvent.WINDOW_SHOWN, show); s.removeEventFilter(WindowEvent.WINDOW_HIDING, hide); + s.outputScaleXProperty().addListener(scale); if (parent.get() != null) { parent.get().boundsInParentProperty().removeListener(bounds); parent.set(null); @@ -97,6 +111,7 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { s.widthProperty().addListener(update); s.heightProperty().addListener(update); s.iconifiedProperty().addListener(iconified); + s.outputScaleXProperty().removeListener(scale); s.addEventFilter(WindowEvent.WINDOW_SHOWN, show); s.addEventFilter(WindowEvent.WINDOW_HIDING, hide); // As in practice this node is wrapped in another stack pane @@ -113,14 +128,22 @@ public class TerminalDockHubComp extends SimpleRegionBuilder { return; } - var bounds = region.localToScreen(region.getBoundsInLocal()); + var bounds = region.localToScene(region.getBoundsInLocal()); var p = region.getPadding(); var sx = region.getScene().getWindow().getOutputScaleX(); var sy = region.getScene().getWindow().getOutputScaleY(); + + var scene = region.getScene(); + var windowRect = NativeWinWindowControl.MAIN_WINDOW.getBounds(); + var x = windowRect.getX() + ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx); + var y = windowRect.getY() + ((bounds.getMinY() + p.getTop() + scene.getY()) * sy); + var w = (bounds.getWidth() * sx) - p.getRight() - p.getLeft(); + var h = (bounds.getHeight() * sy) - p.getBottom() - p.getTop(); + model.resizeView( - (int) Math.round(bounds.getMinX() * sx + p.getLeft()), - (int) Math.round(bounds.getMinY() * sy + p.getTop()), - (int) Math.round(bounds.getWidth() * sx - p.getRight() - p.getLeft()), - (int) Math.round(bounds.getHeight() * sy - p.getBottom() - p.getTop())); + (int) Math.round(x), + (int) Math.round(y), + (int) Math.round(w), + (int) Math.round(h)); } } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java index fac77c18f..0aa21a355 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockHubManager.java @@ -14,13 +14,17 @@ import io.xpipe.app.util.Rect; import io.xpipe.core.OsType; import javafx.application.Platform; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ListChangeListener; import javafx.stage.Screen; import lombok.Getter; +import org.kordamp.ikonli.Ikon; +import org.kordamp.ikonli.Ikonli; import org.kordamp.ikonli.javafx.FontIcon; +import org.kordamp.ikonli.materialdesign2.MaterialDesignC; import java.time.Duration; import java.util.HashSet; @@ -59,12 +63,6 @@ public class TerminalDockHubManager { return false; } - var primaryScreen = Screen.getPrimary(); - var uniformScale = Screen.getScreens().stream().allMatch(screen -> screen.getOutputScaleX() == primaryScreen.getOutputScaleX()); - if (!uniformScale) { - return false; - } - return true; } @@ -107,10 +105,13 @@ public class TerminalDockHubManager { }); private final AppLayoutModel.QueueEntry queueEntry = new AppLayoutModel.QueueEntry( AppI18n.observable("toggleTerminalDock"), new LabelGraphic.NodeGraphic(() -> { - var fi = new FontIcon("mdi2c-console"); - fi.getStyleClass().add("graphic"); - fi.getStyleClass().add("terminal-dock-button"); - return fi; + var inner = new FontIcon(); + inner.iconCodeProperty().bind(PlatformThread.sync(Bindings.createObjectBinding(() -> { + return detached.get() || minimized.get() || !showing.get() ? MaterialDesignC.CONSOLE_LINE : MaterialDesignC.CONSOLE; + }, detached, minimized, showing))); + inner.getStyleClass().add("graphic"); + inner.getStyleClass().add("terminal-dock-button"); + return inner; }), () -> { refreshDockStatus(); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java index 27551dce0..549dfddd1 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockView.java @@ -47,7 +47,22 @@ public class TerminalDockView { public synchronized void updateCustomBounds() { terminalInstances.forEach(terminal -> { + var wasCustom = terminal.isCustomBounds(); terminal.updateBoundsState(); + + if (wasCustom && viewBounds != null) { + var currentBounds = terminal.getLastBounds(); + var targetBounds = windowBoundsFunction.apply(viewBounds); + var sum = Math.abs(targetBounds.getX() - currentBounds.getX()) + + Math.abs(targetBounds.getY() - currentBounds.getY()) + + Math.abs(targetBounds.getW() - currentBounds.getW()) + + Math.abs(targetBounds.getH() - currentBounds.getH()); + if (sum < 30) { + trackTerminal(terminal, true); + return; + } + } + if (terminal.isCustomBounds()) { terminal.disown(); } @@ -82,16 +97,16 @@ public class TerminalDockView { } public synchronized boolean closeOtherTerminals(UUID request) { - var sessions = TerminalView.get().getSessions(); - var tv = sessions.stream() - .filter(s -> request.equals(s.getRequest()) && s.getTerminal().isRunning()) - .map(s -> s.getTerminal().controllable()) - .flatMap(Optional::stream) + var others = terminalInstances.stream() + .filter(terminal -> terminal.getTerminalProcess().isAlive()) + .filter(terminal -> TerminalView.get().getSessions().stream() + .noneMatch(shellSession -> shellSession.getRequest().equals(request) && + shellSession.getTerminal().equals(terminal))) .toList(); - for (int i = 0; i < tv.size() - 1; i++) { - closeTerminal(tv.get(i)); + for (ControllableTerminalSession other : others) { + closeTerminal(other); } - return tv.size() > 1; + return others.size() > 0; } public synchronized void closeTerminal(ControllableTerminalSession terminal) { diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java index eb366ee8c..510cf1ab7 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java @@ -167,7 +167,7 @@ public class TerminalLauncher { var effectivePreferTabs = preferTabs && AppPrefs.get().preferTerminalTabs().get(); - var launchConfig = new TerminalLaunchConfiguration(color, adjustedTitle, cleanTitle, preferTabs, paneList); + var launchConfig = new TerminalLaunchConfiguration(color, adjustedTitle, cleanTitle, effectivePreferTabs, paneList); if (effectivePreferTabs && AppPrefs.get().enableConnectionHubTerminalDocking().get() diff --git a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java index 70a8eec9e..db5953c77 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/WezTerminalType.java @@ -115,7 +115,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal var base = getWeztermCommandBase(); var activeSocket = waitForInstanceStart(1); // Always start a new window for split panes as we can't find the pane index to start with - if (activeSocket.isEmpty() || configuration.getPanes().size() > 1) { + if (activeSocket.isEmpty() || configuration.getPanes().size() > 1 || !configuration.isPreferTabs()) { var gui = CommandBuilder.of().add(base.buildSimple().replace("wezterm.exe", "wezterm-gui.exe")); var command = CommandBuilder.of() .add(gui) diff --git a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java index 87a24bf66..ed98ae824 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java @@ -63,7 +63,7 @@ public class DesktopHelper { if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) { if (OsType.ofLocal() == OsType.LINUX) { - LocalExec.readStdoutIfPossible("xdg-open", file.toString()); + LocalExec.executeAsync("xdg-open", file.toString()); return; } } @@ -99,7 +99,7 @@ public class DesktopHelper { // Windows does not support Action.BROWSE_FILE_DIR if (OsType.ofLocal() == OsType.WINDOWS) { // Explorer does not support single quotes, so use normal quotes - LocalExec.readStdoutIfPossible("explorer", "/select,", "\"" + file + "\""); + LocalExec.executeAsync("explorer", "/select,", "\"" + file + "\""); return; } diff --git a/app/src/main/java/io/xpipe/app/util/DocumentationLink.java b/app/src/main/java/io/xpipe/app/util/DocumentationLink.java index ccd857c59..564dfa8d3 100644 --- a/app/src/main/java/io/xpipe/app/util/DocumentationLink.java +++ b/app/src/main/java/io/xpipe/app/util/DocumentationLink.java @@ -13,8 +13,8 @@ public enum DocumentationLink { LICENSE_ACTIVATION("troubleshoot/license-activation"), TLS_DECRYPTION("troubleshoot/license-activation#tls-decryption"), UPDATE_FAIL("troubleshoot/update-fail"), - PRIVACY("legal/privacy"), - EULA("legal/eula"), + PRIVACY("legal/privacy-policy"), + EULA("legal/end-user-license-agreement"), WEBTOP_UPDATE("guide/webtop#updating"), WEBTOP_TUN("guide/webtop#networking-tailscale-and-netbird"), SYNC("guide/sync"), diff --git a/dist/changelog/21.2.md b/dist/changelog/21.2.md new file mode 100644 index 000000000..754f9a598 --- /dev/null +++ b/dist/changelog/21.2.md @@ -0,0 +1,9 @@ +- Fix various SSH agents not working if the socket path contained spaces +- Fix terminal docking not working properly on multiple displays with different scale factor +- Fix terminal docking sometimes not automatically reattaching windows that were moved back to the dock +- The terminal dock indicator icon will now show a different icon to indicate when the terminal is detached +- Automatically delete corrupted git index file when detected +- Fix terminal test button not showing for the terminal selection in the settings menu +- Fix network switch ports being included in total connection count +- Fix various NullPointers +- Fix some broken documentation links \ No newline at end of file diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java index 794055cef..12312908d 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java @@ -26,6 +26,10 @@ public class RunTerminalScriptActionProvider implements ActionProvider { public void executeImpl() throws Exception { var sc = ref.getStore().getOrStartSession(); var script = scriptStore.getStore().assembleScriptChain(sc, false); + if (script == null) { + return; + } + TerminalLaunch.builder() .entry(ref.get()) .title(scriptStore.get().getName()) diff --git a/version b/version index 2d978e312..c598172d8 100644 --- a/version +++ b/version @@ -1 +1 @@ -21.1.1 +21.2