diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33648c595..57a77dedb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ You should therefore always check out the matching version tag for your local re You can find the available version tags at https://github.com/xpipe-io/xpipe/tags. So for example if you currently have XPipe `11.3` installed, you should run `git reset --hard 11.3` first to properly compile against it. -You need to have JDK for Java 21 installed to compile the project. +You need to have JDK for Java 22 installed to compile the project. If you are on Linux or macOS, you can easily accomplish that by running ```bash curl -s "https://get.sdkman.io" | bash @@ -57,7 +57,7 @@ to connect to that debugger through [AttachMe](https://plugins.jetbrains.com/plu ## Modularity and IDEs -All XPipe components target [Java 21](https://openjdk.java.net/projects/jdk/21/) and make full use of the Java Module System (JPMS). +All XPipe components target [Java 22](https://openjdk.java.net/projects/jdk/22/) and make full use of the Java Module System (JPMS). All components are modularized, including all their dependencies. In case a dependency is (sadly) not modularized yet, module information is manually added using [extra-java-module-info](https://github.com/gradlex-org/extra-java-module-info). Further, note that as this is a pretty complicated Java project that fully utilizes modularity, @@ -65,7 +65,7 @@ many IDEs still have problems building this project properly. For example, you can't build this project in eclipse or vscode as it will complain about missing modules. The tested and recommended IDE is IntelliJ. -When setting up the project in IntelliJ, make sure that the correct JDK (Java 21) +When setting up the project in IntelliJ, make sure that the correct JDK (Java 22) is selected both for the project and for gradle itself. ## Contributing guide diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserHomeTabModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserHomeTabModel.java index 8f0deb262..cd144a8d6 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserHomeTabModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserHomeTabModel.java @@ -10,7 +10,7 @@ import io.xpipe.app.storage.DataColor; public final class BrowserHomeTabModel extends BrowserSessionTab { public BrowserHomeTabModel(BrowserAbstractSessionModel browserModel) { - super(browserModel, AppI18n.get("overview"), null); + super(browserModel, AppI18n.get("overview")); } @Override diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTerminalDockTabModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserTerminalDockTabModel.java index d46a6a843..bd456a59f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTerminalDockTabModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTerminalDockTabModel.java @@ -11,6 +11,9 @@ import io.xpipe.app.terminal.TerminalDockComp; import io.xpipe.app.terminal.TerminalDockModel; import io.xpipe.app.terminal.TerminalView; import io.xpipe.app.terminal.TerminalViewInstance; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableBooleanValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -23,9 +26,10 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab { private final ObservableList terminalRequests; private final TerminalDockModel dockModel = new TerminalDockModel(); private TerminalView.Listener listener; + private ObservableBooleanValue viewActive; public BrowserTerminalDockTabModel(BrowserAbstractSessionModel browserModel, BrowserSessionTab origin, ObservableList terminalRequests) { - super(browserModel, AppI18n.get("terminal"), null); + super(browserModel, AppI18n.get("terminal")); this.origin = origin; this.terminalRequests = terminalRequests; } @@ -84,11 +88,14 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab { } }; TerminalView.get().addListener(listener); - this.browserModel.getSelectedEntry().addListener((observable, oldValue, newValue) -> { - dockModel.toggleView(newValue == origin); - }); - AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> { - dockModel.toggleView(AppLayoutModel.get().getEntries().indexOf(newValue) == 1); + + viewActive = Bindings.createBooleanBinding(() -> { + return this.browserModel.getSelectedEntry().getValue() == origin && AppLayoutModel.get().getEntries().indexOf(AppLayoutModel.get().getSelected().getValue()) == 1; + }, this.browserModel.getSelectedEntry(), AppLayoutModel.get().getSelected()); + viewActive.subscribe(aBoolean -> { + Platform.runLater(() -> { + dockModel.toggleView(aBoolean); + }); }); } diff --git a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java index 3faa151f6..cdd107920 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java @@ -24,11 +24,6 @@ public interface LeafAction extends BrowserAction { default Button toButton(Region root, OpenFileSystemModel model, List selected) { var b = new Button(); b.setOnAction(event -> { - // Only accept shortcut actions in the current tab - if (!model.equals(model.getBrowserModel().getSelectedEntry().getValue())) { - return; - } - ThreadHelper.runFailableAsync(() -> { BooleanScope.executeExclusive(model.getBusy(), () -> { if (model.getFileSystem() == null) { diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java index 83aa20f08..0a7173a47 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java @@ -40,6 +40,7 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; @@ -274,8 +275,15 @@ public final class BrowserFileListComp extends SimpleComp { } table.getSelectionModel().setCellSelectionEnabled(false); + var updateFromModel = new BooleanScope(new SimpleBooleanProperty()); table.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> { - fileList.getSelection().setAll(c.getList()); + if (updateFromModel.get()) { + return; + } + + try (var ignored = updateFromModel) { + fileList.getSelection().setAll(c.getList()); + } }); fileList.getSelection().addListener((ListChangeListener) c -> { @@ -284,16 +292,27 @@ public final class BrowserFileListComp extends SimpleComp { } Platform.runLater(() -> { - if (c.getList().isEmpty()) { + var tableIndices = table.getSelectionModel().getSelectedItems().stream() + .mapToInt(entry -> table.getItems().indexOf(entry)) + .toArray(); + var indices = c.getList().stream() + .mapToInt(entry -> table.getItems().indexOf(entry)) + .toArray(); + if (Arrays.equals(indices, tableIndices)) { + return; + } + + if (indices.length == 0) { table.getSelectionModel().clearSelection(); return; } - var indices = c.getList().stream() - .mapToInt(entry -> table.getItems().indexOf(entry)) - .toArray(); - table.getSelectionModel() - .selectIndices(table.getItems().indexOf(c.getList().getFirst()), indices); + if (indices.length == 1) { + table.getSelectionModel().clearAndSelect(indices[0]); + } else { + table.getSelectionModel().clearSelection(); + table.getSelectionModel().selectIndices(indices[0], indices); + } }); }); } diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java index b36e4273f..e763c075a 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java @@ -9,9 +9,9 @@ import io.xpipe.app.comp.store.StoreEntryWrapper; import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.ext.ShellStore; import io.xpipe.app.fxcomps.Comp; +import io.xpipe.app.fxcomps.CompStructure; import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.AnchorComp; -import io.xpipe.app.fxcomps.impl.LabelComp; import io.xpipe.app.fxcomps.impl.StackComp; import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.fxcomps.util.BindingsHelper; @@ -25,8 +25,10 @@ import javafx.beans.property.SimpleDoubleProperty; import javafx.geometry.Insets; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; +import java.util.HashMap; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Predicate; @@ -41,6 +43,66 @@ public class BrowserSessionComp extends SimpleComp { @Override protected Region createSimple() { + var vertical = createLeftSide(); + + var leftSplit = new SimpleDoubleProperty(); + var rightSplit = new SimpleDoubleProperty(); + var tabs = new BrowserSessionTabsComp(model, leftSplit, rightSplit); + tabs.apply(struc -> { + struc.get().setViewOrder(1); + struc.get().setPickOnBounds(false); + AnchorPane.setTopAnchor(struc.get(), 0.0); + AnchorPane.setBottomAnchor(struc.get(), 0.0); + AnchorPane.setLeftAnchor(struc.get(), 0.0); + AnchorPane.setRightAnchor(struc.get(), 0.0); + }); + + vertical.apply(struc -> { + struc.get() + .paddingProperty() + .bind(Bindings.createObjectBinding( + () -> new Insets(tabs.getHeaderHeight().get(), 0, 0, 0), tabs.getHeaderHeight())); + }); + var loadingIndicator = LoadingOverlayComp.noProgress(Comp.empty(), model.getBusy()) + .apply(struc -> { + AnchorPane.setTopAnchor(struc.get(), 3.0); + AnchorPane.setRightAnchor(struc.get(), 0.0); + }) + .styleClass("tab-loading-indicator"); + + var pinnedStack = createSplitStack(rightSplit, tabs); + + var loadingStack = new AnchorComp(List.of(tabs, pinnedStack, loadingIndicator)); + var splitPane = new LeftSplitPaneComp(vertical, loadingStack) + .withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()) + .withOnDividerChange(d -> { + AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d); + leftSplit.set(d); + }); + splitPane.apply(struc -> { + struc.getLeft().setMinWidth(200); + struc.getLeft().setMaxWidth(500); + struc.get().setPickOnBounds(false); + }); + + splitPane.apply(struc -> { + struc.get().skinProperty().subscribe(newValue -> { + if (newValue != null) { + Platform.runLater(() -> { + struc.get().getChildrenUnmodifiable().forEach(node -> { + node.setClip(null); + node.setPickOnBounds(false); + }); + struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1)); + }); + } + }); + }); + splitPane.styleClass("browser"); + return splitPane.createRegion(); + } + + private Comp> createLeftSide() { Predicate applicable = storeEntryWrapper -> { if (!storeEntryWrapper.getEntry().getValidity().isUsable()) { return false; @@ -104,42 +166,30 @@ public class BrowserSessionComp extends SimpleComp { localDownloadStage.maxHeight(200); var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left"); + return vertical; + } - var leftSplit = new SimpleDoubleProperty(); - var rightSplit = new SimpleDoubleProperty(); - var tabs = new BrowserSessionTabsComp(model, leftSplit, rightSplit); - tabs.apply(struc -> { - struc.get().setViewOrder(1); - struc.get().setPickOnBounds(false); - AnchorPane.setTopAnchor(struc.get(), 0.0); - AnchorPane.setBottomAnchor(struc.get(), 0.0); - AnchorPane.setLeftAnchor(struc.get(), 0.0); - AnchorPane.setRightAnchor(struc.get(), 0.0); - }); - - vertical.apply(struc -> { - struc.get() - .paddingProperty() - .bind(Bindings.createObjectBinding( - () -> new Insets(tabs.getHeaderHeight().get(), 0, 0, 0), tabs.getHeaderHeight())); - }); - var loadingIndicator = LoadingOverlayComp.noProgress(Comp.empty(), model.getBusy()) - .apply(struc -> { - AnchorPane.setTopAnchor(struc.get(), 3.0); - AnchorPane.setRightAnchor(struc.get(), 0.0); - }) - .styleClass("tab-loading-indicator"); - - var pinnedStack = new StackComp(List.of(new LabelComp("a"))); + private StackComp createSplitStack(SimpleDoubleProperty rightSplit, BrowserSessionTabsComp tabs) { + var cache = new HashMap(); + var pinnedStack = new StackComp(List.of()); pinnedStack.apply(struc -> { model.getEffectiveRightTab().subscribe( (newValue) -> { PlatformThread.runLaterIfNeeded(() -> { - if (newValue != null) { - var r = newValue.comp().createRegion(); - struc.get().getChildren().add(r); - } else { + var all = model.getAllTabs(); + cache.keySet().removeIf(browserSessionTab -> !all.contains(browserSessionTab)); + + if (newValue == null) { struc.get().getChildren().clear(); + return; } + + var cached = cache.containsKey(newValue); + if (!cached) { + cache.put(newValue, newValue.comp().createRegion()); + } + var r = cache.get(newValue); + struc.get().getChildren().clear(); + struc.get().getChildren().add(r); }); }); @@ -154,35 +204,6 @@ public class BrowserSessionComp extends SimpleComp { tabs.getHeaderHeight().subscribe(number -> { AnchorPane.setTopAnchor(struc.get(), number.doubleValue()); }); - }); - - var loadingStack = new AnchorComp(List.of(tabs, pinnedStack, loadingIndicator)); - var splitPane = new LeftSplitPaneComp(vertical, loadingStack) - .withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()) - .withOnDividerChange(d -> { - AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d); - leftSplit.set(d); - }); - splitPane.apply(struc -> { - struc.getLeft().setMinWidth(200); - struc.getLeft().setMaxWidth(500); - struc.get().setPickOnBounds(false); - }); - - splitPane.apply(struc -> { - struc.get().skinProperty().subscribe(newValue -> { - if (newValue != null) { - Platform.runLater(() -> { - struc.get().getChildrenUnmodifiable().forEach(node -> { - node.setClip(null); - node.setPickOnBounds(false); - }); - struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1)); - }); - } - }); - }); - splitPane.styleClass("browser"); - return splitPane.createRegion(); + }); return pinnedStack; } } diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java index 3f731c714..3fdb7dc14 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java @@ -24,9 +24,7 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableMap; import lombok.Getter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; +import java.util.*; @Getter public class BrowserSessionModel extends BrowserAbstractSessionModel { @@ -79,6 +77,16 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel getAllTabs() { + var set = new HashSet(); + set.addAll(sessionEntries); + set.addAll(splits.values()); + if (globalPinnedTab.getValue() != null) { + set.add(globalPinnedTab.getValue()); + } + return set; + } + public void splitTab(BrowserSessionTab tab, BrowserSessionTab split) { if (splits.containsKey(tab)) { return; @@ -98,7 +106,6 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel browserModel; protected final String name; - protected final String tooltip; protected final Property splitTab = new SimpleObjectProperty<>(); - public BrowserSessionTab(BrowserAbstractSessionModel browserModel, String name, String tooltip) { + public BrowserSessionTab(BrowserAbstractSessionModel browserModel, String name) { this.browserModel = browserModel; this.name = name; - this.tooltip = tooltip; } public abstract Comp comp(); diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java index 1f3dd77b0..c953b723a 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java @@ -426,28 +426,6 @@ public class BrowserSessionTabsComp extends SimpleComp { }); tab.setContent(split); -// var lastSplitRegion = new AtomicReference(); -// model.getGlobalPinnedTab().subscribe( (newValue) -> { -// PlatformThread.runLaterIfNeeded(() -> { -// if (newValue != null) { -// var r = newValue.comp().createRegion(); -// split.getItems().add(r); -// lastSplitRegion.set(r); -// } else if (split.getItems().size() > 1) { -// split.getItems().removeLast(); -// } -// }); -// }); -// model.getSelectedEntry().addListener((observable, oldValue, newValue) -> { -// PlatformThread.runLaterIfNeeded(() -> { -// if (newValue != null && newValue.equals(model.getGlobalPinnedTab().getValue()) && split.getItems().size() > 1) { -// split.getItems().remove(lastSplitRegion.get()); -// } else if (split.getItems().size() > 1 && !split.getItems().contains(lastSplitRegion.get())) { -// split.getItems().add(lastSplitRegion.get()); -// } -// }); -// }); - var id = UUID.randomUUID().toString(); tab.setId(id); @@ -471,7 +449,6 @@ public class BrowserSessionTabsComp extends SimpleComp { if (color != null) { c.getStyleClass().add(color.getId()); } - new TooltipAugment<>(new SimpleStringProperty(tabModel.getTooltip()), null).augment(c); c.addEventHandler( DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater( diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserStoreSessionTab.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserStoreSessionTab.java index 47efd378b..286e1ddb7 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserStoreSessionTab.java +++ b/app/src/main/java/io/xpipe/app/browser/session/BrowserStoreSessionTab.java @@ -16,8 +16,7 @@ public abstract class BrowserStoreSessionTab extends Browse public BrowserStoreSessionTab(BrowserAbstractSessionModel browserModel, DataStoreEntryRef entry) { super( browserModel, - DataStorage.get().getStoreEntryDisplayName(entry.get()), - DataStorage.get().getStorePath(entry.getEntry()).toString()); + DataStorage.get().getStoreEntryDisplayName(entry.get())); this.entry = entry; } diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListOverviewComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListOverviewComp.java index a4483e82d..0a3081edb 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListOverviewComp.java @@ -69,18 +69,7 @@ public class StoreEntryListOverviewComp extends SimpleComp { .getValue() .getRoot() .equals(rootCategory); - // Sadly the all binding does not update when the individual visibility of entries changes - // But it is good enough. - var showProvider = true; - try { - showProvider = storeEntryWrapper.getEntry().getProvider() == null - || storeEntryWrapper - .getEntry() - .getProvider() - .shouldShow(storeEntryWrapper); - } catch (Exception ignored) { - } - return inRootCategory && showProvider; + return inRootCategory; }, StoreViewState.get().getActiveCategory()); var count = new CountComp<>(all.getList(), all.getList()); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockComp.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockComp.java index 505577727..b5e35841e 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockComp.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockComp.java @@ -60,7 +60,11 @@ public class TerminalDockComp extends SimpleComp { model.onClose(); }); s.focusedProperty().addListener((observable, oldValue, newValue) -> { - + if (newValue) { + model.onFocusGain(); + } else { + model.onFocusLost(); + } }); stack.setOnMouseClicked(event -> { model.clickView(); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalDockModel.java b/app/src/main/java/io/xpipe/app/terminal/TerminalDockModel.java index 32856a656..2ad278af2 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalDockModel.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalDockModel.java @@ -29,7 +29,9 @@ public class TerminalDockModel { public synchronized void trackTerminal(TerminalViewInstance terminal) { terminalInstances.add(terminal); terminal.alwaysInFront(); - terminal.updatePosition(viewBounds); + if (viewBounds != null) { + terminal.updatePosition(viewBounds); + } } public synchronized void closeTerminal(TerminalViewInstance terminal) { diff --git a/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalViewInstance.java b/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalViewInstance.java index 1a64b9d6e..6f6da612d 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalViewInstance.java +++ b/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalViewInstance.java @@ -30,7 +30,7 @@ public final class WindowsTerminalViewInstance extends TerminalViewInstance { @Override public void alwaysInFront() { this.control.alwaysInFront(); - this.control.removeBorders(); + // this.control.removeBorders(); } @Override diff --git a/app/src/main/java/io/xpipe/app/util/BooleanScope.java b/app/src/main/java/io/xpipe/app/util/BooleanScope.java index 328a0ad3d..98c518af2 100644 --- a/app/src/main/java/io/xpipe/app/util/BooleanScope.java +++ b/app/src/main/java/io/xpipe/app/util/BooleanScope.java @@ -13,6 +13,10 @@ public class BooleanScope implements AutoCloseable { this.prop = prop; } + public boolean get() { + return prop.get(); + } + public static void executeExclusive(BooleanProperty prop, FailableRunnable r) throws E { try (var ignored = new BooleanScope(prop).exclusive().start()) { r.run(); diff --git a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java index a1cbcb3e3..ab39afb06 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java @@ -99,12 +99,13 @@ public class DesktopShortcuts { } public static Path create(String executable, String args, String name) throws Exception { + var compat = OsType.getLocal().makeFileSystemCompatible(name); if (OsType.getLocal().equals(OsType.WINDOWS)) { - return createWindowsShortcut(executable, args, name); + return createWindowsShortcut(executable, args, compat); } else if (OsType.getLocal().equals(OsType.LINUX)) { - return createLinuxShortcut(executable, args, name); + return createLinuxShortcut(executable, args, compat); } else { - return createMacOSShortcut(executable, args, name); + return createMacOSShortcut(executable, args, compat); } } } 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 3bdfc5528..060d9c331 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 @@ -35,7 +35,13 @@ public class OpenTerminalAction implements LeafAction { } if (AppPrefs.get().enableTerminalDocking().get() && model.getBrowserModel() instanceof BrowserSessionModel sessionModel) { - sessionModel.splitTab(model,new BrowserTerminalDockTabModel(sessionModel, model, model.getTerminalRequests())); + // Check if the right side is already occupied + var existingSplit = sessionModel.getSplits().get(model); + if (existingSplit != null && !(existingSplit instanceof BrowserTerminalDockTabModel)) { + return; + } + + sessionModel.splitTab(model, new BrowserTerminalDockTabModel(sessionModel, model, model.getTerminalRequests())); } }