Browser fixes

This commit is contained in:
crschnick 2024-11-06 01:35:38 +00:00
parent ddfa70d68b
commit ff36cfb12a
17 changed files with 163 additions and 134 deletions

View file

@ -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. 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. 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 If you are on Linux or macOS, you can easily accomplish that by running
```bash ```bash
curl -s "https://get.sdkman.io" | 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 ## 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. 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). 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, 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. 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. 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. is selected both for the project and for gradle itself.
## Contributing guide ## Contributing guide

View file

@ -10,7 +10,7 @@ import io.xpipe.app.storage.DataColor;
public final class BrowserHomeTabModel extends BrowserSessionTab { public final class BrowserHomeTabModel extends BrowserSessionTab {
public BrowserHomeTabModel(BrowserAbstractSessionModel<?> browserModel) { public BrowserHomeTabModel(BrowserAbstractSessionModel<?> browserModel) {
super(browserModel, AppI18n.get("overview"), null); super(browserModel, AppI18n.get("overview"));
} }
@Override @Override

View file

@ -11,6 +11,9 @@ import io.xpipe.app.terminal.TerminalDockComp;
import io.xpipe.app.terminal.TerminalDockModel; import io.xpipe.app.terminal.TerminalDockModel;
import io.xpipe.app.terminal.TerminalView; import io.xpipe.app.terminal.TerminalView;
import io.xpipe.app.terminal.TerminalViewInstance; 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.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
@ -23,9 +26,10 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
private final ObservableList<UUID> terminalRequests; private final ObservableList<UUID> terminalRequests;
private final TerminalDockModel dockModel = new TerminalDockModel(); private final TerminalDockModel dockModel = new TerminalDockModel();
private TerminalView.Listener listener; private TerminalView.Listener listener;
private ObservableBooleanValue viewActive;
public BrowserTerminalDockTabModel(BrowserAbstractSessionModel<?> browserModel, BrowserSessionTab origin, ObservableList<UUID> terminalRequests) { public BrowserTerminalDockTabModel(BrowserAbstractSessionModel<?> browserModel, BrowserSessionTab origin, ObservableList<UUID> terminalRequests) {
super(browserModel, AppI18n.get("terminal"), null); super(browserModel, AppI18n.get("terminal"));
this.origin = origin; this.origin = origin;
this.terminalRequests = terminalRequests; this.terminalRequests = terminalRequests;
} }
@ -84,11 +88,14 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
} }
}; };
TerminalView.get().addListener(listener); TerminalView.get().addListener(listener);
this.browserModel.getSelectedEntry().addListener((observable, oldValue, newValue) -> {
dockModel.toggleView(newValue == origin); viewActive = Bindings.createBooleanBinding(() -> {
}); return this.browserModel.getSelectedEntry().getValue() == origin && AppLayoutModel.get().getEntries().indexOf(AppLayoutModel.get().getSelected().getValue()) == 1;
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> { }, this.browserModel.getSelectedEntry(), AppLayoutModel.get().getSelected());
dockModel.toggleView(AppLayoutModel.get().getEntries().indexOf(newValue) == 1); viewActive.subscribe(aBoolean -> {
Platform.runLater(() -> {
dockModel.toggleView(aBoolean);
});
}); });
} }

View file

