diff --git a/app/build.gradle b/app/build.gradle index 51ea59e2..c6fc4b25 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,7 +60,7 @@ dependencies { implementation 'com.jfoenix:jfoenix:9.0.10' implementation 'org.controlsfx:controlsfx:11.1.2' implementation 'net.synedra:validatorfx:0.3.1' - implementation ('io.github.mkpaz:atlantafx-base:2.0.0') { + implementation ('io.github.mkpaz:atlantafx-base:2.0.1') { exclude group: 'org.openjfx', module: 'javafx-base' exclude group: 'org.openjfx', module: 'javafx-controls' } diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java index 0c3bef2c..aca08c36 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -282,12 +282,8 @@ public final class OpenFileSystemModel { }); } - public void createFileAsync(String name) { - if (name == null || name.isBlank()) { - return; - } - - if (getCurrentDirectory() == null) { + public void createLinkAsync(String linkName, String targetFile) { + if (linkName == null || linkName.isBlank() || targetFile == null || targetFile.isBlank()) { return; } @@ -297,6 +293,32 @@ public final class OpenFileSystemModel { return; } + if (getCurrentDirectory() == null) { + return; + } + + var abs = FileNames.join(getCurrentDirectory().getPath(), linkName); + fileSystem.symbolicLink(abs, targetFile); + refreshSync(); + }); + }); + } + + public void createFileAsync(String name) { + if (name == null || name.isBlank()) { + return; + } + + ThreadHelper.runFailableAsync(() -> { + BusyProperty.execute(busy, () -> { + if (fileSystem == null) { + return; + } + + if (getCurrentDirectory() == null) { + return; + } + var abs = FileNames.join(getCurrentDirectory().getPath(), name); fileSystem.touch(abs); refreshSync(); diff --git a/app/src/main/java/io/xpipe/app/core/AppI18n.java b/app/src/main/java/io/xpipe/app/core/AppI18n.java index 603369db..04da4188 100644 --- a/app/src/main/java/io/xpipe/app/core/AppI18n.java +++ b/app/src/main/java/io/xpipe/app/core/AppI18n.java @@ -1,5 +1,6 @@ package io.xpipe.app.core; +import io.xpipe.app.comp.base.ModalOverlayComp; import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.fxcomps.impl.FancyTooltipAugment; import io.xpipe.app.issue.ErrorEvent; @@ -131,6 +132,7 @@ public class AppI18n { for (Class caller : callers) { if (caller.equals(CallingClass.class) || caller.equals(ModuleHelper.class) + || caller.equals(ModalOverlayComp.class) || caller.equals(AppI18n.class) || caller.equals(FancyTooltipAugment.class) || caller.equals(PrefsChoiceValue.class) diff --git a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java b/app/src/main/java/io/xpipe/app/fxcomps/Comp.java index 669acb3e..04da304e 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/Comp.java @@ -51,6 +51,14 @@ public abstract class Comp> { return (T) this; } + public Comp prefWidth(int width) { + return apply(struc -> struc.get().setPrefWidth(width)); + } + + public Comp prefHeight(int height) { + return apply(struc -> struc.get().setPrefHeight(height)); + } + public Comp hgrow() { return apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)); } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java index 74789bea..0e5277fb 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java @@ -8,6 +8,7 @@ import io.xpipe.app.util.ScriptHelper; import io.xpipe.app.util.WindowsRegistry; import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.LocalStore; +import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellDialects; @@ -21,7 +22,7 @@ import java.util.stream.Stream; public interface ExternalTerminalType extends PrefsChoiceValue { - ExternalTerminalType CMD = new SimpleType("app.cmd", "cmd.exe", "cmd.exe") { + ExternalTerminalType CMD = new SimpleType("app.cmd", "cmd.exe") { @Override protected String toCommand(String name, String file) { @@ -34,7 +35,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType POWERSHELL_WINDOWS = new SimpleType("app.powershell", "powershell", "PowerShell") { + ExternalTerminalType POWERSHELL_WINDOWS = new SimpleType("app.powershell", "powershell") { @Override protected String toCommand(String name, String file) { @@ -47,7 +48,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType PWSH_WINDOWS = new SimpleType("app.pwsh", "pwsh", "PowerShell Core") { + ExternalTerminalType PWSH_WINDOWS = new SimpleType("app.pwsh", "pwsh") { @Override protected String toCommand(String name, String file) { @@ -62,7 +63,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType WINDOWS_TERMINAL = new SimpleType("app.windowsTerminal", "wt.exe", "Windows Terminal") { + ExternalTerminalType WINDOWS_TERMINAL = new SimpleType("app.windowsTerminal", "wt.exe") { @Override protected String toCommand(String name, String file) { @@ -120,12 +121,12 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType GNOME_TERMINAL = new SimpleType("app.gnomeTerminal", "gnome-terminal", "Gnome Terminal") { + ExternalTerminalType GNOME_TERMINAL = new SimpleType("app.gnomeTerminal", "gnome-terminal") { @Override public void launch(String name, String file, boolean elevated) throws Exception { try (ShellControl pc = LocalStore.getShell()) { - ApplicationHelper.checkSupport(pc, executable, getDisplayName(), null); + ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null); var toExecute = executable + " " + toCommand(name, file); // In order to fix this bug which also affects us: @@ -146,7 +147,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType KONSOLE = new SimpleType("app.konsole", "konsole", "Konsole") { + ExternalTerminalType KONSOLE = new SimpleType("app.konsole", "konsole") { @Override protected String toCommand(String name, String file) { @@ -162,7 +163,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; - ExternalTerminalType XFCE = new SimpleType("app.xfce", "xfce4-terminal", "Xfce") { + ExternalTerminalType XFCE = new SimpleType("app.xfce", "xfce4-terminal") { @Override protected String toCommand(String name, String file) { @@ -175,6 +176,97 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; + ExternalTerminalType TERMINATOR = new SimpleType("app.terminator", "terminator") { + + @Override + protected String toCommand(String name, String file) { + return CommandBuilder.of().add("-e").addQuoted(file).add("-T").addQuoted(name).add("--new-tab").build(); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + }; + + ExternalTerminalType KITTY = new SimpleType("app.kitty", "kitty") { + + @Override + protected String toCommand(String name, String file) { + return CommandBuilder.of().add("-T").addQuoted(name).addQuoted(file).build(); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + }; + + ExternalTerminalType TERMINOLOGY = new SimpleType("app.terminology", "terminology") { + + @Override + protected String toCommand(String name, String file) { + return CommandBuilder.of().add("-T").addQuoted(name).add("-2").add("-e").addQuoted(file).build(); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + }; + + ExternalTerminalType COOL_RETRO_TERM = new SimpleType("app.coolRetroTerm", "cool-retro-term") { + + @Override + protected String toCommand(String name, String file) { + return CommandBuilder.of().add("-T").addQuoted(name).add("-e").addQuoted(file).build(); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + }; + + ExternalTerminalType GUAKE = new SimpleType("app.guake", "guake") { + + @Override + protected String toCommand(String name, String file) { + return CommandBuilder.of().add("-r").addQuoted(name).add("-e").addQuoted(file).build(); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + }; + + ExternalTerminalType ALACRITTY = new SimpleType("app.alacritty", "alacritty") { + + @Override + protected String toCommand(String name, String file) { + return CommandBuilder.of().add("-t").addQuoted(name).add("-e").addQuoted(file).build(); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + }; + + ExternalTerminalType TILDA = new SimpleType("app.tilda", "tilda") { + + @Override + protected String toCommand(String name, String file) { + return CommandBuilder.of().add("-c").addQuoted(file).build(); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + }; + ExternalTerminalType MACOS_TERMINAL = new MacOsTerminalType(); ExternalTerminalType ITERM2 = new ITerm2Type(); @@ -194,6 +286,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { KONSOLE, XFCE, GNOME_TERMINAL, + TERMINATOR, + KITTY, + TERMINOLOGY, + COOL_RETRO_TERM, + GUAKE, + ALACRITTY, + TILDA, ITERM2, TABBY_MAC_OS, WARP, @@ -357,11 +456,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Getter abstract class SimpleType extends ExternalApplicationType.PathApplication implements ExternalTerminalType { - private final String displayName; - - public SimpleType(String id, String executable, String displayName) { + public SimpleType(String id, String executable) { super(id, executable); - this.displayName = displayName; } @Override @@ -371,7 +467,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { try (ShellControl pc = LocalStore.getShell() .subShell(ShellDialects.POWERSHELL) .start()) { - ApplicationHelper.checkSupport(pc, executable, displayName, null); + ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null); var toExecute = "Start-Process \"" + executable + "\" -Verb RunAs -ArgumentList \"" + toCommand(name, file).replaceAll("\"", "`\"") + "\""; pc.executeSimpleCommand(toExecute); @@ -381,7 +477,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } try (ShellControl pc = LocalStore.getShell()) { - ApplicationHelper.checkSupport(pc, executable, displayName, null); + ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null); var toExecute = executable + " " + toCommand(name, file); if (pc.getOsType().equals(OsType.WINDOWS)) { diff --git a/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java b/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java index bbc80ff3..43907ad5 100644 --- a/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java +++ b/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java @@ -1,5 +1,6 @@ package io.xpipe.app.util; +import atlantafx.base.controls.Spacer; import io.xpipe.app.core.AppI18n; import io.xpipe.app.fxcomps.Comp; import io.xpipe.app.fxcomps.impl.*; @@ -7,6 +8,7 @@ import io.xpipe.core.util.SecretValue; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; +import javafx.geometry.Orientation; import javafx.scene.control.Label; import javafx.scene.layout.Region; import net.synedra.validatorfx.Check; @@ -108,6 +110,10 @@ public class OptionsBuilder { return this; } + public OptionsBuilder spacer(double size) { + return addComp(Comp.of(() -> new Spacer(size, Orientation.VERTICAL))); + } + public OptionsBuilder name(String nameKey) { finishCurrent(); name = AppI18n.observable(nameKey); diff --git a/core/src/main/java/io/xpipe/core/process/CommandBuilder.java b/core/src/main/java/io/xpipe/core/process/CommandBuilder.java index 78d56e24..ee45c21f 100644 --- a/core/src/main/java/io/xpipe/core/process/CommandBuilder.java +++ b/core/src/main/java/io/xpipe/core/process/CommandBuilder.java @@ -17,6 +17,26 @@ public class CommandBuilder { private final boolean noQuoting; private final StringBuilder builder = new StringBuilder(); + public CommandBuilder add(String... s) { + for (String s1 : s) { + add(s1); + } + return this; + } + + public CommandBuilder addQuoted(String s) { + if (!builder.isEmpty()) { + builder.append(' '); + } + + if (noQuoting) { + throw new IllegalArgumentException("No quoting rule conflicts with spaces an argument"); + } + + builder.append("\"").append(s).append("\""); + return this; + } + public CommandBuilder add(String s) { if (!builder.isEmpty()) { builder.append(' '); diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialect.java b/core/src/main/java/io/xpipe/core/process/ShellDialect.java index 55d2ec33..0566335d 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialect.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialect.java @@ -147,6 +147,8 @@ public interface ShellDialect { CommandControl createFileExistsCommand(ShellControl sc, String file); + CommandControl symbolicLink(ShellControl sc, String linkFile, String targetFile); + String getFileTouchCommand(String file); String getWhichCommand(String executable); diff --git a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java index ddc4da6e..c1c6f478 100644 --- a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java @@ -134,7 +134,14 @@ public class ConnectionFileSystem implements FileSystem { public void touch(String file) throws Exception { try (var pc = shellControl .command(proc -> proc.getShellDialect().getFileTouchCommand(file)) - .complex() + .start()) { + pc.discardOrThrow(); + } + } + + @Override + public void symbolicLink(String linkFile, String targetFile) throws Exception { + try (var pc = shellControl.getShellDialect().symbolicLink(shellControl,linkFile, targetFile) .start()) { pc.discardOrThrow(); } diff --git a/core/src/main/java/io/xpipe/core/store/FileSystem.java b/core/src/main/java/io/xpipe/core/store/FileSystem.java index 46613ffe..cf74f511 100644 --- a/core/src/main/java/io/xpipe/core/store/FileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/FileSystem.java @@ -108,6 +108,8 @@ public interface FileSystem extends Closeable, AutoCloseable { void touch(String file) throws Exception; + void symbolicLink(String linkFile, String targetFile) throws Exception; + boolean directoryExists(String file) throws Exception; void directoryAccessible(String file) throws Exception; diff --git a/dist/changelogs/1.2.0.md b/dist/changelogs/1.2.0.md new file mode 100644 index 00000000..0b82aa40 --- /dev/null +++ b/dist/changelogs/1.2.0.md @@ -0,0 +1,8 @@ +## Changes in 1.2.0 + +- Introduce new landing page in file browser +- Show commands that are executed to open a shell in connection creation wizard to +- Merge settings and about pages +- Add support for handling symbolic links in file browser +- Add support for many more Linux terminals +- Many small miscellaneous fixes and improvements diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java index bb70d2ff..bd229de1 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java @@ -8,6 +8,8 @@ import io.xpipe.app.browser.action.LeafAction; import io.xpipe.app.browser.icon.BrowserIcons; import io.xpipe.app.comp.base.ModalOverlayComp; import io.xpipe.app.fxcomps.Comp; +import io.xpipe.app.util.OptionsBuilder; +import io.xpipe.core.process.OsType; import javafx.beans.property.SimpleStringProperty; import javafx.scene.Node; import javafx.scene.control.TextField; @@ -104,6 +106,45 @@ public class NewItemAction implements BrowserAction, BranchAction { public Node getIcon(OpenFileSystemModel model, List entries) { return BrowserIcons.createDefaultDirectoryIcon().createRegion(); } + }, + new LeafAction() { + @Override + public String getName(OpenFileSystemModel model, List entries) { + return "Symbolic link"; + } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS; + } + + @Override + public void execute(OpenFileSystemModel model, List entries) { + var linkName = new SimpleStringProperty(); + var target = new SimpleStringProperty(); + model.getOverlay() + .setValue(new ModalOverlayComp.OverlayContent( + "base.newLink", + new OptionsBuilder() + .spacer(10) + .name("linkName") + .addString(linkName) + .spacer(10) + .name("targetPath") + .addString(target) + .buildComp() + .prefWidth(400) + .prefHeight(130), + "finish", + () -> { + model.createLinkAsync(linkName.getValue(), target.getValue()); + })); + } + + @Override + public Node getIcon(OpenFileSystemModel model, List entries) { + return BrowserIcons.createDefaultFileIcon().createRegion(); + } }); } } diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index 7e7a487d..9ad6ba9d 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -25,6 +25,7 @@ open module io.xpipe.ext.base { requires org.kordamp.ikonli.javafx; requires com.sun.jna; requires com.sun.jna.platform; + requires atlantafx.base; provides BrowserAction with FollowLinkAction, diff --git a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties index 99f99d29..fb892c9a 100644 --- a/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties +++ b/ext/base/src/main/resources/io/xpipe/ext/base/resources/lang/translations_en.properties @@ -10,6 +10,9 @@ configuration=Configuration selectOutput=Select Output options=Options newFile=New file +newLink=New link +linkName=Link name +targetPath=Target path newDirectory=New directory copyShareLink=Copy share link selectStore=Select Store diff --git a/gradle/gradle_scripts/extension.gradle b/gradle/gradle_scripts/extension.gradle index d646428c..2b2b1051 100644 --- a/gradle/gradle_scripts/extension.gradle +++ b/gradle/gradle_scripts/extension.gradle @@ -46,6 +46,10 @@ dependencies { compileOnly project(':beacon') compileOnly project(':app') compileOnly 'net.synedra:validatorfx:0.3.1' + compileOnly ('io.github.mkpaz:atlantafx-base:2.0.1') { + exclude group: 'org.openjfx', module: 'javafx-base' + exclude group: 'org.openjfx', module: 'javafx-controls' + } if (project != project(':base')) { compileOnly project(':base') diff --git a/version b/version index 9c1218c2..867e5243 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.1.3 \ No newline at end of file +1.2.0 \ No newline at end of file