diff --git a/README.md b/README.md index 139f7788f..4bbdebbee 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ [![Build Status](https://github.com/xpipe-io/xpipe_java/actions/workflows/build.yml/badge.svg)](https://github.com/xpipe-io/xpipe_java/actions/workflows/build.yml) -[![Publish Status](https://github.com/xpipe-io/xpipe_java/actions/workflows/publishb.yml/badge.svg)](https://github.com/xpipe-io/xpipe_java/actions/workflows/publish.yml) +[![Publish Status](https://github.com/xpipe-io/xpipe_java/actions/workflows/publish.yml/badge.svg)](https://github.com/xpipe-io/xpipe_java/actions/workflows/publish.yml) ## X-Pipe Java The fundamental components of the [X-Pipe project](https://docs.xpipe.io). This repository contains the following four modules: -- Core - Shared core classes of the Java API and the X-Pipe daemon implementation +- Core - Shared core classes of the Java API, extensions, and the X-Pipe daemon implementation - API - The API that can be used to interact with X-Pipe from any JVM-based languages - Beacon - The X-Pipe beacon component is responsible for handling all communications between the X-Pipe daemon and the client applications, for example the various programming language APIs and the CLI diff --git a/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java b/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java index 1cb8f7b1d..ea0e86bbd 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java +++ b/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java @@ -68,7 +68,7 @@ public abstract class Charsetter { if (charset.hasByteOrderMark()) { var bom = stream.readNBytes(charset.getByteOrderMark().length); if (bom.length != 0 && !Arrays.equals(bom, charset.getByteOrderMark())) { - throw new IllegalStateException("Invalid charset: " + toString()); + throw new IllegalStateException("Charset does not match: " + charset.toString()); } } diff --git a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java index 2c3bbb393..373b06ed7 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java +++ b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java @@ -16,31 +16,45 @@ public class StreamCharset { public static final StreamCharset UTF8 = new StreamCharset(StandardCharsets.UTF_8, null); public static final StreamCharset UTF8_BOM = new StreamCharset(StandardCharsets.UTF_8, new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}); - public static final StreamCharset UTF16 = new StreamCharset(StandardCharsets.UTF_16, null); - public static final StreamCharset UTF16_BOM = - new StreamCharset(StandardCharsets.UTF_16, new byte[] {(byte) 0xFE, (byte) 0xFF}); + public static final StreamCharset UTF16_BE = new StreamCharset(StandardCharsets.UTF_16BE, null); + public static final StreamCharset UTF16_BE_BOM = + new StreamCharset(StandardCharsets.UTF_16BE, new byte[] {(byte) 0xFE, (byte) 0xFF}); public static final StreamCharset UTF16_LE = new StreamCharset(StandardCharsets.UTF_16LE, null); public static final StreamCharset UTF16_LE_BOM = new StreamCharset(StandardCharsets.UTF_16LE, new byte[] {(byte) 0xFF, (byte) 0xFE}); - public static final StreamCharset UTF32 = new StreamCharset(Charset.forName("utf-32"), null); - public static final StreamCharset UTF32_BOM = - new StreamCharset(Charset.forName("utf-32"), new byte[] {0x00, 0x00, (byte) 0xFE, (byte) 0xFF}); + + public static final StreamCharset UTF32_LE = new StreamCharset(Charset.forName("utf-32le"), null); + public static final StreamCharset UTF32_LE_BOM = + new StreamCharset(Charset.forName("utf-32le"), new byte[] {0x00, 0x00, (byte) 0xFE, (byte) 0xFF}); + public static final StreamCharset UTF32_BE = new StreamCharset(Charset.forName("utf-32be"), null); + public static final StreamCharset UTF32_BE_BOM = + new StreamCharset(Charset.forName("utf-32be"), new byte[] {(byte) 0xFF, (byte) 0xFE, 0x00, 0x00, }); + public static final List COMMON = List.of( UTF8, UTF8_BOM, - UTF16, - UTF16_BOM, + UTF16_BE, + UTF16_BE_BOM, UTF16_LE, UTF16_LE_BOM, - UTF32, - UTF32_BOM, new StreamCharset(StandardCharsets.US_ASCII, null), new StreamCharset(StandardCharsets.ISO_8859_1, null), new StreamCharset(Charset.forName("Windows-1251"), null), new StreamCharset(Charset.forName("Windows-1252"), null)); - public static final List RARE = Charset.availableCharsets().values().stream() - .filter(charset -> COMMON.stream().noneMatch(c -> c.getCharset().equals(charset))) - .map(charset -> new StreamCharset(charset, null)) + private static final List RARE_KNOWN = List.of(UTF32_LE, UTF32_LE_BOM, UTF32_BE, UTF32_BE_BOM); + public static final List RARE = Stream.concat( + RARE_KNOWN.stream(), + Charset.availableCharsets().values().stream() + .filter(charset -> !charset.equals(StandardCharsets.UTF_16) + && !charset.equals(Charset.forName("utf-32")) + && !charset.displayName().startsWith("x-") + && !charset.displayName().startsWith("X-") + && !charset.displayName().endsWith("-BOM") + && COMMON.stream() + .noneMatch(c -> c.getCharset().equals(charset)) + && RARE_KNOWN.stream() + .noneMatch(c -> c.getCharset().equals(charset))) + .map(charset -> new StreamCharset(charset, null))) .toList(); Charset charset; byte[] byteOrderMark; diff --git a/core/src/main/java/io/xpipe/core/source/DataSource.java b/core/src/main/java/io/xpipe/core/source/DataSource.java index abb54d0e2..7a8c03251 100644 --- a/core/src/main/java/io/xpipe/core/source/DataSource.java +++ b/core/src/main/java/io/xpipe/core/source/DataSource.java @@ -45,7 +45,6 @@ public abstract class DataSource extends JacksonizedValue throw new AssertionError(ex); } } - public void test() throws Exception { store.validate(); } diff --git a/core/src/main/java/io/xpipe/core/store/LocalStore.java b/core/src/main/java/io/xpipe/core/store/LocalStore.java index 5bbe3b727..08c9420f7 100644 --- a/core/src/main/java/io/xpipe/core/store/LocalStore.java +++ b/core/src/main/java/io/xpipe/core/store/LocalStore.java @@ -51,14 +51,14 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St } @Override - public ProcessControl prepareCommand(List input, List cmd, Integer timeout) { - return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout)); + public ProcessControl prepareCommand(List input, List cmd, Integer timeout, Charset charset) { + return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout), charset); } @Override - public ProcessControl preparePrivilegedCommand(List input, List cmd, Integer timeOut) + public ProcessControl preparePrivilegedCommand(List input, List cmd, Integer timeOut, Charset charset) throws Exception { - return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut)); + return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut), charset); } @Override @@ -71,14 +71,15 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St private final List input; private final Integer timeout; private final List command; - private Charset charset; + private final Charset charset; private Process process; - LocalProcessControl(List input, List cmd, Integer timeout) { + LocalProcessControl(List input, List cmd, Integer timeout, Charset charset) { this.input = input; this.timeout = timeout; this.command = cmd; + this.charset = charset; } private InputStream createInputStream() { @@ -93,7 +94,6 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St var l = type.switchTo(command); var builder = new ProcessBuilder(l); process = builder.start(); - charset = type.determineCharset(LocalStore.this); var t = new Thread(() -> { try (var inputStream = createInputStream()) { diff --git a/core/src/main/java/io/xpipe/core/store/ProcessControl.java b/core/src/main/java/io/xpipe/core/store/ProcessControl.java index 1768a187a..6ce897ed0 100644 --- a/core/src/main/java/io/xpipe/core/store/ProcessControl.java +++ b/core/src/main/java/io/xpipe/core/store/ProcessControl.java @@ -10,22 +10,54 @@ import java.util.concurrent.atomic.AtomicReference; public abstract class ProcessControl { - public String readOutOnly() throws Exception { - start(); - var errT = discardErr(); - var string = new String(getStdout().readAllBytes(), getCharset()); - waitFor(); - return string.trim(); + public String executeAndReadStdout() throws Exception { + var pc = this; + pc.start(); + pc.discardErr(); + var bytes = pc.getStdout().readAllBytes(); + var string = new String(bytes, pc.getCharset()); + return string; } - public Optional readErrOnly() throws Exception { - start(); - var outT = discardOut(); + public void executeOrThrow() throws Exception { + var pc = this; + pc.start(); + pc.discardOut(); + pc.discardErr(); + pc.waitFor(); + } + + public Optional executeAndReadStderrIfPresent() throws Exception { + var pc = this; + pc.start(); + pc.discardOut(); + var bytes = pc.getStderr().readAllBytes(); + var string = new String(bytes, pc.getCharset()); + var ec = pc.waitFor(); + return ec != 0 ? Optional.of(string) : Optional.empty(); + } + + public String executeAndReadStdoutOrThrow() + throws Exception { + var pc = this; + pc.start(); + + AtomicReference readError = new AtomicReference<>(); + var errorThread = new Thread(() -> { + try { + + readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset())); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + errorThread.setDaemon(true); + errorThread.start(); AtomicReference read = new AtomicReference<>(); var t = new Thread(() -> { try { - read.set(new String(getStderr().readAllBytes(), getCharset())); + read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset())); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -33,8 +65,17 @@ public abstract class ProcessControl { t.setDaemon(true); t.start(); - var ec = waitFor(); - return ec != 0 ? Optional.of(read.get().trim()) : Optional.empty(); + var ec = pc.waitFor(); + if (ec == -1) { + throw new ProcessOutputException("Command timed out"); + } + + if (ec == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) { + return read.get().trim(); + } else { + throw new ProcessOutputException( + "Command returned with " + ec + ": " + readError.get().trim()); + } } public Thread discardOut() { diff --git a/core/src/main/java/io/xpipe/core/store/ShellStore.java b/core/src/main/java/io/xpipe/core/store/ShellStore.java index 8935583bf..7d9a87947 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellStore.java +++ b/core/src/main/java/io/xpipe/core/store/ShellStore.java @@ -2,11 +2,8 @@ package io.xpipe.core.store; import io.xpipe.core.util.SecretValue; -import java.io.IOException; -import java.io.UncheckedIOException; +import java.nio.charset.Charset; import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; public interface ShellStore extends DataStore { @@ -18,78 +15,6 @@ public interface ShellStore extends DataStore { return List.of(); } - public default String executeAndRead(List cmd, Integer timeout) throws Exception { - var pc = prepareCommand(List.of(), cmd, getEffectiveTimeOut(timeout)); - pc.start(); - pc.discardErr(); - var string = new String(pc.getStdout().readAllBytes(), pc.getCharset()); - return string; - } - - public default String executeAndCheckOut(List in, List cmd, Integer timeout) - throws ProcessOutputException, Exception { - var pc = prepareCommand(in, cmd, getEffectiveTimeOut(timeout)); - pc.start(); - - AtomicReference readError = new AtomicReference<>(); - var errorThread = new Thread(() -> { - try { - - readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - errorThread.setDaemon(true); - errorThread.start(); - - AtomicReference read = new AtomicReference<>(); - var t = new Thread(() -> { - try { - read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - t.setDaemon(true); - t.start(); - - var ec = pc.waitFor(); - if (ec == -1) { - throw new ProcessOutputException("Command timed out"); - } - - if (ec == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) { - return read.get().trim(); - } else { - throw new ProcessOutputException( - "Command returned with " + ec + ": " + readError.get().trim()); - } - } - - public default Optional executeAndCheckErr(List in, List cmd) throws Exception { - var pc = prepareCommand(in, cmd, getTimeout()); - pc.start(); - var outT = pc.discardOut(); - - AtomicReference read = new AtomicReference<>(); - var t = new Thread(() -> { - try { - read.set(new String(pc.getStderr().readAllBytes(), pc.getCharset())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - t.setDaemon(true); - t.start(); - - outT.join(); - t.join(); - - var ec = pc.waitFor(); - return ec != 0 ? Optional.of(read.get()) : Optional.empty(); - } - public default Integer getEffectiveTimeOut(Integer timeout) { if (this.getTimeout() == null) { return timeout; @@ -100,18 +25,18 @@ public interface ShellStore extends DataStore { return Math.min(getTimeout(), timeout); } - public default ProcessControl prepareCommand(List cmd, Integer timeout) throws Exception { - return prepareCommand(List.of(), cmd, timeout); + public default ProcessControl prepareCommand(List cmd, Integer timeout, Charset charset) throws Exception { + return prepareCommand(List.of(), cmd, timeout, charset); } - public abstract ProcessControl prepareCommand(List input, List cmd, Integer timeout) + public abstract ProcessControl prepareCommand(List input, List cmd, Integer timeout, Charset charset) throws Exception; - public default ProcessControl preparePrivilegedCommand(List cmd, Integer timeout) throws Exception { - return preparePrivilegedCommand(List.of(), cmd, timeout); + public default ProcessControl preparePrivilegedCommand(List cmd, Integer timeout, Charset charset) throws Exception { + return preparePrivilegedCommand(List.of(), cmd, timeout, charset); } - public default ProcessControl preparePrivilegedCommand(List input, List cmd, Integer timeout) + public default ProcessControl preparePrivilegedCommand(List input, List cmd, Integer timeout, Charset charset) throws Exception { throw new UnsupportedOperationException(); } diff --git a/core/src/main/java/io/xpipe/core/store/ShellTypes.java b/core/src/main/java/io/xpipe/core/store/ShellTypes.java index 31c050e54..1021f75f3 100644 --- a/core/src/main/java/io/xpipe/core/store/ShellTypes.java +++ b/core/src/main/java/io/xpipe/core/store/ShellTypes.java @@ -18,11 +18,18 @@ public class ShellTypes { public static final StandardShellStore.ShellType SH = new Sh(); public static StandardShellStore.ShellType determine(ShellStore store) throws Exception { - var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null).strip(); + var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII) + .executeAndReadStdoutOrThrow() + .strip(); if (!o.equals("$0")) { return SH; } else { - o = store.executeAndCheckOut(List.of(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), null) + o = store.prepareCommand( + List.of(), + List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), + null, + StandardCharsets.UTF_16LE) + .executeAndReadStdoutOrThrow() .trim(); if (o.equals("PowerShell")) { return POWERSHELL; @@ -33,7 +40,8 @@ public class ShellTypes { } public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception { - var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null); + var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII) + .executeAndReadStdoutOrThrow(); if (!o.trim().equals("$0")) { return getLinuxShells(); } else { @@ -43,7 +51,7 @@ public class ShellTypes { public static StandardShellStore.ShellType getDefault() { if (System.getProperty("os.name").startsWith("Windows")) { - return CMD; + return POWERSHELL; } else { return SH; } @@ -71,18 +79,20 @@ public class ShellTypes { var l = new ArrayList<>(cmd); l.add(0, "cmd.exe"); l.add(1, "/c"); - l.add(2, "@chcp 65001"); - l.add(3, ">"); - l.add(4, "nul"); - l.add(5, "&&"); + l.add(2, "@chcp"); + l.add(3, "65001"); + l.add(4, ">"); + l.add(5, "nul"); + l.add(6, "&&"); return l; } @Override public ProcessControl prepareElevatedCommand( - ShellStore st, List in, List cmd, Integer timeout, String pw) throws Exception { + ShellStore st, List in, List cmd, Integer timeout, String pw, Charset charset) + throws Exception { var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0"); - return st.prepareCommand(List.of(), l, timeout); + return st.prepareCommand(List.of(), l, timeout, charset); } @Override @@ -184,12 +194,13 @@ public class ShellTypes { @Override public ProcessControl prepareElevatedCommand( - ShellStore st, List in, List cmd, Integer timeout, String pw) throws Exception { + ShellStore st, List in, List cmd, Integer timeout, String pw, Charset charset) + throws Exception { var l = new ArrayList<>(cmd); l.add(0, "sudo"); l.add(1, "-S"); var pws = new ByteArrayInputStream(pw.getBytes(determineCharset(st))); - return st.prepareCommand(List.of(SecretValue.createForSecretValue(pw)), l, timeout); + return st.prepareCommand(List.of(SecretValue.createForSecretValue(pw)), l, timeout, charset); } @Override diff --git a/core/src/main/java/io/xpipe/core/store/StandardShellStore.java b/core/src/main/java/io/xpipe/core/store/StandardShellStore.java index 9b6076c04..907ca76d4 100644 --- a/core/src/main/java/io/xpipe/core/store/StandardShellStore.java +++ b/core/src/main/java/io/xpipe/core/store/StandardShellStore.java @@ -22,7 +22,12 @@ public interface StandardShellStore extends MachineFileStore, ShellStore { public abstract ShellType determineType() throws Exception; public default String querySystemName() throws Exception { - var result = executeAndCheckOut(List.of(), determineType().getOperatingSystemNameCommand(), getTimeout()); + var result = prepareCommand( + List.of(), + determineType().getOperatingSystemNameCommand(), + getTimeout(), + determineType().determineCharset(this)) + .executeAndReadStdoutOrThrow(); return result.strip(); } @@ -30,7 +35,7 @@ public interface StandardShellStore extends MachineFileStore, ShellStore { public default InputStream openInput(String file) throws Exception { var type = determineType(); var cmd = type.createFileReadCommand(file); - var p = prepareCommand(List.of(), cmd, null); + var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this)); p.start(); return p.getStdout(); } @@ -49,7 +54,7 @@ public interface StandardShellStore extends MachineFileStore, ShellStore { public default boolean exists(String file) throws Exception { var type = determineType(); var cmd = type.createFileExistsCommand(file); - var p = prepareCommand(List.of(), cmd, null); + var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this)); p.start(); return p.waitFor() == 0; } @@ -63,8 +68,9 @@ public interface StandardShellStore extends MachineFileStore, ShellStore { List switchTo(List cmd); default ProcessControl prepareElevatedCommand( - ShellStore st, List in, List cmd, Integer timeout, String pw) throws Exception { - return st.prepareCommand(in, cmd, timeout); + ShellStore st, List in, List cmd, Integer timeout, String pw, Charset charset) + throws Exception { + return st.prepareCommand(in, cmd, timeout, charset); } List createFileReadCommand(String file); diff --git a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java index 68a070124..c50816452 100644 --- a/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java +++ b/extension/src/main/java/io/xpipe/extension/DataSourceProvider.java @@ -19,7 +19,7 @@ public interface DataSourceProvider> { default Category getCategory() { if (getFileProvider() != null) { - return Category.FILE; + return Category.STREAM; } throw new ExtensionException("Provider has no set general type"); @@ -43,7 +43,7 @@ public interface DataSourceProvider> { return getId() + "." + key; } - default Region configGui(Property source, Property appliedSource, boolean all) { + default Region configGui(Property source, boolean all) { return null; } @@ -120,7 +120,7 @@ public interface DataSourceProvider> { List getPossibleNames(); static enum Category { - FILE, + STREAM, DATABASE; } diff --git a/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java b/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java index 70c9a322a..17b05a717 100644 --- a/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java +++ b/extension/src/main/java/io/xpipe/extension/SupportedApplicationProvider.java @@ -46,6 +46,11 @@ public interface SupportedApplicationProvider { PASSIVE } + enum Direction { + RETRIEVE, + UPDATE + } + @Value @AllArgsConstructor public static class InstructionsDisplay { diff --git a/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java b/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java index 4e373ff29..c54efbb8c 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/CharsetChoiceComp.java @@ -26,18 +26,20 @@ public class CharsetChoiceComp extends SimpleComp { }, new Label(I18n.get("extension.none")), null); + builder.addFilter((charset, filter) -> { + return charset.getCharset().displayName().contains(filter); + }); builder.addHeader(I18n.get("extension.common")); for (var e : StreamCharset.COMMON) { builder.add(e); } builder.addHeader(I18n.get("extension.other")); - builder.addFilter((charset, filter) -> { - return charset.getCharset().displayName().contains(filter); - }); for (var e : StreamCharset.RARE) { builder.add(e); } - return builder.build(); + var comboBox = builder.build(); + comboBox.setVisibleRowCount(16); + return comboBox; } } diff --git a/extension/src/main/java/io/xpipe/extension/comp/CodeSnippetComp.java b/extension/src/main/java/io/xpipe/extension/comp/CodeSnippetComp.java index 93b2b82ab..11c8c6663 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/CodeSnippetComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/CodeSnippetComp.java @@ -8,7 +8,6 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableValue; import javafx.scene.control.Button; import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; import javafx.scene.input.ScrollEvent; import javafx.scene.layout.*; import javafx.scene.paint.Color; @@ -19,7 +18,7 @@ import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; -public class CodeSnippetComp extends Comp> { +public class CodeSnippetComp extends Comp> { private final ObservableValue showLineNumbers; private final ObservableValue value; @@ -79,7 +78,7 @@ public class CodeSnippetComp extends Comp> { } @Override - public CompStructure createBase() { + public CompStructure createBase() { var s = new InlineCssTextArea(); s.setEditable(false); s.setBackground(null); @@ -88,6 +87,7 @@ public class CodeSnippetComp extends Comp> { s.getParent().fireEvent(e); e.consume(); }); + s.prefHeightProperty().setValue(20* this.value.getValue().lines().stream().count()); var lineNumbers = new VBox(); lineNumbers.getStyleClass().add("line-numbers"); @@ -115,13 +115,10 @@ public class CodeSnippetComp extends Comp> { } }); HBox.setHgrow(s, Priority.ALWAYS); - var container = new ScrollPane(content); - container.setFitToWidth(true); - container.setFitToHeight(true); - var c = new StackPane(container); - container.prefHeightProperty().bind(c.heightProperty()); + var c = new StackPane(content); c.getStyleClass().add("code-snippet-container"); + c.prefHeightProperty().bind(content.prefHeightProperty()); var copyButton = createCopyButton(c); var pane = new AnchorPane(copyButton); diff --git a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java index 2f4c88229..e6ca1663b 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/DynamicOptionsComp.java @@ -6,7 +6,6 @@ import io.xpipe.fxcomps.SimpleCompStructure; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.value.ObservableValue; -import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -34,7 +33,6 @@ public class DynamicOptionsComp extends Comp> { flow.setAlignment(Pos.CENTER); flow.setHgap(7); flow.setVgap(7); - flow.setPadding(new Insets(8, 0, 0, 0)); var nameRegions = new ArrayList(); var compRegions = new ArrayList(); diff --git a/extension/src/main/java/io/xpipe/extension/comp/PrettyImageComp.java b/extension/src/main/java/io/xpipe/extension/comp/PrettyImageComp.java new file mode 100644 index 000000000..52705c2a3 --- /dev/null +++ b/extension/src/main/java/io/xpipe/extension/comp/PrettyImageComp.java @@ -0,0 +1,97 @@ +package io.xpipe.extension.comp; + +import io.xpipe.extension.util.XPipeDaemon; +import io.xpipe.fxcomps.SimpleComp; +import io.xpipe.fxcomps.util.PlatformThread; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.web.WebView; + +public class PrettyImageComp extends SimpleComp { + + private final ObservableValue value; + private final double width; + private final double height; + + public PrettyImageComp(ObservableValue value, double width, double height) { + this.value = value; + this.width = width; + this.height = height; + } + + @Override + protected Region createSimple() { + var aspectRatioProperty = new SimpleDoubleProperty(1); + var widthProperty = Bindings.createDoubleBinding( + () -> { + boolean widthLimited = width / height < aspectRatioProperty.doubleValue(); + if (widthLimited) { + return width; + } else { + return height * aspectRatioProperty.doubleValue(); + } + }, + aspectRatioProperty); + var heightProperty = Bindings.createDoubleBinding( + () -> { + boolean widthLimited = width / height < aspectRatioProperty.doubleValue(); + if (widthLimited) { + return width / aspectRatioProperty.doubleValue(); + } else { + return height; + } + }, + aspectRatioProperty); + + Node node; + + if (value.getValue().endsWith(".svg")) { + var storeIcon = + SvgComp.create(Bindings.createStringBinding(() -> XPipeDaemon.getInstance().svgImage(value.getValue()), value)); + aspectRatioProperty.bind(Bindings.createDoubleBinding( + () -> { + return storeIcon.getWidth().getValue().doubleValue() + / storeIcon.getHeight().getValue().doubleValue(); + }, + storeIcon.getWidth(), + storeIcon.getHeight())); + node = storeIcon.createWebview(); + ((WebView) node).prefWidthProperty().bind(widthProperty); + ((WebView) node).maxWidthProperty().bind(widthProperty); + ((WebView) node).minWidthProperty().bind(widthProperty); + ((WebView) node).prefHeightProperty().bind(heightProperty); + ((WebView) node).maxHeightProperty().bind(heightProperty); + ((WebView) node).minHeightProperty().bind(heightProperty); + } else { + var storeIcon = new ImageView(); + storeIcon + .imageProperty() + .bind(Bindings.createObjectBinding( + () -> { + var image = XPipeDaemon.getInstance().image(value.getValue()); + aspectRatioProperty.set(image.getWidth() / image.getHeight()); + return image; + }, + PlatformThread.sync(value))); + ; + storeIcon.fitWidthProperty().bind(widthProperty); + storeIcon.fitHeightProperty().bind(heightProperty); + storeIcon.setSmooth(true); + node = storeIcon; + } + + var stack = new StackPane(node); + stack.setPrefWidth(width); + stack.setMinWidth(width); + stack.setPrefHeight(height); + stack.setMinHeight(height); + stack.setAlignment(Pos.CENTER); + return stack; + } +} diff --git a/extension/src/main/java/io/xpipe/extension/comp/SvgComp.java b/extension/src/main/java/io/xpipe/extension/comp/SvgComp.java index 543214916..917f8d6bc 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/SvgComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/SvgComp.java @@ -100,6 +100,10 @@ public class SvgComp { wv.maxWidthProperty().bind(wv.prefWidthProperty()); wv.maxHeightProperty().bind(wv.prefHeightProperty()); + + wv.minWidthProperty().bind(wv.prefWidthProperty()); + wv.minHeightProperty().bind(wv.prefHeightProperty()); + return wv; } diff --git a/extension/src/main/java/io/xpipe/extension/comp/TextAreaComp.java b/extension/src/main/java/io/xpipe/extension/comp/TextAreaComp.java index 124efb603..db6076327 100644 --- a/extension/src/main/java/io/xpipe/extension/comp/TextAreaComp.java +++ b/extension/src/main/java/io/xpipe/extension/comp/TextAreaComp.java @@ -5,27 +5,47 @@ import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.util.PlatformThread; import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; import javafx.scene.control.TextArea; public class TextAreaComp extends Comp> { private final Property value; + private final Property lazyValue = new SimpleStringProperty(); + private final boolean lazy; public TextAreaComp(Property value) { + this(value, false); + } + + public TextAreaComp(Property value, boolean lazy) { this.value = value; + this.lazy = lazy; + if (!lazy) { + value.bind(lazyValue); + } } @Override public CompStructure