mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
Various small fixes
This commit is contained in:
parent
aa07f88e1d
commit
38efb368ec
13 changed files with 466 additions and 79 deletions
|
@ -73,7 +73,7 @@ public final class XPipeConnection extends BeaconConnection {
|
|||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
var s = BeaconClient.tryConnect();
|
||||
var s = BeaconClient.tryConnect(BeaconClient.ApiClientInformation.builder().version("?").language("Java").build());
|
||||
if (s.isPresent()) {
|
||||
return s;
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ public final class XPipeConnection extends BeaconConnection {
|
|||
}
|
||||
|
||||
try {
|
||||
beaconClient = new BeaconClient();
|
||||
beaconClient = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java").build());
|
||||
} catch (Exception ex) {
|
||||
throw new BeaconException("Unable to connect to running xpipe daemon", ex);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ public class XPipeDaemonController {
|
|||
return;
|
||||
}
|
||||
|
||||
var client = new BeaconClient();
|
||||
var client = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java API Test").build());
|
||||
if (!BeaconServer.tryStop(client)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package io.xpipe.beacon;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
@ -9,7 +11,12 @@ import com.fasterxml.jackson.databind.node.TextNode;
|
|||
import io.xpipe.beacon.exchange.MessageExchanges;
|
||||
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
|
||||
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
|
||||
import io.xpipe.core.store.ProcessControl;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
|
@ -23,27 +30,90 @@ import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR;
|
|||
|
||||
public class BeaconClient implements AutoCloseable {
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
public static abstract class ClientInformation {
|
||||
|
||||
public final CliClientInformation cli() {
|
||||
return (CliClientInformation) this;
|
||||
}
|
||||
|
||||
public abstract String toDisplayString();
|
||||
}
|
||||
|
||||
@JsonTypeName("cli")
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class CliClientInformation extends ClientInformation {
|
||||
|
||||
String version;
|
||||
int consoleWidth;
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return "X-Pipe CLI " + version;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("gateway")
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class GatewayClientInformation extends ClientInformation {
|
||||
|
||||
String version;
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return "X-Pipe Gateway " + version;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeName("api")
|
||||
@Value
|
||||
@Builder
|
||||
@Jacksonized
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public static class ApiClientInformation extends ClientInformation {
|
||||
|
||||
String version;
|
||||
String language;
|
||||
|
||||
@Override
|
||||
public String toDisplayString() {
|
||||
return String.format("X-Pipe %s API v%s", language, version);
|
||||
}
|
||||
}
|
||||
|
||||
private final Closeable closeable;
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
|
||||
public BeaconClient() throws IOException {
|
||||
var socket = new Socket(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort());
|
||||
closeable = socket;
|
||||
in = socket.getInputStream();
|
||||
out = socket.getOutputStream();
|
||||
}
|
||||
|
||||
public BeaconClient(Closeable closeable, InputStream in, OutputStream out) {
|
||||
private BeaconClient(Closeable closeable, InputStream in, OutputStream out) {
|
||||
this.closeable = closeable;
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public static Optional<BeaconClient> tryConnect() {
|
||||
public static BeaconClient connect(ClientInformation information) throws Exception {
|
||||
var socket = new Socket(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort());
|
||||
var client = new BeaconClient(socket, socket.getInputStream(), socket.getOutputStream());
|
||||
client.sendObject(JacksonMapper.newMapper().valueToTree(information));
|
||||
return client;
|
||||
}
|
||||
|
||||
public static BeaconClient connectGateway(ProcessControl control, GatewayClientInformation information) throws Exception {
|
||||
var client = new BeaconClient(() -> {}, control.getStdout(), control.getStdin());
|
||||
client.sendObject(JacksonMapper.newMapper().valueToTree(information));
|
||||
return client;
|
||||
}
|
||||
|
||||
public static Optional<BeaconClient> tryConnect(ClientInformation information) {
|
||||
try {
|
||||
return Optional.of(new BeaconClient());
|
||||
} catch (IOException ex) {
|
||||
return Optional.of(connect(information));
|
||||
} catch (Exception ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
@ -95,10 +165,14 @@ public class BeaconClient implements AutoCloseable {
|
|||
"Sending request to server of type " + req.getClass().getName());
|
||||
}
|
||||
|
||||
sendObject(msg);
|
||||
}
|
||||
|
||||
public void sendObject(JsonNode node) throws ConnectorException {
|
||||
var writer = new StringWriter();
|
||||
var mapper = JacksonMapper.newMapper();
|
||||
try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
|
||||
g.writeTree(msg);
|
||||
g.writeTree(node);
|
||||
} catch (IOException ex) {
|
||||
throw new ConnectorException("Couldn't serialize request", ex);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package io.xpipe.beacon;
|
||||
|
||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
|
||||
public class BeaconJacksonModule extends SimpleModule {
|
||||
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
context.registerSubtypes(
|
||||
new NamedType(BeaconClient.ApiClientInformation.class),
|
||||
new NamedType(BeaconClient.CliClientInformation.class),
|
||||
new NamedType(BeaconClient.GatewayClientInformation.class));
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ public class BeaconServer {
|
|||
}
|
||||
|
||||
public static boolean isRunning() {
|
||||
try (var socket = new BeaconClient()) {
|
||||
try (var socket = BeaconClient.connect(null)) {
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import com.fasterxml.jackson.databind.Module;
|
||||
import io.xpipe.beacon.BeaconJacksonModule;
|
||||
import io.xpipe.beacon.exchange.*;
|
||||
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
|
||||
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
||||
|
@ -24,6 +26,7 @@ module io.xpipe.beacon {
|
|||
|
||||
uses MessageExchange;
|
||||
|
||||
provides Module with BeaconJacksonModule;
|
||||
provides io.xpipe.beacon.exchange.MessageExchange with
|
||||
ForwardExchange,
|
||||
InstanceExchange,
|
||||
|
|
|
@ -16,6 +16,7 @@ public abstract class ProcessControl {
|
|||
pc.discardErr();
|
||||
var bytes = pc.getStdout().readAllBytes();
|
||||
var string = new String(bytes, pc.getCharset());
|
||||
pc.waitFor();
|
||||
return string;
|
||||
}
|
||||
|
||||
|
@ -27,6 +28,15 @@ public abstract class ProcessControl {
|
|||
pc.waitFor();
|
||||
}
|
||||
|
||||
public boolean executeAndCheckStatus() {
|
||||
try {
|
||||
executeOrThrow();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> executeAndReadStderrIfPresent() throws Exception {
|
||||
var pc = this;
|
||||
pc.start();
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package io.xpipe.extension;
|
||||
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.extension.event.ErrorEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public interface DataStoreActionProvider<T extends DataStore> {
|
||||
|
||||
static List<DataStoreActionProvider<?>> ALL = new ArrayList<>();
|
||||
|
||||
public static void init(ModuleLayer layer) {
|
||||
if (ALL.size() == 0) {
|
||||
ALL.addAll(ServiceLoader.load(layer, DataStoreActionProvider.class).stream()
|
||||
.map(p -> (DataStoreActionProvider<?>) p.get())
|
||||
.filter(provider -> {
|
||||
try {
|
||||
return provider.isActive();
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
|
||||
Class<T> getApplicableClass();
|
||||
|
||||
default boolean isActive() throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
default boolean isApplicable(T o) throws Exception {
|
||||
return true;
|
||||
}
|
||||
|
||||
default void applyToRegion(T store, Region region) {}
|
||||
|
||||
String getName(T store);
|
||||
|
||||
String getIcon(T store);
|
||||
|
||||
default void execute(T store) throws Exception {}
|
||||
}
|
|
@ -6,17 +6,204 @@ import io.xpipe.fxcomps.CompStructure;
|
|||
import io.xpipe.fxcomps.augment.Augment;
|
||||
import io.xpipe.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.fxcomps.util.Shortcuts;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.event.WeakEventHandler;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||
|
||||
static {
|
||||
JFXTooltip.setHoverDelay(Duration.millis(400));
|
||||
JFXTooltip.setVisibleDuration(Duration.INDEFINITE);
|
||||
private static final TooltipBehavior BEHAVIOR = new TooltipBehavior(Duration.millis(400), Duration.INDEFINITE, Duration.millis(100));
|
||||
|
||||
private static class TooltipBehavior {
|
||||
|
||||
private static String TOOLTIP_PROP = "jfoenix-tooltip";
|
||||
private Timeline hoverTimer = new Timeline();
|
||||
private Timeline visibleTimer = new Timeline();
|
||||
private Timeline leftTimer = new Timeline();
|
||||
/**
|
||||
* the currently hovered node
|
||||
*/
|
||||
private Node hoveredNode;
|
||||
/**
|
||||
* the next tooltip to be shown
|
||||
*/
|
||||
private JFXTooltip nextTooltip;
|
||||
/**
|
||||
* the current showing tooltip
|
||||
*/
|
||||
private JFXTooltip currentTooltip;
|
||||
|
||||
private TooltipBehavior(Duration hoverDelay, Duration visibleDuration, Duration leftDelay) {
|
||||
setHoverDelay(hoverDelay);
|
||||
hoverTimer.setOnFinished(event -> {
|
||||
ensureHoveredNodeIsVisible(() -> {
|
||||
// set tooltip orientation
|
||||
NodeOrientation nodeOrientation = hoveredNode.getEffectiveNodeOrientation();
|
||||
nextTooltip.getScene().setNodeOrientation(nodeOrientation);
|
||||
//show tooltip
|
||||
showTooltip(nextTooltip);
|
||||
currentTooltip = nextTooltip;
|
||||
hoveredNode = null;
|
||||
// start visible timer
|
||||
visibleTimer.playFromStart();
|
||||
});
|
||||
// clear next tooltip
|
||||
nextTooltip = null;
|
||||
});
|
||||
setVisibleDuration(visibleDuration);
|
||||
visibleTimer.setOnFinished(event -> hideCurrentTooltip());
|
||||
setLeftDelay(leftDelay);
|
||||
leftTimer.setOnFinished(event -> hideCurrentTooltip());
|
||||
}
|
||||
|
||||
private void setHoverDelay(Duration duration) {
|
||||
hoverTimer.getKeyFrames().setAll(new KeyFrame(duration));
|
||||
}
|
||||
|
||||
private void setVisibleDuration(Duration duration) {
|
||||
visibleTimer.getKeyFrames().setAll(new KeyFrame(duration));
|
||||
}
|
||||
|
||||
private void setLeftDelay(Duration duration) {
|
||||
leftTimer.getKeyFrames().setAll(new KeyFrame(duration));
|
||||
}
|
||||
|
||||
private void hideCurrentTooltip() {
|
||||
currentTooltip.hide();
|
||||
currentTooltip = null;
|
||||
hoveredNode = null;
|
||||
}
|
||||
|
||||
private void showTooltip(JFXTooltip tooltip) {
|
||||
// anchors are computed differently for each tooltip
|
||||
tooltip.show(hoveredNode, -1, -1);
|
||||
}
|
||||
|
||||
private EventHandler<MouseEvent> moveHandler = (MouseEvent event) -> {
|
||||
// if tool tip is already showing, do nothing
|
||||
if (visibleTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||
return;
|
||||
}
|
||||
hoveredNode = (Node) event.getSource();
|
||||
Object property = hoveredNode.getProperties().get(TOOLTIP_PROP);
|
||||
if (property instanceof JFXTooltip) {
|
||||
JFXTooltip tooltip = (JFXTooltip) property;
|
||||
ensureHoveredNodeIsVisible(() -> {
|
||||
// if a tooltip is already showing then show this tooltip immediately
|
||||
if (leftTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||
if (currentTooltip != null) {
|
||||
currentTooltip.hide();
|
||||
}
|
||||
currentTooltip = tooltip;
|
||||
// show the tooltip
|
||||
showTooltip(tooltip);
|
||||
// stop left timer and start the visible timer to hide the tooltip
|
||||
// once finished
|
||||
leftTimer.stop();
|
||||
visibleTimer.playFromStart();
|
||||
} else {
|
||||
// else mark the tooltip as the next tooltip to be shown once the hover
|
||||
// timer is finished (restart the timer)
|
||||
// t.setActivated(true);
|
||||
nextTooltip = tooltip;
|
||||
hoverTimer.stop();
|
||||
hoverTimer.playFromStart();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uninstall(hoveredNode);
|
||||
}
|
||||
};
|
||||
private WeakEventHandler<MouseEvent> weakMoveHandler = new WeakEventHandler<>(moveHandler);
|
||||
|
||||
private EventHandler<MouseEvent> exitHandler = (MouseEvent event) -> {
|
||||
// stop running hover timer as the mouse exited the node
|
||||
if (hoverTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||
hoverTimer.stop();
|
||||
} else if (visibleTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||
// if tool tip was already showing, stop the visible timer
|
||||
// and start the left timer to hide the current tooltip
|
||||
visibleTimer.stop();
|
||||
leftTimer.playFromStart();
|
||||
}
|
||||
hoveredNode = null;
|
||||
nextTooltip = null;
|
||||
};
|
||||
private WeakEventHandler<MouseEvent> weakExitHandler = new WeakEventHandler<>(exitHandler);
|
||||
|
||||
// if mouse is pressed then stop all timers / clear all fields
|
||||
private EventHandler<MouseEvent> pressedHandler = (MouseEvent event) -> {
|
||||
// stop timers
|
||||
hoverTimer.stop();
|
||||
visibleTimer.stop();
|
||||
leftTimer.stop();
|
||||
// hide current tooltip
|
||||
if (currentTooltip != null) {
|
||||
currentTooltip.hide();
|
||||
}
|
||||
// clear fields
|
||||
hoveredNode = null;
|
||||
currentTooltip = null;
|
||||
nextTooltip = null;
|
||||
};
|
||||
private WeakEventHandler<MouseEvent> weakPressedHandler = new WeakEventHandler<>(pressedHandler);
|
||||
|
||||
private void install(Node node, JFXTooltip tooltip) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
if (tooltip == null) {
|
||||
uninstall(node);
|
||||
return;
|
||||
}
|
||||
node.removeEventHandler(MouseEvent.MOUSE_MOVED, weakMoveHandler);
|
||||
node.removeEventHandler(MouseEvent.MOUSE_EXITED, weakExitHandler);
|
||||
node.removeEventHandler(MouseEvent.MOUSE_PRESSED, weakPressedHandler);
|
||||
node.addEventHandler(MouseEvent.MOUSE_MOVED, weakMoveHandler);
|
||||
node.addEventHandler(MouseEvent.MOUSE_EXITED, weakExitHandler);
|
||||
node.addEventHandler(MouseEvent.MOUSE_PRESSED, weakPressedHandler);
|
||||
node.getProperties().put(TOOLTIP_PROP, tooltip);
|
||||
}
|
||||
|
||||
private void uninstall(Node node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.removeEventHandler(MouseEvent.MOUSE_MOVED, weakMoveHandler);
|
||||
node.removeEventHandler(MouseEvent.MOUSE_EXITED, weakExitHandler);
|
||||
node.removeEventHandler(MouseEvent.MOUSE_PRESSED, weakPressedHandler);
|
||||
Object tooltip = node.getProperties().get(TOOLTIP_PROP);
|
||||
if (tooltip != null) {
|
||||
node.getProperties().remove(TOOLTIP_PROP);
|
||||
if (tooltip.equals(currentTooltip) || tooltip.equals(nextTooltip)) {
|
||||
weakPressedHandler.handle(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureHoveredNodeIsVisible(Runnable visibleRunnable) {
|
||||
final Window owner = getWindow(hoveredNode);
|
||||
if (owner != null && owner.isShowing()) {
|
||||
final boolean treeVisible = true; // NodeHelper.isTreeVisible(hoveredNode);
|
||||
if (treeVisible && owner.isFocused()) {
|
||||
visibleRunnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Window getWindow(final Node node) {
|
||||
final Scene scene = node == null ? null : node.getScene();
|
||||
return scene == null ? null : scene.getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private final ObservableValue<String> text;
|
||||
|
@ -31,21 +218,11 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
|
|||
|
||||
@Override
|
||||
public void augment(S struc) {
|
||||
var tt = new FocusTooltip();
|
||||
var toDisplay = text.getValue();
|
||||
if (Shortcuts.getShortcut(struc.get()) != null) {
|
||||
toDisplay = toDisplay + " (" + Shortcuts.getShortcut(struc.get()).getDisplayText() + ")";
|
||||
}
|
||||
tt.textProperty().setValue(toDisplay);
|
||||
tt.setStyle("-fx-font-size: 11pt;");
|
||||
JFXTooltip.install(struc.get(), tt);
|
||||
tt.setWrapText(true);
|
||||
tt.setMaxWidth(400);
|
||||
tt.getStyleClass().add("fancy-tooltip");
|
||||
augment(struc.get());
|
||||
}
|
||||
|
||||
public void augment(Node region) {
|
||||
var tt = new FocusTooltip();
|
||||
var tt = new JFXTooltip();
|
||||
var toDisplay = text.getValue();
|
||||
if (Shortcuts.getShortcut((Region) region) != null) {
|
||||
toDisplay =
|
||||
|
@ -53,57 +230,10 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
|
|||
}
|
||||
tt.textProperty().setValue(toDisplay);
|
||||
tt.setStyle("-fx-font-size: 11pt;");
|
||||
JFXTooltip.install(region, tt);
|
||||
tt.setWrapText(true);
|
||||
tt.setMaxWidth(400);
|
||||
tt.getStyleClass().add("fancy-tooltip");
|
||||
}
|
||||
|
||||
private static class FocusTooltip extends JFXTooltip {
|
||||
|
||||
public FocusTooltip() {}
|
||||
|
||||
public FocusTooltip(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void show() {
|
||||
Window owner = getOwnerWindow();
|
||||
if (owner == null || owner.isFocused()) {
|
||||
super.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Node ownerNode, double anchorX, double anchorY) {
|
||||
Window owner = getOwnerWindow();
|
||||
if (owner == null || owner.isFocused()) {
|
||||
super.show(ownerNode, anchorX, anchorY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showOnAnchors(Node ownerNode, double anchorX, double anchorY) {
|
||||
Window owner = getOwnerWindow();
|
||||
if (owner == null || owner.isFocused()) {
|
||||
super.showOnAnchors(ownerNode, anchorX, anchorY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Window owner) {
|
||||
if (owner == null || owner.isFocused()) {
|
||||
super.show(owner);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Window ownerWindow, double anchorX, double anchorY) {
|
||||
Window owner = getOwnerWindow();
|
||||
if (owner == null || owner.isFocused()) {
|
||||
super.show(ownerWindow, anchorX, anchorY);
|
||||
}
|
||||
}
|
||||
BEHAVIOR.install(region, tt);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import io.xpipe.extension.event.ErrorEvent;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class OsHelper {
|
||||
|
||||
public static String getFileSystemCompatibleName(String name) {
|
||||
return name.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||
}
|
||||
|
||||
public static Path getUserDocumentsPath() {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return Paths.get(System.getProperty("user.home"));
|
||||
} else {
|
||||
return Paths.get(System.getProperty("user.home"), ".local", "share");
|
||||
}
|
||||
}
|
||||
|
||||
public static void browseFile(Path file) {
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.run(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().open(file.toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omit().handle();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void browseFileInDirectory(Path file) {
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
|
||||
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.run(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().open(file.getParent().toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omit().handle();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.run(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().browseFileDirectory(file.toFile());
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omit().handle();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package io.xpipe.extension.util;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ThreadHelper {
|
||||
|
||||
public static Thread run(Runnable r) {
|
||||
var t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
return t;
|
||||
}
|
||||
|
||||
public static <T> T run(Supplier<T> r) {
|
||||
AtomicReference<T> ret = new AtomicReference<>();
|
||||
var t = new Thread(() -> ret.set(r.get()));
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
try {
|
||||
t.join();
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
return ret.get();
|
||||
}
|
||||
|
||||
public static Thread create(String name, boolean daemon, Runnable r) {
|
||||
var t = new Thread(r);
|
||||
t.setDaemon(daemon);
|
||||
t.setName(name);
|
||||
return t;
|
||||
}
|
||||
|
||||
public static void sleep(long ms) {
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import io.xpipe.extension.DataSourceProvider;
|
||||
import io.xpipe.extension.DataStoreActionProvider;
|
||||
import io.xpipe.extension.SupportedApplicationProvider;
|
||||
import io.xpipe.extension.util.XPipeDaemon;
|
||||
|
||||
|
@ -31,6 +32,7 @@ open module io.xpipe.extension {
|
|||
|
||||
uses DataSourceProvider;
|
||||
uses SupportedApplicationProvider;
|
||||
uses DataStoreActionProvider;
|
||||
uses io.xpipe.extension.I18n;
|
||||
uses io.xpipe.extension.event.EventHandler;
|
||||
uses io.xpipe.extension.prefs.PrefsProvider;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
io.xpipe.beacon.BeaconJacksonModule
|
Loading…
Reference in a new issue