mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-08-07 10:01:01 +00:00
Rework terminal tracking
This commit is contained in:
@@ -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);
|
||||
|
||||
+3
-5
@@ -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
-1
@@ -1,6 +1,6 @@
|
||||
package io.xpipe.app.terminal;
|
||||
|
||||
public interface DockableTerminalType {
|
||||
public interface TrackableTerminalType {
|
||||
|
||||
public default int getProcessHierarchyOffset() {
|
||||
return 0;
|
||||
+2
-2
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user