feat(macOS): Separate multi-window and instance#637
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds support for creating multiple instances of the application on macOS, alongside the existing multi-window functionality. The implementation distinguishes between opening a new window within the current instance (openNewWindow) and launching an entirely new application instance (newInstance).
- Adds a new
newInstance()method to the ApplicationMenu Pigeon API that spawns a new application process - Implements platform-specific logic for macOS, Windows, and Linux to launch new instances
- Adds a "New Instance" menu item to the macOS dock menu
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pigeons/application_menu.dart | Adds new newInstance() method to the ApplicationMenu API definition |
| macos/Runner/ApplicationMenu.g.swift | Auto-generated Pigeon code for the new Swift-side API (v26.1.0) |
| macos/Runner/DirectoryBookmark.g.swift | Updated to Pigeon v26.1.0 (version bump only) |
| macos/Runner/AppDelegate.swift | Adds "New Instance" dock menu item and handler that calls the new API |
| lib/logic/application_menu.dart | Implements newInstance() with platform-specific process spawning logic |
| lib/src/application_menu.g.dart | Auto-generated Pigeon code for the Dart-side API (v26.1.0) |
| lib/src/*.g.dart | Auto-generated files updated to Pigeon v26.1.0 |
| android/app/src/main/kotlin/**/*.g.kt | Auto-generated Kotlin files updated to Pigeon v26.1.0 with API improvements |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -14,4 +14,5 @@ import 'package:pigeon/pigeon.dart'; | |||
| @FlutterApi() | |||
| abstract class ApplicationMenu { | |||
| void openNewWindow(); | |||
There was a problem hiding this comment.
The new newInstance() method lacks documentation. Consider adding a doc comment to explain what it does and how it differs from openNewWindow(). This will help API consumers understand when to use each method.
| void openNewWindow(); | |
| void openNewWindow(); | |
| /// Creates a new instance of the application. | |
| /// | |
| /// Unlike [openNewWindow], which opens a new window in the current application | |
| /// process, [newInstance] launches a completely new application process. | |
| /// Use this when a full, separate instance is required rather than just a new window. |
| if (Platform.isMacOS) { | ||
| await Process.start('open', ['-n', '-a', Platform.resolvedExecutable]); | ||
| } else if (Platform.isWindows) { | ||
| await Process.start(Platform.resolvedExecutable, []); |
There was a problem hiding this comment.
The Windows implementation doesn't use ProcessStartMode.detached unlike Linux. This may cause the spawned process to be tied to the parent process lifecycle. Consider using mode: ProcessStartMode.detached for consistency with the Linux implementation and to ensure the new instance runs independently.
| await Process.start(Platform.resolvedExecutable, []); | |
| await Process.start(Platform.resolvedExecutable, [], mode: ProcessStartMode.detached); |
| let newInstanceItem = NSMenuItem( | ||
| title: "New Instance", | ||
| action: #selector(handleNewInstanceAction(_:)), | ||
| keyEquivalent: "n" |
There was a problem hiding this comment.
Both dock menu items have the same keyboard shortcut 'n'. This will cause a conflict where only one of the menu items can be triggered with the shortcut. Consider using a different shortcut for the "New Instance" action, such as 'N' (uppercase) or another key combination.
| keyEquivalent: "n" | |
| keyEquivalent: "N" |
| if (Platform.isMacOS) { | ||
| await Process.start('open', ['-n', '-a', Platform.resolvedExecutable]); | ||
| } else if (Platform.isWindows) { | ||
| await Process.start(Platform.resolvedExecutable, []); | ||
| } else if (Platform.isLinux) { | ||
| await Process.start(Platform.resolvedExecutable, [], mode: ProcessStartMode.detached); |
There was a problem hiding this comment.
The spawned process is not being awaited or its result handled. If Process.start fails (e.g., due to permissions or invalid path), the error will be silently ignored. Consider handling potential errors or at least logging failures. Additionally, the process handle is not stored, which may lead to resource leaks on some platforms.
| if (Platform.isMacOS) { | |
| await Process.start('open', ['-n', '-a', Platform.resolvedExecutable]); | |
| } else if (Platform.isWindows) { | |
| await Process.start(Platform.resolvedExecutable, []); | |
| } else if (Platform.isLinux) { | |
| await Process.start(Platform.resolvedExecutable, [], mode: ProcessStartMode.detached); | |
| try { | |
| if (Platform.isMacOS) { | |
| final process = await Process.start('open', ['-n', '-a', Platform.resolvedExecutable]); | |
| // Close the process handle to avoid resource leaks | |
| process.stdin.close(); | |
| process.stdout.drain(); | |
| process.stderr.drain(); | |
| process.kill(); | |
| } else if (Platform.isWindows) { | |
| final process = await Process.start(Platform.resolvedExecutable, []); | |
| // Close the process handle to avoid resource leaks | |
| process.stdin.close(); | |
| process.stdout.drain(); | |
| process.stderr.drain(); | |
| process.kill(); | |
| } else if (Platform.isLinux) { | |
| await Process.start(Platform.resolvedExecutable, [], mode: ProcessStartMode.detached); | |
| } | |
| } catch (e) { | |
| print('Failed to start new instance: $e'); |
| self.appMenuApi?.openNewWindow(completion: {_ in print("New window opened")}) | ||
| } | ||
|
|
||
| @objc func handleNewInstanceAction(_ sender: NSMenuItem) { | ||
| self.appMenuApi?.newInstance(completion: {_ in print("New instance opened")}) |
There was a problem hiding this comment.
Consider adding error handling for the completion callback. Currently, all results (success or failure) are treated the same way with a generic print statement. It would be more robust to handle potential errors from the Pigeon API call.
| self.appMenuApi?.openNewWindow(completion: {_ in print("New window opened")}) | |
| } | |
| @objc func handleNewInstanceAction(_ sender: NSMenuItem) { | |
| self.appMenuApi?.newInstance(completion: {_ in print("New instance opened")}) | |
| self.appMenuApi?.openNewWindow(completion: { error in | |
| if let error = error { | |
| print("Failed to open new window: \(error)") | |
| } else { | |
| print("New window opened") | |
| } | |
| }) | |
| } | |
| @objc func handleNewInstanceAction(_ sender: NSMenuItem) { | |
| self.appMenuApi?.newInstance(completion: { error in | |
| if let error = error { | |
| print("Failed to open new instance: \(error)") | |
| } else { | |
| print("New instance opened") | |
| } | |
| }) |
Pull Request Description
Add support for multiple window/instances on macOS