diff --git a/app/build.gradle b/app/build.gradle index 01405bfad..0c76b8e47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java b/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java index 72efc6bdd..5cd7e44ed 100644 --- a/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java +++ b/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonModeExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonModeExchangeImpl.java index d27564f9c..dd1a54cf9 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonModeExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonModeExchangeImpl.java @@ -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 diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java b/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java index 7d2194b7b..903a7ea9b 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/AppMcpServer.java @@ -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(); }); diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/HttpStreamableServerTransportProvider.java b/app/src/main/java/io/xpipe/app/beacon/mcp/HttpStreamableServerTransportProvider.java index 18e17bf63..ded3ee75e 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/HttpStreamableServerTransportProvider.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/HttpStreamableServerTransportProvider.java @@ -4,6 +4,8 @@ package io.xpipe.app.beacon.mcp; +import io.xpipe.app.issue.TrackEvent; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.net.httpserver.HttpExchange; @@ -13,7 +15,6 @@ import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.*; import io.modelcontextprotocol.util.Assert; import io.modelcontextprotocol.util.KeepAliveScheduler; -import io.xpipe.app.issue.TrackEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; @@ -29,593 +30,579 @@ import java.util.concurrent.locks.ReentrantLock; public class HttpStreamableServerTransportProvider implements McpStreamableServerTransportProvider { - private static final Logger logger = LoggerFactory.getLogger(HttpStreamableServerTransportProvider.class); - - /** - * Event type for JSON-RPC messages sent through the SSE connection. - */ - public static final String MESSAGE_EVENT_TYPE = "message"; - - /** - * Event type for sending the message endpoint URI to clients. - */ - public static final String ENDPOINT_EVENT_TYPE = "endpoint"; - - /** - * Header name for the response media types accepted by the requester. - */ - private static final String ACCEPT = "Accept"; - - public static final String UTF_8 = "UTF-8"; - - public static final String APPLICATION_JSON = "application/json"; - - public static final String TEXT_EVENT_STREAM = "text/event-stream"; - - public static final String FAILED_TO_SEND_ERROR_RESPONSE = "Failed to send error response: {}"; - - /** - * The endpoint URI where clients should send their JSON-RPC messages. Defaults to - * "/mcp". - */ - private final String mcpEndpoint; - - /** - * Flag indicating whether DELETE requests are disallowed on the endpoint. - */ - private final boolean disallowDelete; - - private final ObjectMapper objectMapper; - - private McpStreamableServerSession.Factory sessionFactory; - - /** - * Map of active client sessions, keyed by mcp-session-id. - */ - private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); - - private final McpTransportContextExtractor contextExtractor; - - /** - * Flag indicating if the transport is shutting down. - */ - private volatile boolean isClosing = false; - - /** - * Keep-alive scheduler for managing session pings. Activated if keepAliveInterval is - * set. Disabled by default. - */ - private KeepAliveScheduler keepAliveScheduler; - - HttpStreamableServerTransportProvider(ObjectMapper objectMapper, String mcpEndpoint, - boolean disallowDelete, McpTransportContextExtractor contextExtractor, - Duration keepAliveInterval) { - Assert.notNull(objectMapper, "ObjectMapper must not be null"); - Assert.notNull(mcpEndpoint, "MCP endpoint must not be null"); - Assert.notNull(contextExtractor, "Context extractor must not be null"); - - this.objectMapper = objectMapper; - this.mcpEndpoint = mcpEndpoint; - this.disallowDelete = disallowDelete; - this.contextExtractor = contextExtractor; - - if (keepAliveInterval != null) { - - this.keepAliveScheduler = KeepAliveScheduler - .builder(() -> (isClosing) ? Flux.empty() : Flux.fromIterable(sessions.values())) - .initialDelay(keepAliveInterval) - .interval(keepAliveInterval) - .build(); - - this.keepAliveScheduler.start(); - } - - } - - @Override - public String protocolVersion() { - return "2025-03-26"; - } - - @Override - public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - @Override - public Mono notifyClients(String method, Object params) { - if (this.sessions.isEmpty()) { - logger.debug("No active sessions to broadcast message to"); - return Mono.empty(); - } - - logger.debug("Attempting to broadcast message to {} active sessions", this.sessions.size()); - - return Mono.fromRunnable(() -> { - this.sessions.values().parallelStream().forEach(session -> { - try { - session.sendNotification(method, params).block(); - } - catch (Exception e) { - logger.error("Failed to send message to session {}: {}", session.getId(), e.getMessage()); - } - }); - }); - } - - /** - * Initiates a graceful shutdown of the transport. - * @return A Mono that completes when all cleanup operations are finished - */ - @Override - public Mono closeGracefully() { - return Mono.fromRunnable(() -> { - this.isClosing = true; - logger.debug("Initiating graceful shutdown with {} active sessions", this.sessions.size()); - - this.sessions.values().parallelStream().forEach(session -> { - try { - session.closeGracefully().block(); - } - catch (Exception e) { - logger.error("Failed to close session {}: {}", session.getId(), e.getMessage()); - } - }); - - this.sessions.clear(); - logger.debug("Graceful shutdown completed"); - }).then().doOnSuccess(v -> { - sessions.clear(); - logger.debug("Graceful shutdown completed"); - if (this.keepAliveScheduler != null) { - this.keepAliveScheduler.shutdown(); - } - }); - } - - public void doGet(HttpExchange exchange) - throws IOException { - - String requestURI = exchange.getRequestURI().toString(); - if (!requestURI.endsWith(mcpEndpoint)) { - sendError(exchange, 404, null); - return; - } - - if (this.isClosing) { - sendError(exchange, 503, "Server is shutting down"); - return; - } - - List badRequestErrors = new ArrayList<>(); - - String accept = exchange.getRequestHeaders().getFirst(ACCEPT); - if (accept == null || !accept.contains(TEXT_EVENT_STREAM)) { - badRequestErrors.add("text/event-stream required in Accept header"); - } - - String sessionId = exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); - - if (sessionId == null || sessionId.isBlank()) { - badRequestErrors.add("Session ID required in mcp-session-id header"); - } - - if (!badRequestErrors.isEmpty()) { - String combinedMessage = String.join("; ", badRequestErrors); - this.sendError(exchange, 400, combinedMessage); - return; - } - - McpStreamableServerSession session = this.sessions.get(sessionId); - - if (session == null) { - sendError(exchange, 404, null); - return; - } - - logger.debug("Handling GET request for session: {}", sessionId); - - McpTransportContext transportContext = this.contextExtractor.extract(exchange, new DefaultMcpTransportContext()); - - try { - exchange.getResponseHeaders().add("Content-Type", TEXT_EVENT_STREAM); - exchange.getResponseHeaders().add("Content-Encoding", UTF_8); - exchange.getResponseHeaders().add("Cache-Control", "no-cache"); - exchange.getResponseHeaders().add("Connection", "keep-alive"); - exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); - exchange.sendResponseHeaders(200, 0); - - var writer = new PrintWriter(exchange.getResponseBody()); - HttpServletStreamableMcpSessionTransport sessionTransport = new HttpServletStreamableMcpSessionTransport( - sessionId, exchange, writer); - - // Check if this is a replay request - if (exchange.getRequestHeaders().getFirst(HttpHeaders.LAST_EVENT_ID) != null) { - String lastId = exchange.getRequestHeaders().getFirst(HttpHeaders.LAST_EVENT_ID); - - try { - session.replay(lastId) - .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) - .toIterable() - .forEach(message -> { - try { - sessionTransport.sendMessage(message) - .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) - .block(); - } - catch (Exception e) { - logger.error("Failed to replay message: {}", e.getMessage()); - exchange.close(); - } - }); - } - catch (Exception e) { - logger.error("Failed to replay messages: {}", e.getMessage()); - exchange.close(); - } - } - } - catch (Exception e) { - logger.error("Failed to handle GET request for session {}: {}", sessionId, e.getMessage()); - sendError(exchange, 500, null); - } - } - - public void sendError(HttpExchange exchange, int code, String message) throws IOException { - var b = message != null ? message.getBytes(StandardCharsets.UTF_8) : new byte[0]; - exchange.getResponseHeaders().add("Content-Encoding", UTF_8); - exchange.sendResponseHeaders(code, b.length != 0 ? b.length : -1); - try (OutputStream os = exchange.getResponseBody()) { - os.write(b); - } - - TrackEvent.error("MCP server error: " + message); - } - - public void doPost(HttpExchange exchange) - throws IOException { - - String requestURI = exchange.getRequestURI().toString(); - if (!requestURI.endsWith(mcpEndpoint)) { - sendError(exchange, 404, null); - return; - } - - if (this.isClosing) { - sendError(exchange, 503, "Server is shutting down"); - return; - } - - List badRequestErrors = new ArrayList<>(); - - String accept = exchange.getRequestHeaders().getFirst(ACCEPT); - if (accept == null || !accept.contains(TEXT_EVENT_STREAM)) { - badRequestErrors.add("text/event-stream required in Accept header"); - } - if (accept == null || !accept.contains(APPLICATION_JSON)) { - badRequestErrors.add("application/json required in Accept header"); - } - - McpTransportContext transportContext = this.contextExtractor.extract(exchange, new DefaultMcpTransportContext()); - - try { - var body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); - - McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body); - - // Handle initialization request - if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest - && jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) { - if (!badRequestErrors.isEmpty()) { - String combinedMessage = String.join("; ", badRequestErrors); - this.sendError(exchange, 400, combinedMessage); - return; - } - - McpSchema.InitializeRequest initializeRequest = objectMapper.convertValue(jsonrpcRequest.params(), new TypeReference<>() {}); - McpStreamableServerSession.McpStreamableServerSessionInit init = this.sessionFactory - .startSession(initializeRequest); - this.sessions.put(init.session().getId(), init.session()); - - try { - McpSchema.InitializeResult initResult = init.initResult().block(); - - String jsonResponse = objectMapper.writeValueAsString(new McpSchema.JSONRPCResponse( - McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(), initResult, null)); - var jsonBytes = jsonResponse.getBytes(StandardCharsets.UTF_8); - - exchange.getResponseHeaders().add("Content-Type", APPLICATION_JSON); - exchange.getResponseHeaders().add("Content-Encoding", UTF_8); - exchange.getResponseHeaders().add(HttpHeaders.MCP_SESSION_ID, init.session().getId()); - exchange.sendResponseHeaders(200, jsonBytes.length); - exchange.getResponseBody().write(jsonBytes); - return; - } - catch (Exception e) { - logger.error("Failed to initialize session: {}", e.getMessage()); - this.sendError(exchange, 500, "Failed to initialize session: " + e.getMessage()); - return; - } - } - - String sessionId = exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); - - if (sessionId == null || sessionId.isBlank()) { - badRequestErrors.add("Session ID required in mcp-session-id header"); - } - - if (!badRequestErrors.isEmpty()) { - String combinedMessage = String.join("; ", badRequestErrors); - this.sendError(exchange, 400, combinedMessage); - return; - } - - McpStreamableServerSession session = this.sessions.get(sessionId); - - if (session == null) { - this.sendError(exchange, 404, "Session not found: " + sessionId + ". Was the session not refreshed?"); - return; - } - - if (message instanceof McpSchema.JSONRPCResponse jsonrpcResponse) { - session.accept(jsonrpcResponse) - .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) - .block(); - exchange.sendResponseHeaders(200, -1); - } - else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { - session.accept(jsonrpcNotification) - .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) - .block(); - exchange.sendResponseHeaders(202, -1); - } - else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) { - // For streaming responses, we need to return SSE - exchange.getResponseHeaders().add("Content-Type", TEXT_EVENT_STREAM); - exchange.getResponseHeaders().add("Content-Encoding", UTF_8); - exchange.getResponseHeaders().add("Cache-Control", "no-cache"); - exchange.getResponseHeaders().add("Connection", "keep-alive"); - exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); - exchange.sendResponseHeaders(200, 0); - - var writer = new PrintWriter(exchange.getResponseBody()); - - HttpServletStreamableMcpSessionTransport sessionTransport = new HttpServletStreamableMcpSessionTransport( - sessionId, exchange, writer); - - try { - session.responseStream(jsonrpcRequest, sessionTransport) - .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) - .block(); - } - catch (Exception e) { - logger.error("Failed to handle request stream: {}", e.getMessage()); - exchange.close(); - } - } - else { - this.sendError(exchange, 500, "Unknown message type"); - } - } - catch (IllegalArgumentException | IOException e) { - logger.error("Failed to deserialize message: {}", e.getMessage()); - this.sendError(exchange, 400, "Invalid message format: " + e.getMessage()); - } - catch (Exception e) { - logger.error("Error handling message: {}", e.getMessage()); - try { - this.sendError(exchange, 500, "Error processing message: " + e.getMessage()); - } - catch (IOException ex) { - logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); - sendError(exchange, 500, "Error processing message"); - } - } - } - - - public void doOther(HttpExchange exchange) - throws IOException { - sendError(exchange, 405, "Unsupported HTTP method: " + exchange.getRequestMethod()); - } - - protected void doDelete(HttpExchange exchange) - throws IOException { - - String requestURI = exchange.getRequestURI().toString(); - if (!requestURI.endsWith(mcpEndpoint)) { - sendError(exchange, 404, null); - return; - } - - if (this.isClosing) { - sendError(exchange, 503, "Server is shutting down"); - return; - } - - if (this.disallowDelete) { - sendError(exchange, 405, null); - return; - } - - McpTransportContext transportContext = this.contextExtractor.extract(exchange, new DefaultMcpTransportContext()); - - if (exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID) == null) { - sendError(exchange, 400, "Session ID required in mcp-session-id header"); - return; - } - - String sessionId = exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); - McpStreamableServerSession session = this.sessions.get(sessionId); - - if (session == null) { - sendError(exchange, 404, null); - return; - } - - try { - session.delete().contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)).block(); - this.sessions.remove(sessionId); - exchange.sendResponseHeaders(200, -1); - } - catch (Exception e) { - logger.error("Failed to delete session {}: {}", sessionId, e.getMessage()); - try { - sendError(exchange, 500, e.getMessage()); - } - catch (IOException ex) { - logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); - sendError(exchange, 500, "Error deleting session"); - } - } - } - - /** - * Sends an SSE event to a client with a specific ID. - * @param writer The writer to send the event through - * @param eventType The type of event (message or endpoint) - * @param data The event data - * @param id The event ID - * @throws IOException If an error occurs while writing the event - */ - private void sendEvent(PrintWriter writer, String eventType, String data, String id) throws IOException { - if (id != null) { - writer.write("id: " + id + "\n"); - } - writer.write("event: " + eventType + "\n"); - writer.write("data: " + data + "\n\n"); - writer.flush(); - - if (writer.checkError()) { - throw new IOException("Client disconnected"); - } - } - - /** - * Implementation of McpStreamableServerTransport for HttpServlet SSE sessions. This - * class handles the transport-level communication for a specific client session. - * - *

- * This class is thread-safe and uses a ReentrantLock to synchronize access to the - * underlying PrintWriter to prevent race conditions when multiple threads attempt to - * send messages concurrently. - */ - - private class HttpServletStreamableMcpSessionTransport implements McpStreamableServerTransport { - - private final String sessionId; - - private final HttpExchange exchange; - - private final PrintWriter writer; - - private volatile boolean closed = false; - - private final ReentrantLock lock = new ReentrantLock(); - - HttpServletStreamableMcpSessionTransport(String sessionId, HttpExchange exchange, PrintWriter writer) { - this.sessionId = sessionId; - this.exchange = exchange; - this.writer = writer; - logger.debug("Streamable session transport {} initialized with SSE writer", sessionId); - } - - /** - * Sends a JSON-RPC message to the client through the SSE connection. - * @param message The JSON-RPC message to send - * @return A Mono that completes when the message has been sent - */ - @Override - public Mono sendMessage(McpSchema.JSONRPCMessage message) { - return sendMessage(message, null); - } - - /** - * Sends a JSON-RPC message to the client through the SSE connection with a - * specific message ID. - * @param message The JSON-RPC message to send - * @param messageId The message ID for SSE event identification - * @return A Mono that completes when the message has been sent - */ - @Override - public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId) { - return Mono.fromRunnable(() -> { - if (this.closed) { - logger.debug("Attempted to send message to closed session: {}", this.sessionId); - return; - } - - lock.lock(); - try { - if (this.closed) { - logger.debug("Session {} was closed during message send attempt", this.sessionId); - return; - } - - String jsonText = objectMapper.writeValueAsString(message); - HttpStreamableServerTransportProvider.this.sendEvent(writer, MESSAGE_EVENT_TYPE, jsonText, - messageId != null ? messageId : this.sessionId); - logger.debug("Message sent to session {} with ID {}", this.sessionId, messageId); - } - catch (Exception e) { - logger.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage()); - HttpStreamableServerTransportProvider.this.sessions.remove(this.sessionId); - exchange.close(); - } - finally { - lock.unlock(); - } - }); - } - - /** - * Converts data from one type to another using the configured ObjectMapper. - * @param data The source data object to convert - * @param typeRef The target type reference - * @return The converted object of type T - * @param The target type - */ - @Override - public T unmarshalFrom(Object data, TypeReference typeRef) { - return objectMapper.convertValue(data, typeRef); - } - - /** - * Initiates a graceful shutdown of the transport. - * @return A Mono that completes when the shutdown is complete - */ - @Override - public Mono closeGracefully() { - return Mono.fromRunnable(() -> { - HttpServletStreamableMcpSessionTransport.this.close(); - }); - } - - /** - * Closes the transport immediately. - */ - @Override - public void close() { - lock.lock(); - try { - if (this.closed) { - logger.debug("Session transport {} already closed", this.sessionId); - return; - } - - this.closed = true; - - // HttpServletStreamableServerTransportProvider.this.sessions.remove(this.sessionId); - exchange.close(); - logger.debug("Successfully completed async context for session {}", sessionId); - } - catch (Exception e) { - logger.warn("Failed to complete async context for session {}: {}", sessionId, e.getMessage()); - } - finally { - lock.unlock(); - } - } - - } + private static final Logger logger = LoggerFactory.getLogger(HttpStreamableServerTransportProvider.class); + + /** + * Event type for JSON-RPC messages sent through the SSE connection. + */ + public static final String MESSAGE_EVENT_TYPE = "message"; + + /** + * Event type for sending the message endpoint URI to clients. + */ + public static final String ENDPOINT_EVENT_TYPE = "endpoint"; + + /** + * Header name for the response media types accepted by the requester. + */ + private static final String ACCEPT = "Accept"; + + public static final String UTF_8 = "UTF-8"; + + public static final String APPLICATION_JSON = "application/json"; + + public static final String TEXT_EVENT_STREAM = "text/event-stream"; + + public static final String FAILED_TO_SEND_ERROR_RESPONSE = "Failed to send error response: {}"; + + /** + * The endpoint URI where clients should send their JSON-RPC messages. Defaults to + * "/mcp". + */ + private final String mcpEndpoint; + + /** + * Flag indicating whether DELETE requests are disallowed on the endpoint. + */ + private final boolean disallowDelete; + + private final ObjectMapper objectMapper; + + private McpStreamableServerSession.Factory sessionFactory; + + /** + * Map of active client sessions, keyed by mcp-session-id. + */ + private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + + private final McpTransportContextExtractor contextExtractor; + + /** + * Flag indicating if the transport is shutting down. + */ + private volatile boolean isClosing = false; + + /** + * Keep-alive scheduler for managing session pings. Activated if keepAliveInterval is + * set. Disabled by default. + */ + private KeepAliveScheduler keepAliveScheduler; + + HttpStreamableServerTransportProvider( + ObjectMapper objectMapper, + String mcpEndpoint, + boolean disallowDelete, + McpTransportContextExtractor contextExtractor, + Duration keepAliveInterval) { + Assert.notNull(objectMapper, "ObjectMapper must not be null"); + Assert.notNull(mcpEndpoint, "MCP endpoint must not be null"); + Assert.notNull(contextExtractor, "Context extractor must not be null"); + + this.objectMapper = objectMapper; + this.mcpEndpoint = mcpEndpoint; + this.disallowDelete = disallowDelete; + this.contextExtractor = contextExtractor; + + if (keepAliveInterval != null) { + + this.keepAliveScheduler = KeepAliveScheduler.builder( + () -> (isClosing) ? Flux.empty() : Flux.fromIterable(sessions.values())) + .initialDelay(keepAliveInterval) + .interval(keepAliveInterval) + .build(); + + this.keepAliveScheduler.start(); + } + } + + @Override + public String protocolVersion() { + return "2025-03-26"; + } + + @Override + public void setSessionFactory(McpStreamableServerSession.Factory sessionFactory) { + this.sessionFactory = sessionFactory; + } + + @Override + public Mono notifyClients(String method, Object params) { + if (this.sessions.isEmpty()) { + logger.debug("No active sessions to broadcast message to"); + return Mono.empty(); + } + + logger.debug("Attempting to broadcast message to {} active sessions", this.sessions.size()); + + return Mono.fromRunnable(() -> { + this.sessions.values().parallelStream().forEach(session -> { + try { + session.sendNotification(method, params).block(); + } catch (Exception e) { + logger.error("Failed to send message to session {}: {}", session.getId(), e.getMessage()); + } + }); + }); + } + + /** + * Initiates a graceful shutdown of the transport. + * @return A Mono that completes when all cleanup operations are finished + */ + @Override + public Mono closeGracefully() { + return Mono.fromRunnable(() -> { + this.isClosing = true; + logger.debug("Initiating graceful shutdown with {} active sessions", this.sessions.size()); + + this.sessions.values().parallelStream().forEach(session -> { + try { + session.closeGracefully().block(); + } catch (Exception e) { + logger.error("Failed to close session {}: {}", session.getId(), e.getMessage()); + } + }); + + this.sessions.clear(); + logger.debug("Graceful shutdown completed"); + }) + .then() + .doOnSuccess(v -> { + sessions.clear(); + logger.debug("Graceful shutdown completed"); + if (this.keepAliveScheduler != null) { + this.keepAliveScheduler.shutdown(); + } + }); + } + + public void doGet(HttpExchange exchange) throws IOException { + + String requestURI = exchange.getRequestURI().toString(); + if (!requestURI.endsWith(mcpEndpoint)) { + sendError(exchange, 404, null); + return; + } + + if (this.isClosing) { + sendError(exchange, 503, "Server is shutting down"); + return; + } + + List badRequestErrors = new ArrayList<>(); + + String accept = exchange.getRequestHeaders().getFirst(ACCEPT); + if (accept == null || !accept.contains(TEXT_EVENT_STREAM)) { + badRequestErrors.add("text/event-stream required in Accept header"); + } + + String sessionId = exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); + + if (sessionId == null || sessionId.isBlank()) { + badRequestErrors.add("Session ID required in mcp-session-id header"); + } + + if (!badRequestErrors.isEmpty()) { + String combinedMessage = String.join("; ", badRequestErrors); + this.sendError(exchange, 400, combinedMessage); + return; + } + + McpStreamableServerSession session = this.sessions.get(sessionId); + + if (session == null) { + sendError(exchange, 404, null); + return; + } + + logger.debug("Handling GET request for session: {}", sessionId); + + McpTransportContext transportContext = + this.contextExtractor.extract(exchange, new DefaultMcpTransportContext()); + + try { + exchange.getResponseHeaders().add("Content-Type", TEXT_EVENT_STREAM); + exchange.getResponseHeaders().add("Content-Encoding", UTF_8); + exchange.getResponseHeaders().add("Cache-Control", "no-cache"); + exchange.getResponseHeaders().add("Connection", "keep-alive"); + exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); + exchange.sendResponseHeaders(200, 0); + + var writer = new PrintWriter(exchange.getResponseBody()); + HttpServletStreamableMcpSessionTransport sessionTransport = + new HttpServletStreamableMcpSessionTransport(sessionId, exchange, writer); + + // Check if this is a replay request + if (exchange.getRequestHeaders().getFirst(HttpHeaders.LAST_EVENT_ID) != null) { + String lastId = exchange.getRequestHeaders().getFirst(HttpHeaders.LAST_EVENT_ID); + + try { + session.replay(lastId) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) + .toIterable() + .forEach(message -> { + try { + sessionTransport + .sendMessage(message) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) + .block(); + } catch (Exception e) { + logger.error("Failed to replay message: {}", e.getMessage()); + exchange.close(); + } + }); + } catch (Exception e) { + logger.error("Failed to replay messages: {}", e.getMessage()); + exchange.close(); + } + } + } catch (Exception e) { + logger.error("Failed to handle GET request for session {}: {}", sessionId, e.getMessage()); + sendError(exchange, 500, null); + } + } + + public void sendError(HttpExchange exchange, int code, String message) throws IOException { + var b = message != null ? message.getBytes(StandardCharsets.UTF_8) : new byte[0]; + exchange.getResponseHeaders().add("Content-Encoding", UTF_8); + exchange.sendResponseHeaders(code, b.length != 0 ? b.length : -1); + try (OutputStream os = exchange.getResponseBody()) { + os.write(b); + } + + TrackEvent.error("MCP server error: " + message); + } + + public void doPost(HttpExchange exchange) throws IOException { + + String requestURI = exchange.getRequestURI().toString(); + if (!requestURI.endsWith(mcpEndpoint)) { + sendError(exchange, 404, null); + return; + } + + if (this.isClosing) { + sendError(exchange, 503, "Server is shutting down"); + return; + } + + List badRequestErrors = new ArrayList<>(); + + String accept = exchange.getRequestHeaders().getFirst(ACCEPT); + if (accept == null || !accept.contains(TEXT_EVENT_STREAM)) { + badRequestErrors.add("text/event-stream required in Accept header"); + } + if (accept == null || !accept.contains(APPLICATION_JSON)) { + badRequestErrors.add("application/json required in Accept header"); + } + + McpTransportContext transportContext = + this.contextExtractor.extract(exchange, new DefaultMcpTransportContext()); + + try { + var body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + + McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body); + + // Handle initialization request + if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest + && jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) { + if (!badRequestErrors.isEmpty()) { + String combinedMessage = String.join("; ", badRequestErrors); + this.sendError(exchange, 400, combinedMessage); + return; + } + + McpSchema.InitializeRequest initializeRequest = + objectMapper.convertValue(jsonrpcRequest.params(), new TypeReference<>() {}); + McpStreamableServerSession.McpStreamableServerSessionInit init = + this.sessionFactory.startSession(initializeRequest); + this.sessions.put(init.session().getId(), init.session()); + + try { + McpSchema.InitializeResult initResult = init.initResult().block(); + + String jsonResponse = objectMapper.writeValueAsString(new McpSchema.JSONRPCResponse( + McpSchema.JSONRPC_VERSION, jsonrpcRequest.id(), initResult, null)); + var jsonBytes = jsonResponse.getBytes(StandardCharsets.UTF_8); + + exchange.getResponseHeaders().add("Content-Type", APPLICATION_JSON); + exchange.getResponseHeaders().add("Content-Encoding", UTF_8); + exchange.getResponseHeaders() + .add(HttpHeaders.MCP_SESSION_ID, init.session().getId()); + exchange.sendResponseHeaders(200, jsonBytes.length); + exchange.getResponseBody().write(jsonBytes); + return; + } catch (Exception e) { + logger.error("Failed to initialize session: {}", e.getMessage()); + this.sendError(exchange, 500, "Failed to initialize session: " + e.getMessage()); + return; + } + } + + String sessionId = exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); + + if (sessionId == null || sessionId.isBlank()) { + badRequestErrors.add("Session ID required in mcp-session-id header"); + } + + if (!badRequestErrors.isEmpty()) { + String combinedMessage = String.join("; ", badRequestErrors); + this.sendError(exchange, 400, combinedMessage); + return; + } + + McpStreamableServerSession session = this.sessions.get(sessionId); + + if (session == null) { + this.sendError(exchange, 404, "Session not found: " + sessionId + ". Was the session not refreshed?"); + return; + } + + if (message instanceof McpSchema.JSONRPCResponse jsonrpcResponse) { + session.accept(jsonrpcResponse) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) + .block(); + exchange.sendResponseHeaders(200, -1); + } else if (message instanceof McpSchema.JSONRPCNotification jsonrpcNotification) { + session.accept(jsonrpcNotification) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) + .block(); + exchange.sendResponseHeaders(202, -1); + } else if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) { + // For streaming responses, we need to return SSE + exchange.getResponseHeaders().add("Content-Type", TEXT_EVENT_STREAM); + exchange.getResponseHeaders().add("Content-Encoding", UTF_8); + exchange.getResponseHeaders().add("Cache-Control", "no-cache"); + exchange.getResponseHeaders().add("Connection", "keep-alive"); + exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*"); + exchange.sendResponseHeaders(200, 0); + + var writer = new PrintWriter(exchange.getResponseBody()); + + HttpServletStreamableMcpSessionTransport sessionTransport = + new HttpServletStreamableMcpSessionTransport(sessionId, exchange, writer); + + try { + session.responseStream(jsonrpcRequest, sessionTransport) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) + .block(); + } catch (Exception e) { + logger.error("Failed to handle request stream: {}", e.getMessage()); + exchange.close(); + } + } else { + this.sendError(exchange, 500, "Unknown message type"); + } + } catch (IllegalArgumentException | IOException e) { + logger.error("Failed to deserialize message: {}", e.getMessage()); + this.sendError(exchange, 400, "Invalid message format: " + e.getMessage()); + } catch (Exception e) { + logger.error("Error handling message: {}", e.getMessage()); + try { + this.sendError(exchange, 500, "Error processing message: " + e.getMessage()); + } catch (IOException ex) { + logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); + sendError(exchange, 500, "Error processing message"); + } + } + } + + public void doOther(HttpExchange exchange) throws IOException { + sendError(exchange, 405, "Unsupported HTTP method: " + exchange.getRequestMethod()); + } + + protected void doDelete(HttpExchange exchange) throws IOException { + + String requestURI = exchange.getRequestURI().toString(); + if (!requestURI.endsWith(mcpEndpoint)) { + sendError(exchange, 404, null); + return; + } + + if (this.isClosing) { + sendError(exchange, 503, "Server is shutting down"); + return; + } + + if (this.disallowDelete) { + sendError(exchange, 405, null); + return; + } + + McpTransportContext transportContext = + this.contextExtractor.extract(exchange, new DefaultMcpTransportContext()); + + if (exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID) == null) { + sendError(exchange, 400, "Session ID required in mcp-session-id header"); + return; + } + + String sessionId = exchange.getRequestHeaders().getFirst(HttpHeaders.MCP_SESSION_ID); + McpStreamableServerSession session = this.sessions.get(sessionId); + + if (session == null) { + sendError(exchange, 404, null); + return; + } + + try { + session.delete() + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, transportContext)) + .block(); + this.sessions.remove(sessionId); + exchange.sendResponseHeaders(200, -1); + } catch (Exception e) { + logger.error("Failed to delete session {}: {}", sessionId, e.getMessage()); + try { + sendError(exchange, 500, e.getMessage()); + } catch (IOException ex) { + logger.error(FAILED_TO_SEND_ERROR_RESPONSE, ex.getMessage()); + sendError(exchange, 500, "Error deleting session"); + } + } + } + + /** + * Sends an SSE event to a client with a specific ID. + * @param writer The writer to send the event through + * @param eventType The type of event (message or endpoint) + * @param data The event data + * @param id The event ID + * @throws IOException If an error occurs while writing the event + */ + private void sendEvent(PrintWriter writer, String eventType, String data, String id) throws IOException { + if (id != null) { + writer.write("id: " + id + "\n"); + } + writer.write("event: " + eventType + "\n"); + writer.write("data: " + data + "\n\n"); + writer.flush(); + + if (writer.checkError()) { + throw new IOException("Client disconnected"); + } + } + + /** + * Implementation of McpStreamableServerTransport for HttpServlet SSE sessions. This + * class handles the transport-level communication for a specific client session. + * + *

+ * This class is thread-safe and uses a ReentrantLock to synchronize access to the + * underlying PrintWriter to prevent race conditions when multiple threads attempt to + * send messages concurrently. + */ + private class HttpServletStreamableMcpSessionTransport implements McpStreamableServerTransport { + + private final String sessionId; + + private final HttpExchange exchange; + + private final PrintWriter writer; + + private volatile boolean closed = false; + + private final ReentrantLock lock = new ReentrantLock(); + + HttpServletStreamableMcpSessionTransport(String sessionId, HttpExchange exchange, PrintWriter writer) { + this.sessionId = sessionId; + this.exchange = exchange; + this.writer = writer; + logger.debug("Streamable session transport {} initialized with SSE writer", sessionId); + } + + /** + * Sends a JSON-RPC message to the client through the SSE connection. + * @param message The JSON-RPC message to send + * @return A Mono that completes when the message has been sent + */ + @Override + public Mono sendMessage(McpSchema.JSONRPCMessage message) { + return sendMessage(message, null); + } + + /** + * Sends a JSON-RPC message to the client through the SSE connection with a + * specific message ID. + * @param message The JSON-RPC message to send + * @param messageId The message ID for SSE event identification + * @return A Mono that completes when the message has been sent + */ + @Override + public Mono sendMessage(McpSchema.JSONRPCMessage message, String messageId) { + return Mono.fromRunnable(() -> { + if (this.closed) { + logger.debug("Attempted to send message to closed session: {}", this.sessionId); + return; + } + + lock.lock(); + try { + if (this.closed) { + logger.debug("Session {} was closed during message send attempt", this.sessionId); + return; + } + + String jsonText = objectMapper.writeValueAsString(message); + HttpStreamableServerTransportProvider.this.sendEvent( + writer, MESSAGE_EVENT_TYPE, jsonText, messageId != null ? messageId : this.sessionId); + logger.debug("Message sent to session {} with ID {}", this.sessionId, messageId); + } catch (Exception e) { + logger.error("Failed to send message to session {}: {}", this.sessionId, e.getMessage()); + HttpStreamableServerTransportProvider.this.sessions.remove(this.sessionId); + exchange.close(); + } finally { + lock.unlock(); + } + }); + } + + /** + * Converts data from one type to another using the configured ObjectMapper. + * @param data The source data object to convert + * @param typeRef The target type reference + * @return The converted object of type T + * @param The target type + */ + @Override + public T unmarshalFrom(Object data, TypeReference typeRef) { + return objectMapper.convertValue(data, typeRef); + } + + /** + * Initiates a graceful shutdown of the transport. + * @return A Mono that completes when the shutdown is complete + */ + @Override + public Mono closeGracefully() { + return Mono.fromRunnable(() -> { + HttpServletStreamableMcpSessionTransport.this.close(); + }); + } + + /** + * Closes the transport immediately. + */ + @Override + public void close() { + lock.lock(); + try { + if (this.closed) { + logger.debug("Session transport {} already closed", this.sessionId); + return; + } + + this.closed = true; + + // HttpServletStreamableServerTransportProvider.this.sessions.remove(this.sessionId); + exchange.close(); + logger.debug("Successfully completed async context for session {}", sessionId); + } catch (Exception e) { + logger.warn("Failed to complete async context for session {}: {}", sessionId, e.getMessage()); + } finally { + lock.unlock(); + } + } + } } diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/McpResources.java b/app/src/main/java/io/xpipe/app/beacon/mcp/McpResources.java index 536686071..1dc9dc920 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/McpResources.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/McpResources.java @@ -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 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); diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/McpSchemaFiles.java b/app/src/main/java/io/xpipe/app/beacon/mcp/McpSchemaFiles.java index d4ece28c2..68d528dbb 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/McpSchemaFiles.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/McpSchemaFiles.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java b/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java index e094a631b..05a06027f 100644 --- a/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java +++ b/app/src/main/java/io/xpipe/app/beacon/mcp/McpToolHandler.java @@ -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{ +public interface McpToolHandler + extends BiFunction { 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 { - 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(); - 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(); + 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(); } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserAbstractSessionModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserAbstractSessionModel.java index 52de16916..2a6ea1e1c 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserAbstractSessionModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserAbstractSessionModel.java @@ -32,7 +32,8 @@ public class BrowserAbstractSessionModel { } 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) { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionModel.java index 74076668c..1c98f04f6 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionModel.java @@ -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 diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java index 9fa60da9e..6d396daf6 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java @@ -221,7 +221,8 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel progress = new SimpleObjectProperty<>(); - private final ObservableList progressesIntervalHistory = FXCollections.observableArrayList(); - private final LongProperty progressTransferSpeed = new SimpleLongProperty(); - private final Property progressRemaining = new SimpleObjectProperty<>(); + private final ObservableList progressesIntervalHistory = + FXCollections.observableArrayList(); + private final LongProperty progressTransferSpeed = new SimpleLongProperty(); + private final Property progressRemaining = new SimpleObjectProperty<>(); public BrowserFileSystemTabModel( BrowserAbstractSessionModel model, @@ -89,7 +90,9 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab= 1000) { progressesIntervalHistory.add(progress.getValue()); @@ -109,7 +112,8 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab 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") diff --git a/app/src/main/java/io/xpipe/app/browser/menu/MultiExecuteMenuProvider.java b/app/src/main/java/io/xpipe/app/browser/menu/MultiExecuteMenuProvider.java index dd548994e..3ea5c7c3c 100644 --- a/app/src/main/java/io/xpipe/app/browser/menu/MultiExecuteMenuProvider.java +++ b/app/src/main/java/io/xpipe/app/browser/menu/MultiExecuteMenuProvider.java @@ -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); } }); } diff --git a/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java b/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java index f5eddfaba..1f0047a59 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ButtonComp.java @@ -56,14 +56,16 @@ public class ButtonComp extends Comp> { 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())); } }); } diff --git a/app/src/main/java/io/xpipe/app/comp/base/ComboTextFieldComp.java b/app/src/main/java/io/xpipe/app/comp/base/ComboTextFieldComp.java index 65312598b..d62ab4080 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ComboTextFieldComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ComboTextFieldComp.java @@ -29,7 +29,9 @@ public class ComboTextFieldComp extends Comp>> { private ObservableValue prompt; public ComboTextFieldComp( - Property value, ObservableList predefinedValues, Supplier> customCellFactory) { + Property value, + ObservableList predefinedValues, + Supplier> customCellFactory) { this.value = value; this.predefinedValues = predefinedValues; this.customCellFactory = customCellFactory; diff --git a/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java index c190ea956..5f65917de 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ContextualFileReferenceChoiceComp.java @@ -208,9 +208,11 @@ public class ContextualFileReferenceChoiceComp extends Comp> } }; }); - 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); diff --git a/app/src/main/java/io/xpipe/app/comp/base/DropdownComp.java b/app/src/main/java/io/xpipe/app/comp/base/DropdownComp.java index f49febf50..39c97342b 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/DropdownComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/DropdownComp.java @@ -54,7 +54,8 @@ public class DropdownComp extends Comp> { 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); diff --git a/app/src/main/java/io/xpipe/app/comp/base/IconButtonComp.java b/app/src/main/java/io/xpipe/app/comp/base/IconButtonComp.java index 1e7848c9e..38bf38584 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/IconButtonComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/IconButtonComp.java @@ -57,13 +57,15 @@ public class IconButtonComp extends Comp> { 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) { diff --git a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java index 0b49eab1a..ed07327ad 100644 --- a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java +++ b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java @@ -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( diff --git a/app/src/main/java/io/xpipe/app/core/AppInstallation.java b/app/src/main/java/io/xpipe/app/core/AppInstallation.java index 86e532315..0cd589af4 100644 --- a/app/src/main/java/io/xpipe/app/core/AppInstallation.java +++ b/app/src/main/java/io/xpipe/app/core/AppInstallation.java @@ -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"); } } diff --git a/app/src/main/java/io/xpipe/app/core/AppLocations.java b/app/src/main/java/io/xpipe/app/core/AppLocations.java index 32f7a1c64..2871635df 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLocations.java +++ b/app/src/main/java/io/xpipe/app/core/AppLocations.java @@ -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 {} } diff --git a/app/src/main/java/io/xpipe/app/core/AppLogs.java b/app/src/main/java/io/xpipe/app/core/AppLogs.java index 2dc7dd15d..46fef6872 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLogs.java +++ b/app/src/main/java/io/xpipe/app/core/AppLogs.java @@ -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()); } }; diff --git a/app/src/main/java/io/xpipe/app/core/AppRestart.java b/app/src/main/java/io/xpipe/app/core/AppRestart.java index b63b11938..2667b9cf4 100644 --- a/app/src/main/java/io/xpipe/app/core/AppRestart.java +++ b/app/src/main/java/io/xpipe/app/core/AppRestart.java @@ -43,7 +43,8 @@ public class AppRestart { if (OsType.getLocal() == OsType.LINUX) { return "nohup \"" + loc.getDaemonExecutablePath() + "\"" + suffix + " /dev/null 2>&1 & disown"; } else if (OsType.getLocal() == OsType.MACOS) { - return "(sleep 1;open \"" + loc.getBaseInstallationPath() + "\" --args" + suffix + " /dev/null) & disown"; + return "(sleep 1;open \"" + loc.getBaseInstallationPath() + "\" --args" + suffix + + " /dev/null) & disown"; } else { var exe = loc.getDaemonExecutablePath(); if (ShellDialects.isPowershell(dialect)) { diff --git a/app/src/main/java/io/xpipe/app/core/AppSystemInfo.java b/app/src/main/java/io/xpipe/app/core/AppSystemInfo.java index acf0123ee..b4692faef 100644 --- a/app/src/main/java/io/xpipe/app/core/AppSystemInfo.java +++ b/app/src/main/java/io/xpipe/app/core/AppSystemInfo.java @@ -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 {} } diff --git a/app/src/main/java/io/xpipe/app/core/check/AppShellChecker.java b/app/src/main/java/io/xpipe/app/core/check/AppShellChecker.java index b85a3371c..a4f04634c 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppShellChecker.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppShellChecker.java @@ -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(); diff --git a/app/src/main/java/io/xpipe/app/core/check/AppSystemFontCheck.java b/app/src/main/java/io/xpipe/app/core/check/AppSystemFontCheck.java index cda317fbf..599d1e97a 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppSystemFontCheck.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppSystemFontCheck.java @@ -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"); } diff --git a/app/src/main/java/io/xpipe/app/core/check/AppTestCommandCheck.java b/app/src/main/java/io/xpipe/app/core/check/AppTestCommandCheck.java index 3a60685d0..f15d85ff0 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppTestCommandCheck.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppTestCommandCheck.java @@ -17,7 +17,8 @@ public class AppTestCommandCheck { sc.getShellDialect() .directoryExists( sc, - AppInstallation.ofCurrent().getBaseInstallationPath() + AppInstallation.ofCurrent() + .getBaseInstallationPath() .toString()) .execute(); } catch (ProcessOutputException ex) { diff --git a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java index b8606edff..442aa98d7 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java index 011b013fd..7f295f24e 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java b/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java index 739be047c..840d20598 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java @@ -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(); diff --git a/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java b/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java index 14e0fc355..9f40edcb0 100644 --- a/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/CountGroupStoreProvider.java @@ -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()); } - } diff --git a/app/src/main/java/io/xpipe/app/ext/HostAddress.java b/app/src/main/java/io/xpipe/app/ext/HostAddress.java index e440fb639..ca8c44073 100644 --- a/app/src/main/java/io/xpipe/app/ext/HostAddress.java +++ b/app/src/main/java/io/xpipe/app/ext/HostAddress.java @@ -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 available; - private HostAddress(String value, List available) {this.value = value; + private HostAddress(String value, List available) { + this.value = value; this.available = available; } @@ -57,5 +60,4 @@ public class HostAddress { public String get() { return value; } - } diff --git a/app/src/main/java/io/xpipe/app/ext/HostAddressChoice.java b/app/src/main/java/io/xpipe/app/ext/HostAddressChoice.java index e893e700d..2fa74e335 100644 --- a/app/src/main/java/io/xpipe/app/ext/HostAddressChoice.java +++ b/app/src/main/java/io/xpipe/app/ext/HostAddressChoice.java @@ -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; } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/xpipe/app/ext/HostAddressChoiceComp.java b/app/src/main/java/io/xpipe/app/ext/HostAddressChoiceComp.java index 440ccba41..0f2a2514f 100644 --- a/app/src/main/java/io/xpipe/app/ext/HostAddressChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/ext/HostAddressChoiceComp.java @@ -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> { private final ObservableList allAddresses; private final boolean mutable; - public HostAddressChoiceComp(ObjectProperty currentAddress, ObservableList allAddresses, boolean mutable) { + public HostAddressChoiceComp( + ObjectProperty currentAddress, ObservableList allAddresses, boolean mutable) { this.currentAddress = currentAddress; this.allAddresses = allAddresses; this.mutable = mutable; @@ -52,7 +55,7 @@ public class HostAddressChoiceComp extends Comp> { var nodes = new ArrayList>(); nodes.add(combo); - if (mutable) { + if (mutable) { nodes.add(addButton); } @@ -96,9 +99,11 @@ public class HostAddressChoiceComp extends Comp> { 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> { combo.grow(false, true); return combo; } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/xpipe/app/ext/ScanProvider.java b/app/src/main/java/io/xpipe/app/ext/ScanProvider.java index 50cd72751..b3673f71c 100644 --- a/app/src/main/java/io/xpipe/app/ext/ScanProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/ScanProvider.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/ext/SetupToolActionProvider.java b/app/src/main/java/io/xpipe/app/ext/SetupToolActionProvider.java index d6906a439..aaf731329 100644 --- a/app/src/main/java/io/xpipe/app/ext/SetupToolActionProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/SetupToolActionProvider.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/hub/action/impl/HostAddressSwitchBranchProvider.java b/app/src/main/java/io/xpipe/app/hub/action/impl/HostAddressSwitchBranchProvider.java index cab9d1211..a3b17d02e 100644 --- a/app/src/main/java/io/xpipe/app/hub/action/impl/HostAddressSwitchBranchProvider.java +++ b/app/src/main/java/io/xpipe/app/hub/action/impl/HostAddressSwitchBranchProvider.java @@ -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 ref) { @@ -51,9 +53,12 @@ public class HostAddressSwitchBranchProvider implements HubBranchProvider> getChildren(DataStoreEntryRef 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 diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java index 595fb6ade..aff88be86 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java @@ -31,35 +31,22 @@ public class StoreChoiceComp extends SimpleComp { private final Mode mode; private final Property> selected; - - private final StoreChoicePopover popover; - - - - public StoreChoiceComp( - - - Mode mode, DataStoreEntry self, Property> selected, Class storeClass, - - - Predicate> applicableCheck, StoreCategoryWrapper initialCategory - - - ) { - + Mode mode, + DataStoreEntry self, + Property> selected, + Class storeClass, + Predicate> 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 StoreChoiceComp other( diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreChoicePopover.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreChoicePopover.java index fa78e2821..267bdd1f0 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreChoicePopover.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreChoicePopover.java @@ -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 { Predicate 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 { 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(); diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationMenu.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationMenu.java index db9e1d146..0f5f103a8 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationMenu.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCreationMenu.java @@ -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(); }); diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java index a748b6680..331c52c25 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryWrapper.java @@ -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(); diff --git a/app/src/main/java/io/xpipe/app/icon/SystemIconManager.java b/app/src/main/java/io/xpipe/app/icon/SystemIconManager.java index ea30fdc12..441e8b790 100644 --- a/app/src/main/java/io/xpipe/app/icon/SystemIconManager.java +++ b/app/src/main/java/io/xpipe/app/icon/SystemIconManager.java @@ -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(); } diff --git a/app/src/main/java/io/xpipe/app/prefs/ApiCategory.java b/app/src/main/java/io/xpipe/app/prefs/ApiCategory.java index 3eb4f6b7a..238461f08 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ApiCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/ApiCategory.java @@ -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(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 424ebbce5..1442bfafd 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -362,6 +362,7 @@ public class AppPrefs { public ObservableBooleanValue pinLocalMachineOnStartup() { return pinLocalMachineOnStartup; } + @Getter private final List 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; diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java index b6f29b979..ded0e9e4d 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java b/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java index 442b33ff4..ebc36c59a 100644 --- a/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java @@ -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); diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index 9f2d6f476..7e51f126c 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -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 WINDOWS_EDITORS = List.of( VOID_WINDOWS, diff --git a/app/src/main/java/io/xpipe/app/prefs/FileBrowserCategory.java b/app/src/main/java/io/xpipe/app/prefs/FileBrowserCategory.java index ada3c7545..3d05247ad 100644 --- a/app/src/main/java/io/xpipe/app/prefs/FileBrowserCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/FileBrowserCategory.java @@ -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)) diff --git a/app/src/main/java/io/xpipe/app/prefs/IconsCategory.java b/app/src/main/java/io/xpipe/app/prefs/IconsCategory.java index 93cf5d669..6526d67a8 100644 --- a/app/src/main/java/io/xpipe/app/prefs/IconsCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/IconsCategory.java @@ -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()); diff --git a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java index 1a54f737d..450dc5630 100644 --- a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java @@ -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)); diff --git a/app/src/main/java/io/xpipe/app/prefs/RdpCategory.java b/app/src/main/java/io/xpipe/app/prefs/RdpCategory.java index 0c0077006..b5229ae42 100644 --- a/app/src/main/java/io/xpipe/app/prefs/RdpCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/RdpCategory.java @@ -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(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java index 822a0699a..f386a86bd 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -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); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java b/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java index adf2b62a6..638ce1c0c 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java @@ -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") diff --git a/app/src/main/java/io/xpipe/app/prefs/WorkspaceCreationDialog.java b/app/src/main/java/io/xpipe/app/prefs/WorkspaceCreationDialog.java index c348fb115..7c89d7349 100644 --- a/app/src/main/java/io/xpipe/app/prefs/WorkspaceCreationDialog.java +++ b/app/src/main/java/io/xpipe/app/prefs/WorkspaceCreationDialog.java @@ -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, diff --git a/app/src/main/java/io/xpipe/app/process/ProcessControl.java b/app/src/main/java/io/xpipe/app/process/ProcessControl.java index fd240201c..c1742f892 100644 --- a/app/src/main/java/io/xpipe/app/process/ProcessControl.java +++ b/app/src/main/java/io/xpipe/app/process/ProcessControl.java @@ -45,5 +45,4 @@ public interface ProcessControl extends AutoCloseable { InputStream getStderr(); Charset getCharset(); - } diff --git a/app/src/main/java/io/xpipe/app/process/ShellLaunchCommand.java b/app/src/main/java/io/xpipe/app/process/ShellLaunchCommand.java index 40cd4d947..7e499e709 100644 --- a/app/src/main/java/io/xpipe/app/process/ShellLaunchCommand.java +++ b/app/src/main/java/io/xpipe/app/process/ShellLaunchCommand.java @@ -15,5 +15,4 @@ public interface ShellLaunchCommand { } List loginCommand(OsType.Any os); - } diff --git a/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java index 871665755..df7b14d50 100644 --- a/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java index 9c26ee044..5f1a89b1b 100644 --- a/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java index e535cd4f4..f54b8af5d 100644 --- a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java index 3f4eae047..233dcee53 100644 --- a/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java index b8fac70d3..15e44563f 100644 --- a/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/ExternalTerminalType.java @@ -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; } diff --git a/app/src/main/java/io/xpipe/app/terminal/KittyTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/KittyTerminalType.java index d397ef507..246aa37e6 100644 --- a/app/src/main/java/io/xpipe/app/terminal/KittyTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/KittyTerminalType.java @@ -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"); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java b/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java index e1c5679e6..6c572cffe 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLaunch.java @@ -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 script) throws Exception { + public TerminalLaunchBuilder localScript(FailableFunction script) + throws Exception { var c = LocalShell.getShell().command(script.apply(LocalShell.getShell())); return command(c); } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalLaunchConfiguration.java b/app/src/main/java/io/xpipe/app/terminal/TerminalLaunchConfiguration.java index d40fe87cb..7ac96cbe5 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLaunchConfiguration.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLaunchConfiguration.java @@ -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); diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java index a0ba01816..5dff00402 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java @@ -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)); } } diff --git a/app/src/main/java/io/xpipe/app/terminal/TerminalView.java b/app/src/main/java/io/xpipe/app/terminal/TerminalView.java index e78eedc5e..55c50d28c 100644 --- a/app/src/main/java/io/xpipe/app/terminal/TerminalView.java +++ b/app/src/main/java/io/xpipe/app/terminal/TerminalView.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/terminal/WaveTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/WaveTerminalType.java index 45fba6353..64c81f807 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WaveTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/WaveTerminalType.java @@ -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)); } diff --git a/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalType.java index 9f3da9110..b70e14802 100644 --- a/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/WindowsTerminalType.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/update/AppDistributionType.java b/app/src/main/java/io/xpipe/app/update/AppDistributionType.java index fea904e33..7856a87b9 100644 --- a/app/src/main/java/io/xpipe/app/update/AppDistributionType.java +++ b/app/src/main/java/io/xpipe/app/update/AppDistributionType.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/update/AppInstaller.java b/app/src/main/java/io/xpipe/app/update/AppInstaller.java index a31c36eef..076e9e22f 100644 --- a/app/src/main/java/io/xpipe/app/update/AppInstaller.java +++ b/app/src/main/java/io/xpipe/app/update/AppInstaller.java @@ -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(); }); } diff --git a/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java b/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java index f1d27434f..93ce53bd3 100644 --- a/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java +++ b/app/src/main/java/io/xpipe/app/update/ChocoUpdater.java @@ -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"; diff --git a/app/src/main/java/io/xpipe/app/update/CommandUpdater.java b/app/src/main/java/io/xpipe/app/update/CommandUpdater.java index 969695d49..ef6df656e 100644 --- a/app/src/main/java/io/xpipe/app/update/CommandUpdater.java +++ b/app/src/main/java/io/xpipe/app/update/CommandUpdater.java @@ -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(); diff --git a/app/src/main/java/io/xpipe/app/update/WingetUpdater.java b/app/src/main/java/io/xpipe/app/update/WingetUpdater.java index c0b6418da..6471345d3 100644 --- a/app/src/main/java/io/xpipe/app/update/WingetUpdater.java +++ b/app/src/main/java/io/xpipe/app/update/WingetUpdater.java @@ -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( diff --git a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java index ca49f64b1..8e6a3fa2b 100644 --- a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java +++ b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java @@ -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 { - - - - @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 { - - - - @Override - - public HostAddress deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { var tree = (JsonNode) p.getCodec().readTree(p); if (tree.isTextual()) { diff --git a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java index 40254c9fa..f947e95b1 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/util/GlobalBooleanProperty.java b/app/src/main/java/io/xpipe/app/util/GlobalBooleanProperty.java index 30b854de6..f1c4700ef 100644 --- a/app/src/main/java/io/xpipe/app/util/GlobalBooleanProperty.java +++ b/app/src/main/java/io/xpipe/app/util/GlobalBooleanProperty.java @@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener; public class GlobalBooleanProperty extends SimpleBooleanProperty { - public GlobalBooleanProperty() { - } + public GlobalBooleanProperty() {} public GlobalBooleanProperty(boolean initialValue) { super(initialValue); diff --git a/app/src/main/java/io/xpipe/app/util/GlobalDoubleProperty.java b/app/src/main/java/io/xpipe/app/util/GlobalDoubleProperty.java index d6db068c1..817d30634 100644 --- a/app/src/main/java/io/xpipe/app/util/GlobalDoubleProperty.java +++ b/app/src/main/java/io/xpipe/app/util/GlobalDoubleProperty.java @@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener; public class GlobalDoubleProperty extends SimpleDoubleProperty { - public GlobalDoubleProperty() { - } + public GlobalDoubleProperty() {} public GlobalDoubleProperty(Double initialValue) { super(initialValue); diff --git a/app/src/main/java/io/xpipe/app/util/GlobalObjectProperty.java b/app/src/main/java/io/xpipe/app/util/GlobalObjectProperty.java index 273cc9b70..4dbb65e4f 100644 --- a/app/src/main/java/io/xpipe/app/util/GlobalObjectProperty.java +++ b/app/src/main/java/io/xpipe/app/util/GlobalObjectProperty.java @@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener; public class GlobalObjectProperty extends SimpleObjectProperty { - public GlobalObjectProperty() { - } + public GlobalObjectProperty() {} public GlobalObjectProperty(T initialValue) { super(initialValue); diff --git a/app/src/main/java/io/xpipe/app/util/GlobalStringProperty.java b/app/src/main/java/io/xpipe/app/util/GlobalStringProperty.java index faa1e2b27..f36beca66 100644 --- a/app/src/main/java/io/xpipe/app/util/GlobalStringProperty.java +++ b/app/src/main/java/io/xpipe/app/util/GlobalStringProperty.java @@ -6,8 +6,7 @@ import javafx.beans.value.ChangeListener; public class GlobalStringProperty extends SimpleStringProperty { - public GlobalStringProperty() { - } + public GlobalStringProperty() {} public GlobalStringProperty(String initialValue) { super(initialValue); diff --git a/app/src/main/java/io/xpipe/app/util/NativeBridge.java b/app/src/main/java/io/xpipe/app/util/NativeBridge.java index 80a73b571..48a22cff8 100644 --- a/app/src/main/java/io/xpipe/app/util/NativeBridge.java +++ b/app/src/main/java/io/xpipe/app/util/NativeBridge.java @@ -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") diff --git a/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java b/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java index c6855af07..06759382b 100644 --- a/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java +++ b/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java @@ -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) { diff --git a/app/src/main/java/io/xpipe/app/util/RemminaHelper.java b/app/src/main/java/io/xpipe/app/util/RemminaHelper.java index 9573f7c12..52bf95373 100644 --- a/app/src/main/java/io/xpipe/app/util/RemminaHelper.java +++ b/app/src/main/java/io/xpipe/app/util/RemminaHelper.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/util/ScanDialogBase.java b/app/src/main/java/io/xpipe/app/util/ScanDialogBase.java index b2d2b3eb9..cc32e7ec5 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanDialogBase.java +++ b/app/src/main/java/io/xpipe/app/util/ScanDialogBase.java @@ -47,8 +47,7 @@ public class ScanDialogBase { Runnable closeAction, ScanDialogAction action, ObservableList> entries, - boolean showButton - ) { + boolean showButton) { this.expand = expand; this.closeAction = closeAction; this.action = action; diff --git a/app/src/main/java/io/xpipe/app/util/ScanMultiDialogComp.java b/app/src/main/java/io/xpipe/app/util/ScanMultiDialogComp.java index af71982ee..1aae67065 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanMultiDialogComp.java +++ b/app/src/main/java/io/xpipe/app/util/ScanMultiDialogComp.java @@ -31,7 +31,7 @@ class ScanMultiDialogComp extends ModalOverlayContentComp { }, action, list, - false); + false); } void finish() { diff --git a/app/src/main/java/io/xpipe/app/util/SshLocalBridge.java b/app/src/main/java/io/xpipe/app/util/SshLocalBridge.java index d84e51ff7..194a6a0bc 100644 --- a/app/src/main/java/io/xpipe/app/util/SshLocalBridge.java +++ b/app/src/main/java/io/xpipe/app/util/SshLocalBridge.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/vnc/InternalVncClient.java b/app/src/main/java/io/xpipe/app/vnc/InternalVncClient.java index 591ccf983..26ec1123c 100644 --- a/app/src/main/java/io/xpipe/app/vnc/InternalVncClient.java +++ b/app/src/main/java/io/xpipe/app/vnc/InternalVncClient.java @@ -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; diff --git a/app/src/main/java/io/xpipe/app/vnc/VncLaunchConfig.java b/app/src/main/java/io/xpipe/app/vnc/VncLaunchConfig.java index f7db44c2e..846cc54a2 100644 --- a/app/src/main/java/io/xpipe/app/vnc/VncLaunchConfig.java +++ b/app/src/main/java/io/xpipe/app/vnc/VncLaunchConfig.java @@ -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); } } diff --git a/dist/licenses/commons-lang.properties b/dist/licenses/commons-lang.properties index 9929c584d..91c740ca1 100644 --- a/dist/licenses/commons-lang.properties +++ b/dist/licenses/commons-lang.properties @@ -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/ \ No newline at end of file diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java index b05344ab2..7f17e9f55 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java @@ -190,31 +190,32 @@ public class IdentitySelectComp extends Comp> { } }); - 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> { }); 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 diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java index 840da1a97..c8ae8fe35 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java @@ -23,14 +23,18 @@ public class SshIdentityStrategyHelper { private static OptionsBuilder agent(Property 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 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 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 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 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()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java index 7414874b6..206ab8044 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/RunTerminalScriptActionProvider.java @@ -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(); } } } diff --git a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerConsoleActionProvider.java b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerConsoleActionProvider.java index 2d958066e..4e9fe6f67 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerConsoleActionProvider.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerConsoleActionProvider.java @@ -54,7 +54,11 @@ public class IncusContainerConsoleActionProvider implements HubLeafProvider