diff --git a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java b/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java index 5050d150..0dbd4d0f 100644 --- a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java +++ b/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java @@ -22,6 +22,7 @@ import lombok.SneakyThrows; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; public class AppLayoutComp extends Comp> { @@ -71,26 +72,36 @@ public class AppLayoutComp extends Comp> { @Override public CompStructure createBase() { var map = new HashMap(); - entries.forEach(entry -> map.put(entry, entry.comp().createRegion())); + getRegion(entries.get(0), map); + getRegion(entries.get(1), map); var pane = new BorderPane(); var sidebar = new SideMenuBarComp(selected, entries); - pane.setCenter(map.get(selected.getValue())); + pane.setCenter(getRegion(selected.getValue(), map)); pane.setRight(sidebar.createRegion()); selected.addListener((c, o, n) -> { if (o != null && o.equals(entries.get(2))) { AppPrefs.get().save(); } - pane.setCenter(map.get(n)); + pane.setCenter(getRegion(n, map)); }); - pane.setCenter(map.get(selected.getValue())); pane.setPrefWidth(1280); pane.setPrefHeight(720); AppFont.normal(pane); return new SimpleCompStructure<>(pane); } + private Region getRegion(SideMenuBarComp.Entry entry, Map map) { + if (map.containsKey(entry)) { + return map.get(entry); + } + + Region r = entry.comp().createRegion(); + map.put(entry, r); + return r; + } + public List getEntries() { return entries; } diff --git a/app/src/main/java/io/xpipe/app/core/App.java b/app/src/main/java/io/xpipe/app/core/App.java index 94136f7d..041bbfec 100644 --- a/app/src/main/java/io/xpipe/app/core/App.java +++ b/app/src/main/java/io/xpipe/app/core/App.java @@ -80,15 +80,15 @@ public class App extends Application { }, XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate()); - var appWindow = new AppMainWindow(stage); + var appWindow = AppMainWindow.init(stage); appWindow.getStage().titleProperty().bind(PlatformThread.sync(titleBinding)); appWindow.initialize(); + appWindow.show(); appWindow.setContent(content); TrackEvent.info("Application window initialized"); stage.setOnShown(event -> { focus(); }); - appWindow.show(); // For demo purposes // if (true) { 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 3857dc27..93e777d7 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLogs.java +++ b/app/src/main/java/io/xpipe/app/core/AppLogs.java @@ -26,6 +26,7 @@ import java.time.temporal.ChronoField; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; public class AppLogs { @@ -362,10 +363,16 @@ public class AppLogs { @Override protected void handleNormalizedLoggingCall( Level level, Marker marker, String msg, Object[] arguments, Throwable throwable) { + var formatted = msg; + if (arguments != null) { + for (var arg : arguments) { + msg = msg.replaceFirst("\\{}", Objects.toString(arg)); + } + } TrackEvent.builder() .category(name) .type(level.toString().toLowerCase()) - .message(msg) + .message(formatted) .build() .handle(); } diff --git a/app/src/main/java/io/xpipe/app/core/AppMainWindow.java b/app/src/main/java/io/xpipe/app/core/AppMainWindow.java index 1158fb97..b8cd9bc0 100644 --- a/app/src/main/java/io/xpipe/app/core/AppMainWindow.java +++ b/app/src/main/java/io/xpipe/app/core/AppMainWindow.java @@ -11,9 +11,13 @@ import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; import javafx.stage.Screen; import javafx.stage.Stage; +import lombok.Builder; import lombok.Getter; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; import java.time.Duration; import java.time.Instant; @@ -25,6 +29,7 @@ public class AppMainWindow { @Getter private final Stage stage; + private final BooleanProperty windowActive = new SimpleBooleanProperty(false); private Thread thread; private volatile Instant lastUpdate; @@ -33,8 +38,12 @@ public class AppMainWindow { this.stage = stage; } - public static void init(Stage stage) { + public static AppMainWindow init(Stage stage) { INSTANCE = new AppMainWindow(stage); + var scene = new Scene(new Region(), -1, -1, false); + stage.setScene(scene); + AppWindowHelper.setupStylesheets(stage.getScene()); + return INSTANCE; } private synchronized void onChange() { @@ -117,11 +126,6 @@ public class AppMainWindow { } }); - stage.setOnCloseRequest(event -> { - // Close other windows - Stage.getWindows().stream().filter(w -> !w.equals(stage)).toList().forEach(w -> w.fireEvent(event)); - }); - stage.setOnHiding(e -> { saveState(); }); @@ -136,7 +140,12 @@ public class AppMainWindow { return; } + // Close other windows + Stage.getWindows().stream().filter(w -> !w.equals(stage)).toList().forEach(w -> w.fireEvent(e)); + stage.close(); + AppPrefs.get().closeBehaviour().getValue().getExit().run(); + e.consume(); }); TrackEvent.debug("Window listeners added"); @@ -151,6 +160,9 @@ public class AppMainWindow { // stage.setMaximized(state.maximized); TrackEvent.debug("Window loaded saved bounds"); + } else { + stage.setWidth(1280); + stage.setHeight(720); } } @@ -159,9 +171,13 @@ public class AppMainWindow { return; } - var newState = new WindowState( - stage.isMaximized(), (int) stage.getX(), (int) stage.getY(), (int) stage.getWidth(), (int) - stage.getHeight()); + var newState = WindowState.builder() + .maximized(stage.isMaximized()) + .windowX((int) stage.getX()) + .windowY((int) stage.getY()) + .windowWidth((int) stage.getWidth()) + .windowHeight((int) stage.getHeight()) + .build(); AppCache.update("windowState", newState); } @@ -190,20 +206,6 @@ public class AppMainWindow { return inBounds ? state : null; } - private void setupUndoRedo(Scene scene) { - scene.addEventHandler(KeyEvent.KEY_PRESSED, event -> { - if ((event.isControlDown() && event.getCode().equals(KeyCode.Y)) - || (event.isControlDown() - && event.isShiftDown() - && event.getCode().equals(KeyCode.Z))) { - event.consume(); - } else if (event.isControlDown() && event.getCode().equals(KeyCode.Z)) { - event.consume(); - } - }); - TrackEvent.debug("Set undo/redo handler"); - } - public void initialize() { initializeWindow(); setupListeners(); @@ -217,18 +219,14 @@ public class AppMainWindow { private void setupContent(Comp content) { var contentR = content.createRegion(); - var scene = new Scene(contentR, -1, -1, false); - TrackEvent.debug("Created initial scene"); - stage.setScene(scene); contentR.requestFocus(); + stage.getScene().setRoot(contentR); TrackEvent.debug("Set content scene"); - setupUndoRedo(scene); - - scene.addEventHandler(KeyEvent.KEY_PRESSED, event -> { + stage.getScene().addEventHandler(KeyEvent.KEY_PRESSED, event -> { if (AppProperties.get().isDeveloperMode() && event.getCode().equals(KeyCode.F6)) { var newR = content.createRegion(); - scene.setRoot(newR); + stage.getScene().setRoot(newR); newR.requestFocus(); TrackEvent.debug("Rebuilt content"); @@ -240,8 +238,16 @@ public class AppMainWindow { public void setContent(Comp content) { setupContent(content); - AppWindowHelper.setupStylesheets(stage.getScene()); } - private static record WindowState(boolean maximized, int windowX, int windowY, int windowWidth, int windowHeight) {} + @Builder + @Jacksonized + @Value + private static class WindowState { + boolean maximized; + int windowX; + int windowY; + int windowWidth; + int windowHeight; + } } diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java index 6df9696f..af4e52fe 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java @@ -20,6 +20,10 @@ import java.util.List; public abstract class LauncherInput { public static void handle(List arguments) { + if (arguments.size() == 0) { + return; + } + TrackEvent.withDebug("launcher", "Handling arguments") .elements(arguments) .handle(); diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java index d4960dbe..2ad2311b 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java @@ -5,6 +5,7 @@ import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.ApplicationHelper; import io.xpipe.app.util.MacOsPermissions; import io.xpipe.app.util.ScriptHelper; +import io.xpipe.app.util.WindowsRegistry; import io.xpipe.core.impl.FileNames; import io.xpipe.core.impl.LocalStore; import io.xpipe.core.process.OsType; @@ -12,7 +13,10 @@ import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellDialects; import lombok.Getter; +import java.io.IOException; +import java.nio.file.Path; import java.util.List; +import java.util.Optional; import java.util.stream.Stream; public interface ExternalTerminalType extends PrefsChoiceValue { @@ -77,6 +81,44 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } }; + + public abstract static class WindowsFullPathType extends ExternalApplicationType.WindowsFullPathType + implements ExternalTerminalType { + + public WindowsFullPathType(String id) { + super(id); + } + + @Override + public void launch(String name, String file, boolean elevated) throws Exception { + var path = determinePath(); + if (path.isEmpty()) { + throw new IOException("Unable to find installation of " + getId()); + } + + ApplicationHelper.executeLocalApplication( + sc -> createCommand(sc, name, path.get().toString(), file), false); + } + + protected abstract String createCommand(ShellControl shellControl, String name, String path, String file); + } + + ExternalTerminalType TABBY_WINDOWS = new WindowsFullPathType("app.tabbyWindows") { + + @Override + protected String createCommand(ShellControl shellControl, String name, String path, String file) { + return shellControl.getShellDialect().fileArgument(path) + " run " + shellControl.getShellDialect().fileArgument(file); + } + + @Override + protected Optional determinePath() { + Optional launcherDir; + launcherDir = WindowsRegistry.readString(WindowsRegistry.HKEY_CURRENT_USER, "SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5", "InstallLocation") + .map(p -> p + "\\Tabby.exe"); + return launcherDir.map(Path::of); + } + }; + ExternalTerminalType GNOME_TERMINAL = new SimpleType("app.gnomeTerminal", "gnome-terminal", "Gnome Terminal") { @@ -143,6 +185,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { ExternalTerminalType CUSTOM = new CustomType(); List ALL = Stream.of( + TABBY_WINDOWS, WINDOWS_TERMINAL, PWSH_WINDOWS, POWERSHELL_WINDOWS, diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties index e8631910..d3df9a2b 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties @@ -28,7 +28,7 @@ deleteAlertTitle=Confirm deletion deleteAlertHeader=Do you want to delete the ($COUNT$) selected elements? selectedElements=Selected elements: mustNotBeEmpty=$NAME$ must not be empty -download=Drop to transfer +download=Drop to download dragFiles=Drag files from here null=$VALUE$ must be not null roots=Roots