Rework terminal tracking

This commit is contained in:
crschnick
2024-11-09 03:29:48 +00:00
parent 3b3c81428d
commit 27d38c7030
18 changed files with 94 additions and 108 deletions
@@ -42,10 +42,6 @@ public class AskpassExchangeImpl extends AskpassExchange {
}
private void focusTerminalIfNeeded(long pid) {
if (!TerminalView.isSupported()) {
return;
}
var found = TerminalView.get().findSession(pid);
if (found.isEmpty()) {
return;
@@ -56,7 +52,10 @@ public class AskpassExchangeImpl extends AskpassExchange {
return;
}
term.get().focus();
var control = term.get().controllable();
control.ifPresent(controllableTerminalSession -> {
controllableTerminalSession.focus();
});
}
@Override
@@ -10,7 +10,6 @@ import io.xpipe.app.storage.DataColor;
import io.xpipe.app.terminal.TerminalDockComp;
import io.xpipe.app.terminal.TerminalDockModel;
import io.xpipe.app.terminal.TerminalView;
import io.xpipe.app.terminal.TerminalViewInstance;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
@@ -18,6 +17,7 @@ import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.Optional;
import java.util.UUID;
public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
@@ -49,11 +49,11 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
@Override
public void init() throws Exception {
var sessions = new ArrayList<TerminalView.Session>();
var terminals = new ArrayList<TerminalViewInstance>();
var sessions = new ArrayList<TerminalView.ShellSession>();
var terminals = new ArrayList<TerminalView.TerminalSession>();
listener = new TerminalView.Listener() {
@Override
public void onSessionOpened(TerminalView.Session session) {
public void onSessionOpened(TerminalView.ShellSession session) {
if (!terminalRequests.contains(session.getRequest())) {
return;
}
@@ -62,6 +62,8 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
var tv = terminals.stream()
.filter(instance -> sessions.stream()
.anyMatch(s -> instance.getTerminalProcess().equals(s.getTerminal())))
.map(terminalSession -> terminalSession.controllable())
.flatMap(Optional::stream)
.toList();
if (tv.isEmpty()) {
return;
@@ -76,17 +78,17 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
}
@Override
public void onSessionClosed(TerminalView.Session session) {
public void onSessionClosed(TerminalView.ShellSession session) {
sessions.remove(session);
}
@Override
public void onTerminalOpened(TerminalViewInstance instance) {
public void onTerminalOpened(TerminalView.TerminalSession instance) {
terminals.add(instance);
}
@Override
public void onTerminalClosed(TerminalViewInstance instance) {
public void onTerminalClosed(TerminalView.TerminalSession instance) {
terminals.remove(instance);
if (terminals.isEmpty()) {
((BrowserFullSessionModel) browserModel).unsplitTab(BrowserTerminalDockTabModel.this);
@@ -65,20 +65,12 @@ public class AppLayoutModel {
selected.setValue(entries.get(1));
}
public void selectTerminal() {
if (!TerminalView.isSupported()) {
return;
}
public void selectSettings() {
selected.setValue(entries.get(2));
}
public void selectSettings() {
selected.setValue(entries.get(TerminalView.isSupported() ? 3 : 2));
}
public void selectLicense() {
selected.setValue(entries.get(TerminalView.isSupported() ? 4 : 3));
selected.setValue(entries.get(3));
}
public void selectConnections() {
@@ -99,12 +91,6 @@ public class AppLayoutModel {
new BrowserFullSessionComp(BrowserFullSessionModel.DEFAULT),
null,
new KeyCodeCombination(KeyCode.DIGIT2, KeyCombination.SHORTCUT_DOWN)),
// new Entry(
// AppI18n.observable("terminal"),
// new LabelGraphic.IconGraphic("mdi2m-monitor-screenshot"),
// new TerminalDockComp(),
// null,
// new KeyCodeCombination(KeyCode.DIGIT3, KeyCombination.SHORTCUT_DOWN)),
new Entry(
AppI18n.observable("settings"),
new LabelGraphic.IconGraphic("mdsmz-miscellaneous_services"),
@@ -135,19 +121,15 @@ public class AppLayoutModel {
null,
() -> Hyperlinks.open(
"http://localhost:" + AppBeaconServer.get().getPort()),
null)
// new Entry(
// AppI18n.observable("webtop"),
// "mdi2d-desktop-mac",
// null,
// () -> Hyperlinks.open(Hyperlinks.GITHUB_WEBTOP),
// null)
null),
new Entry(
AppI18n.observable("webtop"),
new LabelGraphic.IconGraphic("mdi2d-desktop-mac"),
null,
() -> Hyperlinks.open(Hyperlinks.GITHUB_WEBTOP),
null)
));
if (!TerminalView.isSupported()) {
// l.remove(2);
}
return l;
}
@@ -578,10 +578,6 @@ public class AppPrefs {
ErrorEvent.fromThrowable(e).expected().build().handle();
storageDirectory.setValue(DEFAULT_STORAGE_DIR);
}
if (!TerminalView.isSupported()) {
enableTerminalDocking.set(false);
}
}
private void loadSharedRemote() {
@@ -62,7 +62,6 @@ public class TerminalCategory extends AppPrefsCategory {
.apply(struc -> struc.get().setPromptText("myterminal -e $CMD"))
.hide(prefs.terminalType.isNotEqualTo(ExternalTerminalType.CUSTOM)))
.addComp(terminalTest)
.hide(new SimpleBooleanProperty(!TerminalView.isSupported()))
.pref(prefs.clearTerminalOnInit)
.addToggle(prefs.clearTerminalOnInit))
.buildComp();
@@ -3,7 +3,7 @@ package io.xpipe.app.terminal;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.CommandBuilder;
public interface AlacrittyTerminalType extends ExternalTerminalType, DockableTerminalType {
public interface AlacrittyTerminalType extends ExternalTerminalType, TrackableTerminalType {
ExternalTerminalType ALACRITTY_WINDOWS = new Windows();
ExternalTerminalType ALACRITTY_LINUX = new Linux();
@@ -3,7 +3,7 @@ package io.xpipe.app.terminal;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellDialects;
public class CmdTerminalType extends ExternalTerminalType.SimplePathType implements DockableTerminalType {
public class CmdTerminalType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
public CmdTerminalType() {
super("app.cmd", "cmd.exe", true);
@@ -5,15 +5,13 @@ import io.xpipe.app.util.Rect;
import lombok.Getter;
@Getter
public abstract class TerminalViewInstance {
private final ProcessHandle terminalProcess;
public abstract class ControllableTerminalSession extends TerminalView.TerminalSession {
protected Rect lastBounds;
protected boolean customBounds;
protected TerminalViewInstance(ProcessHandle terminalProcess) {
this.terminalProcess = terminalProcess;
protected ControllableTerminalSession(ProcessHandle terminalProcess) {
super(terminalProcess);
}
public abstract void show();
@@ -1073,7 +1073,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
}
abstract class MacOsType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
abstract class MacOsType extends ExternalApplicationType.MacApplication implements ExternalTerminalType, TrackableTerminalType {
public MacOsType(String id, String applicationName) {
super(id, applicationName);
@@ -1089,7 +1089,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
@Getter
abstract class SimplePathType extends PathCheckType {
abstract class SimplePathType extends PathCheckType implements TrackableTerminalType {
public SimplePathType(String id, String executable, boolean explicitAsync) {
super(id, executable, explicitAsync);
@@ -6,7 +6,7 @@ import io.xpipe.core.process.ShellDialects;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class PowerShellTerminalType extends ExternalTerminalType.SimplePathType implements DockableTerminalType {
public class PowerShellTerminalType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
public PowerShellTerminalType() {
super("app.powershell", "powershell", true);
@@ -5,7 +5,7 @@ import io.xpipe.core.process.CommandBuilder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class PwshTerminalType extends ExternalTerminalType.SimplePathType implements DockableTerminalType {
public class PwshTerminalType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType {
public PwshTerminalType() {
super("app.pwsh", "pwsh", true);
@@ -9,7 +9,7 @@ import io.xpipe.core.process.TerminalInitFunction;
import java.nio.file.Path;
import java.util.Optional;
public interface TabbyTerminalType extends ExternalTerminalType, DockableTerminalType {
public interface TabbyTerminalType extends ExternalTerminalType, TrackableTerminalType {
ExternalTerminalType TABBY_WINDOWS = new Windows();
ExternalTerminalType TABBY_MAC_OS = new MacOs();
@@ -12,21 +12,13 @@ import java.util.Set;
public class TerminalDockModel {
public static boolean isSupported() {
return OsType.getLocal() == OsType.WINDOWS;
}
private Rect viewBounds;
private boolean viewActive;
@Getter
private final Set<TerminalViewInstance> terminalInstances = new HashSet<>();
private final Set<ControllableTerminalSession> terminalInstances = new HashSet<>();
public TerminalDockModel() {
int a = 0;
}
public synchronized void trackTerminal(TerminalViewInstance terminal) {
public synchronized void trackTerminal(ControllableTerminalSession terminal) {
terminalInstances.add(terminal);
terminal.alwaysInFront();
if (viewBounds != null) {
@@ -34,7 +26,7 @@ public class TerminalDockModel {
}
}
public synchronized void closeTerminal(TerminalViewInstance terminal) {
public synchronized void closeTerminal(ControllableTerminalSession terminal) {
if (!terminalInstances.contains(terminal)) {
return;
}
@@ -43,10 +35,6 @@ public class TerminalDockModel {
terminalInstances.remove(terminal);
}
public boolean isEnabled() {
return isSupported() && AppPrefs.get().enableTerminalDocking().get();
}
public synchronized void toggleView(boolean active) {
TrackEvent.withTrace("Terminal view toggled").tag("active", active).handle();
if (viewActive == active) {
@@ -1,7 +1,5 @@
package io.xpipe.app.terminal;
import io.xpipe.app.browser.BrowserFullSessionModel;
import io.xpipe.app.browser.file.BrowserTerminalDockTabModel;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.beacon.BeaconServerException;
@@ -9,7 +7,6 @@ import io.xpipe.core.process.ProcessControl;
import io.xpipe.core.process.TerminalInitScriptConfig;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.SequencedMap;
import java.util.UUID;
@@ -20,16 +17,12 @@ public class TerminalLauncherManager {
private static final SequencedMap<UUID, TerminalLaunchRequest> entries = new LinkedHashMap<>();
public static void init() {
if (!TerminalView.isSupported()) {
return;
}
TerminalView.get().addListener(new TerminalView.Listener() {
@Override
public void onSessionOpened(TerminalView.Session session) {}
public void onSessionOpened(TerminalView.ShellSession session) {}
@Override
public void onSessionClosed(TerminalView.Session session) {
public void onSessionClosed(TerminalView.ShellSession session) {
var affectedEntry = entries.values().stream().filter(terminalLaunchRequest -> {
return terminalLaunchRequest.getRequest().equals(session.getRequest());
}).findFirst();
@@ -41,12 +34,12 @@ public class TerminalLauncherManager {
}
@Override
public void onTerminalOpened(TerminalViewInstance instance) {
public void onTerminalOpened(TerminalView.TerminalSession instance) {
}
@Override
public void onTerminalClosed(TerminalViewInstance instance) {
public void onTerminalClosed(TerminalView.TerminalSession instance) {
}
});
@@ -21,32 +21,46 @@ public class TerminalView {
}
@Value
public static class Session {
public static class ShellSession {
UUID request;
ProcessHandle shell;
ProcessHandle terminal;
}
public static interface Listener {
@Getter
public static class TerminalSession {
void onSessionOpened(Session session);
protected final ProcessHandle terminalProcess;
void onSessionClosed(Session session);
protected TerminalSession(ProcessHandle terminalProcess) {
this.terminalProcess = terminalProcess;
}
void onTerminalOpened(TerminalViewInstance instance);
void onTerminalClosed(TerminalViewInstance instance);
public Optional<ControllableTerminalSession> controllable() {
return Optional.ofNullable(this instanceof ControllableTerminalSession c ? c : null);
}
}
private final List<Session> sessions = new ArrayList<>();
private final List<TerminalViewInstance> terminalInstances = new ArrayList<>();
public static interface Listener {
void onSessionOpened(ShellSession session);
void onSessionClosed(ShellSession session);
void onTerminalOpened(TerminalSession instance);
void onTerminalClosed(TerminalSession instance);
}
private final List<ShellSession> sessions = new ArrayList<>();
private final List<TerminalSession> terminalInstances = new ArrayList<>();
private final List<Listener> listeners = new ArrayList<>();
public synchronized List<Session> getSessions() {
public synchronized List<ShellSession> getSessions() {
return new ArrayList<>(sessions);
}
public synchronized List<TerminalViewInstance> getTerminalInstances() {
public synchronized List<TerminalSession> getTerminalInstances() {
return new ArrayList<>(terminalInstances);
}
@@ -74,18 +88,18 @@ public class TerminalView {
return;
}
var session = new Session(request, shell.get(), terminal.get());
var session = new ShellSession(request, shell.get(), terminal.get());
var instance = terminalInstances.stream()
.filter(i -> i.getTerminalProcess().equals(terminal.get()))
.findFirst();
if (instance.isEmpty()) {
var control = NativeWinWindowControl.byPid(terminal.get().pid());
if (control.isEmpty()) {
var tv = createTerminalSession(terminal.get());
if (tv.isEmpty()) {
return;
}
var tv = new WindowsTerminalViewInstance(terminal.get(), control.get());
terminalInstances.add(tv);
listeners.forEach(listener -> listener.onTerminalOpened(tv));
terminalInstances.add(tv.get());
listeners.forEach(listener -> listener.onTerminalOpened(tv.get()));
}
sessions.add(session);
@@ -96,13 +110,28 @@ public class TerminalView {
.handle();
}
private Optional<TerminalSession> createTerminalSession(ProcessHandle terminalProcess) {
return switch (OsType.getLocal()) {
case OsType.Linux linux -> Optional.of(new TerminalSession(terminalProcess));
case OsType.MacOs macOs -> Optional.of(new TerminalSession(terminalProcess));
case OsType.Windows windows -> {
var control = NativeWinWindowControl.byPid(terminalProcess.pid());
if (control.isEmpty()) {
yield Optional.empty();
}
yield Optional.of(new WindowsTerminalSession(terminalProcess, control.get()));
}
};
}
private Optional<ProcessHandle> getTerminalProcess(ProcessHandle shell) {
var t = AppPrefs.get().terminalType().getValue();
if (!(t instanceof DockableTerminalType dockableTerminalType)) {
if (!(t instanceof TrackableTerminalType trackableTerminalType)) {
return Optional.empty();
}
var off = dockableTerminalType.getProcessHierarchyOffset();
var off = trackableTerminalType.getProcessHierarchyOffset();
var current = Optional.of(shell);
for (int i = 0; i < 1 + off; i++) {
current = current.flatMap(processHandle -> processHandle.parent());
@@ -110,7 +139,7 @@ public class TerminalView {
return current;
}
public synchronized Optional<Session> findSession(long pid) {
public synchronized Optional<ShellSession> findSession(long pid) {
var proc = ProcessHandle.of(pid);
while (true) {
if (proc.isEmpty()) {
@@ -128,7 +157,7 @@ public class TerminalView {
}
public synchronized void tick() {
for (Session session : new ArrayList<>(sessions)) {
for (ShellSession session : new ArrayList<>(sessions)) {
var alive = session.shell.isAlive() && session.terminal.isAlive();
if (!alive) {
sessions.remove(session);
@@ -136,7 +165,7 @@ public class TerminalView {
}
}
for (TerminalViewInstance terminalInstance : new ArrayList<>(terminalInstances)) {
for (TerminalSession terminalInstance : new ArrayList<>(terminalInstances)) {
var alive = terminalInstance.getTerminalProcess().isAlive();
if (!alive) {
terminalInstances.remove(terminalInstance);
@@ -1,6 +1,6 @@
package io.xpipe.app.terminal;
public interface DockableTerminalType {
public interface TrackableTerminalType {
public default int getProcessHierarchyOffset() {
return 0;
@@ -7,11 +7,11 @@ import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public final class WindowsTerminalViewInstance extends TerminalViewInstance {
public final class WindowsTerminalSession extends ControllableTerminalSession {
NativeWinWindowControl control;
public WindowsTerminalViewInstance(ProcessHandle terminal, NativeWinWindowControl control) {
public WindowsTerminalSession(ProcessHandle terminal, NativeWinWindowControl control) {
super(terminal);
this.control = control;
}
@@ -11,7 +11,7 @@ import io.xpipe.core.store.FileNames;
import java.nio.file.Files;
import java.nio.file.Path;
public interface WindowsTerminalType extends ExternalTerminalType, DockableTerminalType {
public interface WindowsTerminalType extends ExternalTerminalType, TrackableTerminalType {
ExternalTerminalType WINDOWS_TERMINAL = new Standard();
ExternalTerminalType WINDOWS_TERMINAL_PREVIEW = new Preview();