mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-03 19:30:31 +00:00
Rework
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package io.xpipe.app.auxw;
|
||||
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.core.window.AppModifiedStage;
|
||||
import io.xpipe.app.core.window.AppWindowStyle;
|
||||
import io.xpipe.app.platform.DerivedObservableList;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
@@ -10,6 +9,7 @@ import io.xpipe.app.storage.DataStoreColor;
|
||||
import io.xpipe.app.util.GlobalTimer;
|
||||
import io.xpipe.app.util.Rect;
|
||||
import io.xpipe.core.OsType;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -22,7 +22,6 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
@@ -34,7 +33,7 @@ import java.util.function.Predicate;
|
||||
|
||||
public class AppAuxiliaryWindow {
|
||||
|
||||
private AppAuxiliaryWindow(WindowState state, AuxDockImpl model) {
|
||||
private AppAuxiliaryWindow(State state, AuxDockImpl model) {
|
||||
this.state = state;
|
||||
this.model = model;
|
||||
}
|
||||
@@ -48,7 +47,7 @@ public class AppAuxiliaryWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
WindowState state = AppCache.getNonNull("auxiliaryWindowState", WindowState.class, () -> null);
|
||||
State state = AppCache.getNonNull("auxiliaryWindowState", State.class, () -> null);
|
||||
var model = new AuxDockImpl(rect -> rect, () -> {
|
||||
return INSTANCE.nativeWinWindowControl;
|
||||
});
|
||||
@@ -56,7 +55,7 @@ public class AppAuxiliaryWindow {
|
||||
INSTANCE.startStateListener();
|
||||
}
|
||||
|
||||
private WindowState state;
|
||||
private State state;
|
||||
private Stage stage;
|
||||
private NativeWinWindowControl nativeWinWindowControl;
|
||||
|
||||
@@ -65,9 +64,11 @@ public class AppAuxiliaryWindow {
|
||||
|
||||
@Getter
|
||||
private final ObjectProperty<AuxEntry> selected = new SimpleObjectProperty<>();
|
||||
|
||||
@Getter
|
||||
private final ObservableList<AuxEntry> processes = FXCollections.observableArrayList();
|
||||
|
||||
@Getter
|
||||
private final BooleanProperty locked = new SimpleBooleanProperty();
|
||||
|
||||
private void createStage() {
|
||||
@@ -127,9 +128,18 @@ public class AppAuxiliaryWindow {
|
||||
selected.set(entry);
|
||||
}
|
||||
|
||||
public void close(AuxEntry entry) {
|
||||
model.closeWindow(entry);
|
||||
}
|
||||
|
||||
public void toggleLock() {
|
||||
locked.set(!locked.get());
|
||||
state = state.toBuilder().locked(locked.get()).build();
|
||||
}
|
||||
|
||||
public void track(String name, String icon, DataStoreColor color, Process process, Duration maxWait, Predicate<ControllableWindowProcess> filter) {
|
||||
var start = Instant.now();
|
||||
GlobalTimer.scheduleUntil(Duration.ofSeconds(1), false, () -> {
|
||||
GlobalTimer.scheduleUntil(Duration.ofMillis(200), false, () -> {
|
||||
if (Duration.between(start, Instant.now()).compareTo(maxWait) > 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -181,8 +191,8 @@ public class AppAuxiliaryWindow {
|
||||
private void updateState() {
|
||||
var oldSize = processes.size();
|
||||
model.clearDead();
|
||||
selected.set(model.getSelected());
|
||||
DerivedObservableList.wrap(processes, true).setContent(model.getEntries());
|
||||
selected.set(model.getSelected());
|
||||
if (oldSize > 0 && processes.isEmpty()) {
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
stage.hide();
|
||||
@@ -190,25 +200,35 @@ public class AppAuxiliaryWindow {
|
||||
}
|
||||
}
|
||||
|
||||
private void onWindowChange() {
|
||||
state = new WindowState(stage.isMaximized(), stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight());
|
||||
private void onWindowStateChange() {
|
||||
state = new State(state != null && state.locked, stage.isMaximized(), stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight());
|
||||
}
|
||||
|
||||
private void setupWindowListeners() {
|
||||
stage.xProperty().addListener((c, o, n) -> {
|
||||
onWindowChange();
|
||||
onWindowStateChange();
|
||||
});
|
||||
stage.yProperty().addListener((c, o, n) -> {
|
||||
onWindowChange();
|
||||
onWindowStateChange();
|
||||
});
|
||||
stage.widthProperty().addListener((c, o, n) -> {
|
||||
onWindowChange();
|
||||
onWindowStateChange();
|
||||
locked.set(false);
|
||||
});
|
||||
stage.heightProperty().addListener((c, o, n) -> {
|
||||
onWindowChange();
|
||||
onWindowStateChange();
|
||||
locked.set(false);
|
||||
});
|
||||
stage.maximizedProperty().addListener((c, o, n) -> {
|
||||
onWindowChange();
|
||||
onWindowStateChange();
|
||||
locked.set(false);
|
||||
Platform.runLater(() -> {
|
||||
stage.setWidth(state.getWindowWidth());
|
||||
stage.setHeight(state.getWindowHeight());
|
||||
});
|
||||
});
|
||||
locked.addListener((v, o, n) -> {
|
||||
stage.setResizable(!n);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,10 +242,11 @@ public class AppAuxiliaryWindow {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Builder
|
||||
@Builder(toBuilder = true)
|
||||
@Jacksonized
|
||||
@Value
|
||||
public static class WindowState {
|
||||
public static class State {
|
||||
boolean locked;
|
||||
boolean maximized;
|
||||
double windowX;
|
||||
double windowY;
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
package io.xpipe.app.auxw;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.SimpleRegionBuilder;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.platform.LabelGraphic;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AuxDockCompImpl extends SimpleRegionBuilder {
|
||||
|
||||
@@ -26,8 +37,9 @@ public class AuxDockCompImpl extends SimpleRegionBuilder {
|
||||
vbox.focusWithinProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
var w = AppAuxiliaryWindow.get();
|
||||
var target = vbox.getScene().getRoot().lookup(".button:hover");
|
||||
if (target == null || target.getProperties().get("entry").equals(w.getSelected().getValue())) {
|
||||
var target = vbox.getScene().getRoot().lookup("*:hover");
|
||||
if (target == null || (target.getProperties().get("entry") != null &&
|
||||
target.getProperties().get("entry").equals(w.getSelected().getValue()))) {
|
||||
Platform.runLater(() -> {
|
||||
w.focus();
|
||||
});
|
||||
@@ -37,39 +49,80 @@ public class AuxDockCompImpl extends SimpleRegionBuilder {
|
||||
return vbox;
|
||||
}
|
||||
|
||||
private void fillToolbar(ToolBar bar, List<? extends AuxEntry> list) {
|
||||
var w = AppAuxiliaryWindow.get();
|
||||
bar.getItems().clear();
|
||||
for (var entry : list) {
|
||||
var graphic = PrettyImageHelper.ofFixedSizeSquare(entry.getIcon(), 16).style("graphic").build();
|
||||
|
||||
var label = new LabelComp(entry.getName()).build();
|
||||
label.setGraphic(graphic);
|
||||
|
||||
var close = new IconButtonComp("mdi2c-close", () -> {
|
||||
w.close(entry);
|
||||
}).style("close-button")
|
||||
.describe(d -> d.nameKey("close")).build();
|
||||
AppFontSizes.sm(close);
|
||||
|
||||
var hbox = new HBox(label, close);
|
||||
hbox.setSpacing(6);
|
||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var b = new Button(null, hbox);
|
||||
if (entry.getColor() != null) {
|
||||
b.getStyleClass().add(entry.getColor().getId());
|
||||
}
|
||||
b.getStyleClass().add("color-box");
|
||||
b.getStyleClass().add("tab-button");
|
||||
b.getProperties().put("entry", entry);
|
||||
b.setOnAction(event -> {
|
||||
w.select(entry);
|
||||
event.consume();
|
||||
});
|
||||
bar.getItems().add(b);
|
||||
}
|
||||
|
||||
bar.getItems().add(new Spacer());
|
||||
var lockIcon = Bindings.createObjectBinding(() -> {
|
||||
return new LabelGraphic.IconGraphic(w.getLocked().get() ? "mdi2l-lock-outline" : "mdi2l-lock-open-variant-outline");
|
||||
}, w.getLocked());
|
||||
var lock = new IconButtonComp(lockIcon, () -> {
|
||||
w.toggleLock();
|
||||
w.focus();
|
||||
}).describe(d -> d.nameKey("toggleSizeLock").showTooltips(true)).style("lock-button").build();
|
||||
bar.getItems().add(lock);
|
||||
}
|
||||
|
||||
private void updateSelection(ToolBar bar, AuxEntry entry) {
|
||||
for (Node item : bar.getItems()) {
|
||||
if (item.getProperties().get("entry") != null) {
|
||||
if (item.getProperties().get("entry").equals(entry)) {
|
||||
item.pseudoClassStateChanged(PseudoClass.getPseudoClass("selected"), true);
|
||||
} else {
|
||||
item.pseudoClassStateChanged(PseudoClass.getPseudoClass("selected"), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Region createBar() {
|
||||
var w = AppAuxiliaryWindow.get();
|
||||
var bar = new ToolBar();
|
||||
|
||||
fillToolbar(bar, w.getProcesses());
|
||||
w.getProcesses().addListener((ListChangeListener<? super AuxEntry>) c -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
bar.getItems().clear();
|
||||
for (var entry : c.getList()) {
|
||||
var b = new Button(entry.getName());
|
||||
b.setGraphic(PrettyImageHelper.ofFixedSizeSquare(entry.getIcon(), 16).build());
|
||||
if (entry.getColor() != null) {
|
||||
b.getStyleClass().add(entry.getColor().getId());
|
||||
}
|
||||
b.getStyleClass().add("color-box");
|
||||
b.getProperties().put("entry", entry);
|
||||
b.setOnAction(event -> {
|
||||
w.select(entry);
|
||||
event.consume();
|
||||
});
|
||||
bar.getItems().add(b);
|
||||
}
|
||||
fillToolbar(bar, c.getList());
|
||||
});
|
||||
});
|
||||
|
||||
updateSelection(bar, w.getSelected().getValue());
|
||||
w.getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
for (Node item : bar.getItems()) {
|
||||
if (item.getProperties().get("entry").equals(newValue)) {
|
||||
item.pseudoClassStateChanged(PseudoClass.getPseudoClass("selected"), true);
|
||||
} else {
|
||||
item.pseudoClassStateChanged(PseudoClass.getPseudoClass("selected"), false);
|
||||
}
|
||||
}
|
||||
updateSelection(bar, newValue);
|
||||
});
|
||||
});
|
||||
|
||||
return bar;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,7 @@ public class AuxDockImpl implements WindowDockListener {
|
||||
public synchronized void clearDead() {
|
||||
for (AuxEntry entry : new ArrayList<>(entries)) {
|
||||
if (!entry.getProcess().isRunning()) {
|
||||
if (entry.equals(selected)) {
|
||||
var index = entries.indexOf(entry);
|
||||
var fallback = index == 0 ? (entries.size() > 1 ? entries.get(1) : null) : entries.get(index - 1);
|
||||
select(fallback);
|
||||
}
|
||||
entries.remove(entry);
|
||||
closeWindow(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,10 +65,10 @@ public class AuxDockImpl implements WindowDockListener {
|
||||
|
||||
private synchronized void show(AuxEntry e) {
|
||||
var controllable = e.getProcess();
|
||||
if (!controllable.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent.get().moveToFront();
|
||||
|
||||
controllable.moveToFront();
|
||||
controllable.removeIcon();
|
||||
controllable.own(parent.get());
|
||||
controllable.removeStyle(true);
|
||||
@@ -81,7 +76,7 @@ public class AuxDockImpl implements WindowDockListener {
|
||||
updatePositions();
|
||||
}
|
||||
|
||||
public synchronized void hide(AuxEntry e) {
|
||||
private synchronized void hide(AuxEntry e) {
|
||||
var controllable = e.getProcess();
|
||||
controllable.disown();
|
||||
controllable.backOfWindow(parent.get());
|
||||
@@ -94,13 +89,19 @@ public class AuxDockImpl implements WindowDockListener {
|
||||
}
|
||||
|
||||
var p = e.getProcess();
|
||||
// Reset style in case close is blocked by terminal
|
||||
p.restoreIcon();
|
||||
p.disown();
|
||||
p.restoreStyle(true);
|
||||
if (p.isRunning()) {
|
||||
// Reset style in case close is prevented by application
|
||||
p.restoreIcon();
|
||||
p.disown();
|
||||
p.restoreStyle(true);
|
||||
p.close();
|
||||
}
|
||||
|
||||
p.close();
|
||||
// If the process blocked the exit, still don't track it anymore
|
||||
if (e.equals(selected)) {
|
||||
var index = entries.indexOf(e);
|
||||
var fallback = index == 0 ? (entries.size() > 1 ? entries.get(1) : null) : entries.get(index - 1);
|
||||
select(fallback);
|
||||
}
|
||||
entries.remove(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public abstract class ControllableWindowProcess {
|
||||
|
||||
public abstract void minimize();
|
||||
|
||||
public abstract void frontOfMainWindow();
|
||||
public abstract void moveToFront();
|
||||
|
||||
public abstract void backOfWindow(NativeWinWindowControl window);
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ public final class ControllableWindowsProcess extends ControllableWindowProcess
|
||||
}
|
||||
|
||||
@Override
|
||||
public void frontOfMainWindow() {
|
||||
public void moveToFront() {
|
||||
this.control.moveToFront();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,8 +62,14 @@ public class MstscRdpClient implements ExternalApplicationType.PathApplication,
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var aux = AppAuxiliaryWindow.get();
|
||||
String width = null;
|
||||
String height = null;
|
||||
if (aux != null) {
|
||||
aux.show();
|
||||
if (aux.getLocked().get()) {
|
||||
width = "/w:" + aux.getDockBounds().getW();
|
||||
height = "/h:" + aux.getDockBounds().getH();
|
||||
}
|
||||
}
|
||||
|
||||
var adaptedRdpConfig = getAuxWindowConfig(getAdaptedConfig(configuration));
|
||||
@@ -71,7 +77,7 @@ public class MstscRdpClient implements ExternalApplicationType.PathApplication,
|
||||
var setCache = prepareLocalhostRegistryCache(configuration);
|
||||
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), adaptedRdpConfig);
|
||||
var process = LocalExec.executeAsync(getExecutable(), file.toString());
|
||||
var process = LocalExec.executeAsync(getExecutable(), file.toString(), width, height);
|
||||
if (process != null && aux != null) {
|
||||
aux.show();
|
||||
var entry = configuration.getEntry();
|
||||
|
||||
@@ -255,7 +255,7 @@ public interface WezTerminalType extends ExternalTerminalType, TrackableTerminal
|
||||
public void onSessionOpened(TerminalView.ShellSession session) {
|
||||
TerminalView.get().removeListener(this);
|
||||
if (session.getTerminal() instanceof TerminalView.ControllableTerminalSession t) {
|
||||
t.getControllable().frontOfMainWindow();
|
||||
t.getControllable().moveToFront();
|
||||
t.getControllable().focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@ import io.xpipe.app.core.AppSystemInfo;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public class LocalExec {
|
||||
|
||||
public static Process executeAsync(String... command) {
|
||||
var list = Arrays.stream(command).filter(s -> s != null).toList();
|
||||
try {
|
||||
TrackEvent.withTrace("Running local command")
|
||||
.tag("command", String.join(" ", command))
|
||||
.tag("command", String.join(" ", list))
|
||||
.handle();
|
||||
|
||||
var pb = new ProcessBuilder(command)
|
||||
var pb = new ProcessBuilder(list)
|
||||
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
.redirectError(ProcessBuilder.Redirect.DISCARD);
|
||||
pb.directory(AppSystemInfo.ofCurrent().getUserHome().toFile());
|
||||
@@ -26,7 +28,7 @@ public class LocalExec {
|
||||
return pb.start();
|
||||
} catch (Exception ex) {
|
||||
TrackEvent.withTrace("Local command finished")
|
||||
.tag("command", String.join(" ", command))
|
||||
.tag("command", String.join(" ", list))
|
||||
.tag("error", ex.toString())
|
||||
.handle();
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
.remote-desktop-dock .tab-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-border-radius: 4px;
|
||||
-fx-border-width: 1;
|
||||
-fx-padding: 3 7 3 7;
|
||||
-fx-background-insets: 0;
|
||||
-fx-opacity: 0.8;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .lock-button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-border-radius: 4px;
|
||||
-fx-border-width: 1;
|
||||
-fx-padding: 3 7 3 7;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .tab-button .graphic {
|
||||
-fx-opacity: 0.3;
|
||||
}
|
||||
|
||||
.root:nord .remote-desktop-dock .tab-button {
|
||||
-fx-background-radius: 0;
|
||||
-fx-border-radius: 0;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .tab-button:hover, .root:key-navigation .remote-desktop-dock .tab-button:focused {
|
||||
-fx-background-color: -color-bg-default-transparent;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .tab-button:selected, .remote-desktop-dock .tab-button:hover {
|
||||
-fx-opacity: 1.0;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .tab-button:selected .graphic, .remote-desktop-dock .tab-button:hover .graphic {
|
||||
-fx-opacity: 1.0;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .content {
|
||||
-fx-background-color: black;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .close-button {
|
||||
-fx-padding: 3 3 1 3;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .tool-bar {
|
||||
-fx-border-width: 1 0 1 0;
|
||||
-fx-border-color: -color-border-default;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
.remote-desktop-dock .button {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-radius: 4px;
|
||||
-fx-border-radius: 4px;
|
||||
-fx-border-width: 1;
|
||||
-fx-padding: 3 7 3 7;
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.root:nord .remote-desktop-dock .button {
|
||||
-fx-background-radius: 0;
|
||||
-fx-border-radius: 0;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .button:hover, .root:key-navigation .remote-desktop-dock .button:focused {
|
||||
-fx-background-color: -color-bg-default-transparent;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .button:selected {
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
|
||||
.remote-desktop-dock .content {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
Generated
+1
@@ -2057,3 +2057,4 @@ sftpNoticeTitle=Large file transfers
|
||||
sftpNoticeContent=The files you are trying to transfer are quite large. The current session is based on raw SSH, which does not give you the best possible transfer speed. You can look into opening an SFTP session to the system in the right-click menu in the file browser if you need to increase the transfer speed.
|
||||
hideVaultEntryNames=Hide vault entry names
|
||||
hideVaultEntryNamesDescription=Removes all connection names and other information from any READMEs and commit messages. This option prevents any insight into the structure of the encrypted vault contents.
|
||||
toggleSizeLock=Toggle size lock
|
||||
Reference in New Issue
Block a user