mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-06-04 21:58:02 -04:00
Rework
This commit is contained in:
@@ -87,4 +87,6 @@ public abstract class ProcessControlProvider {
|
||||
|
||||
public abstract void addAskpassEnvironment(
|
||||
CommandBuilder b, String prefix, UUID requestId, UUID secretId, String... askpassName);
|
||||
|
||||
public abstract void refreshWsl() throws Exception;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public interface StartOnInitStore extends SelfReferentialStore, DataStore {
|
||||
ErrorEventFactory.fromThrowable(ex)
|
||||
.description("Unable to automatically start connection "
|
||||
+ DataStorage.get().getStoreEntryDisplayName(i.getSelfEntry()))
|
||||
.expected()
|
||||
.handle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package io.xpipe.app.hub.comp;
|
||||
|
||||
import io.xpipe.app.comp.RegionBuilder;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.ext.DataStore;
|
||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||
import io.xpipe.app.ext.DataStoreProvider;
|
||||
@@ -168,6 +170,14 @@ public class StoreCreationDialog {
|
||||
return model;
|
||||
}
|
||||
|
||||
private static void showNotice() {
|
||||
var shown = AppCache.getBoolean("creationMinimizeDialogShown", false);
|
||||
if (!shown) {
|
||||
AppDialog.information("creationMinimizeNotice");
|
||||
AppCache.update("creationMinimizeDialogShown", true);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModalOverlay createModalOverlay(StoreCreationModel model) {
|
||||
var comp = new StoreCreationComp(model);
|
||||
comp.prefWidth(650);
|
||||
@@ -187,7 +197,12 @@ public class StoreCreationDialog {
|
||||
|
||||
if (!model.isQuickConnect()) {
|
||||
var queueEntry = StoreCreationQueueEntry.of(model, modal);
|
||||
modal.hideable(queueEntry);
|
||||
|
||||
modal.setHideAction(() -> {
|
||||
AppLayoutModel.get().getQueueEntries().add(queueEntry);
|
||||
showNotice();
|
||||
});
|
||||
|
||||
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
if (model.getFinished().get() || !modal.isShowing()) {
|
||||
return;
|
||||
@@ -195,6 +210,7 @@ public class StoreCreationDialog {
|
||||
|
||||
modal.hide();
|
||||
AppLayoutModel.get().getQueueEntries().add(queueEntry);
|
||||
showNotice();
|
||||
});
|
||||
modal.setRequireCloseButtonForClose(true);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ public class StoreEntryWrapper {
|
||||
private final BooleanProperty pinToTop = new SimpleBooleanProperty();
|
||||
private final IntegerProperty orderIndex = new SimpleIntegerProperty();
|
||||
private final BooleanProperty effectiveBusy = new SimpleBooleanProperty();
|
||||
private final Property<StoreCategoryWrapper> lastInformationCategory = new SimpleObjectProperty<>();
|
||||
private final ObservableList<String> tags = FXCollections.observableArrayList();
|
||||
private boolean effectiveBusyProviderBound = false;
|
||||
|
||||
@@ -223,11 +224,12 @@ public class StoreEntryWrapper {
|
||||
sessionActive.setValue(entry.getStore() instanceof SingletonSessionStore<?> ss
|
||||
&& entry.getStore() instanceof ShellStore
|
||||
&& ss.isSessionRunning());
|
||||
category.setValue(StoreViewState.get().getCategories().getList().stream()
|
||||
var newCat = StoreViewState.get().getCategories().getList().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(entry.getCategoryUuid()))
|
||||
.findFirst()
|
||||
.orElse(StoreViewState.get().getAllConnectionsCategory()));
|
||||
.orElse(StoreViewState.get().getAllConnectionsCategory());
|
||||
category.setValue(newCat);
|
||||
perUser.setValue(
|
||||
!category.getValue().getRoot().equals(StoreViewState.get().getAllIdentitiesCategory())
|
||||
&& entry.isPerUserStore());
|
||||
@@ -238,7 +240,14 @@ public class StoreEntryWrapper {
|
||||
|
||||
var storeChanged = store.getValue() != entry.getStore();
|
||||
store.setValue(entry.getStore());
|
||||
if (storeChanged || !information.isBound()) {
|
||||
|
||||
var selectedCat = StoreViewState.get().getActiveCategory().getValue();
|
||||
var switchedCat = !selectedCat.equals(lastInformationCategory.getValue());
|
||||
lastInformationCategory.setValue(selectedCat);
|
||||
|
||||
// Some infos depend on the section info, which might change the top level state based
|
||||
// on the selected category
|
||||
if (StoreViewState.get().isInitialized() && (storeChanged || !information.isBound() || switchedCat)) {
|
||||
information.unbind();
|
||||
shownInformation.unbind();
|
||||
if (entry.getValidity().isUsable()
|
||||
|
||||
@@ -143,11 +143,12 @@ public class StoreIconChoiceComp extends ModalOverlayContentComp {
|
||||
struc.setPrefWidth(300);
|
||||
});
|
||||
text.style(Styles.TEXT_SUBTLE);
|
||||
text.visible(busy);
|
||||
text.show(busy);
|
||||
|
||||
var loading = new LoadingIconComp(busy, AppFontSizes::title);
|
||||
loading.prefWidth(50);
|
||||
loading.prefHeight(50);
|
||||
loading.show(busy);
|
||||
|
||||
var vbox = new VerticalComp(List.of(text, loading, refreshButton)).spacing(25);
|
||||
vbox.apply(struc -> {
|
||||
|
||||
@@ -340,6 +340,10 @@ public class StoreViewState {
|
||||
DataStorage.get().setSelectedCategory(newValue.getCategory());
|
||||
batchModeSelection.getList().clear();
|
||||
batchModeSelectionSet.clear();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
updateWrappers();
|
||||
});
|
||||
});
|
||||
var selected = AppCache.getNonNull("selectedCategory", UUID.class, () -> DataStorage.DEFAULT_CATEGORY_UUID);
|
||||
activeCategory.setValue(categories.getList().stream()
|
||||
|
||||
@@ -7,14 +7,12 @@ import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.ext.ScanProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.hub.comp.StoreChoiceComp;
|
||||
import io.xpipe.app.hub.comp.StoreViewState;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.platform.BindingsHelper;
|
||||
import io.xpipe.app.platform.LabelGraphic;
|
||||
import io.xpipe.app.platform.OptionsBuilder;
|
||||
import io.xpipe.app.platform.OptionsChoiceBuilder;
|
||||
import io.xpipe.app.platform.*;
|
||||
import io.xpipe.app.process.ShellScript;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
@@ -23,6 +21,8 @@ import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.OsType;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -244,14 +244,34 @@ public class TerminalCategory extends AppPrefsCategory {
|
||||
});
|
||||
var proxyChoice = new DelayedInitComp(
|
||||
RegionBuilder.of(() -> {
|
||||
var comp = new StoreChoiceComp<>(
|
||||
var choice = new StoreChoiceComp<>(
|
||||
null,
|
||||
ref,
|
||||
ShellStore.class,
|
||||
r -> r.get().equals(DataStorage.get().local()) || TerminalProxyManager.canUseAsProxy(r),
|
||||
StoreViewState.get().getAllConnectionsCategory(),
|
||||
null);
|
||||
return comp.build();
|
||||
choice.hgrow();
|
||||
|
||||
var refresh = new ButtonComp(null, new LabelGraphic.IconGraphic("mdi2r-refresh"), null);
|
||||
refresh.describe(d -> d.nameKey("refresh"));
|
||||
refresh.apply(button -> {
|
||||
var disable = new SimpleBooleanProperty();
|
||||
button.disableProperty().bind(PlatformThread.sync(disable));
|
||||
button.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(disable, () -> {
|
||||
ProcessControlProvider.get().refreshWsl();
|
||||
});
|
||||
});
|
||||
event.consume();
|
||||
});
|
||||
});
|
||||
refresh.hide(new ReadOnlyBooleanWrapper(OsType.ofLocal() != OsType.WINDOWS));
|
||||
|
||||
var box = new HorizontalComp(List.of(choice, refresh));
|
||||
box.spacing(12);
|
||||
return box.build();
|
||||
}),
|
||||
() -> StoreViewState.get() != null && StoreViewState.get().isInitialized());
|
||||
proxyChoice.maxWidth(getCompWidth());
|
||||
|
||||
@@ -80,7 +80,7 @@ public interface ExternalRdpClient extends PrefsValue {
|
||||
yield windowsApp;
|
||||
}
|
||||
case OsType.Windows ignored -> {
|
||||
yield MstscRdpClient.builder().smartSizing(false).build();
|
||||
yield MstscRdpClient.builder().smartSizing(true).dock(true).build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package io.xpipe.app.secret;
|
||||
|
||||
import io.xpipe.app.comp.base.IntegratedTextAreaComp;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.ext.ValidationException;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.platform.OptionsBuilder;
|
||||
import io.xpipe.app.process.LocalShell;
|
||||
import io.xpipe.app.process.ShellScript;
|
||||
import io.xpipe.app.util.Validators;
|
||||
import io.xpipe.core.InPlaceSecretValue;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
@@ -28,7 +32,9 @@ public class SecretCustomCommandStrategy implements SecretRetrievalStrategy {
|
||||
Property<SecretCustomCommandStrategy> p, SecretStrategyChoiceConfig config) {
|
||||
var options = new OptionsBuilder();
|
||||
var cmdProperty = options.map(p, SecretCustomCommandStrategy::getCommand);
|
||||
return options.addComp(new TextFieldComp(cmdProperty), cmdProperty)
|
||||
return options
|
||||
.nameAndDescription("customCommandValue")
|
||||
.addComp(IntegratedTextAreaComp.script(cmdProperty, new ReadOnlyObjectWrapper<>(LocalShell.getDialect().getScriptFileEnding()), true), cmdProperty)
|
||||
.nonNull()
|
||||
.bind(
|
||||
() -> {
|
||||
@@ -37,7 +43,7 @@ public class SecretCustomCommandStrategy implements SecretRetrievalStrategy {
|
||||
p);
|
||||
}
|
||||
|
||||
String command;
|
||||
ShellScript command;
|
||||
|
||||
@Override
|
||||
public void checkComplete() throws ValidationException {
|
||||
@@ -49,14 +55,14 @@ public class SecretCustomCommandStrategy implements SecretRetrievalStrategy {
|
||||
return new SecretQuery() {
|
||||
@Override
|
||||
public SecretQueryResult query(String prompt, boolean forceFocus) {
|
||||
if (command == null || command.isBlank()) {
|
||||
if (command == null || command.getValue().isBlank()) {
|
||||
throw ErrorEventFactory.expected(new IllegalStateException("No custom command specified"));
|
||||
}
|
||||
|
||||
try (var cc = ProcessControlProvider.get()
|
||||
try (var sc = ProcessControlProvider.get()
|
||||
.createLocalProcessControl(true)
|
||||
.command(command)
|
||||
.start()) {
|
||||
var cc = sc.command(command);
|
||||
return new SecretQueryResult(
|
||||
InPlaceSecretValue.of(cc.readStdoutOrThrow()), SecretQueryState.NORMAL);
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -29,7 +29,7 @@ public class TerminalDockView implements WindowDockListener {
|
||||
|
||||
public synchronized void removeTerminal(TerminalView.TerminalSession s) {
|
||||
terminalInstances.removeIf(controllableTerminalSession ->
|
||||
controllableTerminalSession.getTerminalProcess().equals(s.getTerminalProcess()));
|
||||
controllableTerminalSession.equals(s));
|
||||
}
|
||||
|
||||
public synchronized boolean isActive() {
|
||||
@@ -125,11 +125,17 @@ public class TerminalDockView implements WindowDockListener {
|
||||
}
|
||||
|
||||
public synchronized boolean closeOtherTerminals(UUID request) {
|
||||
var owner = TerminalView.get().getSessions().stream()
|
||||
.filter(shellSession -> shellSession.getRequest().equals(request))
|
||||
.map(shellSession -> shellSession.getTerminal())
|
||||
.findFirst().orElse(null);
|
||||
if (owner == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var others = terminalInstances.stream()
|
||||
.filter(terminal -> terminal.getTerminalProcess().isAlive())
|
||||
.filter(terminal -> TerminalView.get().getSessions().stream()
|
||||
.noneMatch(shellSession -> shellSession.getRequest().equals(request)
|
||||
&& shellSession.getTerminal().equals(terminal)))
|
||||
.filter(terminal -> !terminal.equals(owner))
|
||||
.toList();
|
||||
for (TerminalView.ControllableTerminalSession other : others) {
|
||||
closeTerminal(other);
|
||||
|
||||
@@ -86,11 +86,11 @@ public class TerminalPaneConfiguration {
|
||||
ShellDialects.POWERSHELL.terminalLauncherScript(request, title, alwaysPromptRestart));
|
||||
var content = """
|
||||
%s
|
||||
echo 'Transcript started, output file is "sessions\\%s"'
|
||||
echo 'Session logging is active, output file is "sessions\\%s"'
|
||||
Start-Transcript -Force -LiteralPath "%s" | Out-Null
|
||||
& "%s"
|
||||
Stop-Transcript | Out-Null
|
||||
echo 'Transcript stopped, output file is "sessions\\%s"'
|
||||
echo 'Session logging is finished, output file is "sessions\\%s"'
|
||||
""".formatted(
|
||||
TerminalLauncher.getTerminalRegisterCommand(
|
||||
request, LocalShell.getLocalPowershell().orElseThrow()),
|
||||
@@ -131,9 +131,9 @@ public class TerminalPaneConfiguration {
|
||||
: "script --quiet --command '%s' \"%s\"".formatted(command, logFile);
|
||||
var content = """
|
||||
%s
|
||||
echo "Transcript started, output file is sessions/%s"
|
||||
echo "Session logging is active, output file is sessions/%s"
|
||||
%s
|
||||
echo "Transcript stopped, output file is sessions/%s"
|
||||
echo "Session logging is finished, output file is sessions/%s"
|
||||
cat "%s" | "%s" terminal-clean > "%s.txt"
|
||||
""".formatted(
|
||||
TerminalLauncher.getTerminalRegisterCommand(request, sc),
|
||||
|
||||
@@ -282,6 +282,19 @@ public class TerminalView {
|
||||
this.controllable = controllable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ControllableTerminalSession that)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(terminalProcess.pid(), that.terminalProcess.pid()) && Objects.equals(controllable.getRawHandle(), that.controllable.getRawHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(terminalProcess.pid(), controllable.getRawHandle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return super.isRunning() && !controllable.isDestroyed();
|
||||
|
||||
@@ -51,6 +51,8 @@ public abstract class ControllableWindowProcess {
|
||||
|
||||
public abstract Rect queryBounds();
|
||||
|
||||
public abstract Object getRawHandle();
|
||||
|
||||
public void updateBoundsState() {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
|
||||
@@ -151,6 +151,11 @@ public final class ControllableWindowsProcess extends ControllableWindowProcess
|
||||
return control.getBounds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getRawHandle() {
|
||||
return control.getWindowHandle();
|
||||
}
|
||||
|
||||
public void updateBoundsState() {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
|
||||
@@ -86,6 +86,8 @@ public class RemoteDesktopDockView implements WindowDockListener {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeWinWindowControl.MAIN_WINDOW.activate();
|
||||
|
||||
var controllable = e.getProcess();
|
||||
controllable.show();
|
||||
controllable.moveToFront();
|
||||
|
||||
@@ -159,7 +159,7 @@ public class ScanDialogBase {
|
||||
so -> null,
|
||||
selected,
|
||||
scanOperation -> scanOperation.isDisabled(),
|
||||
() -> available.size() > 3)
|
||||
() -> available.stream().filter(sa -> !sa.isDisabled()).count() >= 2)
|
||||
.build();
|
||||
stackPane.getChildren().add(r);
|
||||
|
||||
|
||||
2
lang/strings/fixed_en.properties
generated
2
lang/strings/fixed_en.properties
generated
@@ -54,7 +54,7 @@ nullPointer=Null Pointer
|
||||
discord=Discord
|
||||
slack=Slack
|
||||
github=GitHub
|
||||
mstsc=Microsoft Terminal Services Client (MSTSC)
|
||||
mstsc=Microsoft Terminal Services Client (mstsc)
|
||||
remmina=Remmina
|
||||
microsoftRemoteDesktopApp=Microsoft Remote Desktop.app
|
||||
bitwarden=Bitwarden
|
||||
|
||||
4
lang/strings/translations_en.properties
generated
4
lang/strings/translations_en.properties
generated
@@ -73,6 +73,8 @@ remove=Remove
|
||||
createNewCategory=New subcategory
|
||||
prompt=Prompt
|
||||
customCommand=Custom command
|
||||
customCommandValue=Commands
|
||||
customCommandValueDescription=The command/script to execute in the local shell
|
||||
other=Other
|
||||
setLock=Set lock
|
||||
selectConnection=Select connection
|
||||
@@ -2156,3 +2158,5 @@ connectionConfiguration=Connection configuration
|
||||
move=Move
|
||||
confirmDeletionTitle=Confirm deletion
|
||||
confirmDeletionContent=Do you want to delete the selected entries?
|
||||
creationMinimizeNoticeTitle=Dialog minimized
|
||||
creationMinimizeNoticeContent=The current dialog has been minimized. You can reopen it again by clicking on the minimized icon in the bottom right corner.
|
||||
|
||||
Reference in New Issue
Block a user