@ -24,11 +24,6 @@ public interface LeafAction extends BrowserAction {
default Button toButton(Region root, OpenFileSystemModel model, List<BrowserEntry> selected) { default Button toButton(Region root, OpenFileSystemModel model, List<BrowserEntry> selected) {
var b = new Button(); var b = new Button();
b.setOnAction(event -> { b.setOnAction(event -> {
// Only accept shortcut actions in the current tab
if (!model.equals(model.getBrowserModel().getSelectedEntry().getValue())) {
return;
}
ThreadHelper.runFailableAsync(() -> { ThreadHelper.runFailableAsync(() -> {
BooleanScope.executeExclusive(model.getBusy(), () -> { BooleanScope.executeExclusive(model.getBusy(), () -> {
if (model.getFileSystem() == null) { if (model.getFileSystem() == null) {

View file

@ -40,6 +40,7 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -274,8 +275,15 @@ public final class BrowserFileListComp extends SimpleComp {
} }
table.getSelectionModel().setCellSelectionEnabled(false); table.getSelectionModel().setCellSelectionEnabled(false);
var updateFromModel = new BooleanScope(new SimpleBooleanProperty());
table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super BrowserEntry>) c -> { table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<? super BrowserEntry>) c -> {
fileList.getSelection().setAll(c.getList()); if (updateFromModel.get()) {
return;
}
try (var ignored = updateFromModel) {
fileList.getSelection().setAll(c.getList());
}
}); });
fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> { fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
@ -284,16 +292,27 @@ public final class BrowserFileListComp extends SimpleComp {
} }
Platform.runLater(() -> { 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(); table.getSelectionModel().clearSelection();
return; return;
} }
var indices = c.getList().stream() if (indices.length == 1) {
.mapToInt(entry -> table.getItems().indexOf(entry)) table.getSelectionModel().clearAndSelect(indices[0]);
.toArray(); } else {
table.getSelectionModel() table.getSelectionModel().clearSelection();
.selectIndices(table.getItems().indexOf(c.getList().getFirst()), indices); table.getSelectionModel().selectIndices(indices[0], indices);
}
}); });
}); });
} }

View file

@ -9,9 +9,9 @@ import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.ext.ShellStore; import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.AnchorComp; 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.StackComp;
import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.fxcomps.util.BindingsHelper;
@ -25,8 +25,10 @@ import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -41,6 +43,66 @@ public class BrowserSessionComp extends SimpleComp {
@Override @Override
protected Region createSimple() { 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<CompStructure<VBox>> createLeftSide() {
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> { Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
if (!storeEntryWrapper.getEntry().getValidity().isUsable()) { if (!storeEntryWrapper.getEntry().getValidity().isUsable()) {
return false; return false;
@ -104,42 +166,30 @@ public class BrowserSessionComp extends SimpleComp {
localDownloadStage.maxHeight(200); localDownloadStage.maxHeight(200);
var vertical = var vertical =
new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left"); new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left");
return vertical;
}
var leftSplit = new SimpleDoubleProperty(); private StackComp createSplitStack(SimpleDoubleProperty rightSplit, BrowserSessionTabsComp tabs) {
var rightSplit = new SimpleDoubleProperty(); var cache = new HashMap<BrowserSessionTab, Region>();
var tabs = new BrowserSessionTabsComp(model, leftSplit, rightSplit); var pinnedStack = new StackComp(List.of());
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")));
pinnedStack.apply(struc -> { pinnedStack.apply(struc -> {
model.getEffectiveRightTab().subscribe( (newValue) -> { model.getEffectiveRightTab().subscribe( (newValue) -> {
PlatformThread.runLaterIfNeeded(() -> { PlatformThread.runLaterIfNeeded(() -> {
if (newValue != null) { var all = model.getAllTabs();
var r = newValue.comp().createRegion(); cache.keySet().removeIf(browserSessionTab -> !all.contains(browserSessionTab));
struc.get().getChildren().add(r);
} else { if (newValue == null) {
struc.get().getChildren().clear(); 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 -> { tabs.getHeaderHeight().subscribe(number -> {
AnchorPane.setTopAnchor(struc.get(), number.doubleValue()); AnchorPane.setTopAnchor(struc.get(), number.doubleValue());
}); });
}); }); return pinnedStack;
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();
} }
} }

View file

@ -24,9 +24,7 @@ import javafx.collections.ListChangeListener;
import javafx.collections.ObservableMap; import javafx.collections.ObservableMap;
import lombok.Getter; import lombok.Getter;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.Map;
@Getter @Getter
public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionTab> { public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSessionTab> {
@ -79,6 +77,16 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
}); });
} }
public Set<BrowserSessionTab> getAllTabs() {
var set = new HashSet<BrowserSessionTab>();
set.addAll(sessionEntries);
set.addAll(splits.values());
if (globalPinnedTab.getValue() != null) {
set.add(globalPinnedTab.getValue());
}
return set;
}
public void splitTab(BrowserSessionTab tab, BrowserSessionTab split) { public void splitTab(BrowserSessionTab tab, BrowserSessionTab split) {
if (splits.containsKey(tab)) { if (splits.containsKey(tab)) {
return; return;
@ -98,7 +106,6 @@ public class BrowserSessionModel extends BrowserAbstractSessionModel<BrowserSess
} }
} }
public void pinTab(BrowserSessionTab tab) { public void pinTab(BrowserSessionTab tab) {
if (tab.equals(globalPinnedTab.getValue())) { if (tab.equals(globalPinnedTab.getValue())) {
return; return;

View file

@ -18,13 +18,11 @@ public abstract class BrowserSessionTab {
protected final BooleanProperty busy = new SimpleBooleanProperty(); protected final BooleanProperty busy = new SimpleBooleanProperty();
protected final BrowserAbstractSessionModel<?> browserModel; protected final BrowserAbstractSessionModel<?> browserModel;
protected final String name; protected final String name;
protected final String tooltip;
protected final Property<BrowserSessionTab> splitTab = new SimpleObjectProperty<>(); protected final Property<BrowserSessionTab> splitTab = new SimpleObjectProperty<>();
public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, String name, String tooltip) { public BrowserSessionTab(BrowserAbstractSessionModel<?> browserModel, String name) {
this.browserModel = browserModel; this.browserModel = browserModel;
this.name = name; this.name = name;
this.tooltip = tooltip;
} }
public abstract Comp<?> comp(); public abstract Comp<?> comp();

View file

@ -426,28 +426,6 @@ public class BrowserSessionTabsComp extends SimpleComp {
}); });
tab.setContent(split); tab.setContent(split);
// var lastSplitRegion = new AtomicReference<Region>();
// 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(); var id = UUID.randomUUID().toString();
tab.setId(id); tab.setId(id);
@ -471,7 +449,6 @@ public class BrowserSessionTabsComp extends SimpleComp {
if (color != null) { if (color != null) {
c.getStyleClass().add(color.getId()); c.getStyleClass().add(color.getId());
} }
new TooltipAugment<>(new SimpleStringProperty(tabModel.getTooltip()), null).augment(c);
c.addEventHandler( c.addEventHandler(
DragEvent.DRAG_ENTERED, DragEvent.DRAG_ENTERED,
mouseEvent -> Platform.runLater( mouseEvent -> Platform.runLater(

View file

@ -16,8 +16,7 @@ public abstract class BrowserStoreSessionTab<T extends DataStore> extends Browse
public BrowserStoreSessionTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends T> entry) { public BrowserStoreSessionTab(BrowserAbstractSessionModel<?> browserModel, DataStoreEntryRef<? extends T> entry) {
super( super(
browserModel, browserModel,
DataStorage.get().getStoreEntryDisplayName(entry.get()), DataStorage.get().getStoreEntryDisplayName(entry.get()));
DataStorage.get().getStorePath(entry.getEntry()).toString());
this.entry = entry; this.entry = entry;
} }

View file

@ -69,18 +69,7 @@ public class StoreEntryListOverviewComp extends SimpleComp {
.getValue() .getValue()
.getRoot() .getRoot()
.equals(rootCategory); .equals(rootCategory);
// Sadly the all binding does not update when the individual visibility of entries changes return inRootCategory;
// 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;
}, },
StoreViewState.get().getActiveCategory()); StoreViewState.get().getActiveCategory());
var count = new CountComp<>(all.getList(), all.getList()); var count = new CountComp<>(all.getList(), all.getList());

View file

@ -60,7 +60,11 @@ public class TerminalDockComp extends SimpleComp {
model.onClose(); model.onClose();
}); });
s.focusedProperty().addListener((observable, oldValue, newValue) -> { s.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
model.onFocusGain();
} else {
model.onFocusLost();
}
}); });
stack.setOnMouseClicked(event -> { stack.setOnMouseClicked(event -> {
model.clickView(); model.clickView();

View file

@ -29,7 +29,9 @@ public class TerminalDockModel {
public synchronized void trackTerminal(TerminalViewInstance terminal) { public synchronized void trackTerminal(TerminalViewInstance terminal) {
terminalInstances.add(terminal); terminalInstances.add(terminal);
terminal.alwaysInFront(); terminal.alwaysInFront();
terminal.updatePosition(viewBounds); if (viewBounds != null) {
terminal.updatePosition(viewBounds);
}
} }
public synchronized void closeTerminal(TerminalViewInstance terminal) { public synchronized void closeTerminal(TerminalViewInstance terminal) {

View file

@ -30,7 +30,7 @@ public final class WindowsTerminalViewInstance extends TerminalViewInstance {
@Override @Override
public void alwaysInFront() { public void alwaysInFront() {
this.control.alwaysInFront(); this.control.alwaysInFront();
this.control.removeBorders(); // this.control.removeBorders();
} }
@Override @Override

View file

@ -13,6 +13,10 @@ public class BooleanScope implements AutoCloseable {
this.prop = prop; this.prop = prop;
} }
public boolean get() {
return prop.get();
}
public static <E extends Throwable> void executeExclusive(BooleanProperty prop, FailableRunnable<E> r) throws E { public static <E extends Throwable> void executeExclusive(BooleanProperty prop, FailableRunnable<E> r) throws E {
try (var ignored = new BooleanScope(prop).exclusive().start()) { try (var ignored = new BooleanScope(prop).exclusive().start()) {
r.run(); r.run();

View file

@ -99,12 +99,13 @@ public class DesktopShortcuts {
} }
public static Path create(String executable, String args, String name) throws Exception { public static Path create(String executable, String args, String name) throws Exception {
var compat = OsType.getLocal().makeFileSystemCompatible(name);
if (OsType.getLocal().equals(OsType.WINDOWS)) { if (OsType.getLocal().equals(OsType.WINDOWS)) {
return createWindowsShortcut(executable, args, name); return createWindowsShortcut(executable, args, compat);
} else if (OsType.getLocal().equals(OsType.LINUX)) { } else if (OsType.getLocal().equals(OsType.LINUX)) {
return createLinuxShortcut(executable, args, name); return createLinuxShortcut(executable, args, compat);
} else { } else {
return createMacOSShortcut(executable, args, name); return createMacOSShortcut(executable, args, compat);
} }
} }
} }

View file

@ -35,7 +35,13 @@ public class OpenTerminalAction implements LeafAction {
} }
if (AppPrefs.get().enableTerminalDocking().get() && model.getBrowserModel() instanceof BrowserSessionModel sessionModel) { 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()));
} }
} }