Various fixes

This commit is contained in:
crschnick
2026-01-18 02:35:42 +00:00
parent ea39af876b
commit 30568dd762
12 changed files with 159 additions and 71 deletions

View File

@@ -27,12 +27,13 @@ import javafx.collections.ObservableList;
import java.util.Optional;
import java.util.UUID;
import java.util.function.UnaryOperator;
public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
private final BrowserSessionTab origin;
private final ObservableList<UUID> terminalRequests;
private final TerminalDockView dockModel = new TerminalDockView();
private final TerminalDockView dockModel = new TerminalDockView(UnaryOperator.identity());
private final BooleanProperty opened = new SimpleBooleanProperty();
private TerminalView.Listener listener;
private ObservableBooleanValue viewActive;

View File

@@ -9,6 +9,7 @@ import io.xpipe.app.core.window.AppMainWindow;
import io.xpipe.app.platform.InputHelper;
import io.xpipe.app.terminal.TerminalDockHubComp;
import io.xpipe.app.terminal.TerminalDockHubManager;
import io.xpipe.app.terminal.TerminalDockMode;
import io.xpipe.app.util.ObservableSubscriber;
import javafx.scene.input.KeyCode;
@@ -51,10 +52,15 @@ public class StoreLayoutComp extends SimpleComp {
});
});
var model = TerminalDockHubManager.get();
var dock = new TerminalDockHubComp(model.getDockModel());
var stack = new StackPane(comp.createRegion(), dock.createRegion());
var stack = new StackPane(comp.createRegion());
stack.getStyleClass().add("store-layout");
if (TerminalDockHubManager.isPossiblySupported()) {
var model = TerminalDockHubManager.get();
var dock = new TerminalDockHubComp(model.getDockModel());
stack.getChildren().add(dock.createRegion());
}
return stack;
}
}

View File

@@ -76,8 +76,6 @@ public class NativeWinWindowControl {
User32.INSTANCE.SetWindowLong(windowHandle, User32.GWL_STYLE, mod);
}
public void removeShadow() {}
public boolean isIconified() {
return (User32.INSTANCE.GetWindowLong(windowHandle, User32.GWL_STYLE) & User32.WS_MINIMIZE) != 0;
}

View File

