diff --git a/.gitignore b/.gitignore index 75c56a761..694fd5428 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ dev.properties extensions.txt dev_storage local/ +local*/ local_*/ .vs .vscode diff --git a/README.md b/README.md index 1ef525db2..b07351320 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,8 @@ It currently supports: - [Kubernetes](https://kubernetes.io/) clusters, pods, and containers - [Windows Subsystem for Linux](https://ubuntu.com/wsl), [Cygwin](https://www.cygwin.com/), and [MSYS2](https://www.msys2.org/) instances - [Powershell Remote Sessions](https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.3) -- [Teleport tsh connections](https://goteleport.com/) -- VNC connections -- Any other custom remote connection methods that work through the command-line +- [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh) and [Teleport](https://goteleport.com/) connections +- RDP and VNC connections ## Connection hub diff --git a/app/build.gradle b/app/build.gradle index 6aa569d65..301049013 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,9 +48,10 @@ dependencies { api 'com.vladsch.flexmark:flexmark-ext-yaml-front-matter:0.64.8' api 'com.vladsch.flexmark:flexmark-ext-toc:0.64.8' + api("com.github.weisj:jsvg:1.7.0") api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar") api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar") - api 'org.bouncycastle:bcprov-jdk18on:1.79' + api 'org.bouncycastle:bcprov-jdk18on:1.80' api 'info.picocli:picocli:4.7.6' api ('org.kohsuke:github-api:1.326') { exclude group: 'org.apache.commons', module: 'commons-lang3' @@ -108,6 +109,8 @@ run { def exts = files(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0].outputs.files.singleFile).toList()); classpath += exts + + dependsOn(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0]).toList()) } task runAttachedDebugger(type: JavaExec) { @@ -118,7 +121,7 @@ task runAttachedDebugger(type: JavaExec) { modularity.inferModulePath = true jvmArgs += jvmRunArgs jvmArgs += List.of( - "-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.4.jar=port:7857,host:localhost".toString(), + "-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.9.jar=port:7857,host:localhost".toString(), "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0" ) jvmArgs += ['-XX:+EnableDynamicAgentLoading'] @@ -126,6 +129,8 @@ task runAttachedDebugger(type: JavaExec) { def exts = files(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0].outputs.files.singleFile).toList()); classpath += exts + dependsOn(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0]).toList()) + } processResources { diff --git a/app/src/main/java/io/xpipe/app/Main.java b/app/src/main/java/io/xpipe/app/Main.java index 1f87356fb..0943ff784 100644 --- a/app/src/main/java/io/xpipe/app/Main.java +++ b/app/src/main/java/io/xpipe/app/Main.java @@ -19,7 +19,7 @@ public class Main { """ The daemon executable xpiped does not accept any command-line arguments. - For a reference on what you can do from the CLI, take a look at the xpipe CLI executable instead. + For a reference on how to use xpipe from the command-line, take a look at https://docs.xpipe.io/cli. """); return; } diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/CategoryAddExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/CategoryAddExchangeImpl.java index 1f9b90954..7437ea490 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/CategoryAddExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/CategoryAddExchangeImpl.java @@ -15,9 +15,10 @@ public class CategoryAddExchangeImpl extends CategoryAddExchange { throw new BeaconClientException("Parent category with id " + msg.getParent() + " does not exist"); } - var found = DataStorage.get().getStoreCategories().stream().filter( - dataStoreCategory -> msg.getParent().equals(dataStoreCategory.getParentCategory()) && - msg.getName().equals(dataStoreCategory.getName())).findAny(); + var found = DataStorage.get().getStoreCategories().stream() + .filter(dataStoreCategory -> msg.getParent().equals(dataStoreCategory.getParentCategory()) + && msg.getName().equals(dataStoreCategory.getName())) + .findAny(); if (found.isPresent()) { return Response.builder().category(found.get().getUuid()).build(); } diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/FsReadExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/FsReadExchangeImpl.java index 590a3f229..b56c90841 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/FsReadExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/FsReadExchangeImpl.java @@ -2,10 +2,10 @@ package io.xpipe.app.beacon.impl; import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.beacon.BlobManager; +import io.xpipe.app.ext.ConnectionFileSystem; import io.xpipe.app.util.FixedSizeInputStream; import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.api.FsReadExchange; -import io.xpipe.core.store.ConnectionFileSystem; import com.sun.net.httpserver.HttpExchange; import lombok.SneakyThrows; diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/FsWriteExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/FsWriteExchangeImpl.java index d3207064c..e30982696 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/FsWriteExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/FsWriteExchangeImpl.java @@ -2,8 +2,8 @@ package io.xpipe.app.beacon.impl; import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.beacon.BlobManager; +import io.xpipe.app.ext.ConnectionFileSystem; import io.xpipe.beacon.api.FsWriteExchange; -import io.xpipe.core.store.ConnectionFileSystem; import com.sun.net.httpserver.HttpExchange; import lombok.SneakyThrows; diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalPrepareExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalPrepareExchangeImpl.java new file mode 100644 index 000000000..711552a37 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalPrepareExchangeImpl.java @@ -0,0 +1,32 @@ +package io.xpipe.app.beacon.impl; + +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.app.terminal.TerminalLauncherManager; +import io.xpipe.app.terminal.TerminalView; +import io.xpipe.beacon.BeaconClientException; +import io.xpipe.beacon.api.TerminalPrepareExchange; + +import com.sun.net.httpserver.HttpExchange; + +public class TerminalPrepareExchangeImpl extends TerminalPrepareExchange { + + @Override + public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException { + TerminalView.get().open(msg.getRequest(), msg.getPid()); + TerminalLauncherManager.registerPid(msg.getRequest(), msg.getPid()); + var term = AppPrefs.get().terminalType().getValue(); + var unicode = term.supportsUnicode(); + var escapes = term.supportsEscapes(); + var finished = TerminalLauncherManager.isCompletedSuccessfully(msg.getRequest()); + return Response.builder() + .supportsUnicode(unicode) + .supportsEscapeSequences(escapes) + .alreadyFinished(finished) + .build(); + } + + @Override + public boolean requiresEnabledApi() { + return false; + } +} 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 131450996..2765ed077 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 @@ -1,7 +1,6 @@ package io.xpipe.app.beacon.impl; import io.xpipe.app.terminal.TerminalLauncherManager; -import io.xpipe.app.terminal.TerminalView; import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.BeaconServerException; import io.xpipe.beacon.api.TerminalWaitExchange; @@ -9,10 +8,10 @@ import io.xpipe.beacon.api.TerminalWaitExchange; import com.sun.net.httpserver.HttpExchange; public class TerminalWaitExchangeImpl extends TerminalWaitExchange { + @Override public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException { - TerminalView.get().open(msg.getRequest(), msg.getPid()); - TerminalLauncherManager.waitExchange(msg.getRequest(), msg.getPid()); + TerminalLauncherManager.waitExchange(msg.getRequest()); return Response.builder().build(); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionComp.java index e332c2b70..e35c3d372 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionComp.java @@ -11,7 +11,6 @@ import io.xpipe.app.comp.base.LeftSplitPaneComp; import io.xpipe.app.comp.base.StackComp; import io.xpipe.app.comp.base.VerticalComp; import io.xpipe.app.comp.store.StoreEntryWrapper; -import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.ext.ShellStore; import io.xpipe.app.storage.DataStoreEntryRef; @@ -62,7 +61,6 @@ public class BrowserFileChooserSessionComp extends DialogComp { }); var comp = new BrowserFileChooserSessionComp(stage, model); comp.apply(struc -> struc.get().setPrefSize(1200, 700)) - .apply(struc -> AppFont.normal(struc.get())) .styleClass("browser") .styleClass("chooser"); return comp; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionComp.java index 0609562a6..510f4ed29 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionComp.java @@ -93,13 +93,14 @@ public class BrowserFullSessionComp extends SimpleComp { node.setClip(null); node.setPickOnBounds(false); }); - struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1)); + struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(-1)); }); } }); }); splitPane.styleClass("browser"); - return splitPane.createRegion(); + var r = splitPane.createRegion(); + return r; } private Comp> createLeftSide() { @@ -194,7 +195,6 @@ public class BrowserFullSessionComp extends SimpleComp { struc.get().setMinWidth(rightSplit.get()); struc.get().setPrefWidth(rightSplit.get()); struc.get().setMaxWidth(rightSplit.get()); - struc.get().getParent().requestLayout(); }); }); @@ -202,7 +202,6 @@ public class BrowserFullSessionComp extends SimpleComp { struc.get().setMinWidth(newValue.doubleValue()); struc.get().setPrefWidth(newValue.doubleValue()); struc.get().setMaxWidth(newValue.doubleValue()); - struc.get().getParent().requestLayout(); }); AnchorPane.setBottomAnchor(struc.get(), 0.0); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java index 01988bfa9..9eb9dfc49 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java @@ -2,7 +2,6 @@ package io.xpipe.app.browser; import io.xpipe.app.browser.file.BrowserFileSystemTabModel; import io.xpipe.app.browser.file.BrowserHistorySavedState; -import io.xpipe.app.browser.file.BrowserHistorySavedStateImpl; import io.xpipe.app.browser.file.BrowserHistoryTabModel; import io.xpipe.app.browser.file.BrowserTransferModel; import io.xpipe.app.prefs.AppPrefs; @@ -189,7 +188,9 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel 0) { + ThreadHelper.sleep(1000); + } } // Delete all files diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java index 3d4556746..ed23d3daa 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java @@ -57,7 +57,7 @@ public class BrowserSessionTabsComp extends SimpleComp { var tabs = createTabPane(); var topBackground = Comp.hspacer().styleClass("top-spacer").createRegion(); leftPadding.subscribe(number -> { - StackPane.setMargin(topBackground, new Insets(0, 0, 0, -number.doubleValue() - 6)); + StackPane.setMargin(topBackground, new Insets(0, 0, 0, -number.doubleValue() - 3)); }); var stack = new StackPane(topBackground, tabs); stack.setAlignment(Pos.TOP_CENTER); @@ -217,7 +217,8 @@ public class BrowserSessionTabsComp extends SimpleComp { headerArea .paddingProperty() .bind(Bindings.createObjectBinding( - () -> new Insets(2, 0, 4, -leftPadding.get() + 2), leftPadding)); + () -> new Insets(2, 0, 4, -leftPadding.get() + 3), leftPadding)); + tabs.setPadding(new Insets(0, 0, 0, -5)); headerHeight.bind(headerArea.heightProperty()); }); } @@ -431,7 +432,7 @@ public class BrowserSessionTabsComp extends SimpleComp { }, tabModel.getName(), global, - AppPrefs.get().language(), + AppI18n.activeLanguage(), AppPrefs.get().censorMode())); } else { tab.textProperty().bind(tabModel.getName()); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserClipboard.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserClipboard.java index ff1b380d4..c01972148 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserClipboard.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserClipboard.java @@ -45,7 +45,12 @@ public class BrowserClipboard { } List data = (List) clipboard.getData(DataFlavor.javaFileListFlavor); - var files = data.stream().map(f -> f.toPath()).toList(); + // Sometimes file data can contain invalid chars. Why? + var files = data.stream() + .filter(file -> + file.toString().chars().noneMatch(value -> Character.isISOControl(value))) + .map(f -> f.toPath()) + .toList(); if (files.size() == 0) { return; } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListFilterComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListFilterComp.java index 1a9f5688e..428457d20 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListFilterComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListFilterComp.java @@ -5,7 +5,7 @@ import io.xpipe.app.comp.base.FilterComp; import io.xpipe.app.comp.base.HorizontalComp; import io.xpipe.app.comp.store.StoreCategoryWrapper; import io.xpipe.app.comp.store.StoreViewState; -import io.xpipe.app.core.AppFont; +import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.util.DataStoreCategoryChoiceComp; import javafx.beans.property.Property; @@ -33,14 +33,14 @@ public final class BrowserConnectionListFilterComp extends SimpleComp { this.category) .styleClass(Styles.LEFT_PILL) .apply(struc -> { - AppFont.medium(struc.get()); + AppFontSizes.base(struc.get()); }); var filter = new FilterComp(this.filter) .styleClass(Styles.RIGHT_PILL) .minWidth(0) .hgrow() .apply(struc -> { - AppFont.medium(struc.get()); + AppFontSizes.base(struc.get()); }); var top = new HorizontalComp(List.of(category, filter)) @@ -51,6 +51,7 @@ public final class BrowserConnectionListFilterComp extends SimpleComp { first.prefHeightProperty().bind(second.heightProperty()); first.minHeightProperty().bind(second.heightProperty()); first.maxHeightProperty().bind(second.heightProperty()); + AppFontSizes.xl(struc.get()); }) .styleClass("bookmarks-header") .createRegion(); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java index ce70027b0..b11ea11c3 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java @@ -1,7 +1,7 @@ package io.xpipe.app.browser.file; import io.xpipe.app.browser.action.BrowserAction; -import io.xpipe.app.core.AppFont; +import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.util.InputHelper; import javafx.scene.control.ContextMenu; @@ -24,13 +24,13 @@ public final class BrowserContextMenu extends ContextMenu { } private void createMenu() { + AppFontSizes.lg(getStyleableNode()); + InputHelper.onLeft(this, false, e -> { hide(); e.consume(); }); - AppFont.normal(this.getStyleableNode()); - var empty = source == null; var selected = new ArrayList<>( empty 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 a8a4b08fc..d141f1969 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 @@ -125,7 +125,7 @@ public final class BrowserFileListComp extends SimpleComp { fileList.setComparator(table.getComparator()); return true; }); - table.setFixedCellSize(32.0); + table.setFixedCellSize(30.0); prepareColumnVisibility(table, ownerCol, filenameCol); prepareTableScrollFix(table); @@ -134,7 +134,6 @@ public final class BrowserFileListComp extends SimpleComp { prepareTableEntries(table); prepareTableChanges(table, filenameCol, mtimeCol, modeCol, ownerCol); prepareTypedSelectionModel(table); - return table; } @@ -291,7 +290,7 @@ public final class BrowserFileListComp extends SimpleComp { }); fileList.getSelection().addListener((ListChangeListener) c -> { - var existing = new HashSet<>(fileList.getSelection()); + var existing = new HashSet<>(table.getSelectionModel().getSelectedItems()); var toApply = new HashSet<>(c.getList()); if (existing.equals(toApply)) { return; diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOpener.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOpener.java index 80a31116a..e1f1c0c90 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOpener.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOpener.java @@ -1,13 +1,13 @@ package io.xpipe.app.browser.file; import io.xpipe.app.core.window.AppWindowHelper; +import io.xpipe.app.ext.ConnectionFileSystem; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.FileBridge; import io.xpipe.app.util.FileOpener; import io.xpipe.core.process.ElevationFunction; import io.xpipe.core.process.OsType; -import io.xpipe.core.store.ConnectionFileSystem; import io.xpipe.core.store.FileEntry; import io.xpipe.core.store.FileInfo; import io.xpipe.core.store.FileNames; @@ -45,8 +45,8 @@ public class BrowserFileOpener { return fileSystem.openOutput(file.getPath(), totalBytes); } - var rootSc = sc.identicalSubShell() - .elevated(ElevationFunction.elevated("sudo")) + var rootSc = sc.identicalDialectSubShell() + .elevated(ElevationFunction.elevated(null)) .start(); var rootFs = new ConnectionFileSystem(rootSc); try { diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java index e6bba8efa..f26c94c1f 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java @@ -6,7 +6,6 @@ import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.augment.GrowAugment; import io.xpipe.app.comp.base.HorizontalComp; import io.xpipe.app.comp.base.ListBoxViewComp; -import io.xpipe.app.comp.base.VBoxViewComp; import io.xpipe.core.store.FileEntry; import javafx.collections.ObservableList; @@ -48,12 +47,10 @@ public class BrowserFileOverviewComp extends SimpleComp { }); }; - if (grow) { - var c = new ListBoxViewComp<>(list, list, factory, true).styleClass("overview-file-list"); - return c.createRegion(); - } else { - var c = new VBoxViewComp<>(list, list, factory).styleClass("overview-file-list"); - return c.createRegion(); + var c = new ListBoxViewComp<>(list, list, factory, true).styleClass("overview-file-list"); + if (!grow) { + c.apply(struc -> struc.get().setFitToHeight(true)); } + return c.createRegion(); } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSelectionListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSelectionListComp.java index d87ca9a4a..a47efd4b2 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSelectionListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSelectionListComp.java @@ -60,6 +60,7 @@ public class BrowserFileSelectionListComp extends SimpleComp { .createRegion(); var t = nameTransformation.apply(entry); var l = new Label(t.getValue(), image); + l.setGraphicTextGap(6); l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS); t.addListener((observable, oldValue, newValue) -> { PlatformThread.runLaterIfNeeded(() -> { diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabComp.java index 0d0ee4951..e775993b6 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabComp.java @@ -7,7 +7,7 @@ import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.SimpleCompStructure; import io.xpipe.app.comp.augment.ContextMenuAugment; import io.xpipe.app.comp.base.*; -import io.xpipe.app.core.AppFont; +import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.util.InputHelper; import io.xpipe.app.util.PlatformThread; @@ -80,9 +80,10 @@ public class BrowserFileSystemTabComp extends SimpleComp { var topBar = new HBox(); topBar.setAlignment(Pos.CENTER); topBar.getStyleClass().add("top-bar"); + AppFontSizes.xl(topBar); var navBar = new BrowserNavBarComp(model).createStructure(); filter.textField().prefHeightProperty().bind(navBar.get().heightProperty()); - AppFont.medium(navBar.get()); + AppFontSizes.base(navBar.get()); topBar.getChildren() .setAll( overview, 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 d8f6f4d2b..1e39933b7 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 @@ -10,7 +10,6 @@ import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.ext.ShellStore; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.prefs.AppPrefs; -import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.terminal.*; import io.xpipe.app.util.BooleanScope; @@ -26,6 +25,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import lombok.Getter; +import lombok.NonNull; import lombok.SneakyThrows; import java.io.IOException; @@ -121,13 +121,15 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab adjustedPath .toLowerCase() .startsWith(dialect.getExecutableName().toLowerCase()))) { - var cc = fileSystem - .getShell() - .get() - .singularSubShell(ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false)); - openTerminalAsync(name, directory, cc, true); + var sub = fileSystem.getShell().get().subShell(); + var open = new ShellOpenFunction() { + + @Override + public CommandBuilder prepareWithoutInitCommand() { + return CommandBuilder.ofString(adjustedPath); + } + + @Override + public CommandBuilder prepareWithInitCommand(@NonNull String command) { + return CommandBuilder.ofString(command); + } + }; + sub.setDumbOpen(open); + sub.setTerminalOpen(open); + openTerminalAsync(name, directory, sub, true); } else { var cc = fileSystem.getShell().get().command(adjustedPath); openTerminalAsync(name, directory, cc, true); @@ -328,7 +341,7 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab(state.getEntries(), true) .filtered(e -> { + if (DataStorage.get() == null) { + return false; + } + var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); if (entry.isEmpty()) { return false; @@ -82,20 +55,22 @@ public class BrowserHistoryTabComp extends SimpleComp { }) .getList(); var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list); + var contentDisplay = createListDisplay(list); + var emptyDisplay = createEmptyDisplay(); + var map = new LinkedHashMap, ObservableValue>(); + map.put(emptyDisplay, empty); + map.put(contentDisplay, empty.not()); + var stack = new MultiContentComp(map); + return stack.createRegion(); + } - var headerBinding = BindingsHelper.flatMap(empty, b -> { - if (b) { - return AppI18n.observable("browserWelcomeEmpty"); - } else { - return AppI18n.observable("browserWelcomeSystems"); - } - }); - var header = new LabelComp(headerBinding).createRegion(); - AppFont.setSize(header, 1); - vbox.getChildren().add(header); + private Comp createListDisplay(ObservableList list) { + var state = BrowserHistorySavedStateImpl.get(); - var storeList = new VBox(); - storeList.setSpacing(8); + var welcome = new BrowserGreetingComp(); + var header = new LabelComp(AppI18n.observable("browserWelcomeSystems")); + var vbox = new VerticalComp(List.of(welcome, Comp.vspacer(4), header)); + vbox.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)); var listBox = new ListBoxViewComp<>( list, @@ -117,32 +92,38 @@ public class BrowserHistoryTabComp extends SimpleComp { .apply(struc -> { VBox vBox = (VBox) struc.get().getContent(); vBox.setSpacing(10); - }) - .hide(empty) - .createRegion(); - - var layout = new VBox(); - layout.getStyleClass().add("welcome"); - layout.setPadding(new Insets(25, 40, 40, 40)); - layout.setSpacing(18); - layout.getChildren().add(hbox); - layout.getChildren().add(Comp.separator().hide(empty).createRegion()); - layout.getChildren().add(listBox); - VBox.setVgrow(layout.getChildren().get(2), Priority.NEVER); - layout.getChildren().add(Comp.separator().hide(empty).createRegion()); + }); var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> { model.restoreState(state); actionEvent.consume(); }) .grow(true, false) - .hide(empty) .accessibleTextKey("restoreAllSessions"); - layout.getChildren().add(tile.createRegion()); - AppFont.medium(layout); + + var layout = new VerticalComp(List.of(vbox, Comp.vspacer(5), listBox, Comp.separator(), tile)); + layout.styleClass("welcome"); + layout.spacing(14); + layout.maxWidth(1000); + layout.padding(new Insets(45, 40, 40, 50)); + layout.apply(struc -> { + struc.get().setMaxWidth(1000); + }); return layout; } + private Comp createEmptyDisplay() { + var intro = new IntroComp( + "browserWelcomeEmpty", + new LabelGraphic.CompGraphic(PrettyImageHelper.ofSpecificFixedSize("graphics/Hips.svg", 100, 122))); + intro.setButtonAction(() -> { + BrowserFullSessionModel.DEFAULT.openFileSystemAsync( + DataStorage.get().local().ref(), null, null); + }); + intro.setButtonDefault(true); + return intro; + } + private Comp entryButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) { var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); var graphic = entry.get().getEffectiveIconFile(); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserNavBarComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserNavBarComp.java index 13a34a747..c0e6d4859 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserNavBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserNavBarComp.java @@ -9,12 +9,15 @@ import io.xpipe.app.comp.base.PrettyImageHelper; import io.xpipe.app.comp.base.TextFieldComp; import io.xpipe.app.comp.base.TooltipAugment; import io.xpipe.app.util.BooleanScope; +import io.xpipe.app.util.ContextMenuHelper; +import io.xpipe.app.util.PlatformThread; import io.xpipe.app.util.ThreadHelper; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleStringProperty; import javafx.css.PseudoClass; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.*; @@ -84,15 +87,25 @@ public class BrowserNavBarComp extends Comp { pathRegion.focusedProperty(), model.getInOverview())); var stack = new StackPane(pathRegion, breadcrumbsRegion); + stack.setAlignment(Pos.CENTER_LEFT); pathRegion.prefHeightProperty().bind(stack.heightProperty()); + stack.widthProperty().addListener((observable, oldValue, newValue) -> { + setMargin(stack, breadcrumbsRegion); + }); + + model.getCurrentPath().addListener((observable, oldValue, newValue) -> { + PlatformThread.runLaterIfNeeded(() -> { + setMargin(stack, breadcrumbsRegion); + }); + }); + // Prevent overflow var clip = new Rectangle(); clip.widthProperty().bind(stack.widthProperty()); clip.heightProperty().bind(stack.heightProperty()); - breadcrumbsRegion.setClip(clip); + stack.setClip(clip); - stack.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(stack, Priority.ALWAYS); var topBox = new HBox(homeButton, stack, historyButton); @@ -110,6 +123,15 @@ public class BrowserNavBarComp extends Comp { return new Structure(topBox, pathRegion, historyButton); } + private void setMargin(StackPane stackPane, Region region) { + var off = region.getWidth() - stackPane.getWidth(); + if (off <= 0) { + StackPane.setMargin(region, new Insets(0, 0, 0, 0)); + } else { + StackPane.setMargin(region, new Insets(0, 20, 0, -off - 20)); + } + } + private Comp> createPathBar() { var path = new SimpleStringProperty(model.getCurrentPath().get()); model.getCurrentPath().subscribe((newValue) -> { @@ -172,7 +194,7 @@ public class BrowserNavBarComp extends Comp { } private ContextMenu createContextMenu() { - var cm = new ContextMenu(); + var cm = ContextMenuHelper.create(); var f = model.getHistory().getForwardHistory(8).stream().toList(); for (int i = f.size() - 1; i >= 0; i--) { diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserOverviewComp.java index 536a2852c..a41d68c9c 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserOverviewComp.java @@ -3,7 +3,6 @@ package io.xpipe.app.browser.file; import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.base.SimpleTitledPaneComp; import io.xpipe.app.comp.base.VerticalComp; -import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.DerivedObservableList; @@ -77,7 +76,6 @@ public class BrowserOverviewComp extends SimpleComp { var vbox = new VerticalComp(List.of(recentPane, commonPane, rootsPane)).styleClass("overview"); var r = vbox.createRegion(); - AppFont.medium(r); return r; } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java index 8239646f9..bc55c0b9a 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java @@ -2,6 +2,7 @@ package io.xpipe.app.browser.file; import io.xpipe.app.browser.icon.BrowserIconManager; import io.xpipe.app.comp.base.PrettyImageHelper; +import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.util.BooleanAnimationTimer; import io.xpipe.app.util.InputHelper; import io.xpipe.app.util.ThreadHelper; @@ -41,6 +42,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { this.base = base; this.model = model; + AppFontSizes.lg(getStyleableNode()); addEventFilter(Menu.ON_SHOWING, e -> { Node content = getSkin().getNode(); if (content instanceof Region r) { @@ -115,7 +117,13 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { var dirs = browserEntries.stream() .filter(e -> e.getRawFileEntry().getKind() == FileKind.DIRECTORY) .toList(); - if (dirs.size() == 1) { + // Expand subdir if only one + // Note that if we have a link to the directory itself, we shouldn't do it, otherwise we are stuck in a loop + if (dirs.size() == 1 + && !dirs.getFirst() + .getRawFileEntry() + .getPath() + .equals(entry.getRawFileEntry().getPath())) { updateMenuItems((Menu) menus.get(dirs.getFirst()), dirs.getFirst(), true); } newItems.addAll(menus.values()); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java index af565eda5..7b2cd2e6d 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java @@ -7,7 +7,7 @@ import io.xpipe.app.comp.augment.ContextMenuAugment; import io.xpipe.app.comp.base.HorizontalComp; import io.xpipe.app.comp.base.IconButtonComp; import io.xpipe.app.comp.base.LabelComp; -import io.xpipe.app.core.AppFont; +import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.util.BindingsHelper; import io.xpipe.app.util.HumanReadableFormat; import io.xpipe.app.util.PlatformThread; @@ -49,7 +49,7 @@ public class BrowserStatusBarComp extends SimpleComp { event.consume(); r.startFullDrag(); }); - AppFont.small(r); + AppFontSizes.xs(r); simulateEmptyCell(r); return r; } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java index 6761e5cd5..702b19e42 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java @@ -3,7 +3,6 @@ package io.xpipe.app.browser.file; import io.xpipe.app.comp.Comp; import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.base.*; -import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; import io.xpipe.app.util.DerivedObservableList; import io.xpipe.app.util.ThreadHelper; @@ -81,7 +80,6 @@ public class BrowserTransferComp extends SimpleComp { .grow(false, true); var dragNotice = new LabelComp(AppI18n.observable("dragLocalFiles")) .apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left"))) - .apply(struc -> AppFont.medium(struc.get())) .apply(struc -> struc.get().setWrapText(true)) .hide(model.getEmpty()); @@ -193,6 +191,7 @@ public class BrowserTransferComp extends SimpleComp { }); }); - return stack.styleClass("transfer").createRegion(); + var r = stack.styleClass("transfer").createRegion(); + return r; } } diff --git a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconVariant.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconVariant.java index 820ed2f1e..4df99cc60 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconVariant.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconVariant.java @@ -17,7 +17,7 @@ public class BrowserIconVariant { } protected final String getIcon() { - var t = AppPrefs.get() != null ? AppPrefs.get().theme.getValue() : null; + var t = AppPrefs.get() != null ? AppPrefs.get().theme().getValue() : null; if (t == null) { return lightIcon; } diff --git a/app/src/main/java/io/xpipe/app/comp/Comp.java b/app/src/main/java/io/xpipe/app/comp/Comp.java index 318f09e31..aad527642 100644 --- a/app/src/main/java/io/xpipe/app/comp/Comp.java +++ b/app/src/main/java/io/xpipe/app/comp/Comp.java @@ -142,7 +142,15 @@ public abstract class Comp> { } public Comp visible(ObservableValue o) { - return apply(struc -> struc.get().visibleProperty().bind(o)); + return apply(struc -> { + var region = struc.get(); + BindingsHelper.preserve(region, o); + o.subscribe(n -> { + PlatformThread.runLaterIfNeeded(() -> { + region.setVisible(n); + }); + }); + }); } public Comp disable(ObservableValue o) { diff --git a/app/src/main/java/io/xpipe/app/comp/base/AppLayoutComp.java b/app/src/main/java/io/xpipe/app/comp/base/AppLayoutComp.java index a35b229d3..d1cc17984 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/AppLayoutComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/AppLayoutComp.java @@ -3,7 +3,6 @@ package io.xpipe.app.comp.base; import io.xpipe.app.comp.Comp; import io.xpipe.app.comp.CompStructure; import io.xpipe.app.comp.store.StoreViewState; -import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; @@ -68,7 +67,6 @@ public class AppLayoutComp extends Comp { } }); }); - AppFont.normal(pane); pane.getStyleClass().add("layout"); return new Structure(pane, multiR, sidebarR, new ArrayList<>(multiR.getChildren())); } diff --git a/app/src/main/java/io/xpipe/app/comp/base/AppMainWindowContentComp.java b/app/src/main/java/io/xpipe/app/comp/base/AppMainWindowContentComp.java index 3c98e64b2..2184af967 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/AppMainWindowContentComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/AppMainWindowContentComp.java @@ -2,7 +2,7 @@ package io.xpipe.app.comp.base; import io.xpipe.app.comp.Comp; import io.xpipe.app.comp.SimpleComp; -import io.xpipe.app.core.AppFont; +import io.xpipe.app.core.AppFontSizes; import io.xpipe.app.core.AppProperties; import io.xpipe.app.core.window.AppDialog; import io.xpipe.app.core.window.AppMainWindow; @@ -57,7 +57,7 @@ public class AppMainWindowContentComp extends SimpleComp { var version = new LabelComp((AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " " + AppProperties.get().getVersion()); version.apply(struc -> { - AppFont.setSize(struc.get(), 1); + AppFontSizes.xxl(struc.get()); struc.get().setOpacity(0.6); }); @@ -83,12 +83,13 @@ public class AppMainWindowContentComp extends SimpleComp { loaded.subscribe(struc -> { if (struc != null) { PlatformThread.runNestedLoopIteration(); - struc.prepareAddition(); anim.stop(); + struc.prepareAddition(); pane.getChildren().add(struc.get()); - struc.show(); - pane.getChildren().remove(vbox); + PlatformThread.runNestedLoopIteration(); pane.getStyleClass().remove("background"); + pane.getChildren().remove(vbox); + struc.show(); } }); diff --git a/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java b/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java index aab6f8f50..b7e30cd5b 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java @@ -66,7 +66,9 @@ public class ButtonComp extends Comp> { } }); } - button.setOnAction(e -> getListener().run()); + if (listener != null) { + button.setOnAction(e -> getListener().run()); + } button.getStyleClass().add("button-comp"); return new SimpleCompStructure<>(button); } diff --git a/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java index 8531f113d..2f99df2fa 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java @@ -68,10 +68,7 @@ public class ContextualFileReferenceChoiceComp extends Comp> BrowserFileChooserSessionComp.openSingleFile( () -> fileSystem.getValue(), fileStore -> { - if (fileStore == null) { - filePath.setValue(null); - fileSystem.setValue(null); - } else { + if (fileStore != null) { filePath.setValue(fileStore.getPath()); fileSystem.setValue(fileStore.getFileSystem()); } diff --git a/app/src/main/java/io/xpipe/app/comp/base/CountComp.java b/app/src/main/java/io/xpipe/app/comp/base/CountComp.java index 52276f9d7..83e7d5acb 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/CountComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/CountComp.java @@ -6,45 +6,38 @@ import io.xpipe.app.comp.SimpleCompStructure; import io.xpipe.app.util.PlatformThread; import javafx.beans.binding.Bindings; -import javafx.collections.ObservableList; +import javafx.beans.value.ObservableIntegerValue; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.OverrunStyle; +import lombok.AllArgsConstructor; + import java.util.function.Function; -public class CountComp extends Comp> { +@AllArgsConstructor +public class CountComp extends Comp> { - private final ObservableList sub; - private final ObservableList all; + private final ObservableIntegerValue sub; + private final ObservableIntegerValue all; private final Function transformation; - public CountComp(ObservableList sub, ObservableList all) { - this(sub, all, Function.identity()); - } - - public CountComp(ObservableList sub, ObservableList all, Function transformation) { - this.sub = PlatformThread.sync(sub); - this.all = PlatformThread.sync(all); - this.transformation = transformation; - } - @Override public CompStructure