This commit is contained in:
crschnick
2025-08-09 21:55:51 +00:00
parent b968de533c
commit d5404fb083
101 changed files with 1449 additions and 1202 deletions

View File

@@ -57,7 +57,7 @@ dependencies {
api 'io.xpipe:vernacular:1.15'
api 'org.bouncycastle:bcprov-jdk18on:1.81'
api 'info.picocli:picocli:4.7.6'
api 'org.apache.commons:commons-lang3:3.17.0'
api 'org.apache.commons:commons-lang3:3.18.0'
api 'io.sentry:sentry:8.13.3'
api 'commons-io:commons-io:2.19.0'
api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.19.1"

View File

@@ -1,8 +1,8 @@
package io.xpipe.app.beacon;
import io.xpipe.app.beacon.mcp.AppMcpServer;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.beacon.mcp.AppMcpServer;
import io.xpipe.app.util.DocumentationLink;
import io.xpipe.beacon.BeaconConfig;
import io.xpipe.beacon.BeaconInterface;

View File

@@ -21,9 +21,7 @@ public class DaemonModeExchangeImpl extends DaemonModeExchange {
}
OperationMode.switchToSyncIfPossible(mode);
return DaemonModeExchange.Response.builder()
.usedMode(msg.getMode())
.build();
return DaemonModeExchange.Response.builder().usedMode(msg.getMode()).build();
}
@Override

View File

