Skip to content

feat(macOS): Separate multi-window and instance#637

Merged
PartyDonut merged 1 commit intodevelopfrom
feat/Add-multi-window-support-to-macOS
Nov 30, 2025
Merged

feat(macOS): Separate multi-window and instance#637
PartyDonut merged 1 commit intodevelopfrom
feat/Add-multi-window-support-to-macOS

Conversation

@PartyDonut
Copy link
Copy Markdown
Collaborator

Pull Request Description

Add support for multiple window/instances on macOS

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.
if (Platform.isMacOS) {
await Process.start('open', ['-n', '-a', Platform.resolvedExecutable]);
} else if (Platform.isWindows) {
await Process.start(Platform.resolvedExecutable, []);
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
await Process.start(Platform.resolvedExecutable, []);
await Process.start(Platform.resolvedExecutable, [], mode: ProcessStartMode.detached);

Copilot uses AI. Check for mistakes.
let newInstanceItem = NSMenuItem(
title: "New Instance",
action: #selector(handleNewInstanceAction(_:)),
keyEquivalent: "n"
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
keyEquivalent: "n"
keyEquivalent: "N"

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +30
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);
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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');

Copilot uses AI. Check for mistakes.
Comment on lines 43 to +47
self.appMenuApi?.openNewWindow(completion: {_ in print("New window opened")})
}

@objc func handleNewInstanceAction(_ sender: NSMenuItem) {
self.appMenuApi?.newInstance(completion: {_ in print("New instance opened")})
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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")
}
})

Copilot uses AI. Check for mistakes.
@PartyDonut PartyDonut merged commit ab3830b into develop Nov 30, 2025
7 checks passed
@PartyDonut PartyDonut deleted the feat/Add-multi-window-support-to-macOS branch November 30, 2025 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants