This commit is contained in:
crschnick
2026-07-22 21:51:44 +00:00
parent 1714c6db9b
commit 0da317d445
16 changed files with 267 additions and 201 deletions

View File

@@ -28,6 +28,8 @@ import io.xpipe.app.prefs.WorkspaceManager;
import io.xpipe.app.process.LocalShell;
import io.xpipe.app.pwman.KeePassXcPasswordManager;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStorageMigration;
import io.xpipe.app.storage.DataStorageMigrationDialog;
import io.xpipe.app.storage.DataStorageSyncHandler;
import io.xpipe.app.terminal.TerminalDockHubManager;
import io.xpipe.app.terminal.TerminalLauncherManager;
@@ -194,6 +196,7 @@ public class AppBaseMode extends AppOperationMode {
StartOnInitStore.init();
AppConfigurationDialog.showIfNeeded();
DataStorageMigration.showLegacyVaultMigrationErrorIfNeeded();
TrackEvent.info("Finished base components initialization");
initialized = true;

View File

@@ -78,37 +78,7 @@ public class VaultCategory extends AppPrefsCategory {
uh.setCurrentGroupStrategy(nv);
});
builder.title("vault")
.sub(new OptionsBuilder()
.name("vaultTypeName" + vaultTypeKey)
.description("vaultTypeContent" + vaultTypeKey)
.documentationLink(DocumentationLink.TEAM_VAULTS)
.addComp(RegionBuilder.empty())
.nameAndDescription("vaultAuthentication")
.addComp(authChoice, prefs.vaultAuthentication)
.nameAndDescription(Bindings.createStringBinding(
() -> {
var empty = uh.getUserCount() == 0;
if (prefs.vaultAuthentication.get() == VaultAuthentication.GROUP) {
return empty ? "groupManagementEmpty" : "groupManagement";
}
return empty ? "userManagementEmpty" : "userManagement";
},
prefs.vaultAuthentication))
.addComp(uh.createOverview().maxWidth(getCompWidth()))
.addComp(uh.createGroupStrategyOptions(groupStrategy).buildComp(), groupStrategy)
.hide(prefs.vaultAuthentication.isNotEqualTo(VaultAuthentication.GROUP))
.nameAndDescription("syncVault")
.addComp(new ButtonComp(AppI18n.observable("enableGitSync"), () -> AppPrefs.get()
.selectCategory("vaultSync")))
.hide(new SimpleBooleanProperty(
DataStorageSyncHandler.getInstance().supportsSync()))
.nameAndDescription("teamVaults")
.addComp(RegionBuilder.empty())
.licenseRequirement("team")
.disable(!LicenseProvider.get().getFeature("team").isSupported())
.hide(uh.getUserCount() > 1));
builder.title("vault");
builder.sub(new OptionsBuilder()
.pref(prefs.encryptAllVaultData)
.addToggle(encryptVault)

View File

@@ -0,0 +1,78 @@
package io.xpipe.app.storage;
import io.xpipe.app.core.AppVersion;
import io.xpipe.app.issue.ErrorEventFactory;
import org.apache.commons.io.FileUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public class DataStorageMigration {
private static boolean show = false;
private static Optional<String> loadVaultVersion(Path dir) throws IOException {
var file = dir.resolve("vaultversion");
if (Files.exists(file)) {
var s = Files.readString(file);
return Optional.of(s);
} else {
return Optional.empty();
}
}
public static void showLegacyVaultMigrationErrorIfNeeded() {
if (!show) {
return;
}
DataStorageMigrationDialog.show();
}
public static void init() throws IOException {
var dir = DataStorage.getStorageDirectory();
var version = loadVaultVersion(dir);
if (version.isPresent()) {
var canonicalVersion = AppVersion.parse(version.get());
if (canonicalVersion.isEmpty()) {
show = true;
return;
}
if (canonicalVersion.get().getMajor() < 23 || (canonicalVersion.get().getMajor() == 23 && canonicalVersion.get().getMinor() < 9)) {
show = true;
return;
}
} else {
show = true;
}
}
private static StandardStorage getStorage() {
return (StandardStorage) DataStorage.get();
}
public static void migrate() throws IOException {
var dir = DataStorage.getStorageDirectory();
var file = dir.resolve("vaultkey");
try {
var vaultKey = DataStorageVaultKey.generate();
DataStorageVaultKey.write(vaultKey, file);
getStorage().vaultKey = vaultKey;
} catch (Exception e) {
ErrorEventFactory.fromThrowable(
"Unable to convert vault key file " + file, e)
.terminal(true)
.build()
.handle();
}
DataStorageUserHandler.getInstance().migrate();
getStorage().forceRewrite();
getStorage().save(false);
}
}

View File

@@ -0,0 +1,67 @@
package io.xpipe.app.storage;
import io.xpipe.app.comp.RegionBuilder;
import io.xpipe.app.comp.base.LabelComp;
import io.xpipe.app.comp.base.LoadingIconComp;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.platform.PlatformThread;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.InPlaceSecretValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
public class DataStorageMigrationDialog {
public static void show() {
var content = AppDialog.dialogTextKey("storageMigrationContent");
var busy = new SimpleBooleanProperty();
var modal = ModalOverlay.of("storageMigrationTitle", content);
modal.addButtonBarComp(new LabelComp(AppI18n.observable("applyingVaultChanges")).visible(busy));
modal.addButtonBarComp(RegionBuilder.hspacer());
modal.addButton(ModalButton.cancel());
modal.addButton(new ModalButton(
"apply",
() -> {
ThreadHelper.runFailableAsync(() -> {
if (busy.get()) {
return;
}
BooleanScope.executeExclusive(busy, () -> {
DataStorageMigration.migrate();
modal.close();
});
});
},
false,
true))
.augment(button -> {
button.graphicProperty()
.bind(Bindings.createObjectBinding(
() -> {
return busy.get()
? new LoadingIconComp(busy, AppFontSizes::base)
.style("busy-loading-icon")
.build()
: null;
},
PlatformThread.sync(busy)));
button.textProperty()
.bind(Bindings.createStringBinding(
() -> {
return !busy.get() ? AppI18n.get("apply") : null;
},
PlatformThread.sync(busy),
AppI18n.activeLanguage()));;
});
modal.show();
}
}

View File

@@ -20,16 +20,14 @@ public interface DataStorageUserHandler {
void init() throws IOException;
void migrate() throws IOException;
void save();
void login();
SecretKey getEncryptionKey();
BaseRegionBuilder<?, ?> createOverview();
OptionsBuilder createGroupStrategyOptions(ObjectProperty<DataStorageGroupStrategy> groupStrategy);
String getActiveUser();
VaultAuthentication getVaultAuthenticationType();

View File

@@ -37,7 +37,7 @@ public class StandardStorage extends DataStorage {
private final DataStorageUserHandler dataStorageUserHandler;
private final ReentrantLock busyIo = new ReentrantLock();
private DataStorageVaultKey vaultKey;
DataStorageVaultKey vaultKey;
@Getter
private boolean disposed;
@@ -315,6 +315,8 @@ public class StandardStorage extends DataStorage {
return;
}
var dirExists = Files.isDirectory(dir);
try {
FileUtils.forceMkdir(dir.toFile());
} catch (Exception e) {
@@ -324,6 +326,21 @@ public class StandardStorage extends DataStorage {
.handle();
}
try {
if (!dirExists) {
Files.writeString(dir.resolve("vaultversion"), AppProperties.get().getVersion());
}
DataStorageMigration.init();
Files.writeString(dir.resolve("vaultversion"), AppProperties.get().getVersion());
} catch (IOException e) {
ErrorEventFactory.fromThrowable("Unable to load vault version data", e)
.terminal(true)
.build()
.handle();
}
try {
initSystemInfo();
} catch (Exception e) {

View File

@@ -1,6 +1,5 @@
package io.xpipe.app.terminal;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.WindowDockComp;
import javafx.application.Platform;
@@ -31,20 +30,18 @@ public class TerminalDockHubComp extends WindowDockComp<TerminalDockView> {
var window = scene.getWindow();
window.focusedProperty().subscribe(focus -> {
if (!focus) {
if (!model.isActive()) {
return;
}
var target = scene.getRoot().lookup(".icon-button-comp:hover");
ThreadHelper.runFailableAsync(() -> {
if (!model.isActive()) {
return;
}
if (focus) {
var target = scene.getRoot().lookup(".icon-button-comp:hover");
if (target == null) {
model.focus();
Platform.runLater(() -> {
model.focus();
});
}
});
}
});
});
return stack;

View File

@@ -14,7 +14,6 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.GlobalTimer;
import io.xpipe.app.util.NativeWinWindowControl;
import io.xpipe.app.util.Rect;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.OsType;
import javafx.application.Platform;
@@ -165,11 +164,9 @@ public class TerminalDockHubManager {
if (!showing.get()) {
// Run later to guarantee order of operations
Platform.runLater(() -> {
ThreadHelper.runAsync(() -> {
AppLayoutModel.get().selectConnections();
showDock();
attach();
});
AppLayoutModel.get().selectConnections();
showDock();
attach();
});
return false;
}
@@ -211,20 +208,18 @@ public class TerminalDockHubManager {
var wasShowing = new SimpleBooleanProperty();
var wasAttached = new SimpleBooleanProperty();
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
ThreadHelper.runAsync(() -> {
if (AppLayoutModel.get().getEntries().indexOf(newValue) == 0) {
if (wasShowing.get()) {
showDock();
}
if (wasAttached.get()) {
attach();
}
} else if (AppLayoutModel.get().getEntries().indexOf(oldValue) == 0) {
wasAttached.set(!minimized.get() && !detached.get() && showing.get());
wasShowing.set(showing.get());
hideDock();
if (AppLayoutModel.get().getEntries().indexOf(newValue) == 0) {
if (wasShowing.get()) {
showDock();
}
});
if (wasAttached.get()) {
attach();
}
} else if (AppLayoutModel.get().getEntries().indexOf(oldValue) == 0) {
wasAttached.set(!minimized.get() && !detached.get() && showing.get());
wasShowing.set(showing.get());
hideDock();
}
});
}
@@ -251,10 +246,8 @@ public class TerminalDockHubManager {
enableDock();
showDock();
Platform.runLater(() -> {
ThreadHelper.runAsync(() -> {
dockModel.trackTerminal(t, dock);
dockModel.closeOtherTerminals(session.getRequest());
});
dockModel.trackTerminal(t, dock);
dockModel.closeOtherTerminals(session.getRequest());
});
}
@@ -321,31 +314,35 @@ public class TerminalDockHubManager {
}
public void enableDock() {
if (enabled.get()) {
return;
}
PlatformThread.runLaterIfNeeded(() -> {
if (enabled.get()) {
return;
}
dockModel.activateView();
enabled.set(true);
showing.set(true);
dockModel.activateView();
enabled.set(true);
showing.set(true);
NativeWinWindowControl.MAIN_WINDOW.setWindowsTransitionsEnabled(false);
AppLayoutModel.get().getQueueEntries().add(queueEntry);
NativeWinWindowControl.MAIN_WINDOW.setWindowsTransitionsEnabled(false);
AppLayoutModel.get().getQueueEntries().add(queueEntry);
});
}
public void disableDock() {
if (!enabled.get()) {
return;
}
PlatformThread.runLaterIfNeeded(() -> {
if (!enabled.get()) {
return;
}
dockModel.deactivateView();
enabled.set(false);
showing.set(false);
dockModel.deactivateView();
enabled.set(false);
showing.set(false);
showDialogIfNeeded();
showDialogIfNeeded();
NativeWinWindowControl.MAIN_WINDOW.setWindowsTransitionsEnabled(true);
AppLayoutModel.get().getQueueEntries().remove(queueEntry);
NativeWinWindowControl.MAIN_WINDOW.setWindowsTransitionsEnabled(true);
AppLayoutModel.get().getQueueEntries().remove(queueEntry);
});
}
public void triggerDock() {
@@ -359,22 +356,26 @@ public class TerminalDockHubManager {
}
public void showDock() {
if (showing.get()) {
return;
}
PlatformThread.runLaterIfNeeded(() -> {
if (showing.get()) {
return;
}
dockModel.activateView();
showing.set(true);
AppLayoutModel.get().selectConnections();
dockModel.activateView();
showing.set(true);
AppLayoutModel.get().selectConnections();
});
}
public void hideDock() {
if (!showing.get()) {
return;
}
PlatformThread.runLaterIfNeeded(() -> {
if (!showing.get()) {
return;
}
dockModel.deactivateView();
showing.set(false);
dockModel.deactivateView();
showing.set(false);
});
}
public void attach() {

View File

@@ -225,25 +225,26 @@ public class TerminalDockView implements WindowDockListener {
TrackEvent.withTrace("Terminal view window shown").handle();
terminalInstances.forEach(terminalInstance -> {
var controllable = terminalInstance.getControllable();
if (controllable.isActive()) {
return;
}
controllable.updateBoundsState();
if (controllable.isCustomBounds()) {
return;
}
if (controllable.isActive()) {
return;
}
if (!viewActive) {
return;
}
controllable.show();
controllable.removeIcon();
controllable.own(NativeWinWindowControl.MAIN_WINDOW);
controllable.removeStyle(terminalInstance.manageBorders());
controllable.focus();
if (viewActive) {
controllable.removeIcon();
controllable.own(NativeWinWindowControl.MAIN_WINDOW);
controllable.removeStyle(terminalInstance.manageBorders());
controllable.focus();
} else {
controllable.restoreIcon();
controllable.disown();
controllable.backOfWindow(NativeWinWindowControl.MAIN_WINDOW);
}
});
}

View File

@@ -62,24 +62,14 @@ public class WindowDockComp<T extends WindowDockListener> extends SimpleRegionBu
update(stack);
}
};
var maximized = new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
update(stack);
}
};
var iconified = new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
ThreadHelper.runAsync(() -> {
model.onWindowMinimize();
});
model.onWindowMinimize();
} else {
Platform.runLater(() -> {
ThreadHelper.runAsync(() -> {
model.onWindowShow();
});
model.onWindowShow();
});
}
}
@@ -99,9 +89,7 @@ public class WindowDockComp<T extends WindowDockListener> extends SimpleRegionBu
var hide = new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
ThreadHelper.runAsync(() -> {
model.onClose();
});
model.onClose();
}
};
@@ -113,7 +101,6 @@ public class WindowDockComp<T extends WindowDockListener> extends SimpleRegionBu
s.yProperty().removeListener(update);
s.widthProperty().removeListener(update);
s.heightProperty().removeListener(update);
s.maximizedProperty().removeListener(maximized);
s.iconifiedProperty().removeListener(iconified);
s.removeEventFilter(WindowEvent.WINDOW_SHOWN, show);
s.removeEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, hide);
@@ -129,7 +116,6 @@ public class WindowDockComp<T extends WindowDockListener> extends SimpleRegionBu
s.yProperty().addListener(update);
s.widthProperty().addListener(update);
s.heightProperty().addListener(update);
s.maximizedProperty().addListener(maximized);
s.iconifiedProperty().addListener(iconified);
s.outputScaleXProperty().removeListener(scale);
s.addEventFilter(WindowEvent.WINDOW_SHOWN, show);
@@ -160,29 +146,27 @@ public class WindowDockComp<T extends WindowDockListener> extends SimpleRegionBu
return;
}
ThreadHelper.runAsync(() -> {
var windowRect = new NativeWinWindowControl(handle.get()).getBounds();
if (windowRect.getX() == 0.0 && windowRect.getY() == 0.0 && windowRect.getW() == 0 && windowRect.getH() == 0) {
return;
}
var windowRect = new NativeWinWindowControl(handle.get()).getBounds();
if (windowRect.getX() == 0.0 && windowRect.getY() == 0.0 && windowRect.getW() == 0 && windowRect.getH() == 0) {
return;
}
var xPadding = ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx);
var yPadding = ((bounds.getMinY() + p.getTop() + scene.getY()) * sy);
var x = windowRect.getX() + xPadding;
var y = windowRect.getY() + yPadding;
var w = (bounds.getWidth() * sx) - p.getRight() - p.getLeft();
var h = (bounds.getHeight() * sy) - p.getBottom() - p.getTop();
var xPadding = ((bounds.getMinX() + p.getLeft() + scene.getX()) * sx);
var yPadding = ((bounds.getMinY() + p.getTop() + scene.getY()) * sy);
var x = windowRect.getX() + xPadding;
var y = windowRect.getY() + yPadding;
var w = (bounds.getWidth() * sx) - p.getRight() - p.getLeft();
var h = (bounds.getHeight() * sy) - p.getBottom() - p.getTop();
if (x + w > windowRect.getX() + windowRect.getW()) {
x = windowRect.getX() + 10;
w = windowRect.getW() - 20;
}
if (y + h > windowRect.getY() + windowRect.getH()) {
y = windowRect.getY() + 10;
h = windowRect.getH() - 20;
}
if (x + w > windowRect.getX() + windowRect.getW()) {
x = windowRect.getX() + 10;
w = windowRect.getW() - 20;
}
if (y + h > windowRect.getY() + windowRect.getH()) {
y = windowRect.getY() + 10;
h = windowRect.getH() - 20;
}
model.resizeView((int) Math.round(x), (int) Math.round(y), (int) Math.round(w), (int) Math.round(h));
});
model.resizeView((int) Math.round(x), (int) Math.round(y), (int) Math.round(w), (int) Math.round(h));
}
}

View File

@@ -63,9 +63,7 @@ jlink {
addOptions("--add-modules", groupName + ".ext.${extProject.name}")
}
// We can't use jdwp with AOT
def excludeJdwp = bundleCds
if (!excludeJdwp) {
if (!ci || (!isStage && !isFullRelease)) {
addOptions("--add-modules", "jdk.jdwp.agent")
}

View File

@@ -48,26 +48,11 @@ public abstract class AbstractServiceStore
@Override
public void checkComplete() throws Throwable {
// We do not require the host to be complete
if (getHost() != null) {
Validators.isType(getHost(), HostAddressStore.class);
}
Validators.nonNull(remotePort);
Validators.nonNull(serviceProtocolType);
if (getHost() == null) {
Validators.nonNull(getAddress());
}
if (serviceProtocolType.hasScheme()) {
var addr = serviceProtocolType.formatAddress(getOpenTargetUrl());
if (addr != null) {
try {
URI.create(addr);
} catch (IllegalArgumentException e) {
throw new ValidationException(e.getMessage());
}
}
}
}
public String getOpenTargetUrl() {
@@ -110,10 +95,6 @@ public abstract class AbstractServiceStore
return false;
}
if (!getHost().getStore().isComplete()) {
return false;
}
if (getHost().getStore() instanceof HostAddressGatewayStore g
&& !(getHost().getStore() instanceof NetworkTunnelStore)) {
var gw = g.getTunnelGateway();
@@ -146,10 +127,6 @@ public abstract class AbstractServiceStore
}
if (getHost() != null) {
if (!getHost().getStore().isComplete()) {
return null;
}
if (!(getHost().getStore() instanceof NetworkTunnelStore)
&& getHost().getStore() instanceof HostAddressGatewayStore g) {
if (g.getTunnelGateway() == null

View File

@@ -39,10 +39,6 @@ public abstract class AbstractServiceStoreProvider implements SingletonSessionSt
}
if (abs.getHost() != null) {
if (!abs.getHost().getStore().isComplete()) {
return false;
}
if (abs.getHost().getStore() instanceof HostAddressGatewayStore a) {
if (a.getTunnelGateway() != null
&& a.getTunnelGateway().getStore().requiresTunnel()

View File

@@ -21,8 +21,6 @@ import java.util.Locale;
})
public interface ServiceProtocolType {
boolean hasScheme();
String formatAddress(String base);
void open(String url) throws Exception;
@@ -35,11 +33,6 @@ public interface ServiceProtocolType {
@Builder
class Undefined implements ServiceProtocolType {
@Override
public boolean hasScheme() {
return false;
}
@Override
public String formatAddress(String base) {
return base;
@@ -62,11 +55,6 @@ public interface ServiceProtocolType {
String path;
@Override
public boolean hasScheme() {
return true;
}
@Override
public String formatAddress(String base) {
var url = "http://" + base;
@@ -95,11 +83,6 @@ public interface ServiceProtocolType {
String path;
@Override
public boolean hasScheme() {
return true;
}
@Override
public String formatAddress(String base) {
var url = "https://" + base;
@@ -128,11 +111,6 @@ public interface ServiceProtocolType {
String commandTemplate;
@Override
public boolean hasScheme() {
return false;
}
@Override
public String formatAddress(String base) {
return base;

View File

@@ -2213,4 +2213,5 @@ enpassUnlock=Enter Enpass vault master password
keeperSmsUnlock=Enter Keeper Commander SMS Code
keeper2faUnlock=Enter Keeper 2FA Code
keeperUnlock=Enter your Keeper master password to unlock
storageMigrationTitle=Vault migration
storageMigrationContent=Your XPipe vault comes from an older version of XPipe. To use it for the next versions with v24+, you will need to migrate the vault data to the new format. This will take a while and rewrite the data. If you are using git sync, this will result in large changes.

View File

@@ -1 +1 @@
23.8-3
23.8