@@ -1,15 +1,16 @@
package io.xpipe.app.beacon.mcp;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.ThreadHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.spec.McpSchema;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.ThreadHelper;
import lombok.SneakyThrows;
import lombok.Value;
@@ -74,40 +75,40 @@ public class AppMcpServer {
syncServer.notifyToolsListChanged();
});
// syncServer.addResource(McpResources.connections());
// syncServer.addResource(McpResources.categories());
//
// DataStorage.get().addListener(new StorageListener() {
// @Override
// public void onStoreListUpdate() {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onStoreAdd(DataStoreEntry... entry) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onStoreRemove(DataStoreEntry... entry) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onCategoryAdd(DataStoreCategory category) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onCategoryRemove(DataStoreCategory category) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onEntryCategoryChange() {
// syncServer.notifyResourcesListChanged();
// }
// });
// syncServer.addResource(McpResources.connections());
// syncServer.addResource(McpResources.categories());
//
// DataStorage.get().addListener(new StorageListener() {
// @Override
// public void onStoreListUpdate() {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onStoreAdd(DataStoreEntry... entry) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onStoreRemove(DataStoreEntry... entry) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onCategoryAdd(DataStoreCategory category) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onCategoryRemove(DataStoreCategory category) {
// syncServer.notifyResourcesListChanged();
// }
//
// @Override
// public void onEntryCategoryChange() {
// syncServer.notifyResourcesListChanged();
// }
// });
INSTANCE = new AppMcpServer(syncServer, transportProvider);
}
@@ -124,7 +125,8 @@ public class AppMcpServer {
}
if (!AppPrefs.get().enableMcpServer().get()) {
transportProvider.sendError(exchange, 403, "MCP server is not enabled in the API settings menu");
transportProvider.sendError(
exchange, 403, "MCP server is not enabled in the API settings menu");
if (exchange.getRequestMethod().equals("POST")) {
ThreadHelper.runAsync(() -> {
ErrorEventFactory.fromMessage(
@@ -151,12 +153,14 @@ public class AppMcpServer {
return;
}
var correct = apiKey.replace("Bearer ", "").equals(AppPrefs.get().apiKey().get());
var correct = apiKey.replace("Bearer ", "")
.equals(AppPrefs.get().apiKey().get());
if (!correct) {
transportProvider.sendError(exchange, 403, "Invalid API key");
if (exchange.getRequestMethod().equals("POST")) {
ThreadHelper.runAsync(() -> {
ErrorEventFactory.fromMessage("The Authorization header sent by the MCP client is not correct")
ErrorEventFactory.fromMessage(
"The Authorization header sent by the MCP client is not correct")
.expected()
.handle();
});

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,13 @@
package io.xpipe.app.beacon.mcp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.JacksonMapper;
import io.xpipe.core.StorePath;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.core.JacksonMapper;
import io.xpipe.core.StorePath;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
@@ -51,7 +52,6 @@ public final class McpResources {
Map<String, Object> internalCache;
}
@Jacksonized
@Builder
@Value
@@ -85,21 +85,40 @@ public final class McpResources {
continue;
}
var names = DataStorage.get().getStorePath(DataStorage.get().getStoreCategoryIfPresent(e.getCategoryUuid()).orElseThrow()).getNames();
var names = DataStorage.get()
.getStorePath(DataStorage.get()
.getStoreCategoryIfPresent(e.getCategoryUuid())
.orElseThrow())
.getNames();
var cat = new StorePath(names.subList(1, names.size()));
var cache = e.getStoreCache().entrySet().stream().filter(stringObjectEntry -> {
return stringObjectEntry.getValue() != null && (ClassUtils.isPrimitiveOrWrapper(stringObjectEntry.getValue().getClass()) ||
stringObjectEntry.getValue() instanceof String);
}).collect(Collectors.toMap(stringObjectEntry -> stringObjectEntry.getKey(), stringObjectEntry -> stringObjectEntry.getValue()));
var cache = e.getStoreCache().entrySet().stream()
.filter(stringObjectEntry -> {
return stringObjectEntry.getValue() != null
&& (ClassUtils.isPrimitiveOrWrapper(
stringObjectEntry.getValue().getClass())
|| stringObjectEntry.getValue() instanceof String);
})
.collect(Collectors.toMap(
stringObjectEntry -> stringObjectEntry.getKey(),
stringObjectEntry -> stringObjectEntry.getValue()));
var resourceData = ConnectionResource.builder().lastModified(e.getLastModified()).lastUsed(e.getLastUsed())
.category(cat).name(DataStorage.get().getStorePath(e)).connectionData(e.getStore()).usageCategory(
e.getProvider().getUsageCategory()).type(e.getProvider().getId()).internalState(
e.getStorePersistentState() != null ? e.getStorePersistentState() : new Object()).internalCache(cache).build();
var resourceData = ConnectionResource.builder()
.lastModified(e.getLastModified())
.lastUsed(e.getLastUsed())
.category(cat)
.name(DataStorage.get().getStorePath(e))
.connectionData(e.getStore())
.usageCategory(e.getProvider().getUsageCategory())
.type(e.getProvider().getId())
.internalState(e.getStorePersistentState() != null ? e.getStorePersistentState() : new Object())
.internalCache(cache)
.build();
McpSchema.TextResourceContents c;
try {
c = new McpSchema.TextResourceContents("xpipe://connections/" + e.getUuid(), "application/json",
c = new McpSchema.TextResourceContents(
"xpipe://connections/" + e.getUuid(),
"application/json",
JacksonMapper.getDefault().writeValueAsString(resourceData));
} catch (JsonProcessingException ex) {
throw new RuntimeException(ex);
@@ -111,7 +130,6 @@ public final class McpResources {
});
}
public static McpServerFeatures.SyncResourceSpecification categories() {
McpSchema.Annotations annotations = new McpSchema.Annotations(List.of(McpSchema.Role.ASSISTANT), 0.3);
var resource = McpSchema.Resource.builder()
@@ -134,7 +152,9 @@ public final class McpResources {
McpSchema.TextResourceContents c;
try {
c = new McpSchema.TextResourceContents("xpipe://categories/" + cat.getUuid(), "application/json",
c = new McpSchema.TextResourceContents(
"xpipe://categories/" + cat.getUuid(),
"application/json",
JacksonMapper.getDefault().writeValueAsString(jsonData));
} catch (JsonProcessingException ex) {
throw new RuntimeException(ex);

View File

@@ -1,8 +1,9 @@
package io.xpipe.app.beacon.mcp;
import io.modelcontextprotocol.spec.McpSchema;
import io.xpipe.core.JacksonMapper;
import io.modelcontextprotocol.spec.McpSchema;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

View File

@@ -1,7 +1,5 @@
package io.xpipe.app.beacon.mcp;
import io.modelcontextprotocol.server.McpSyncServerExchange;
import io.modelcontextprotocol.spec.McpSchema;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.storage.DataStorage;
@@ -10,25 +8,30 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.beacon.BeaconClientException;
import io.xpipe.core.FilePath;
import io.modelcontextprotocol.server.McpSyncServerExchange;
import io.modelcontextprotocol.spec.McpSchema;
import lombok.SneakyThrows;
import java.util.Optional;
import java.util.function.BiFunction;
public interface McpToolHandler extends BiFunction<McpSyncServerExchange, McpSchema.CallToolRequest, McpSchema.CallToolResult>{
public interface McpToolHandler
extends BiFunction<McpSyncServerExchange, McpSchema.CallToolRequest, McpSchema.CallToolResult> {
static McpToolHandler of(McpToolHandler t) {
return t;
}
class ToolRequest {
class ToolRequest {
protected final McpSyncServerExchange exchange;
protected final McpSchema.CallToolRequest request;
public ToolRequest(McpSyncServerExchange exchange, McpSchema.CallToolRequest request) {
this.exchange = exchange;
this.request = request;}
this.request = request;
}
public McpSchema.CallToolRequest getRawRequest() {
return request;
@@ -114,8 +117,8 @@ public interface McpToolHandler extends BiFunction<McpSyncServerExchange, McpSch
var ref = getDataStoreRef(name);
var isShell = ref.getStore() instanceof ShellStore;
if (!isShell) {
throw new BeaconClientException(
"Connection " + DataStorage.get().getStorePath(ref.get()).toString() + " is not a shell connection");
throw new BeaconClientException("Connection "
+ DataStorage.get().getStorePath(ref.get()).toString() + " is not a shell connection");
}
return ref.asNeeded();
@@ -124,16 +127,23 @@ public interface McpToolHandler extends BiFunction<McpSyncServerExchange, McpSch
@Override
@SneakyThrows
default McpSchema.CallToolResult apply(McpSyncServerExchange mcpSyncServerExchange, McpSchema.CallToolRequest callToolRequest) {
default McpSchema.CallToolResult apply(
McpSyncServerExchange mcpSyncServerExchange, McpSchema.CallToolRequest callToolRequest) {
var req = new ToolRequest(mcpSyncServerExchange, callToolRequest);
try {
return handle(req);
} catch (BeaconClientException e) {
ErrorEventFactory.fromThrowable(e).expected().omit().handle();
return McpSchema.CallToolResult.builder().addTextContent(e.getMessage()).isError(true).build();
return McpSchema.CallToolResult.builder()
.addTextContent(e.getMessage())
.isError(true)
.build();
} catch (Throwable e) {
ErrorEventFactory.fromThrowable(e).handle();
return McpSchema.CallToolResult.builder().addTextContent(e.getMessage()).isError(true).build();
return McpSchema.CallToolResult.builder()
.addTextContent(e.getMessage())
.isError(true)
.build();
}
}

View File

@@ -1,8 +1,5 @@
package io.xpipe.app.beacon.mcp;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.ext.ConnectionFileSystem;
@@ -20,6 +17,10 @@ import io.xpipe.core.FileInfo;
import io.xpipe.core.FilePath;
import io.xpipe.core.JacksonMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.spec.McpSchema;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
@@ -29,273 +30,346 @@ public final class McpTools {
public static McpServerFeatures.SyncToolSpecification readFile() throws IOException {
var tool = McpSchemaFiles.loadTool("read_file.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
if (!fs.fileExists(path)) {
throw new BeaconClientException("File " + path + " does not exist");
}
if (!fs.fileExists(path)) {
throw new BeaconClientException("File " + path + " does not exist");
}
try (var in = fs.openInput(path)) {
var b = in.readAllBytes();
var s = new String(b, StandardCharsets.UTF_8);
return McpSchema.CallToolResult.builder().addTextContent(s).build();
}
})).build();
try (var in = fs.openInput(path)) {
var b = in.readAllBytes();
var s = new String(b, StandardCharsets.UTF_8);
return McpSchema.CallToolResult.builder()
.addTextContent(s)
.build();
}
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification listFiles() throws IOException {
var tool = McpSchemaFiles.loadTool("list_files.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var recursive = req.getOptionalBooleanArgument("recursive").orElse(false);
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var recursive = req.getOptionalBooleanArgument("recursive").orElse(false);
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
if (!fs.directoryExists(path)) {
throw new BeaconClientException("Directory " + path + " does not exist");
}
if (!fs.directoryExists(path)) {
throw new BeaconClientException("Directory " + path + " does not exist");
}
try (var stream = recursive ? fs.listFilesRecursively(fs, path) : fs.listFiles(fs, path)) {
var list = stream.toList();
var builder = McpSchema.CallToolResult.builder();
for (FileEntry e : list) {
builder.addTextContent(e.getPath().toString());
}
return builder.build();
}
})).build();
try (var stream = recursive ? fs.listFilesRecursively(fs, path) : fs.listFiles(fs, path)) {
var list = stream.toList();
var builder = McpSchema.CallToolResult.builder();
for (FileEntry e : list) {
builder.addTextContent(e.getPath().toString());
}
return builder.build();
}
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification findFile() throws IOException {
var tool = McpSchemaFiles.loadTool("find_file.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var recursive = req.getOptionalBooleanArgument("recursive").orElse(false);
var pattern = req.getStringArgument("name");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var recursive = req.getOptionalBooleanArgument("recursive").orElse(false);
var pattern = req.getStringArgument("name");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
if (!fs.directoryExists(path)) {
throw new BeaconClientException("Directory " + path + " does not exist");
}
if (!fs.directoryExists(path)) {
throw new BeaconClientException("Directory " + path + " does not exist");
}
var regex = Pattern.compile(DataStorageQuery.toRegex(pattern));
try (var stream = recursive ? fs.listFilesRecursively(fs, path) : fs.listFiles(fs, path)) {
var list = stream.toList();
var builder = McpSchema.CallToolResult.builder();
list.stream().filter(fileEntry -> regex.matcher(fileEntry.getPath().toString()).find()).forEach(fileEntry -> {
builder.addTextContent(fileEntry.getPath().toString());
});
return builder.build();
}
})).build();
var regex = Pattern.compile(DataStorageQuery.toRegex(pattern));
try (var stream = recursive ? fs.listFilesRecursively(fs, path) : fs.listFiles(fs, path)) {
var list = stream.toList();
var builder = McpSchema.CallToolResult.builder();
list.stream()
.filter(fileEntry -> regex.matcher(
fileEntry.getPath().toString())
.find())
.forEach(fileEntry -> {
builder.addTextContent(fileEntry.getPath().toString());
});
return builder.build();
}
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification getFileInfo() throws IOException {
var tool = McpSchemaFiles.loadTool("get_file_info.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
if (!fs.fileExists(path)) {
throw new BeaconClientException("File " + path + " does not exist");
}
if (!fs.fileExists(path)) {
throw new BeaconClientException("File " + path + " does not exist");
}
var entry = fs.getFileInfo(path);
if (entry.isEmpty()) {
throw new BeaconClientException("File " + path + " does not exist");
}
var entry = fs.getFileInfo(path);
if (entry.isEmpty()) {
throw new BeaconClientException("File " + path + " does not exist");
}
var map = new LinkedHashMap<String, Object>();
map.put("path", entry.get().getPath().toString());
map.put("size", entry.get().getSize());
if (entry.get().getInfo() instanceof FileInfo.Unix u) {
map.put("permissions", u.getPermissions());
map.put("user", u.getUser());
map.put("group", u.getGroup());
} else if (entry.get().getInfo() instanceof FileInfo.Windows w) {
map.put("attributes", w.getAttributes());
}
map.put("type", entry.get().getKind().toString().toLowerCase());
map.put("date", entry.get().getDate().toString());
map.entrySet().removeIf(e -> e.getValue() == null);
var map = new LinkedHashMap<String, Object>();
map.put("path", entry.get().getPath().toString());
map.put("size", entry.get().getSize());
if (entry.get().getInfo() instanceof FileInfo.Unix u) {
map.put("permissions", u.getPermissions());
map.put("user", u.getUser());
map.put("group", u.getGroup());
} else if (entry.get().getInfo() instanceof FileInfo.Windows w) {
map.put("attributes", w.getAttributes());
}
map.put("type", entry.get().getKind().toString().toLowerCase());
map.put("date", entry.get().getDate().toString());
map.entrySet().removeIf(e -> e.getValue() == null);
return McpSchema.CallToolResult.builder().structuredContent(map).build();
})).build();
return McpSchema.CallToolResult.builder()
.structuredContent(map)
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification createFile() throws IOException {
var tool = McpSchemaFiles.loadTool("create_file.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
if (fs.fileExists(path)) {
throw new BeaconClientException("File " + path + " does already exist");
}
if (fs.fileExists(path)) {
throw new BeaconClientException("File " + path + " does already exist");
}
fs.touch(path);
fs.touch(path);
if (req.getRawRequest().arguments().containsKey("content")) {
var s = req.getRawRequest().arguments().get("content").toString();
var b = s.getBytes(StandardCharsets.UTF_8);
try (var out = fs.openOutput(path, b.length)) {
out.write(b);
}
}
if (req.getRawRequest().arguments().containsKey("content")) {
var s = req.getRawRequest().arguments().get("content").toString();
var b = s.getBytes(StandardCharsets.UTF_8);
try (var out = fs.openOutput(path, b.length)) {
out.write(b);
}
}
return McpSchema.CallToolResult.builder().addTextContent("File created successfully").build();
})).build();
return McpSchema.CallToolResult.builder()
.addTextContent("File created successfully")
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification writeFile() throws IOException {
var tool = McpSchemaFiles.loadTool("write_file.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var content = req.getStringArgument("content");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var content = req.getStringArgument("content");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
var b = content.getBytes(StandardCharsets.UTF_8);
try (var out = fs.openOutput(path, b.length)) {
out.write(b);
}
var b = content.getBytes(StandardCharsets.UTF_8);
try (var out = fs.openOutput(path, b.length)) {
out.write(b);
}
return McpSchema.CallToolResult.builder().addTextContent("File written successfully").build();
})).build();
return McpSchema.CallToolResult.builder()
.addTextContent("File written successfully")
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification createDirectory() throws IOException {
var tool = McpSchemaFiles.loadTool("create_directory.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var path = req.getFilePath("path");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var fs = new ConnectionFileSystem(shellSession.getControl());
if (fs.fileExists(path)) {
throw new BeaconClientException("Directory " + path + " does already exist");
}
if (fs.fileExists(path)) {
throw new BeaconClientException("Directory " + path + " does already exist");
}
fs.mkdirs(path);
fs.mkdirs(path);
return McpSchema.CallToolResult.builder().addTextContent("Directory created successfully").build();
})).build();
return McpSchema.CallToolResult.builder()
.addTextContent("Directory created successfully")
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification runCommand() throws IOException {
var tool = McpSchemaFiles.loadTool("run_command.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var command = req.getStringArgument("command");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var command = req.getStringArgument("command");
var system = req.getStringArgument("system");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var out = shellSession.getControl().command(command).readStdoutOrThrow();
var formatted = CommandDialog.formatOutput(out);
var out = shellSession.getControl().command(command).readStdoutOrThrow();
var formatted = CommandDialog.formatOutput(out);
return McpSchema.CallToolResult.builder().addTextContent(formatted).build();
})).build();
return McpSchema.CallToolResult.builder()
.addTextContent(formatted)
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification runScript() throws IOException {
var tool = McpSchemaFiles.loadTool("run_script.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var script = req.getDataStoreRef("script");
var directory = req.getFilePath("directory");
var arguments = req.getStringArgument("arguments");
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var script = req.getDataStoreRef("script");
var directory = req.getFilePath("directory");
var arguments = req.getStringArgument("arguments");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var clazz = Class.forName(AppExtensionManager.getInstance().getExtendedLayer().findModule("io.xpipe.ext.base").orElseThrow(),
"io.xpipe.ext.base.script.SimpleScriptStore");
var method = clazz.getDeclaredMethod("assembleScriptChain", ShellControl.class);
var command = (String) method.invoke(script.getStore(), shellSession.getControl());
var scriptFile = ScriptHelper.createExecScript(shellSession.getControl(), command);
var out = shellSession.getControl()
.command(shellSession.getControl().getShellDialect()
.runScriptCommand(shellSession.getControl(), scriptFile.toString()) + arguments)
.withWorkingDirectory(directory).readStdoutOrThrow();
var formatted = CommandDialog.formatOutput(out);
var clazz = Class.forName(
AppExtensionManager.getInstance()
.getExtendedLayer()
.findModule("io.xpipe.ext.base")
.orElseThrow(),
"io.xpipe.ext.base.script.SimpleScriptStore");
var method = clazz.getDeclaredMethod("assembleScriptChain", ShellControl.class);
var command = (String) method.invoke(script.getStore(), shellSession.getControl());
var scriptFile = ScriptHelper.createExecScript(shellSession.getControl(), command);
var out = shellSession
.getControl()
.command(shellSession
.getControl()
.getShellDialect()
.runScriptCommand(shellSession.getControl(), scriptFile.toString())
+ arguments)
.withWorkingDirectory(directory)
.readStdoutOrThrow();
var formatted = CommandDialog.formatOutput(out);
return McpSchema.CallToolResult.builder().addTextContent(formatted).build();
})).build();
return McpSchema.CallToolResult.builder()
.addTextContent(formatted)
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification openTerminal() throws IOException {
var tool = McpSchemaFiles.loadTool("open_terminal.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var directory = req.getOptionalStringArgument("directory");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var directory = req.getOptionalStringArgument("directory");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
TerminalLaunch.builder()
.entry(shellStore.get())
.directory(FilePath.of(directory.orElse(null)))
.command(shellSession.getControl())
.launch();
TerminalLaunch.builder()
.entry(shellStore.get())
.directory(FilePath.of(directory.orElse(null)))
.command(shellSession.getControl())
.launch();
return McpSchema.CallToolResult.builder().addTextContent("Terminal is launching").build();
})).build();
return McpSchema.CallToolResult.builder()
.addTextContent("Terminal is launching")
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification openTerminalInline() throws IOException {
var tool = McpSchemaFiles.loadTool("open_terminal_inline.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var directory = req.getOptionalStringArgument("directory");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var directory = req.getOptionalStringArgument("directory");
var shellStore = req.getShellStoreRef(system);
var shellSession = AppBeaconServer.get().getCache().getOrStart(shellStore);
var script = shellSession.getControl().prepareTerminalOpen(TerminalInitScriptConfig.ofName(shellStore.get().getName()), directory.isPresent() ?
WorkingDirectoryFunction.fixed(FilePath.parse(directory.get())) : WorkingDirectoryFunction.none());
var script = shellSession
.getControl()
.prepareTerminalOpen(
TerminalInitScriptConfig.ofName(
shellStore.get().getName()),
directory.isPresent()
? WorkingDirectoryFunction.fixed(FilePath.parse(directory.get()))
: WorkingDirectoryFunction.none());
var json = JsonNodeFactory.instance.objectNode();
json.put("command", script);
return McpSchema.CallToolResult.builder().structuredContent(JacksonMapper.getDefault().writeValueAsString(json)).build();
})).build();
var json = JsonNodeFactory.instance.objectNode();
json.put("command", script);
return McpSchema.CallToolResult.builder()
.structuredContent(JacksonMapper.getDefault().writeValueAsString(json))
.build();
}))
.build();
}
public static McpServerFeatures.SyncToolSpecification toggleState() throws IOException {
var tool = McpSchemaFiles.loadTool("toggle_state.json");
return McpServerFeatures.SyncToolSpecification.builder().tool(tool).callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var state = req.getBooleanArgument("state");
var ref = req.getDataStoreRef(system);
return McpServerFeatures.SyncToolSpecification.builder()
.tool(tool)
.callHandler(McpToolHandler.of((req) -> {
var system = req.getStringArgument("system");
var state = req.getBooleanArgument("state");
var ref = req.getDataStoreRef(system);
if (!(ref.getStore() instanceof SingletonSessionStore<?> singletonSessionStore)) {
throw new BeaconClientException("Not a toggleable connection");
}
if (state) {
singletonSessionStore.startSessionIfNeeded();
} else {
singletonSessionStore.stopSessionIfNeeded();
}
if (!(ref.getStore() instanceof SingletonSessionStore<?> singletonSessionStore)) {
throw new BeaconClientException("Not a toggleable connection");
}
if (state) {
singletonSessionStore.startSessionIfNeeded();
} else {
singletonSessionStore.stopSessionIfNeeded();
}
return McpSchema.CallToolResult.builder().addTextContent("Connection state set to " + state).build();
})).build();
return McpSchema.CallToolResult.builder()
.addTextContent("Connection state set to " + state)
.build();
}))
.build();
}
}

