More desktop rework

This commit is contained in:
crschnick 2024-04-17 08:07:40 +00:00
parent 5e80c1a4a1
commit ba83a9c23a
18 changed files with 286 additions and 73 deletions

View file

@ -7,6 +7,7 @@ import io.xpipe.app.core.check.AppAvCheck;
import io.xpipe.app.core.check.AppCertutilCheck;
import io.xpipe.app.core.check.AppShellCheck;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
@ -62,6 +63,7 @@ public class BaseMode extends OperationMode {
AppPrefs.initSharedRemote();
UnlockAlert.showIfNeeded();
DataStorage.init();
DataStoreProviders.init();
AppFileWatcher.init();
FileBridge.init();
ActionProvider.initProviders();
@ -77,6 +79,7 @@ public class BaseMode extends OperationMode {
TrackEvent.info("Background mode shutdown started");
BrowserSessionModel.DEFAULT.reset();
StoreViewState.reset();
DataStoreProviders.reset();
DataStorage.reset();
AppPrefs.reset();
AppResources.reset();

View file

@ -1,11 +1,13 @@
package io.xpipe.app.core.mode;
import io.xpipe.app.Main;
import io.xpipe.app.core.*;
import io.xpipe.app.core.App;
import io.xpipe.app.core.AppLogs;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.AppState;
import io.xpipe.app.core.check.AppDebugModeCheck;
import io.xpipe.app.core.check.AppTempCheck;
import io.xpipe.app.core.check.AppUserDirectoryCheck;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.*;
import io.xpipe.app.launcher.LauncherCommand;
import io.xpipe.app.launcher.LauncherInput;
@ -166,8 +168,6 @@ public abstract class OperationMode {
}
}
}
DataStoreProviders.postInit(AppExtensionManager.getInstance().getExtendedLayer());
} catch (Throwable ex) {
ErrorEvent.fromThrowable(ex).term().handle();
}

View file

