88
99@interface RVLinkURLHandler : NSObject {
1010 BOOL urlProcessed;
11+ NSAlert *currentAlert;
1112}
1213- (void )handleGetURLEvent : (NSAppleEventDescriptor *)event withReplyEvent : (NSAppleEventDescriptor *)replyEvent ;
1314- (void )processRVLinkURL : (NSString *)rvlinkURL ;
1415- (BOOL )hasProcessedURL ;
1516- (NSMutableArray <NSURL *> *)findRVAppsUsingMDFind ;
1617- (NSMutableArray <NSURL *> *)findRVAppsUsingWorkspace : (NSString *)rvlinkURL ;
18+ + (BOOL )terminateOtherInstancesIfNeeded ;
1719@end
1820
1921@implementation RVLinkURLHandler
2022- (instancetype )init {
2123 self = [super init ];
2224 if (self) {
2325 urlProcessed = NO ;
26+ currentAlert = nil ;
2427 }
2528 return self;
2629}
@@ -29,9 +32,39 @@ - (BOOL)hasProcessedURL {
2932 return urlProcessed;
3033}
3134
35+ + (BOOL )terminateOtherInstancesIfNeeded {
36+ NSString *bundleID = [[NSBundle mainBundle ] bundleIdentifier ];
37+ NSArray <NSRunningApplication *> *runningInstances =
38+ [NSRunningApplication runningApplicationsWithBundleIdentifier: bundleID];
39+
40+ int currentPID = [[NSProcessInfo processInfo ] processIdentifier ];
41+
42+ // Terminate any other running instances to prevent multiple dialogs
43+ for (NSRunningApplication *app in runningInstances) {
44+ int instancePID = [app processIdentifier ];
45+
46+ if (instancePID != currentPID) {
47+ // Use forceTerminate because the other instance may be blocked in a modal dialog
48+ [app forceTerminate ];
49+ [NSThread sleepForTimeInterval: 0.3 ];
50+ }
51+ }
52+
53+ return NO ;
54+ }
55+
3256- (void )handleGetURLEvent : (NSAppleEventDescriptor *)event withReplyEvent : (NSAppleEventDescriptor *)replyEvent
3357{
3458 NSLog (@" *** RVLinkLauncher Apple Event handler called! ***" );
59+
60+ // If we're showing a dialog, close it so we can show a new one with the new URL
61+ if (currentAlert != nil ) {
62+ [[NSApplication sharedApplication ] stopModal ];
63+ [[currentAlert window ] close ];
64+ urlProcessed = NO ;
65+ currentAlert = nil ;
66+ }
67+
3568 @try {
3669 NSAppleEventDescriptor *directObjectDescriptor = [event paramDescriptorForKeyword: keyDirectObject];
3770 if (directObjectDescriptor != nil ) {
@@ -75,7 +108,6 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppl
75108 if ([line length ] > 0 ) {
76109 NSURL *url = [NSURL fileURLWithPath: line];
77110 if (url != nil ) {
78- // Get app details for logging
79111 NSString *displayName = [[NSFileManager defaultManager ] displayNameAtPath: line];
80112 NSBundle *bundle = [NSBundle bundleWithURL: url];
81113 NSString *bundleID = [bundle bundleIdentifier ];
@@ -143,7 +175,7 @@ - (void)processRVLinkURL:(NSString *)rvlinkURL
143175 }
144176 }
145177
146- // Add workspace results (skip duplicates)
178+ // Add workspace results (skip duplicates)
147179 for (NSURL *appURL in registeredApps) {
148180 NSString *path = [appURL path ];
149181 if (![seenPaths containsObject: path]) {
@@ -156,12 +188,13 @@ - (void)processRVLinkURL:(NSString *)rvlinkURL
156188
157189 // Check if we found any RV apps
158190 if ([appURLs count ] == 0 ) {
159- NSAlert *noAppsAlert = [[NSAlert alloc ] init ];
160- [noAppsAlert setMessageText: @" No RV Applications Found" ];
161- [noAppsAlert setInformativeText: @" Could not find any installed RV applications to open the rvlink URL." ];
162- [noAppsAlert addButtonWithTitle: @" OK" ];
163- [noAppsAlert runModal ];
164- [noAppsAlert release ];
191+ currentAlert = [[NSAlert alloc ] init ];
192+ [currentAlert setMessageText: @" No RV Applications Found" ];
193+ [currentAlert setInformativeText: @" Could not find any installed RV applications to open the rvlink URL." ];
194+ [currentAlert addButtonWithTitle: @" OK" ];
195+ [currentAlert runModal ];
196+ [currentAlert release ];
197+ currentAlert = nil ;
165198 return ;
166199 }
167200
@@ -192,11 +225,11 @@ - (void)processRVLinkURL:(NSString *)rvlinkURL
192225 }
193226
194227 // Present chooser UI for multiple apps
195- NSAlert *alert = [[NSAlert alloc ] init ];
196- [alert setMessageText: @" Choose RV Application" ];
197- [alert setInformativeText: [NSString stringWithFormat: @" Opening: %@ " , rvlinkURL]];
198- [alert addButtonWithTitle: @" Open" ];
199- [alert addButtonWithTitle: @" Cancel" ];
228+ currentAlert = [[NSAlert alloc ] init ];
229+ [currentAlert setMessageText: @" Choose RV Application" ];
230+ [currentAlert setInformativeText: [NSString stringWithFormat: @" Opening: %@ " , rvlinkURL]];
231+ [currentAlert addButtonWithTitle: @" Open" ];
232+ [currentAlert addButtonWithTitle: @" Cancel" ];
200233
201234 // Sort apps by path for consistent ordering
202235 NSArray *sortedAppURLs = [appURLs sortedArrayUsingComparator: ^NSComparisonResult (NSURL *url1, NSURL *url2) {
@@ -230,9 +263,12 @@ - (void)processRVLinkURL:(NSString *)rvlinkURL
230263 [popup addItemWithTitle: itemTitle];
231264 [[popup lastItem ] setRepresentedObject: appURL];
232265 }
233- [alert setAccessoryView: popup];
266+ [currentAlert setAccessoryView: popup];
234267
235- NSInteger result = [alert runModal ];
268+ NSInteger result = [currentAlert runModal ];
269+ NSAlert *alert = currentAlert;
270+ currentAlert = nil ;
271+
236272 if (result == NSAlertFirstButtonReturn ) {
237273 NSURL *selectedAppURL = [[popup selectedItem ] representedObject ];
238274 NSURL *targetURL = [NSURL URLWithString: rvlinkURL];
@@ -261,21 +297,21 @@ - (void)processRVLinkURL:(NSString *)rvlinkURL
261297int main (int argc, const char * argv[])
262298{
263299 @autoreleasepool {
264- // 1. Register Apple Event handler for URL events
300+ // Register Apple Event handler for URL events
265301 RVLinkURLHandler *urlHandler = [[RVLinkURLHandler alloc ] init ];
266302 [[NSAppleEventManager sharedAppleEventManager ]
267303 setEventHandler: urlHandler
268304 andSelector: @selector (handleGetURLEvent:withReplyEvent: )
269305 forEventClass: kInternetEventClass
270306 andEventID: kAEGetURL ];
271307
272- // 2. Automate reset of protocol handler on launch
308+ // Register as default handler for rvlink:// URLs
273309 NSString *bundleID = [[NSBundle mainBundle ] bundleIdentifier ];
274310 CFStringRef scheme = CFSTR (" rvlink" );
275311 CFStringRef handler = (__bridge CFStringRef)bundleID;
276312 LSSetDefaultHandlerForURLScheme (scheme, handler);
277313
278- // 3. Check if RVLinkLauncher is the default handler and prompt user if not
314+ // Warn if not the default handler
279315 CFStringRef currentHandler = LSCopyDefaultHandlerForURLScheme (scheme);
280316 if (currentHandler != NULL && !CFEqual (currentHandler, handler)) {
281317 NSAlert *warnAlert = [[NSAlert alloc ] init ];
@@ -289,7 +325,10 @@ int main(int argc, const char * argv[])
289325 CFRelease (currentHandler);
290326 }
291327
292- // Check command line arguments for immediate URL processing
328+ // Terminate any other running instances to prevent multiple dialogs
329+ [RVLinkURLHandler terminateOtherInstancesIfNeeded ];
330+
331+ // Check for command line URL
293332 NSString *rvlinkURL = nil ;
294333 for (int i = 1 ; i < argc; i++) {
295334 NSString *arg = [NSString stringWithUTF8String: argv[i]];
@@ -303,15 +342,15 @@ int main(int argc, const char * argv[])
303342 if (rvlinkURL != nil ) {
304343 NSLog (@" Processing rvlink URL from command line: %@ " , rvlinkURL);
305344 [urlHandler processRVLinkURL: rvlinkURL];
306- return 0 ; // Exit after processing command line URL
345+ return 0 ;
307346 }
308347
309348 // No immediate URL - register handler and wait briefly for Apple Events
310349 NSLog (@" RVLinkLauncher registered and ready to handle rvlink:// URLs" );
311350
312351 // Run the event loop briefly to allow Apple Events to be received
313352 // This is needed when the app is launched by clicking a rvlink:// URL
314- NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow: 2.0 ]; // Wait up to 2 seconds
353+ NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow: 2.0 ];
315354 while ([timeout timeIntervalSinceNow ] > 0 && ![urlHandler hasProcessedURL ]) {
316355 NSEvent *event = [[NSApplication sharedApplication ]
317356 nextEventMatchingMask: NSEventMaskAny
0 commit comments