View File

@@ -32,7 +32,8 @@ public class BrowserAbstractSessionModel<T extends BrowserSessionTab> {
}
public void openSync(T e, BooleanProperty externalBusy) throws Exception {
try (var ignored = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
try (var ignored =
new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
e.init();
// Prevent multiple calls from interfering with each other
synchronized (this) {

View File

@@ -89,7 +89,8 @@ public class BrowserFileChooserSessionModel extends BrowserAbstractSessionModel<
ThreadHelper.runFailableAsync(() -> {
BrowserFileSystemTabModel model;
try (var ignored = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
try (var ignored =
new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
model = new BrowserFileSystemTabModel(this, store, selectionMode);
model.init();
// Prevent multiple calls from interfering with each other

View File

@@ -221,7 +221,8 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel<Browser
boolean select)
throws Exception {
BrowserFileSystemTabModel model;
try (var ignored = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
try (var ignored =
new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
try (var ignored2 = new BooleanScope(busy).exclusive().start()) {
model = new BrowserFileSystemTabModel(this, store, BrowserFileSystemTabModel.SelectionMode.ALL);
model.init();

View File

@@ -4,8 +4,8 @@ import io.xpipe.app.ext.FileEntry;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.util.GlobalClipboard;
import io.xpipe.app.util.GlobalObjectProperty;
import javafx.beans.property.Property;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;

View File

@@ -62,9 +62,10 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
private BrowserFileSystemCache cache;
private final Property<BrowserTransferProgress> progress = new SimpleObjectProperty<>();
private final ObservableList<BrowserTransferProgress> progressesIntervalHistory = FXCollections.observableArrayList();
private final LongProperty progressTransferSpeed = new SimpleLongProperty();
private final Property<Duration> progressRemaining = new SimpleObjectProperty<>();
private final ObservableList<BrowserTransferProgress> progressesIntervalHistory =
FXCollections.observableArrayList();
private final LongProperty progressTransferSpeed = new SimpleLongProperty();
private final Property<Duration> progressRemaining = new SimpleObjectProperty<>();
public BrowserFileSystemTabModel(
BrowserAbstractSessionModel<?> model,
@@ -89,7 +90,9 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
var changedHistory = false;
if (progress.getValue() != null) {
var last = progressesIntervalHistory.isEmpty() ? Instant.EPOCH : progressesIntervalHistory.getLast().getTimestamp();
var last = progressesIntervalHistory.isEmpty()
? Instant.EPOCH
: progressesIntervalHistory.getLast().getTimestamp();
var elapsed = Duration.between(last, n.getTimestamp());
if (elapsed.toMillis() >= 1000) {
progressesIntervalHistory.add(progress.getValue());
@@ -109,7 +112,8 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
var estimate = remaining / (double) speed;
var newDuration = Duration.ofMillis((long) (estimate * 1000.0));
var smooth = progressRemaining.getValue() != null && progressRemaining.getValue().toSeconds() + 1 == newDuration.toSeconds();
var smooth = progressRemaining.getValue() != null
&& progressRemaining.getValue().toSeconds() + 1 == newDuration.toSeconds();
if (!smooth) {
progressRemaining.setValue(newDuration);
}
@@ -522,7 +526,14 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
&& !(fullSessionModel.getSplits().get(this) instanceof BrowserTerminalDockTabModel)) {
fullSessionModel.splitTab(this, new BrowserTerminalDockTabModel(browserModel, this, terminalRequests));
}
TerminalLaunch.builder().entry(entry.get()).title(name).directory(directory).command(processControl).request(uuid).preferTabs(!dock).launch();
TerminalLaunch.builder()
.entry(entry.get())
.title(name)
.directory(directory)
.command(processControl)
.request(uuid)
.preferTabs(!dock)
.launch();
// Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively
startIfNeeded();

View File

@@ -286,7 +286,11 @@ public class BrowserFileTransferOperation {
return;
}
var rel = fileEntry.getPath().relativize(baseRelative).toUnix().toString();
var rel = fileEntry
.getPath()
.relativize(baseRelative)
.toUnix()
.toString();
flatFiles.put(fileEntry, rel);
if (fileEntry.getKind() == FileKind.FILE) {
// This one is up-to-date and does not need to be recalculated
@@ -347,13 +351,7 @@ public class BrowserFileTransferOperation {
}
}
transfer(
sourceFile.getPath(),
optimizedSourceFs,
targetFile,
targetFs,
transferred,
totalSize);
transfer(sourceFile.getPath(), optimizedSourceFs, targetFile, targetFs, transferred, totalSize);
}
}
} finally {
@@ -503,8 +501,8 @@ public class BrowserFileTransferOperation {
outputStream.write(buffer, 0, read);
transferred.addAndGet(read);
readCount.addAndGet(read);
updateProgress(new BrowserTransferProgress(
sourceFile.getFileName(), transferred.get(), total.get()));
updateProgress(
new BrowserTransferProgress(sourceFile.getFileName(), transferred.get(), total.get()));
}
outputStream.flush();

View File

@@ -79,23 +79,28 @@ public class BrowserStatusBarComp extends SimpleComp {
}
private Comp<?> createProgressEstimateStatus() {
var text = Bindings.createStringBinding(() -> {
var p = model.getProgress().getValue();
var expected = model.getProgressRemaining().getValue();
if (p == null || expected == null) {
return null;
}
var text = Bindings.createStringBinding(
() -> {
var p = model.getProgress().getValue();
var expected = model.getProgressRemaining().getValue();
if (p == null || expected == null) {
return null;
}
var elapsed = (p.getTotal() - p.getTransferred() / (double) p.getTotal()) * expected.toMillis();
var show = elapsed > 3000;
if (!show) {
return "...";
}
var elapsed = (p.getTotal() - p.getTransferred() / (double) p.getTotal()) * expected.toMillis();
var show = elapsed > 3000;
if (!show) {
return "...";
}
var time = HumanReadableFormat.duration(expected) + " @ ";
var progress = HumanReadableFormat.transferSpeed(model.getProgressTransferSpeed().getValue());
return time + progress;
}, model.getProgressRemaining(), model.getProgressTransferSpeed(), model.getProgress());
var time = HumanReadableFormat.duration(expected) + " @ ";
var progress = HumanReadableFormat.transferSpeed(
model.getProgressTransferSpeed().getValue());
return time + progress;
},
model.getProgressRemaining(),
model.getProgressTransferSpeed(),
model.getProgress());
var progressComp = new LabelComp(text)
.styleClass("progress")

View File

@@ -35,8 +35,13 @@ public abstract class MultiExecuteMenuProvider implements BrowserMenuBranchProvi
}
var cmd = sc.command(c);
model.openTerminalAsync(entry.getRawFileEntry().getName(),
model.getCurrentDirectory() != null ? model.getCurrentDirectory().getPath() : null, cmd, entries.size() == 1);
model.openTerminalAsync(
entry.getRawFileEntry().getName(),
model.getCurrentDirectory() != null
? model.getCurrentDirectory().getPath()
: null,
cmd,
entries.size() == 1);
}
});
}

View File

@@ -56,14 +56,16 @@ public class ButtonComp extends Comp<CompStructure<Button>> {
var n = t.createGraphicNode();
button.setGraphic(n);
if (n instanceof FontIcon f && button.getFont() != null) {
f.iconSizeProperty().bind(new ReadOnlyIntegerWrapper((int) new Size(button.getFont().getSize(), SizeUnits.PT).pixels()));
f.iconSizeProperty().bind(new ReadOnlyIntegerWrapper((int)
new Size(button.getFont().getSize(), SizeUnits.PT).pixels()));
}
});
});
button.fontProperty().subscribe(c -> {
if (button.getGraphic() instanceof FontIcon f) {
f.iconSizeProperty().bind(new ReadOnlyIntegerWrapper((int) new Size(c.getSize(), SizeUnits.PT).pixels()));
f.iconSizeProperty()
.bind(new ReadOnlyIntegerWrapper((int) new Size(c.getSize(), SizeUnits.PT).pixels()));
}
});
}

View File

@@ -29,7 +29,9 @@ public class ComboTextFieldComp extends Comp<CompStructure<ComboBox<String>>> {
private ObservableValue<String> prompt;
public ComboTextFieldComp(
Property<String> value, ObservableList<String> predefinedValues, Supplier<ListCell<String>> customCellFactory) {
Property<String> value,
ObservableList<String> predefinedValues,
Supplier<ListCell<String>> customCellFactory) {
this.value = value;
this.predefinedValues = predefinedValues;
this.customCellFactory = customCellFactory;

View File

@@ -208,9 +208,11 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
}
};
});
combo.setPrompt(Bindings.createStringBinding(() -> {
return filePath.getValue() != null ? filePath.getValue().toString() : null;
}, filePath));
combo.setPrompt(Bindings.createStringBinding(
() -> {
return filePath.getValue() != null ? filePath.getValue().toString() : null;
},
filePath));
combo.hgrow();
combo.styleClass(Styles.LEFT_PILL);
combo.grow(false, true);

View File

@@ -54,7 +54,8 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
var graphic = new FontIcon("mdi2c-chevron-double-down");
button.fontProperty().subscribe(c -> {
graphic.iconSizeProperty().bind(new ReadOnlyIntegerWrapper((int) new Size(c.getSize(), SizeUnits.PT).pixels()));
graphic.iconSizeProperty()
.bind(new ReadOnlyIntegerWrapper((int) new Size(c.getSize(), SizeUnits.PT).pixels()));
});
button.setGraphic(graphic);

View File

@@ -57,13 +57,15 @@ public class IconButtonComp extends Comp<CompStructure<Button>> {
PlatformThread.runLaterIfNeeded(() -> {
button.setGraphic(labelGraphic.createGraphicNode());
if (button.getGraphic() instanceof FontIcon fi) {
fi.iconSizeProperty().bind(new ReadOnlyIntegerWrapper((int) new Size(button.getFont().getSize(), SizeUnits.PT).pixels()));
fi.iconSizeProperty().bind(new ReadOnlyIntegerWrapper((int)
new Size(button.getFont().getSize(), SizeUnits.PT).pixels()));
}
});
});
button.fontProperty().subscribe((n) -> {
if (button.getGraphic() instanceof FontIcon fi) {
fi.iconSizeProperty().bind(new ReadOnlyIntegerWrapper((int) new Size(n.getSize(), SizeUnits.PT).pixels()));
fi.iconSizeProperty()
.bind(new ReadOnlyIntegerWrapper((int) new Size(n.getSize(), SizeUnits.PT).pixels()));
}
});
if (listener != null) {

View File

@@ -68,9 +68,10 @@ public class AppExtensionManager {
private void determineExtensionDirectories() throws Exception {
if (!AppProperties.get().isFullVersion()) {
var localInstallation = !AppProperties.get().isStaging() && AppProperties.get().isLocatePtb() ?
AppInstallation.ofDefault(true)
: AppInstallation.ofCurrent();
var localInstallation =
!AppProperties.get().isStaging() && AppProperties.get().isLocatePtb()
? AppInstallation.ofDefault(true)
: AppInstallation.ofCurrent();
Path p = localInstallation.getBaseInstallationPath();
if (!Files.exists(p)) {
throw new IllegalStateException(

View File

@@ -8,17 +8,25 @@ import java.nio.file.Path;
public abstract class AppInstallation {
private static final Windows WINDOWS = AppProperties.get().isImage() ?
new Windows(determineCurrentInstallationBasePath()) :
new WindowsDev(determineDefaultInstallationBasePath(AppProperties.get().isStaging()), determineCurrentInstallationBasePath());
private static final Linux LINUX = AppProperties.get().isImage() ?
new Linux(determineCurrentInstallationBasePath()) :
new LinuxDev(determineDefaultInstallationBasePath(AppProperties.get().isStaging()), determineCurrentInstallationBasePath());
private static final MacOs MACOS = AppProperties.get().isImage() ?
new MacOs(determineCurrentInstallationBasePath()) :
new MacOsDev(determineDefaultInstallationBasePath(AppProperties.get().isStaging()), determineCurrentInstallationBasePath());
private static final Windows WINDOWS = AppProperties.get().isImage()
? new Windows(determineCurrentInstallationBasePath())
: new WindowsDev(
determineDefaultInstallationBasePath(AppProperties.get().isStaging()),
determineCurrentInstallationBasePath());
private static final Linux LINUX = AppProperties.get().isImage()
? new Linux(determineCurrentInstallationBasePath())
: new LinuxDev(
determineDefaultInstallationBasePath(AppProperties.get().isStaging()),
determineCurrentInstallationBasePath());
private static final MacOs MACOS = AppProperties.get().isImage()
? new MacOs(determineCurrentInstallationBasePath())
: new MacOsDev(
determineDefaultInstallationBasePath(AppProperties.get().isStaging()),
determineCurrentInstallationBasePath());
private AppInstallation(Path base) {this.base = base;}
private AppInstallation(Path base) {
this.base = base;
}
public static AppInstallation ofCurrent() {
return switch (OsType.getLocal()) {
@@ -100,13 +108,13 @@ public abstract class AppInstallation {
// Resolve root path of installation relative to executable in a JPackage installation
return switch (OsType.getLocal()) {
case OsType.Linux ignored -> {
yield executable.getParent().getParent();
yield executable.getParent().getParent();
}
case OsType.MacOs ignored -> {
yield executable.getParent().getParent().getParent();
yield executable.getParent().getParent().getParent();
}
case OsType.Windows ignored -> {
yield executable.getParent();
yield executable.getParent();
}
};
}
@@ -127,7 +135,7 @@ public abstract class AppInstallation {
.getParent();
}
case OsType.Windows ignored -> {
yield executable.getParent().getParent();
yield executable.getParent().getParent();
}
};
}
@@ -150,15 +158,15 @@ public abstract class AppInstallation {
public abstract Path getDaemonDebugScriptPath();
public abstract Path getBundledFontsPath();
public abstract Path getBundledFontsPath();
public abstract Path getLangPath();
public abstract Path getLangPath();
public abstract Path getCliExecutablePath();
public abstract Path getCliExecutablePath();
public abstract Path getDaemonExecutablePath();
public abstract Path getDaemonExecutablePath();
public abstract Path getExtensionsPath();
public abstract Path getExtensionsPath();
public abstract Path getLogoPath();
@@ -279,7 +287,6 @@ public abstract class AppInstallation {
}
}
public static class LinuxDev extends Linux {
private final Path devBase;
@@ -300,7 +307,7 @@ public abstract class AppInstallation {
}
}
public static class MacOs extends AppInstallation {
public static class MacOs extends AppInstallation {
private MacOs(Path base) {
super(base);
@@ -350,7 +357,10 @@ public abstract class AppInstallation {
return getBaseInstallationPath().resolve("dist").resolve("logo").resolve("logo.icns");
}
return getBaseInstallationPath().resolve("Contents").resolve("Resources").resolve("xpipe.icns");
return getBaseInstallationPath()
.resolve("Contents")
.resolve("Resources")
.resolve("xpipe.icns");
}
}

View File

@@ -87,11 +87,7 @@ public interface AppLocations {
}
}
class Linux implements AppLocations {
class Linux implements AppLocations {}
}
final class MacOs implements AppLocations {
}
final class MacOs implements AppLocations {}
}

View File

@@ -317,13 +317,14 @@ public class AppLogs {
// Only change this when debugging the logs of other libraries
return NOPLogger.NOP_LOGGER;
// // Don't use fully qualified class names
// var normalizedName = FilenameUtils.getExtension(name);
// if (normalizedName == null || normalizedName.isEmpty()) {
// normalizedName = name;
// }
//
// return loggers.computeIfAbsent(normalizedName, s -> new Slf4jLogger());
// // Don't use fully qualified class names
// var normalizedName = FilenameUtils.getExtension(name);
// if (normalizedName == null || normalizedName.isEmpty()) {
// normalizedName = name;
// }
//
// return loggers.computeIfAbsent(normalizedName, s -> new
// Slf4jLogger());
}
};

View File

@@ -43,7 +43,8 @@ public class AppRestart {
if (OsType.getLocal() == OsType.LINUX) {
return "nohup \"" + loc.getDaemonExecutablePath() + "\"" + suffix + " </dev/null >/dev/null 2>&1 & disown";
} else if (OsType.getLocal() == OsType.MACOS) {
return "(sleep 1;open \"" + loc.getBaseInstallationPath() + "\" --args" + suffix + " </dev/null &>/dev/null) & disown";
return "(sleep 1;open \"" + loc.getBaseInstallationPath() + "\" --args" + suffix
+ " </dev/null &>/dev/null) & disown";
} else {
var exe = loc.getDaemonExecutablePath();
if (ShellDialects.isPowershell(dialect)) {

View File

@@ -7,9 +7,7 @@ import java.nio.file.Path;
public class AppSystemInfo {
public static class Windows {
}
public static class Windows {}
public static Linux linux() {
if (OsType.getLocal() != OsType.LINUX) {
@@ -26,7 +24,5 @@ public class AppSystemInfo {
}
}
public static class MacOS {
}
public static class MacOS {}
}

View File

@@ -75,11 +75,7 @@ public abstract class AppShellChecker {
%s
"""
.formatted(
LocalShell.getDialect().getDisplayName(),
modifyOutput(output),
listReasons(),
fallback);
.formatted(LocalShell.getDialect().getDisplayName(), modifyOutput(output), listReasons(), fallback);
}
protected abstract String listReasons();

View File

@@ -17,7 +17,8 @@ public class AppSystemFontCheck {
}
System.setProperty(
"prism.fontdir", AppInstallation.ofCurrent().getBundledFontsPath().toString());
"prism.fontdir",
AppInstallation.ofCurrent().getBundledFontsPath().toString());
System.setProperty("prism.embeddedfonts", "true");
}

View File

@@ -17,7 +17,8 @@ public class AppTestCommandCheck {
sc.getShellDialect()
.directoryExists(
sc,
AppInstallation.ofCurrent().getBaseInstallationPath()
AppInstallation.ofCurrent()
.getBaseInstallationPath()
.toString())
.execute();
} catch (ProcessOutputException ex) {

View File

@@ -4,6 +4,7 @@ import io.xpipe.app.action.AbstractAction;
import io.xpipe.app.action.ActionProvider;
import io.xpipe.app.beacon.AppBeaconServer;
import io.xpipe.app.beacon.BlobManager;
import io.xpipe.app.beacon.mcp.AppMcpServer;
import io.xpipe.app.browser.BrowserFullSessionModel;
import io.xpipe.app.browser.file.BrowserLocalFileSystem;
import io.xpipe.app.browser.icon.BrowserIconManager;
@@ -17,7 +18,6 @@ import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.icon.SystemIconManager;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.beacon.mcp.AppMcpServer;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.pwman.KeePassXcPasswordManager;
import io.xpipe.app.storage.DataStorage;

View File

@@ -79,10 +79,16 @@ public abstract class OperationMode {
}
// There are some accessibility exceptions on macOS, nothing we can do about that
if (Platform.isFxApplicationThread() && ex instanceof NullPointerException && ex.getMessage() != null && ex.getMessage().contains("Accessible")) {
ErrorEventFactory.fromThrowable(ex).expected()
.descriptionPrefix("An error occurred with the Accessibility implementation. A screen reader might not be supported right now")
.build().handle();
if (Platform.isFxApplicationThread()
&& ex instanceof NullPointerException
&& ex.getMessage() != null
&& ex.getMessage().contains("Accessible")) {
ErrorEventFactory.fromThrowable(ex)
.expected()
.descriptionPrefix(
"An error occurred with the Accessibility implementation. A screen reader might not be supported right now")
.build()
.handle();
return;
}

View File

@@ -11,7 +11,7 @@ public class TrayMode extends PlatformMode {
@Override
public boolean isSupported() {
return OsType.getLocal()== OsType.WINDOWS
return OsType.getLocal() == OsType.WINDOWS
&& super.isSupported()
&& Desktop.isDesktopSupported()
&& SystemTray.isSupported();

View File

@@ -2,6 +2,7 @@ package io.xpipe.app.ext;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.hub.comp.StoreSection;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
@@ -19,12 +20,13 @@ public interface CountGroupStoreProvider extends DataStoreProvider {
var string = all.size() == shown.size() ? all.size() : shown.size() + "/" + all.size();
return all.size() > 0
? (all.size() == 1 ? AppI18n.get("hasConnection", string) : AppI18n.get("hasConnections", string))
? (all.size() == 1
? AppI18n.get("hasConnection", string)
: AppI18n.get("hasConnections", string))
: AppI18n.get("noConnections");
},
section.getShownChildren().getList(),
section.getAllChildren().getList(),
AppI18n.activeLanguage());
}
}

View File

@@ -26,14 +26,17 @@ public class HostAddress {
return null;
}
return new HostAddress(host.strip(), addresses.stream().map(s -> s.strip()).toList());
return new HostAddress(
host.strip(), addresses.stream().map(s -> s.strip()).toList());
}
private final String value;
@Getter
private final List<String> available;
private HostAddress(String value, List<String> available) {this.value = value;
private HostAddress(String value, List<String> available) {
this.value = value;
this.available = available;
}
@@ -57,5 +60,4 @@ public class HostAddress {
public String get() {
return value;
}
}

View File

@@ -1,8 +1,10 @@
package io.xpipe.app.ext;
import io.xpipe.app.util.OptionsBuilder;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import lombok.Builder;
import lombok.Value;
@@ -27,8 +29,7 @@ public class HostAddressChoice {
} else {
options.name(translationKey);
}
options.addComp(new HostAddressChoiceComp(val, list, allowMutation))
.addProperty(val);
options.addComp(new HostAddressChoiceComp(val, list, allowMutation)).addProperty(val);
options.bind(
() -> {
var fullList = new ArrayList<>(list);
@@ -36,10 +37,11 @@ public class HostAddressChoice {
fullList.add(val.getValue());
}
var effectiveValue = val.getValue() != null ? val.getValue() : fullList.size() > 0 ? fullList.getFirst() : null;
var effectiveValue =
val.getValue() != null ? val.getValue() : fullList.size() > 0 ? fullList.getFirst() : null;
return HostAddress.of(effectiveValue, fullList);
},
value);
return options;
}
}
}

View File

@@ -1,11 +1,10 @@
package io.xpipe.app.ext;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.comp.SimpleCompStructure;
import io.xpipe.app.comp.base.*;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ListChangeListener;
@@ -15,6 +14,9 @@ import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.scene.layout.HBox;
import atlantafx.base.controls.Spacer;
import atlantafx.base.theme.Styles;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.ArrayList;
@@ -25,7 +27,8 @@ public class HostAddressChoiceComp extends Comp<CompStructure<HBox>> {
private final ObservableList<String> allAddresses;
private final boolean mutable;
public HostAddressChoiceComp(ObjectProperty<String> currentAddress, ObservableList<String> allAddresses, boolean mutable) {
public HostAddressChoiceComp(
ObjectProperty<String> currentAddress, ObservableList<String> allAddresses, boolean mutable) {
this.currentAddress = currentAddress;
this.allAddresses = allAddresses;
this.mutable = mutable;
@@ -52,7 +55,7 @@ public class HostAddressChoiceComp extends Comp<CompStructure<HBox>> {
var nodes = new ArrayList<Comp<?>>();
nodes.add(combo);
if (mutable) {
if (mutable) {
nodes.add(addButton);
}
@@ -96,9 +99,11 @@ public class HostAddressChoiceComp extends Comp<CompStructure<HBox>> {
hbox.getChildren().add(new Label(item));
hbox.getChildren().add(new Spacer());
if (mutable) {
hbox.getChildren().add(new IconButtonComp("mdi2t-trash-can-outline", () -> {
allAddresses.remove(item);
}).createRegion());
hbox.getChildren()
.add(new IconButtonComp("mdi2t-trash-can-outline", () -> {
allAddresses.remove(item);
})
.createRegion());
}
setGraphic(hbox);
@@ -135,4 +140,4 @@ public class HostAddressChoiceComp extends Comp<CompStructure<HBox>> {
combo.grow(false, true);
return combo;
}
}
}

View File

@@ -6,6 +6,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.ModuleLayerLoader;
import javafx.beans.value.ObservableValue;
import lombok.AllArgsConstructor;
import lombok.Value;

View File

@@ -4,6 +4,7 @@ import io.xpipe.app.action.AbstractAction;
import io.xpipe.app.action.ActionProvider;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.storage.DataStorage;
import lombok.SneakyThrows;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;

View File

@@ -8,6 +8,7 @@ import io.xpipe.app.hub.action.HubMenuItemProvider;
import io.xpipe.app.hub.action.StoreActionCategory;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.LabelGraphic;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
@@ -23,7 +24,8 @@ public class HostAddressSwitchBranchProvider implements HubBranchProvider<HostAd
private HostAddressProvider(boolean active, String address) {
this.active = active;
this.address = address;}
this.address = address;
}
@Override
public void execute(DataStoreEntryRef<HostAddressSwitchStore> ref) {
@@ -51,9 +53,12 @@ public class HostAddressSwitchBranchProvider implements HubBranchProvider<HostAd
@Override
public List<HubMenuItemProvider<?>> getChildren(DataStoreEntryRef<HostAddressSwitchStore> store) {
return store.getStore().getHostAddress().getAvailable().stream().map(s -> {
return new HostAddressProvider(s.equals(store.getStore().getHostAddress().get()), s);
}).collect(Collectors.toList());
return store.getStore().getHostAddress().getAvailable().stream()
.map(s -> {
return new HostAddressProvider(
s.equals(store.getStore().getHostAddress().get()), s);
})
.collect(Collectors.toList());
}
@Override

View File

@@ -31,35 +31,22 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
private final Mode mode;
private final Property<DataStoreEntryRef<T>> selected;
private final StoreChoicePopover<T> popover;
public StoreChoiceComp(
Mode mode, DataStoreEntry self, Property<DataStoreEntryRef<T>> selected, Class<?> storeClass,
Predicate<DataStoreEntryRef<T>> applicableCheck, StoreCategoryWrapper initialCategory
) {
Mode mode,
DataStoreEntry self,
Property<DataStoreEntryRef<T>> selected,
Class<?> storeClass,
Predicate<DataStoreEntryRef<T>> applicableCheck,
StoreCategoryWrapper initialCategory) {
this.mode = mode;
this.selected = selected;
this.popover = new StoreChoicePopover<>(self,selected,storeClass, applicableCheck, initialCategory, "selectConnection");
this.popover = new StoreChoicePopover<>(
self, selected, storeClass, applicableCheck, initialCategory, "selectConnection");
}
public static <T extends DataStore> StoreChoiceComp<T> other(

View File

@@ -1,20 +1,18 @@
package io.xpipe.app.hub.comp;
import atlantafx.base.controls.Popover;
import atlantafx.base.theme.Styles;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.ext.DataStore;
import io.xpipe.app.hub.comp.*;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.BindingsHelper;
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
import io.xpipe.app.util.LabelGraphic;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
@@ -26,6 +24,9 @@ import javafx.scene.Node;
import javafx.scene.control.MenuButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Popover;
import atlantafx.base.theme.Styles;
import lombok.RequiredArgsConstructor;
import org.kordamp.ikonli.javafx.FontIcon;
@@ -73,8 +74,9 @@ public class StoreChoicePopover<T extends DataStore> {
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
var e = storeEntryWrapper.getEntry();
if (self != null && (e.equals(self)
|| DataStorage.get().getStoreParentHierarchy(e).contains(self))) {
if (self != null
&& (e.equals(self)
|| DataStorage.get().getStoreParentHierarchy(e).contains(self))) {
return false;
}
@@ -124,9 +126,9 @@ public class StoreChoicePopover<T extends DataStore> {
initialExpanded);
var category = new DataStoreCategoryChoiceComp(
initialCategory != null ? initialCategory.getRoot() : null,
StoreViewState.get().getActiveCategory(),
selectedCategory)
initialCategory != null ? initialCategory.getRoot() : null,
StoreViewState.get().getActiveCategory(),
selectedCategory)
.styleClass(Styles.LEFT_PILL);
var filter =
new FilterComp(filterText).styleClass(Styles.CENTER_PILL).hgrow();

View File

@@ -96,7 +96,6 @@ public class StoreCreationMenu {
menu.getItems().add(setupMenu());
menu.getItems().add(actionMenu);
}
private static Menu categoryMenu(
@@ -149,7 +148,6 @@ public class StoreCreationMenu {
return menu;
}
private static Menu setupMenu() {
var menu = new Menu();
menu.setGraphic(new FontIcon("mdi2t-toy-brick-plus-outline"));
@@ -160,7 +158,8 @@ public class StoreCreationMenu {
item.textProperty().bind(AppI18n.observable(p.getNameKey()));
item.setGraphic(p.getGraphic().createGraphicNode());
item.setOnAction(event -> {
var action = SetupToolActionProvider.Action.builder().type(p.getId()).build();
var action =
SetupToolActionProvider.Action.builder().type(p.getId()).build();
action.executeAsync();
event.consume();
});

View File

@@ -269,7 +269,8 @@ public class StoreEntryWrapper {
.or(() -> {
if (entry.getStore() instanceof GroupStore<?>) {
return Optional.empty();
} else if (entry.getProvider() != null && entry.getProvider().canConfigure()) {
} else if (entry.getProvider() != null
&& entry.getProvider().canConfigure()) {
return Optional.of(new EditHubLeafProvider());
} else {
return Optional.empty();

View File

@@ -67,7 +67,9 @@ public class SystemIconManager {
var split = id.split("/");
if (split.length == 2) {
var source = split[0];
var foundSource = getAllSources().stream().filter(systemIconSource -> systemIconSource.getId().equals(source)).findFirst();
var foundSource = getAllSources().stream()
.filter(systemIconSource -> systemIconSource.getId().equals(source))
.findFirst();
if (foundSource.isEmpty()) {
return Optional.empty();
}

View File

@@ -7,6 +7,7 @@ import io.xpipe.app.comp.base.TextFieldComp;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.util.LabelGraphic;
import io.xpipe.app.util.OptionsBuilder;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
@@ -26,8 +27,10 @@ public class ApiCategory extends AppPrefsCategory {
protected Comp<?> create() {
var prefs = AppPrefs.get();
var mcpConfig = Bindings.createStringBinding(() -> {
var template = """
var mcpConfig = Bindings.createStringBinding(
() -> {
var template =
"""
{
"mcpServers": {
"%s": {
@@ -40,11 +43,15 @@ public class ApiCategory extends AppPrefsCategory {
}
}
""";
return template.formatted(
AppProperties.get().isStaging() ? "xpipe-ptb" : "xpipe",
AppBeaconServer.get().getPort(),
prefs.apiKey().get() != null ? prefs.apiKey().get() : "?").strip();
}, prefs.apiKey());
return template.formatted(
AppProperties.get().isStaging() ? "xpipe-ptb" : "xpipe",
AppBeaconServer.get().getPort(),
prefs.apiKey().get() != null
? prefs.apiKey().get()
: "?")
.strip();
},
prefs.apiKey());
var mcpConfigProp = new SimpleStringProperty();
mcpConfigProp.bind(mcpConfig);
@@ -56,9 +63,9 @@ public class ApiCategory extends AppPrefsCategory {
.pref(prefs.apiKey)
.addComp(new TextFieldComp(prefs.apiKey).maxWidth(getCompWidth()), prefs.apiKey)
.pref(prefs.disableApiAuthentication)
.addToggle(prefs.disableApiAuthentication)
)
.addTitle("mcpServer").sub(new OptionsBuilder()
.addToggle(prefs.disableApiAuthentication))
.addTitle("mcpServer")
.sub(new OptionsBuilder()
.pref(prefs.enableMcpServer)
.addToggle(prefs.enableMcpServer)
.pref(prefs.enableMcpMutationTools)
@@ -68,8 +75,7 @@ public class ApiCategory extends AppPrefsCategory {
struc.getTextArea().setEditable(false);
struc.getTextArea().setPrefRowCount(11);
}))
.hide(prefs.enableMcpServer.not())
)
.hide(prefs.enableMcpServer.not()))
.buildComp();
}
}

View File

@@ -362,6 +362,7 @@ public class AppPrefs {
public ObservableBooleanValue pinLocalMachineOnStartup() {
return pinLocalMachineOnStartup;
}
@Getter
private final List<AppPrefsCategory> categories;
@@ -751,8 +752,8 @@ public class AppPrefs {
Class<?> valueType,
boolean vaultSpecific,
boolean requiresRestart,
boolean log, DocumentationLink documentationLink
) {
boolean log,
DocumentationLink documentationLink) {
this.key = key;
this.property = property;
this.valueType = SimpleType.constructUnsafe(valueType);
@@ -769,8 +770,8 @@ public class AppPrefs {
JavaType valueType,
boolean vaultSpecific,
boolean requiresRestart,
boolean log, DocumentationLink documentationLink
) {
boolean log,
DocumentationLink documentationLink) {
this.key = key;
this.property = property;
this.valueType = valueType;

View File

@@ -59,7 +59,7 @@ public class AppPrefsComp extends SimpleComp {
});
});
AppPrefs.get().getSelectedCategory().addListener((observable, oldValue, val) -> {
AppPrefs.get().getSelectedCategory().addListener((observable, oldValue, val) -> {
if (val == null) {
return;
}

View File

@@ -8,8 +8,8 @@ import io.xpipe.app.util.*;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.layout.Region;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@@ -48,16 +48,17 @@ public class EditorCategory extends AppPrefsCategory {
.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
var choice = ChoiceComp.ofTranslatable(
prefs.externalEditor, PrefsChoiceValue.getSupported(ExternalEditorType.class), false);
prefs.externalEditor, PrefsChoiceValue.getSupported(ExternalEditorType.class), false);
var visit = new ButtonComp(AppI18n.observable("website"), new FontIcon("mdi2w-web"), () -> {
var t = prefs.externalEditor().getValue();
if (t == null || t.getWebsite() == null) {
return;
}
var t = prefs.externalEditor().getValue();
if (t == null || t.getWebsite() == null) {
return;
}
Hyperlinks.open(t.getWebsite());
}).minWidth(Region.USE_PREF_SIZE);
Hyperlinks.open(t.getWebsite());
})
.minWidth(Region.USE_PREF_SIZE);
var h = new HorizontalComp(List.of(choice.hgrow(), visit)).apply(struc -> {
struc.get().setAlignment(Pos.CENTER_LEFT);

View File

@@ -415,10 +415,12 @@ public interface ExternalEditorType extends PrefsChoiceValue {
LinuxPathType MOUSEPAD = new LinuxPathType("app.mousepad", "mousepad", "https://docs.xfce.org/apps/mousepad/start");
LinuxPathType PLUMA = new LinuxPathType("app.pluma", "pluma", "https://github.com/mate-desktop/pluma");
ExternalEditorType TEXT_EDIT = new MacOsEditor("app.textEdit", "TextEdit", "https://support.apple.com/en-gb/guide/textedit/welcome/mac");
ExternalEditorType TEXT_EDIT =
new MacOsEditor("app.textEdit", "TextEdit", "https://support.apple.com/en-gb/guide/textedit/welcome/mac");
ExternalEditorType BBEDIT = new MacOsEditor("app.bbedit", "BBEdit", "https://www.barebones.com/products/bbedit/");
ExternalEditorType SUBLIME_MACOS = new MacOsEditor("app.sublime", "Sublime Text", "https://www.sublimetext.com/");
ExternalEditorType VSCODE_MACOS = new MacOsEditor("app.vscode", "Visual Studio Code", "https://code.visualstudio.com/");
ExternalEditorType VSCODE_MACOS =
new MacOsEditor("app.vscode", "Visual Studio Code", "https://code.visualstudio.com/");
ExternalEditorType VSCODIUM_MACOS = new MacOsEditor("app.vscodium", "VSCodium", "https://vscodium.com/");
ExternalEditorType CURSOR_MACOS = new MacOsEditor("app.cursor", "Cursor", "https://cursor.com/");
ExternalEditorType VOID_MACOS = new MacOsEditor("app.void", "Void", "https://voideditor.com/");
@@ -456,8 +458,12 @@ public interface ExternalEditorType extends PrefsChoiceValue {
var command = CommandBuilder.of()
.add(ExternalApplicationHelper.replaceVariableArgument(format, "FILE", file.toString()));
if (AppPrefs.get().customEditorCommandInTerminal().get()) {
TerminalLaunch.builder().title(file.toString()).localScript(sc -> new ShellScript(command.buildFull(sc)))
.logIfEnabled(false).preferTabs(false).launch();
TerminalLaunch.builder()
.title(file.toString())
.localScript(sc -> new ShellScript(command.buildFull(sc)))
.logIfEnabled(false)
.preferTabs(false)
.launch();
} else {
ExternalApplicationHelper.startAsync(command);
}
@@ -470,8 +476,10 @@ public interface ExternalEditorType extends PrefsChoiceValue {
};
ExternalEditorType FLEET = new GenericPathType("app.fleet", "fleet", false, "https://www.jetbrains.com/fleet/");
ExternalEditorType INTELLIJ = new GenericPathType("app.intellij", "idea", false, "https://www.jetbrains.com/idea/");
ExternalEditorType PYCHARM = new GenericPathType("app.pycharm", "pycharm", false, "https://www.jetbrains.com/pycharm/");
ExternalEditorType WEBSTORM = new GenericPathType("app.webstorm", "webstorm", false, "https://www.jetbrains.com/webstorm/");
ExternalEditorType PYCHARM =
new GenericPathType("app.pycharm", "pycharm", false, "https://www.jetbrains.com/pycharm/");
ExternalEditorType WEBSTORM =
new GenericPathType("app.webstorm", "webstorm", false, "https://www.jetbrains.com/webstorm/");
ExternalEditorType CLION = new GenericPathType("app.clion", "clion", false, "https://www.jetbrains.com/clion/");
List<ExternalEditorType> WINDOWS_EDITORS = List.of(
VOID_WINDOWS,

View File

@@ -5,6 +5,7 @@ import io.xpipe.app.comp.base.ContextualFileReferenceChoiceComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.LabelGraphic;
import io.xpipe.app.util.OptionsBuilder;
import javafx.beans.property.ReadOnlyObjectWrapper;
import java.util.List;
@@ -33,8 +34,14 @@ public class FileBrowserCategory extends AppPrefsCategory {
.addToggle(prefs.editFilesWithDoubleClick)
.pref(prefs.downloadsDirectory)
.addComp(
new ContextualFileReferenceChoiceComp(new ReadOnlyObjectWrapper<>(DataStorage.get().local().ref()), prefs.downloadsDirectory, null,
List.of()).maxWidth(getCompWidth()),
new ContextualFileReferenceChoiceComp(
new ReadOnlyObjectWrapper<>(DataStorage.get()
.local()
.ref()),
prefs.downloadsDirectory,
null,
List.of())
.maxWidth(getCompWidth()),
prefs.downloadsDirectory)
.pref(prefs.pinLocalMachineOnStartup)
.addToggle(prefs.pinLocalMachineOnStartup))

View File

@@ -94,7 +94,9 @@ public class IconsCategory extends AppPrefsCategory {
.remote(remote.get())
.id(UUID.randomUUID().toString())
.build();
if (sources.stream().noneMatch(s -> s instanceof SystemIconSource.GitRepository g && g.getRemote().equals(remote.get()))) {
if (sources.stream()
.noneMatch(s -> s instanceof SystemIconSource.GitRepository g
&& g.getRemote().equals(remote.get()))) {
sources.add(source);
var nl = new ArrayList<>(
AppPrefs.get().getIconSources().getValue());
@@ -135,7 +137,9 @@ public class IconsCategory extends AppPrefsCategory {
.path(path)
.id(UUID.randomUUID().toString())
.build();
if (sources.stream().noneMatch(s -> s instanceof SystemIconSource.Directory d && d.getPath().equals(path))) {
if (sources.stream()
.noneMatch(s -> s instanceof SystemIconSource.Directory d
&& d.getPath().equals(path))) {
sources.add(source);
var nl = new ArrayList<>(
AppPrefs.get().getIconSources().getValue());

View File

@@ -46,7 +46,8 @@ public class PasswordManagerCategory extends AppPrefsCategory {
websiteLinkButton.minWidth(Region.USE_PREF_SIZE);
websiteLinkButton.disable(Bindings.createBooleanBinding(
() -> {
return prefs.passwordManager.getValue() == null || prefs.passwordManager.getValue().getWebsite() == null;
return prefs.passwordManager.getValue() == null
|| prefs.passwordManager.getValue().getWebsite() == null;
},
prefs.passwordManager));

View File

@@ -12,7 +12,9 @@ import io.xpipe.app.util.DocumentationLink;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.LabelGraphic;
import io.xpipe.app.util.OptionsBuilder;
import javafx.geometry.Pos;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.List;
@@ -58,10 +60,12 @@ public class RdpCategory extends AppPrefsCategory {
.longDescription(DocumentationLink.RDP)
.addComp(h, prefs.rdpClientType)
.nameAndDescription("customRdpClientCommand")
.addComp(new TextFieldComp(prefs.customRdpClientCommand, true)
.apply(struc -> struc.get().setPromptText("myrdpclient -c $FILE"))
.hide(prefs.rdpClientType.isNotEqualTo(ExternalRdpClient.CUSTOM))
.prefWidth(600), prefs.customRdpClientCommand))
.addComp(
new TextFieldComp(prefs.customRdpClientCommand, true)
.apply(struc -> struc.get().setPromptText("myrdpclient -c $FILE"))
.hide(prefs.rdpClientType.isNotEqualTo(ExternalRdpClient.CUSTOM))
.prefWidth(600),
prefs.customRdpClientCommand))
.buildComp();
}
}

View File

@@ -164,8 +164,7 @@ public class TerminalCategory extends AppPrefsCategory {
.localScript(new ShellScript(ProcessControlProvider.get()
.getEffectiveLocalDialect()
.getEchoCommand(
"If you can read this, the terminal integration works",
false)))
"If you can read this, the terminal integration works", false)))
.preferTabs(false)
.logIfEnabled(false)
.launch();
@@ -177,7 +176,7 @@ public class TerminalCategory extends AppPrefsCategory {
var builder = new OptionsBuilder().pref(prefs.terminalType);
if (!docsLink) {
builder.longDescription((DocumentationLink) null);
builder.longDescription((DocumentationLink) null);
}
builder.addComp(h, prefs.terminalType);
builder.pref(prefs.customTerminalCommand)
@@ -317,7 +316,9 @@ public class TerminalCategory extends AppPrefsCategory {
.build();
var choice = choiceBuilder.build().buildComp();
choice.maxWidth(getCompWidth());
return new OptionsBuilder().nameAndDescription("terminalPrompt")
.longDescription(DocumentationLink.TERMINAL_PROMPT).addComp(choice, prefs.terminalPrompt);
return new OptionsBuilder()
.nameAndDescription("terminalPrompt")
.longDescription(DocumentationLink.TERMINAL_PROMPT)
.addComp(choice, prefs.terminalPrompt);
}
}

View File

@@ -93,7 +93,8 @@ public class TroubleshootCategory extends AppPrefsCategory {
"openInstallationDirectoryDescription",
"mdomz-snippet_folder",
e -> {
DesktopHelper.browsePathLocal(AppInstallation.ofCurrent().getBaseInstallationPath());
DesktopHelper.browsePathLocal(
AppInstallation.ofCurrent().getBaseInstallationPath());
e.consume();
})
.grow(true, false),
@@ -159,7 +160,8 @@ public class TroubleshootCategory extends AppPrefsCategory {
"uninstallApplicationDescription",
"mdi2d-dump-truck",
e -> {
var file = AppInstallation.ofCurrent().getBaseInstallationPath()
var file = AppInstallation.ofCurrent()
.getBaseInstallationPath()
.resolve("Contents")
.resolve("Resources")
.resolve("scripts")

View File

@@ -10,7 +10,6 @@ import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.util.*;
import io.xpipe.core.OsType;
import javafx.beans.property.SimpleObjectProperty;
public class WorkspaceCreationDialog {
@@ -45,7 +44,9 @@ public class WorkspaceCreationDialog {
var file =
switch (OsType.getLocal()) {
case OsType.Windows ignored -> {
var exec = AppInstallation.ofCurrent().getDaemonExecutablePath().toString();
var exec = AppInstallation.ofCurrent()
.getDaemonExecutablePath()
.toString();
yield DesktopShortcuts.create(
exec,
"-Dio.xpipe.app.dataDir=\""
@@ -53,7 +54,8 @@ public class WorkspaceCreationDialog {
shortcutName);
}
default -> {
var exec = AppInstallation.ofCurrent().getCliExecutablePath()
var exec = AppInstallation.ofCurrent()
.getCliExecutablePath()
.toString();
yield DesktopShortcuts.create(
exec,

View File

@@ -45,5 +45,4 @@ public interface ProcessControl extends AutoCloseable {
InputStream getStderr();
Charset getCharset();
}

View File

@@ -15,5 +15,4 @@ public interface ShellLaunchCommand {
}
List<String> loginCommand(OsType.Any os);
}

View File

@@ -49,7 +49,11 @@ public class BitwardenPasswordManager implements PasswordManager {
var script = ShellScript.lines(
sc.getShellDialect().getEchoCommand("Log in into your Bitwarden account from the CLI:", false),
"bw login");
TerminalLaunch.builder().title("Bitwarden login").localScript(script).logIfEnabled(false).launch();
TerminalLaunch.builder()
.title("Bitwarden login")
.localScript(script)
.logIfEnabled(false)
.launch();
return null;
}

View File

@@ -49,7 +49,11 @@ public class DashlanePasswordManager implements PasswordManager {
var script = ShellScript.lines(
sc.getShellDialect().getEchoCommand("Log in into your Dashlane account from the CLI:", false),
"dcli accounts whoami");
TerminalLaunch.builder().title("Dashlane login").localScript(script).logIfEnabled(false).launch();
TerminalLaunch.builder()
.title("Dashlane login")
.localScript(script)
.logIfEnabled(false)
.launch();
return null;
}

View File

@@ -54,7 +54,11 @@ public class KeeperPasswordManager implements PasswordManager {
var script = ShellScript.lines(
sc.getShellDialect().getEchoCommand("Log in into your Keeper account from the CLI:", false),
getExecutable(sc) + " login");
TerminalLaunch.builder().title("Keeper login").localScript(script).logIfEnabled(false).launch();
TerminalLaunch.builder()
.title("Keeper login")
.localScript(script)
.logIfEnabled(false)
.launch();
return null;
}

View File

@@ -54,7 +54,11 @@ public class LastpassPasswordManager implements PasswordManager {
sc.getShellDialect()
.getEchoCommand("Log in into your LastPass account from the CLI:", false),
"lpass login --trust \"" + email.get() + "\"");
TerminalLaunch.builder().title("LastPass login").localScript(script).logIfEnabled(false).launch();
TerminalLaunch.builder()
.title("LastPass login")
.localScript(script)
.logIfEnabled(false)
.launch();
}
return null;
}

View File

@@ -625,8 +625,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
static ExternalTerminalType determineDefault(ExternalTerminalType existing) {
// Check for incompatibility with fallback shell
if (ExternalTerminalType.CMD.equals(existing)
&& LocalShell.getDialect() != ShellDialects.CMD) {
if (ExternalTerminalType.CMD.equals(existing) && LocalShell.getDialect() != ShellDialects.CMD) {
return ExternalTerminalType.POWERSHELL;
}

View File

@@ -12,7 +12,6 @@ import io.xpipe.app.util.ShellTemp;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.FilePath;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
public interface KittyTerminalType extends ExternalTerminalType, TrackableTerminalType {
@@ -37,8 +36,7 @@ public interface KittyTerminalType extends ExternalTerminalType, TrackableTermin
payload.put("tab_title", configuration.getColoredTitle());
payload.put("type", "tab");
payload.put("logo_alpha", 0.01);
payload.put(
"logo", AppInstallation.ofCurrent().getLogoPath().toString());
payload.put("logo", AppInstallation.ofCurrent().getLogoPath().toString());
var json = JsonNodeFactory.instance.objectNode();
json.put("cmd", "launch");

View File

@@ -13,6 +13,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.FailableFunction;
import io.xpipe.core.FilePath;
import lombok.Builder;
import lombok.Value;
@@ -27,14 +28,19 @@ public class TerminalLaunch {
FilePath directory;
ProcessControl command;
UUID request;
@Builder.Default
boolean preferTabs = true;
@Builder.Default
boolean logIfEnabled = true;
ExternalTerminalType terminal;
public String getFullTitle() {
return entry != null ? DataStorage.get().getStoreEntryDisplayName(entry) + (title != null ? " - " + title : "") : title != null ? title : "?";
return entry != null
? DataStorage.get().getStoreEntryDisplayName(entry) + (title != null ? " - " + title : "")
: title != null ? title : "?";
}
public void launch() throws Exception {
@@ -45,12 +51,23 @@ public class TerminalLaunch {
if (OperationMode.get() == null) {
if (command instanceof CommandControl cc) {
TerminalLauncher.openDirect(getFullTitle(), sc -> new ShellScript(cc.getTerminalCommand().buildFull(sc)), ExternalTerminalType.determineFallbackTerminalToOpen(type));
TerminalLauncher.openDirect(
getFullTitle(),
sc -> new ShellScript(cc.getTerminalCommand().buildFull(sc)),
ExternalTerminalType.determineFallbackTerminalToOpen(type));
}
return;
}
TerminalLauncher.open(entry, getFullTitle(), directory, command, request != null ? request : UUID.randomUUID(), preferTabs, logIfEnabled, type);
TerminalLauncher.open(
entry,
getFullTitle(),
directory,
command,
request != null ? request : UUID.randomUUID(),
preferTabs,
logIfEnabled,
type);
}
public static class TerminalLaunchBuilder {
@@ -65,7 +82,8 @@ public class TerminalLaunch {
return command(c);
}
public TerminalLaunchBuilder localScript(FailableFunction<ShellControl, ShellScript, Exception> script) throws Exception {
public TerminalLaunchBuilder localScript(FailableFunction<ShellControl, ShellScript, Exception> script)
throws Exception {
var c = LocalShell.getShell().command(script.apply(LocalShell.getShell()));
return command(c);
}

View File

@@ -126,7 +126,8 @@ public class TerminalLaunchConfiguration {
var cliExecutable = TerminalProxyManager.getProxy()
.orElse(LocalShell.getShell())
.getLocalSystemAccess()
.translateFromLocalSystemPath(FilePath.of(AppInstallation.ofCurrent().getCliExecutablePath()));
.translateFromLocalSystemPath(
FilePath.of(AppInstallation.ofCurrent().getCliExecutablePath()));
var scriptCommand = sc.getOsType() == OsType.MACOS || sc.getOsType() == OsType.BSD
? "script -e -q '%s' \"%s\"".formatted(logFile, command)
: "script --quiet --command '%s' \"%s\"".formatted(command, logFile);

View File

@@ -11,7 +11,6 @@ import io.xpipe.app.util.ScriptHelper;
import io.xpipe.core.FailableFunction;
import io.xpipe.core.FilePath;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
@@ -115,7 +114,8 @@ public class TerminalLauncher {
UUID request,
boolean preferTabs,
boolean enableLogging,
ExternalTerminalType type) throws Exception {
ExternalTerminalType type)
throws Exception {
var color = entry != null ? DataStorage.get().getEffectiveColor(entry) : null;
var prefix = entry != null && color != null && type.useColoredTitle() ? color.getEmoji() + " " : "";
var cleanTitle = (title != null ? title : entry != null ? entry.getName() : "Unknown");
@@ -161,8 +161,7 @@ public class TerminalLauncher {
return;
}
var changedDialect =
config.getScriptDialect() != LocalShell.getDialect();
var changedDialect = config.getScriptDialect() != LocalShell.getDialect();
config = config.withScript(
LocalShell.getDialect(),
getTerminalRegisterCommand(request) + "\n"
@@ -258,12 +257,7 @@ public class TerminalLauncher {
proxyControl.get().start();
var fullLocalCommand = getTerminalRegisterCommand(request) + "\n" + proxyLaunchCommand;
return Optional.of(new TerminalLaunchConfiguration(
null,
"XPipe",
"XPipe",
false,
fullLocalCommand,
LocalShell.getDialect()));
null, "XPipe", "XPipe", false, fullLocalCommand, LocalShell.getDialect()));
} else {
var multiplexerCommand = multiplexer
.get()
@@ -276,12 +270,7 @@ public class TerminalLauncher {
WorkingDirectoryFunction.none());
var fullLocalCommand = getTerminalRegisterCommand(request) + "\n" + launchCommand;
return Optional.of(new TerminalLaunchConfiguration(
null,
"XPipe",
"XPipe",
false,
fullLocalCommand,
LocalShell.getDialect()));
null, "XPipe", "XPipe", false, fullLocalCommand, LocalShell.getDialect()));
}
}
@@ -302,7 +291,6 @@ public class TerminalLauncher {
// Restart for the next time
proxyControl.get().start();
var fullLocalCommand = getTerminalRegisterCommand(request) + "\n" + launchCommand;
return Optional.ofNullable(launchConfiguration.withScript(
LocalShell.getDialect(), fullLocalCommand));
return Optional.ofNullable(launchConfiguration.withScript(LocalShell.getDialect(), fullLocalCommand));
}
}

View File

@@ -1,9 +1,9 @@
package io.xpipe.app.terminal;
import io.xpipe.app.util.NativeWinWindowControl;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.ExternalApplicationType;
import io.xpipe.app.util.NativeWinWindowControl;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.OsType;

View File

@@ -6,7 +6,6 @@ import io.xpipe.app.process.CommandBuilder;
import io.xpipe.app.util.CommandSupport;
import io.xpipe.app.util.LocalShell;
public interface WaveTerminalType extends ExternalTerminalType, TrackableTerminalType {
ExternalTerminalType WAVE_WINDOWS = new Windows();
@@ -66,7 +65,9 @@ public interface WaveTerminalType extends ExternalTerminalType, TrackableTermina
.formatted(
inPath
? "xpipe open"
: "\"" + AppInstallation.ofCurrent().getCliExecutablePath() + "\" open");
: "\""
+ AppInstallation.ofCurrent()
.getCliExecutablePath() + "\" open");
throw ErrorEventFactory.expected(new IllegalStateException(msg));
}

View File

@@ -9,7 +9,6 @@ import io.xpipe.app.util.LocalShell;
import io.xpipe.core.FilePath;
import io.xpipe.core.JacksonMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import java.io.IOException;

View File

@@ -8,7 +8,6 @@ import io.xpipe.app.util.LocalExec;
import io.xpipe.app.util.Translatable;
import io.xpipe.core.OsType;
import javafx.beans.value.ObservableValue;
import lombok.Getter;

View File

@@ -12,7 +12,6 @@ import io.xpipe.app.util.ScriptHelper;
import io.xpipe.core.FilePath;
import io.xpipe.core.OsType;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
@@ -65,9 +64,7 @@ public class AppInstaller {
var logFile =
FilePath.of(logsDir, "installer_" + file.getFileName().toString() + ".log");
var systemWide = isSystemWide();
var cmdScript =
LocalShell.getDialect() == ShellDialects.CMD
&& !systemWide;
var cmdScript = LocalShell.getDialect() == ShellDialects.CMD && !systemWide;
var command = cmdScript
? getCmdCommand(file.toString(), logFile.toString())
: getPowershellCommand(file.toString(), logFile.toString(), systemWide);
@@ -98,7 +95,8 @@ public class AppInstaller {
}
private boolean isSystemWide() {
return Files.exists(AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
return Files.exists(
AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
}
private String getCmdCommand(String file, String logFile) {
@@ -168,7 +166,10 @@ public class AppInstaller {
""",
file, file, AppRestart.getTerminalRestartCommand()));
OperationMode.executeAfterShutdown(() -> {
TerminalLaunch.builder().title("XPipe Updater").localScript(command).launch();
TerminalLaunch.builder()
.title("XPipe Updater")
.localScript(command)
.launch();
});
}
@@ -201,7 +202,10 @@ public class AppInstaller {
""",
file, file, AppRestart.getTerminalRestartCommand()));
OperationMode.executeAfterShutdown(() -> {
TerminalLaunch.builder().title("XPipe Updater").localScript(command).launch();
TerminalLaunch.builder()
.title("XPipe Updater")
.localScript(command)
.launch();
});
}
@@ -234,7 +238,10 @@ public class AppInstaller {
""",
file, file, AppRestart.getTerminalRestartCommand()));
OperationMode.executeAfterShutdown(() -> {
TerminalLaunch.builder().title("XPipe Updater").localScript(command).launch();
TerminalLaunch.builder()
.title("XPipe Updater")
.localScript(command)
.launch();
});
}

View File

@@ -14,7 +14,6 @@ import io.xpipe.app.terminal.TerminalLaunch;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.LocalShell;
import java.nio.file.Files;
import java.time.Instant;
import java.util.ArrayList;
@@ -120,7 +119,8 @@ public class ChocoUpdater extends UpdateHandler {
var performedUpdate = new PerformedUpdate(p.getVersion(), p.getBody(), p.getVersion());
AppCache.update("performedUpdate", performedUpdate);
OperationMode.executeAfterShutdown(() -> {
var systemWide = Files.exists(AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
var systemWide = Files.exists(
AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
var propertiesArguments = systemWide ? ", --install-arguments=\"'ALLUSERS=1'\"" : "";
TerminalLaunch.builder().title("XPipe Updater").localScript(sc -> {
var pkg = "xpipe";

View File

@@ -57,7 +57,10 @@ public class CommandUpdater extends PortableUpdater {
var performedUpdate = new PerformedUpdate(p.getVersion(), p.getBody(), p.getVersion());
AppCache.update("performedUpdate", performedUpdate);
OperationMode.executeAfterShutdown(() -> {
TerminalLaunch.builder().title("XPipe Updater").localScript(script).launch();
TerminalLaunch.builder()
.title("XPipe Updater")
.localScript(script)
.launch();
});
} catch (Throwable t) {
ErrorEventFactory.fromThrowable(t).handle();

View File

@@ -13,7 +13,6 @@ import io.xpipe.app.terminal.TerminalLaunch;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.LocalShell;
import java.nio.file.Files;
import java.time.Instant;
import java.util.ArrayList;
@@ -121,7 +120,9 @@ public class WingetUpdater extends UpdateHandler {
AppCache.update("performedUpdate", performedUpdate);
OperationMode.executeAfterShutdown(() -> {
TerminalLaunch.builder().title("XPipe Updater").localScript(sc -> {
var systemWide = Files.exists(AppInstallation.ofCurrent().getBaseInstallationPath().resolve("system"));
var systemWide = Files.exists(AppInstallation.ofCurrent()
.getBaseInstallationPath()
.resolve("system"));
var pkgId = "xpipe-io.xpipe";
if (systemWide) {
return ShellScript.lines(

View File

@@ -1,6 +1,5 @@
package io.xpipe.app.util;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import io.xpipe.app.ext.HostAddress;
import io.xpipe.app.process.ShellDialect;
import io.xpipe.app.process.ShellDialects;
@@ -24,6 +23,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.SimpleType;
@@ -370,15 +370,8 @@ public class AppJacksonModule extends SimpleModule {
public static class HostAddressSerializer extends JsonSerializer<HostAddress> {
@Override
public void serialize(HostAddress value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
public void serialize(HostAddress value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
if (value.isSingle()) {
jgen.writeString(value.get());
} else {
@@ -390,19 +383,9 @@ public class AppJacksonModule extends SimpleModule {
}
}
public static class HostAddressDeserializer extends JsonDeserializer<HostAddress> {
@Override
public HostAddress deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
var tree = (JsonNode) p.getCodec().readTree(p);
if (tree.isTextual()) {

View File

@@ -6,7 +6,6 @@ import io.xpipe.app.process.OsFileSystem;
import io.xpipe.core.FilePath;
import io.xpipe.core.OsType;
import java.nio.file.Files;
import java.nio.file.Path;

View File

@@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener;
public class GlobalBooleanProperty extends SimpleBooleanProperty {
public GlobalBooleanProperty() {
}
public GlobalBooleanProperty() {}
public GlobalBooleanProperty(boolean initialValue) {
super(initialValue);

View File

@@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener;
public class GlobalDoubleProperty extends SimpleDoubleProperty {
public GlobalDoubleProperty() {
}
public GlobalDoubleProperty() {}
public GlobalDoubleProperty(Double initialValue) {
super(initialValue);

View File

@@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener;
public class GlobalObjectProperty<T> extends SimpleObjectProperty<T> {
public GlobalObjectProperty() {
}
public GlobalObjectProperty() {}
public GlobalObjectProperty(T initialValue) {
super(initialValue);

View File

@@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener;
public class GlobalStringProperty extends SimpleStringProperty {
public GlobalStringProperty() {
}
public GlobalStringProperty() {}
public GlobalStringProperty(String initialValue) {
super(initialValue);

View File

@@ -35,7 +35,8 @@ public class NativeBridge {
try {
System.setProperty(
"jna.library.path",
AppInstallation.ofCurrent().getBaseInstallationPath()
AppInstallation.ofCurrent()
.getBaseInstallationPath()
.resolve("Contents")
.resolve("runtime")
.resolve("Contents")

View File

@@ -183,11 +183,16 @@ public class OptionsBuilder {
public OptionsBuilder pref(Object property) {
var mapping = AppPrefs.get().getMapping(property);
pref(mapping.getKey(), mapping.isRequiresRestart(), mapping.getLicenseFeatureId(), mapping.getDocumentationLink());
pref(
mapping.getKey(),
mapping.isRequiresRestart(),
mapping.getLicenseFeatureId(),
mapping.getDocumentationLink());
return this;
}
public OptionsBuilder pref(String key, boolean requiresRestart, String licenseFeatureId, DocumentationLink documentationLink) {
public OptionsBuilder pref(
String key, boolean requiresRestart, String licenseFeatureId, DocumentationLink documentationLink) {
var name = key;
name(name);
if (requiresRestart) {

View File

@@ -76,8 +76,10 @@ public class RemminaHelper {
.orElseThrow()
.getValue(),
password != null ? password : "",
Math.round(AppMainWindow.getInstance().getStage().getWidth()),
Math.round(AppMainWindow.getInstance().getStage().getHeight()));
Math.round(
AppMainWindow.getInstance().getStage().getWidth()),
Math.round(
AppMainWindow.getInstance().getStage().getHeight()));
Files.createDirectories(file.getParent());
Files.writeString(file, string);
return file;

View File

@@ -47,8 +47,7 @@ public class ScanDialogBase {
Runnable closeAction,
ScanDialogAction action,
ObservableList<DataStoreEntryRef<ShellStore>> entries,
boolean showButton
) {
boolean showButton) {
this.expand = expand;
this.closeAction = closeAction;
this.action = action;

View File

@@ -31,7 +31,7 @@ class ScanMultiDialogComp extends ModalOverlayContentComp {
},
action,
list,
false);
false);
}
void finish() {

View File

@@ -10,7 +10,6 @@ import io.xpipe.app.process.ShellControl;
import io.xpipe.app.process.ShellDialects;
import io.xpipe.core.FilePath;
import lombok.Getter;
import lombok.Setter;

View File

@@ -4,9 +4,9 @@ import io.xpipe.app.browser.BrowserFullSessionModel;
import io.xpipe.app.browser.BrowserStoreSessionTab;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.util.DocumentationLink;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.util.DocumentationLink;
import lombok.Builder;
import lombok.extern.jackson.Jacksonized;

View File

@@ -37,7 +37,8 @@ public class VncLaunchConfig {
return Optional.empty();
}
var secret = SecretManager.retrieve(strat, "VNC login password", entry.get().getUuid(), 1, true);
var secret =
SecretManager.retrieve(strat, "VNC login password", entry.get().getUuid(), 1, true);
return Optional.ofNullable(secret);
}
}

View File

@@ -1,4 +1,4 @@
name=Commons Lang
version=3.17.0
version=3.18.0
license=Apache License 2.0
link=https://commons.apache.org/proper/commons-lang/

View File

@@ -190,31 +190,32 @@ public class IdentitySelectComp extends Comp<CompStructure<HBox>> {
}
});
var combo = new ComboTextFieldComp(prop, FXCollections.observableList(map.keySet().stream().toList()), () -> {
return new ListCell<>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
return;
}
var combo = new ComboTextFieldComp(
prop, FXCollections.observableList(map.keySet().stream().toList()), () -> {
return new ListCell<>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
return;
}
setText(item);
setText(item);
if (item != null) {
var store = map.get(item);
if (store != null) {
var provider = store.get().getProvider();
var image = provider.getDisplayIconFileName(store.getStore());
setGraphic(
PrettyImageHelper.ofFixedSize(image, 16, 16).createRegion());
if (item != null) {
var store = map.get(item);
if (store != null) {
var provider = store.get().getProvider();
var image = provider.getDisplayIconFileName(store.getStore());
setGraphic(PrettyImageHelper.ofFixedSize(image, 16, 16)
.createRegion());
}
} else {
setGraphic(null);
}
}
} else {
setGraphic(null);
}
}
};
});
};
});
combo.apply(struc -> struc.get().setEditable(allowUserInput));
combo.styleClass(Styles.LEFT_PILL);
combo.grow(false, true);
@@ -244,8 +245,13 @@ public class IdentitySelectComp extends Comp<CompStructure<HBox>> {
});
combo.apply(struc -> {
var popover = new StoreChoicePopover<>(null, selectedReference, IdentityStore.class, null,
StoreViewState.get().getAllIdentitiesCategory(), "selectIdentity");
var popover = new StoreChoicePopover<>(
null,
selectedReference,
IdentityStore.class,
null,
StoreViewState.get().getAllIdentitiesCategory(),
"selectIdentity");
((Region) popover.getPopover().getContentNode()).setMaxHeight(350);
var skin = new ComboBoxListViewSkin<>(struc.get()) {
@Override

View File

@@ -23,14 +23,18 @@ public class SshIdentityStrategyHelper {
private static OptionsBuilder agent(Property<SshIdentityStrategy.SshAgent> p, boolean allowForward) {
var forward =
new SimpleBooleanProperty(p.getValue() != null && p.getValue().isForwardAgent());
var publicKey = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
var publicKey =
new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
return new OptionsBuilder()
.nameAndDescription("forwardAgent")
.addToggle(forward)
.nonNull()
.hide(!allowForward)
.nameAndDescription("publicKey")
.addComp(new TextFieldComp(publicKey).apply(struc -> struc.get().setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")), publicKey)
.addComp(
new TextFieldComp(publicKey).apply(struc -> struc.get()
.setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")),
publicKey)
.bind(
() -> {
return new SshIdentityStrategy.SshAgent(forward.get(), publicKey.get());
@@ -41,14 +45,18 @@ public class SshIdentityStrategyHelper {
private static OptionsBuilder gpgAgent(Property<SshIdentityStrategy.GpgAgent> p, boolean allowForward) {
var forward =
new SimpleBooleanProperty(p.getValue() != null && p.getValue().isForwardAgent());
var publicKey = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
var publicKey =
new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
return new OptionsBuilder()
.nameAndDescription("forwardAgent")
.addToggle(forward)
.nonNull()
.hide(!allowForward)
.nameAndDescription("publicKey")
.addComp(new TextFieldComp(publicKey).apply(struc -> struc.get().setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")), publicKey)
.addComp(
new TextFieldComp(publicKey).apply(struc -> struc.get()
.setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")),
publicKey)
.bind(
() -> {
return new SshIdentityStrategy.GpgAgent(forward.get(), publicKey.get());
@@ -59,14 +67,18 @@ public class SshIdentityStrategyHelper {
private static OptionsBuilder pageant(Property<SshIdentityStrategy.Pageant> p, boolean allowForward) {
var forward =
new SimpleBooleanProperty(p.getValue() != null && p.getValue().isForwardAgent());
var publicKey = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
var publicKey =
new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
return new OptionsBuilder()
.nameAndDescription("forwardAgent")
.addToggle(forward)
.nonNull()
.hide(!allowForward)
.nameAndDescription("publicKey")
.addComp(new TextFieldComp(publicKey).apply(struc -> struc.get().setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")), publicKey)
.addComp(
new TextFieldComp(publicKey).apply(struc -> struc.get()
.setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")),
publicKey)
.bind(
() -> {
return new SshIdentityStrategy.Pageant(forward.get(), publicKey.get());
@@ -78,14 +90,18 @@ public class SshIdentityStrategyHelper {
Property<SshIdentityStrategy.PasswordManagerAgent> p, boolean allowForward) {
var forward =
new SimpleBooleanProperty(p.getValue() != null && p.getValue().isForwardAgent());
var publicKey = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
var publicKey =
new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
return new OptionsBuilder()
.nameAndDescription("forwardAgent")
.addToggle(forward)
.nonNull()
.hide(!allowForward)
.nameAndDescription("publicKey")
.addComp(new TextFieldComp(publicKey).apply(struc -> struc.get().setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")), publicKey)
.addComp(
new TextFieldComp(publicKey).apply(struc -> struc.get()
.setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")),
publicKey)
.bind(
() -> {
return new SshIdentityStrategy.PasswordManagerAgent(forward.get(), publicKey.get());
@@ -96,14 +112,18 @@ public class SshIdentityStrategyHelper {
private static OptionsBuilder otherExternal(Property<SshIdentityStrategy.OtherExternal> p, boolean allowForward) {
var forward =
new SimpleBooleanProperty(p.getValue() != null && p.getValue().isForwardAgent());
var publicKey = new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
var publicKey =
new SimpleStringProperty(p.getValue() != null ? p.getValue().getPublicKey() : null);
return new OptionsBuilder()
.nameAndDescription("forwardAgent")
.addToggle(forward)
.nonNull()
.hide(!allowForward)
.nameAndDescription("publicKey")
.addComp(new TextFieldComp(publicKey).apply(struc -> struc.get().setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")), publicKey)
.addComp(
new TextFieldComp(publicKey).apply(struc -> struc.get()
.setPromptText("ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBmhLUTJiP...== Your Comment")),
publicKey)
.bind(
() -> {
return new SshIdentityStrategy.OtherExternal(forward.get(), publicKey.get());

View File

@@ -31,7 +31,11 @@ public class RunTerminalScriptActionProvider implements ActionProvider {
public void executeImpl() throws Exception {
var sc = ref.getStore().getOrStartSession();
var script = scriptStore.getStore().assembleScriptChain(sc);
TerminalLaunch.builder().entry(ref.get()).title(scriptStore.get().getName()).command(sc.command(script)).launch();
TerminalLaunch.builder()
.entry(ref.get())
.title(scriptStore.get().getName())
.command(sc.command(script))
.launch();
}
}
}

View File

@@ -54,7 +54,11 @@ public class IncusContainerConsoleActionProvider implements HubLeafProvider<Incu
var d = (IncusContainerStore) ref.getStore();
var view = new IncusCommandView(
d.getInstall().getStore().getHost().getStore().getOrStartSession());
TerminalLaunch.builder().entry(ref.get()).title("Console").command(view.console(d.getName())).launch();
TerminalLaunch.builder()
.entry(ref.get())
.title("Console")
.command(view.console(d.getName()))
.launch();
}
}
}

View File

@@ -59,7 +59,11 @@ public class IncusContainerEditConfigActionProvider implements HubLeafProvider<I
var d = (IncusContainerStore) ref.getStore();
var view = new IncusCommandView(
d.getInstall().getStore().getHost().getStore().getOrStartSession());
TerminalLaunch.builder().entry(ref.get()).title("Config").command(view.configEdit(d.getName())).launch();
TerminalLaunch.builder()
.entry(ref.get())
.title("Config")
.command(view.configEdit(d.getName()))
.launch();
}
}
}

View File

@@ -54,7 +54,11 @@ public class LxdContainerConsoleActionProvider implements HubLeafProvider<LxdCon
var d = ref.getStore();
var view = new LxdCommandView(
d.getCmd().getStore().getHost().getStore().getOrStartSession());
TerminalLaunch.builder().entry(ref.get()).title("Console").command(view.console(d.getName())).launch();
TerminalLaunch.builder()
.entry(ref.get())
.title("Console")
.command(view.console(d.getName()))
.launch();
}
}
}

View File

@@ -59,7 +59,11 @@ public class LxdContainerEditConfigActionProvider implements HubLeafProvider<Lxd
var d = ref.getStore();
var view = new LxdCommandView(
d.getCmd().getStore().getHost().getStore().getOrStartSession());
TerminalLaunch.builder().entry(ref.get()).title("Config").command(view.configEdit(d.getName())).launch();
TerminalLaunch.builder()
.entry(ref.get())
.title("Config")
.command(view.configEdit(d.getName()))
.launch();
}
}
}

View File

@@ -47,7 +47,11 @@ public class PodmanContainerAttachActionProvider implements HubLeafProvider<Podm
public void executeImpl() throws Exception {
var d = ref.getStore();
var view = d.commandView(d.getCmd().getStore().getHost().getStore().getOrStartSession());
TerminalLaunch.builder().entry(ref.get()).title("Attach").command(view.attach(d.getContainerName())).launch();
TerminalLaunch.builder()
.entry(ref.get())
.title("Attach")
.command(view.attach(d.getContainerName()))
.launch();
}
}
}

Some files were not shown because too many files have changed in this diff Show More