@ -52,6 +52,10 @@ public interface DataStoreProvider {
}
}
default ActionProvider.Action activateAction(DataStoreEntry store) {
return null;
}
default ActionProvider.Action launchAction(DataStoreEntry store) {
return null;
}
@ -139,13 +143,13 @@ public interface DataStoreProvider {
return null;
}
default boolean init() {
default boolean preInit() {
return true;
}
default void postInit() {}
default void init() {}
default void storageInit() {}
default void reset() {}
default boolean isShareableFromLocalMachine() {
return false;

View file

@ -16,12 +16,22 @@ public class DataStoreProviders {
private static List<DataStoreProvider> ALL;
public static void postInit(ModuleLayer layer) {
ALL.forEach(p -> {
public static void init() {
DataStoreProviders.getAll().forEach(dataStoreProvider -> {
try {
p.postInit();
} catch (Throwable e) {
ErrorEvent.fromThrowable(e).handle();
dataStoreProvider.init();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
}
});
}
public static void reset() {
DataStoreProviders.getAll().forEach(dataStoreProvider -> {
try {
dataStoreProvider.reset();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
}
});
}
@ -80,7 +90,7 @@ public class DataStoreProviders {
.collect(Collectors.toList());
ALL.removeIf(p -> {
try {
if (!p.init()) {
if (!p.preInit()) {
return true;
}

View file

@ -14,7 +14,7 @@ import java.util.function.Supplier;
public interface ExternalRdpClientType extends PrefsChoiceValue {
ExternalRdpClientType MSTSC = new PathCheckType("app.mstsc", "mstsc.exe", true) {
ExternalRdpClientType MSTSC = new PathCheckType("app.mstsc", "mstsc.exe", false) {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {

View file

@ -86,14 +86,6 @@ public abstract class DataStorage {
INSTANCE = shouldPersist() ? new StandardStorage() : new ImpersistentStorage();
INSTANCE.load();
DataStoreProviders.getAll().forEach(dataStoreProvider -> {
try {
dataStoreProvider.storageInit();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
}
});
}
public static void reset() {

View file

@ -204,7 +204,7 @@ public class OptionsBuilder {
this.lastCompHeadReference = comp;
}
public OptionsBuilder stringArea(Property<String> prop, boolean lazy) {
public OptionsBuilder addStringArea(Property<String> prop, boolean lazy) {
var comp = new TextAreaComp(prop, lazy);
pushComp(comp);
props.add(prop);

View file

@ -23,23 +23,11 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
@Override
public ActionProvider.Action browserAction(BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) {
DesktopApplicationStore s = store.getStore().asNeeded();
return new ActionProvider.Action() {
@Override
public boolean requiresJavaFXPlatform() {
return false;
}
@Override
public void execute() throws Exception {
s.getDesktop().getStore().runDesktopScript(store.getName(), s.getDesktop().getStore().getUsedDialect(), s.getFullCommand());
}
};
return launchAction(store);
}
@Override
public ActionProvider.Action launchAction(DataStoreEntry store) {
DesktopApplicationStore s = store.getStore().asNeeded();
return new ActionProvider.Action() {
@Override
public boolean requiresJavaFXPlatform() {
@ -48,7 +36,13 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
@Override
public void execute() throws Exception {
s.getDesktop().getStore().runDesktopScript(store.getName(), s.getDesktop().getStore().getUsedDialect(), s.getFullCommand());
DesktopApplicationStore s = store.getStore().asNeeded();
var baseEntry = s.getDesktop().get();
var baseActivate = baseEntry.getProvider().activateAction(baseEntry);
if (baseActivate != null) {
baseActivate.execute();
}
s.getDesktop().getStore().runDesktopApplication(store.getName(), s);
}
};
}
@ -71,7 +65,7 @@ public class DesktopApplicationStoreProvider implements DataStoreProvider {
var path = new SimpleStringProperty(st.getPath() != null ? st.getPath().toAbsoluteFilePath(null) : null);
var args = new SimpleStringProperty(st.getArguments() != null ? st.getArguments() : null);
return new OptionsBuilder()
.nameAndDescription("desktopEnvironmentBase")
.nameAndDescription("desktopBase")
.addComp(
new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.HOST,

View file

@ -1,17 +1,19 @@
package io.xpipe.ext.base.desktop;
import io.xpipe.app.terminal.ExternalTerminalType;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FilePath;
public interface DesktopBaseStore extends DataStore {
boolean supportsDesktopAccess();
void runDesktopScript(String name, ShellDialect dialect, String script) throws Exception;
void runDesktopApplication(String name, DesktopApplicationStore applicationStore) throws Exception;
void runDesktopTerminal(String name, ExternalTerminalType terminalType, ShellDialect dialect, String script) throws Exception;
void runDesktopScript(String name, String script) throws Exception;
FilePath createScript(ShellDialect dialect, String content) throws Exception;
ShellDialect getUsedDialect();

View file

@ -0,0 +1,28 @@
package io.xpipe.ext.base.desktop;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.Validators;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.ext.base.SelfReferentialStore;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
@Getter
@SuperBuilder
@Jacksonized
@JsonTypeName("desktopCommand")
public class DesktopCommandStore extends JacksonizedValue implements DataStore, SelfReferentialStore {
private final DataStoreEntryRef<DesktopEnvironmentStore> environment;
private final String script;
@Override
public void checkComplete() throws Throwable {
Validators.nonNull(environment);
Validators.isType(environment, DesktopEnvironmentStore.class);
Validators.nonNull(script);
}
}

View file

@ -0,0 +1,129 @@
package io.xpipe.ext.base.desktop;
import io.xpipe.app.browser.session.BrowserSessionModel;
import io.xpipe.app.comp.base.IntegratedTextAreaComp;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.fxcomps.impl.DataStoreChoiceComp;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DataStoreFormatter;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import java.util.List;
public class DesktopCommandStoreProvider implements DataStoreProvider {
@Override
public ActionProvider.Action browserAction(BrowserSessionModel sessionModel, DataStoreEntry store, BooleanProperty busy) {
return launchAction(store);
}
@Override
public ActionProvider.Action launchAction(DataStoreEntry store) {
return new ActionProvider.Action() {
@Override
public boolean requiresJavaFXPlatform() {
return false;
}
@Override
public void execute() throws Exception {
DesktopCommandStore s = store.getStore().asNeeded();
var baseEntry = s.getEnvironment().getStore().getBase().get();
var baseActivate = baseEntry.getProvider().activateAction(baseEntry);
if (baseActivate != null) {
baseActivate.execute();
}
s.getEnvironment().getStore().runDesktopTerminal(store.getName(),s.getScript());
}
};
}
@Override
public CreationCategory getCreationCategory() {
return CreationCategory.DESKSTOP;
}
@Override
public DataStoreEntry getDisplayParent(DataStoreEntry store) {
DesktopCommandStore s = store.getStore().asNeeded();
return s.getEnvironment().get();
}
@Override
public GuiDialog guiDialog(DataStoreEntry entry, Property<DataStore> store) {
DesktopCommandStore st = (DesktopCommandStore) store.getValue();
var env = new SimpleObjectProperty<>(st.getEnvironment());
var script = new SimpleStringProperty(st.getScript());
return new OptionsBuilder()
.nameAndDescription("desktopEnvironmentBase")
.addComp(
new DataStoreChoiceComp<>(
DataStoreChoiceComp.Mode.HOST,
entry,
env,
DesktopEnvironmentStore.class,
desktopStoreDataStoreEntryRef -> desktopStoreDataStoreEntryRef.getStore().supportsDesktopAccess(),
StoreViewState.get().getAllConnectionsCategory()),
env)
.nonNull()
.nameAndDescription("desktopCommandScript")
.addComp(
new IntegratedTextAreaComp(
script,
false,
"commands",
Bindings.createStringBinding(
() -> {
return env.getValue() != null && env.getValue().getStore().getDialect() != null
? env.getValue().getStore().getDialect()
.getScriptFileEnding()
: "sh";
},
env)),
script)
.nonNull()
.bind(
() -> {
return DesktopCommandStore.builder().environment(env.get()).script(script.get()).build();
},
store)
.buildDialog();
}
public String summaryString(StoreEntryWrapper wrapper) {
DesktopCommandStore s = wrapper.getEntry().getStore().asNeeded();
return DataStoreFormatter.toApostropheName(s.getEnvironment().get()) + " config";
}
@Override
public String getDisplayIconFileName(DataStore store) {
return "base:desktopCommand_icon.svg";
}
@Override
public DataStore defaultStore() {
return DesktopCommandStore.builder().build();
}
@Override
public List<String> getPossibleNames() {
return List.of("desktopCommand");
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(DesktopCommandStore.class);
}
}

View file

@ -7,6 +7,7 @@ import io.xpipe.app.util.Validators;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.store.FilePath;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.ext.base.SelfReferentialStore;
import io.xpipe.ext.base.script.ScriptStore;
@ -14,14 +15,15 @@ import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@Getter
@SuperBuilder
@Jacksonized
@JsonTypeName("desktopEnvironment")
public class DesktopEnvironmentStore extends JacksonizedValue implements DesktopBaseStore, DataStore, SelfReferentialStore {
public class DesktopEnvironmentStore extends JacksonizedValue
implements DesktopBaseStore, DataStore, SelfReferentialStore {
private final DataStoreEntryRef<DesktopBaseStore> base;
private final ExternalTerminalType terminal;
@ -45,35 +47,51 @@ public class DesktopEnvironmentStore extends JacksonizedValue implements Desktop
public String getMergedInitCommands(String command) {
var f = ScriptStore.flatten(scripts);
var filtered = f.stream().filter(simpleScriptStore -> simpleScriptStore.getMinimumDialect().isCompatibleTo(dialect) && simpleScriptStore.getExecutionType().runInTerminal()).toList();
var initCommands = Stream.concat(filtered.stream().map(simpleScriptStore -> simpleScriptStore.getCommands()), command != null ? Stream.of(command) : Stream.of()).toList();
var filtered = f.stream()
.filter(simpleScriptStore ->
simpleScriptStore.getMinimumDialect().isCompatibleTo(dialect)
&& simpleScriptStore.getExecutionType().runInTerminal())
.toList();
var initCommands = new ArrayList<>(filtered.stream().map(simpleScriptStore -> simpleScriptStore.getCommands()).toList());
if (initScript != null) {
initCommands.add(initScript);
}
if (command != null) {
initCommands.add(command);
}
var joined = String.join(dialect.getNewLine().getNewLineString(), initCommands);
return !joined.isBlank() ? joined : null;
}
public void launch(String n, String commands) throws Exception {
var fullName = n + " [" + getSelfEntry().getName() + "]";
base.getStore().runDesktopScript(fullName, dialect, getMergedInitCommands(commands));
}
public void launchSelf() throws Exception {
var fullName = getSelfEntry().getName();
base.getStore().runDesktopTerminal(fullName, terminal, dialect, getMergedInitCommands(null));
}
@Override
public boolean supportsDesktopAccess() {
return base.getStore().supportsDesktopAccess();
}
@Override
public void runDesktopScript(String name, ShellDialect dialect, String script) throws Exception {
base.getStore().runDesktopScript(name, dialect, script);
public void runDesktopApplication(String name, DesktopApplicationStore applicationStore) throws Exception {
var fullName = name + " [" + getSelfEntry().getName() + "]";
base.getStore().runDesktopApplication(fullName, applicationStore);
}
@Override
public void runDesktopTerminal(String name, ExternalTerminalType terminalType, ShellDialect dialect, String script) throws Exception {
base.getStore().runDesktopTerminal(name,terminalType,dialect,script);
public void runDesktopScript(String name, String script) throws Exception {
var fullName = getSelfEntry().getName();
base.getStore().runDesktopScript(fullName, getMergedInitCommands(script));
}
@Override
public FilePath createScript(ShellDialect dialect, String content) throws Exception {
return base.getStore().createScript(dialect, content);
}
public void runDesktopTerminal(String name, String script) throws Exception {
var launchCommand = terminal.remoteLaunchCommand();
var toExecute = (script != null ? getMergedInitCommands(script + "\n" + dialect.getPauseCommand() + "\n" + dialect.getNormalExitCommand()): getMergedInitCommands(null));
var scriptFile = base.getStore().createScript(dialect, toExecute);
var launchScriptFile = base.getStore().createScript(dialect, dialect.prepareTerminalInitFileOpenCommand(dialect, null, scriptFile.toString()));
var launchConfig = new ExternalTerminalType.LaunchConfiguration(null, name, name, launchScriptFile, getUsedDialect());
base.getStore().runDesktopScript(name, launchCommand.apply(launchConfig));
}
@Override

View file

@ -34,8 +34,7 @@ public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
}
@Override
public ActionProvider.Action launchAction(DataStoreEntry store) {
DesktopEnvironmentStore s = store.getStore().asNeeded();
public ActionProvider.Action activateAction(DataStoreEntry store) {
return new ActionProvider.Action() {
@Override
public boolean requiresJavaFXPlatform() {
@ -44,7 +43,32 @@ public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
@Override
public void execute() throws Exception {
s.launchSelf();
DesktopEnvironmentStore s = store.getStore().asNeeded();
var a = s.getBase().get().getProvider().activateAction(s.getBase().get());
if (a != null) {
a.execute();
}
}
};
}
@Override
public ActionProvider.Action launchAction(DataStoreEntry store) {
return new ActionProvider.Action() {
@Override
public boolean requiresJavaFXPlatform() {
return false;
}
@Override
public void execute() throws Exception {
DesktopEnvironmentStore s = store.getStore().asNeeded();
var a = s.getBase().get().getProvider().activateAction(s.getBase().get());
if (a != null) {
a.execute();
}
var fullName = store.getName();
s.runDesktopTerminal(fullName, null);
}
};
}
@ -77,8 +101,8 @@ public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
.findModule("io.xpipe.ext.proc")
.orElseThrow(),
"io.xpipe.ext.proc.ShellDialectChoiceComp")
.getDeclaredConstructor(Property.class)
.newInstance(dialect);
.getDeclaredConstructor(Property.class, boolean.class)
.newInstance(dialect, false);
return new OptionsBuilder()
.nameAndDescription("desktopHost")
.addComp(
@ -106,7 +130,6 @@ public class DesktopEnvironmentStoreProvider implements DataStoreProvider {
StoreViewState.get().getAllScriptsCategory()),
scripts)
.nameAndDescription("desktopInitScript")
.longDescription("proc:environmentScript")
.addComp(
new IntegratedTextAreaComp(
initScript,

View file

@ -150,8 +150,8 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
.findModule("io.xpipe.ext.proc")
.orElseThrow(),
"io.xpipe.ext.proc.ShellDialectChoiceComp")
.getDeclaredConstructor(Property.class)
.newInstance(dialect);
.getDeclaredConstructor(Property.class, boolean.class)
.newInstance(dialect, false);
return new OptionsBuilder()
.name("snippets")
.description("snippetsDescription")
@ -210,7 +210,7 @@ public class SimpleScriptStoreProvider implements DataStoreProvider {
}
@Override
public void storageInit() {
public void init() {
DataStorage.get()
.addStoreEntryIfNotPresent(DataStoreEntry.createNew(
UUID.fromString("a9945ad2-db61-4304-97d7-5dc4330691a7"),

View file

@ -3,6 +3,7 @@ import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.ext.base.action.*;
import io.xpipe.ext.base.browser.*;
import io.xpipe.ext.base.desktop.DesktopCommandStoreProvider;
import io.xpipe.ext.base.desktop.DesktopEnvironmentStoreProvider;
import io.xpipe.ext.base.script.ScriptGroupStoreProvider;
import io.xpipe.ext.base.script.SimpleScriptStoreProvider;
@ -66,6 +67,6 @@ open module io.xpipe.ext.base {
DeleteStoreChildrenAction,
BrowseStoreAction;
provides DataStoreProvider with
SimpleScriptStoreProvider, DesktopEnvironmentStoreProvider, DesktopApplicationStoreProvider,
SimpleScriptStoreProvider, DesktopEnvironmentStoreProvider, DesktopApplicationStoreProvider, DesktopCommandStoreProvider,
ScriptGroupStoreProvider;
}

View file

@ -119,12 +119,18 @@ desktopInitScriptDescription=Init commands specific to this environment
desktopTerminal=Terminal application
desktopTerminalDescription=The terminal to use on the desktop to start scripts in
desktopApplication.displayName=Desktop application
desktopApplication.displayDescription=Run an application in a desktop environment
desktopApplication.displayDescription=Run an application on a desktop
desktopBase=Desktop
desktopBaseDescription=The desktop to run this application on
desktopEnvironmentBase=Desktop environment
desktopEnvironmentBaseDescription=The desktop environment to run this application on
desktopApplicationPath=Application path
desktopApplicationPathDescription=The path of the executable to run
desktopApplicationArguments=Arguments
desktopApplicationArgumentsDescription=The optional arguments to pass to the application
desktopCommand.displayName=Desktop command
desktopCommand.displayDescription=Run a command in a desktop environment
desktopCommandScript=Commands
desktopCommandScriptDescription=The commands to run in the environment

View file

@ -34,7 +34,7 @@ start=Start
stop=Stop
pause=Pause
rdpTunnelHost=Tunnel host
rdpTunnelHostDescription=The optional SSH connection to use as a tunnel
rdpTunnelHostDescription=The SSH connection to use as a tunnel
rdpFileLocation=File location
rdpFileLocationDescription=The file path of the .rdp file
rdpPasswordAuthentication=Password authentication
@ -299,3 +299,6 @@ launch=Launch
sshTrustKeyHeader=The host key is not known, and you have enabled manual host key verification.
sshTrustKeyTitle=Unknown host key
vnc=VNC connections
rdpTunnel.displayName=RDP over SSH
rdpTunnel.displayDescription=Connect via RDP over a tunneled SSH connection

View file

@ -1,5 +1,5 @@
## RDP Tunnel Host
You can choose to connect to a remote RDP host via an SSH tunnel. This gives you the ability to use the more advanced SSH authentication features with RDP out of the box.
You can connect to a remote RDP host via an SSH tunnel. This gives you the ability to use the more advanced SSH authentication features with RDP out of the box.
When this option is used, the host address in the RDP file will be replaced by the chosen hostname of the SSH connection. Upon first connection, an SSH tunnel will be established and the RDP client will connect to the tunneled connection via localhost instead.
Upon first connection, an SSH tunnel will be established and the RDP client will connect to the tunneled connection via localhost instead. It will use the credentials of the SSH connection user for RDP authentication.