@@ -35,6 +35,11 @@ public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTe
class Windows implements ExternalApplicationType.PathApplication, ExternalTerminalType, AlacrittyTerminalType {
@Override
public TerminalDockMode getDockMode() {
return TerminalDockMode.WITH_BORDER;
}
@Override
public void launch(TerminalLaunchConfiguration configuration) throws Exception {
// Alacritty is bugged and will not accept arguments with spaces even if they are correctly passed/escaped

View File

@@ -16,8 +16,6 @@ public abstract class ControllableTerminalSession extends TerminalView.TerminalS
public abstract void removeBorders();
public abstract void removeShadow();
public abstract void show();
public abstract void minimize();

View File

@@ -8,6 +8,11 @@ import java.util.Base64;
public class PwshTerminalType implements ExternalApplicationType.PathApplication, TrackableTerminalType {
@Override
public TerminalDockMode getDockMode() {
return TerminalDockMode.WITH_BORDER;
}
@Override
public TerminalOpenFormat getOpenFormat() {
return TerminalOpenFormat.NEW_WINDOW;

View File

@@ -8,6 +8,7 @@ import io.xpipe.app.core.window.AppMainWindow;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.GlobalTimer;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;
@@ -25,6 +26,7 @@ import javafx.stage.WindowEvent;
import org.kordamp.ikonli.javafx.FontIcon;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;
public class TerminalDockBrowserComp extends SimpleComp {
@@ -105,11 +107,13 @@ public class TerminalDockBrowserComp extends SimpleComp {
var focus = new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
model.onFocusGain();
} else {
model.onFocusLost();
}
GlobalTimer.delay(() -> {
if (newValue) {
model.onFocusGain();
} else {
model.onFocusLost();
}
}, Duration.ofMillis(100));
}
};
var show = new EventHandler<WindowEvent>() {

View File

@@ -2,6 +2,7 @@ package io.xpipe.app.terminal;
import io.xpipe.app.comp.SimpleComp;
import io.xpipe.app.core.window.AppMainWindow;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
@@ -66,7 +67,9 @@ public class TerminalDockHubComp extends SimpleComp {
if (newValue) {
model.onFocusGain();
} else {
model.onFocusLost();
Platform.runLater(() -> {
model.onFocusLost();
});
}
}
};
@@ -126,9 +129,9 @@ public class TerminalDockHubComp extends SimpleComp {
var sx = region.getScene().getWindow().getOutputScaleX();
var sy = region.getScene().getWindow().getOutputScaleY();
model.resizeView(
(int) Math.ceil(bounds.getMinX() * sx + p.getLeft()),
(int) Math.ceil(bounds.getMinY() * sy + p.getTop()),
(int) Math.floor(bounds.getWidth() * sx - p.getRight() - p.getLeft()),
(int) Math.floor(bounds.getHeight() * sy - p.getBottom() - p.getTop()));
(int) Math.round(bounds.getMinX() * sx + p.getLeft()),
(int) Math.round(bounds.getMinY() * sy + p.getTop()),
(int) Math.round(bounds.getWidth() * sx - p.getRight() - p.getLeft()),
(int) Math.round(bounds.getHeight() * sy - p.getBottom() - p.getTop()));
}
}

View File

@@ -1,14 +1,19 @@
package io.xpipe.app.terminal;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.GlobalTimer;
import io.xpipe.app.util.Rect;
import io.xpipe.core.OsType;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ListChangeListener;
import lombok.Getter;
import java.time.Duration;
@@ -19,6 +24,14 @@ import java.util.UUID;
@Getter
public class TerminalDockHubManager {
public static boolean isPossiblySupported() {
if (OsType.ofLocal() != OsType.WINDOWS) {
return false;
}
return true;
}
public static boolean isSupported() {
if (OsType.ofLocal() != OsType.WINDOWS) {
return false;
@@ -45,15 +58,14 @@ public class TerminalDockHubManager {
private static TerminalDockHubManager INSTANCE;
public static void init() {
if (!isPossiblySupported()) {
return;
}
INSTANCE = new TerminalDockHubManager();
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
if (AppLayoutModel.get().getEntries().indexOf(newValue) == 0) {
INSTANCE.selectHub();
} else {
INSTANCE.unselectHub();
}
});
INSTANCE.addLayoutListeners();
INSTANCE.addDialogListeners();
TerminalView.get().addListener(INSTANCE.createListener());
@@ -68,15 +80,34 @@ public class TerminalDockHubManager {
}
private final Set<UUID> hubRequests = new HashSet<>();
private final BooleanProperty enabled = new SimpleBooleanProperty();
private final BooleanProperty showing = new SimpleBooleanProperty();
private final BooleanProperty detached = new SimpleBooleanProperty();
private final BooleanProperty minimized = new SimpleBooleanProperty();
private final TerminalDockView dockModel = new TerminalDockView();
private final TerminalDockView dockModel = new TerminalDockView(rect -> {
var term = AppPrefs.get().terminalType().getValue();
var adjust = term instanceof TrackableTerminalType t && t.getDockMode() != TerminalDockMode.BORDERLESS;
return adjust ? new Rect(rect.getX() - 9, rect.getY() - 1, rect.getW() + 16, rect.getH() + 9) : rect;
});
private final AppLayoutModel.QueueEntry queueEntry = new AppLayoutModel.QueueEntry(
AppI18n.observable("toggleTerminalDock"),
new LabelGraphic.IconGraphic("mdi2c-console"), () -> {
refreshDockStatus();
if (!enabled.get()) {
return false;
}
if (!showing.get()) {
// Run later to guarantee order of operations
Platform.runLater(() -> {
AppLayoutModel.get().selectConnections();
showDock();
attach();
});
return false;
}
if (minimized.get() || detached.get()) {
attach();
return false;
@@ -87,14 +118,48 @@ public class TerminalDockHubManager {
return false;
}
if (!showing.get()) {
showDock();
return false;
}
return false;
});
private void addDialogListeners() {
var wasShowing = new SimpleBooleanProperty();
var wasAttached = new SimpleBooleanProperty();
AppDialog.getModalOverlays().addListener((ListChangeListener<? super ModalOverlay>) c -> {
if (c.getList().size() == 0) {
if (wasShowing.get()) {
INSTANCE.showDock();
}
if (wasAttached.get()) {
INSTANCE.attach();
}
} else {
wasAttached.set(!INSTANCE.minimized.get() && !INSTANCE.detached.get() && INSTANCE.showing.get());
wasShowing.set(INSTANCE.showing.get());
INSTANCE.hideDock();
}
});
}
private void addLayoutListeners() {
var wasShowing = new SimpleBooleanProperty();
var wasAttached = new SimpleBooleanProperty();
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
if (AppLayoutModel.get().getEntries().indexOf(newValue) == 0) {
if (wasShowing.get()) {
INSTANCE.showDock();
}
if (wasAttached.get()) {
INSTANCE.attach();
}
} else {
wasAttached.set(!INSTANCE.minimized.get() && !INSTANCE.detached.get() && INSTANCE.showing.get());
wasShowing.set(INSTANCE.showing.get());
INSTANCE.hideDock();
}
});
}
private TerminalView.Listener createListener() {
var listener = new TerminalView.Listener() {
@Override
@@ -114,14 +179,13 @@ public class TerminalDockHubManager {
return;
}
controllable.get().removeShadow();
if (t.getDockMode() == TerminalDockMode.BORDERLESS) {
controllable.get().removeBorders();
}
}
dockModel.trackTerminal(controllable.get(), !detached.get());
dockModel.closeOtherTerminals(session.getRequest());
openDock();
enableDock();
}
@Override
@@ -141,7 +205,7 @@ public class TerminalDockHubManager {
&& s.getTerminal().isRunning())
.toList();
if (remaining.isEmpty()) {
closeDock();
disableDock();
}
}
};
@@ -160,13 +224,6 @@ public class TerminalDockHubManager {
detached.set(dockModel.isCustomBounds());
}
public void selectHub() {
dockModel.onFocusLost();
}
public void unselectHub() {
}
public void openTerminal(UUID request) {
if (!isSupported()) {
return;
@@ -175,19 +232,33 @@ public class TerminalDockHubManager {
hubRequests.add(request);
}
public void openDock() {
public void enableDock() {
PlatformThread.runLaterIfNeeded(() -> {
if (showing.get()) {
if (enabled.get()) {
return;
}
dockModel.toggleView(true);
enabled.set(true);
showing.set(true);
AppLayoutModel.get().getQueueEntries().add(queueEntry);
});
}
public void disableDock() {
PlatformThread.runLaterIfNeeded(() -> {
if (!enabled.get()) {
return;
}
dockModel.toggleView(false);
enabled.set(false);
showing.set(false);
AppLayoutModel.get().getQueueEntries().remove(queueEntry);
});
}
public void showDock() {
PlatformThread.runLaterIfNeeded(() -> {
if (showing.get()) {
@@ -199,11 +270,6 @@ public class TerminalDockHubManager {
});
}
public void attach() {
dockModel.attach();
detached.set(false);
}
public void hideDock() {
PlatformThread.runLaterIfNeeded(() -> {
if (!showing.get()) {
@@ -215,15 +281,8 @@ public class TerminalDockHubManager {
});
}
public void closeDock() {
PlatformThread.runLaterIfNeeded(() -> {
if (!showing.get()) {
return;
}
dockModel.toggleView(false);
showing.set(false);
AppLayoutModel.get().getQueueEntries().remove(queueEntry);
});
public void attach() {
dockModel.attach();
detached.set(false);
}
}

View File

@@ -1,6 +1,7 @@
package io.xpipe.app.terminal;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.platform.NativeWinWindowControl;
import io.xpipe.app.util.Rect;
import lombok.Getter;
@@ -9,15 +10,20 @@ import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.UnaryOperator;
public class TerminalDockView {
@Getter
private final Set<ControllableTerminalSession> terminalInstances = new HashSet<>();
private final UnaryOperator<Rect> windowBoundsFunction;
private Rect viewBounds;
private boolean viewActive;
public TerminalDockView(UnaryOperator<Rect> windowBoundsFunction) {this.windowBoundsFunction = windowBoundsFunction;}
public synchronized boolean isRunning() {
return terminalInstances.stream().anyMatch(terminal -> terminal.isRunning());
}
@@ -31,7 +37,10 @@ public class TerminalDockView {
}
public synchronized void trackTerminal(ControllableTerminalSession terminal, boolean dock) {
terminalInstances.add(terminal);
if (!terminalInstances.add(terminal)) {
return;
}
// The main window always loses focus when the terminal is opened,
// so only put it in front
// If we refocus the main window, it will get put always in front then
@@ -71,7 +80,10 @@ public class TerminalDockView {
this.viewActive = active;
if (active) {
terminalInstances.forEach(terminalInstance -> terminalInstance.alwaysInFront());
terminalInstances.forEach(terminalInstance -> {
terminalInstance.frontOfMainWindow();
terminalInstance.focus();
});
updatePositions();
} else {
terminalInstances.forEach(terminalInstance -> terminalInstance.back());
@@ -183,7 +195,7 @@ public class TerminalDockView {
return;
}
this.viewBounds = new Rect(x, y, w, h);
this.viewBounds = windowBoundsFunction.apply(new Rect(x, y, w, h));
updatePositions();
}
@@ -192,7 +204,8 @@ public class TerminalDockView {
terminalInstances.forEach(terminalInstance -> {
terminalInstance.show();
terminalInstance.alwaysInFront();
terminalInstance.frontOfMainWindow();
terminalInstance.focus();
terminalInstance.updatePosition(viewBounds);
});
}

View File

@@ -169,6 +169,11 @@ public class TerminalLauncher {
preferTabs && AppPrefs.get().preferTerminalTabs().get();
var launchConfig = new TerminalLaunchConfiguration(color, adjustedTitle, cleanTitle, preferTabs, paneList);
// Dock terminal if needed
for (TerminalPaneConfiguration pane : launchConfig.getPanes()) {
TerminalDockHubManager.get().openTerminal(pane.getRequest());
}
if (effectivePreferTabs) {
synchronized (TerminalLauncher.class) {
// There will be timing issues when launching multiple tabs in a short time span
@@ -206,10 +211,6 @@ public class TerminalLauncher {
return;
}
for (TerminalPaneConfiguration pane : config.getPanes()) {
TerminalDockHubManager.get().openTerminal(pane.getRequest());
}
try {
type.launch(config);
latch.await();

View File

@@ -46,11 +46,6 @@ public final class WindowsTerminalSession extends ControllableTerminalSession {
control.removeBorders();
}
@Override
public void removeShadow() {
control.removeShadow();
}
@Override
public void show() {
this.control.show();