On Tuesday January 05 2016 12:55:57 David Faure wrote:

> I'm surprised it's that complex, and that an OSX app cannot launch another 
> one and have it appear in front.
> Is there no app with an action that starts a calculator, a special-character 
> selector, etc.?
> Is there no alternative application launcher? ...

Those would be written using platform-specific (native) APIs, not a middleware 
like Qt. 
It's pretty much a sign that Qt's OS X platform integration layer does a "come 
to the front ignoring anyone else", which is not exactly the preferred way of 
activating oneself.

I'm attaching a hack I wrote a while ago, an alternative to the standard "open" 
utility. It lacks comments, but should be readable enough. It also uses 
deprecated concepts like PSNs, but I presume modern alternatives exists to 
everything used in there. It has a feature to catch termination of the spawned 
application; evidently this is possible since even "open" itself can do so to 
behave as if the GUI application wasn't detached at all.
> This assumes that such messages can be captured from *another* process (the 
> Qt app). From your description, it doesn't sound like that's the case,

See the attachment for a way to do this from within the calling application. 
The alternative would be to let the spawned application send some kind of Qt 
signal to its "parent", but that's probably not as easy as using the native 
mechanism.

BTW, I'm using a very nifty ObjC feature in that code, extension of an existing 
class instead of subclassing it or creating a whole new class.

> SIGCHLD is the posix way for a parent process to know that a child process 
> just exited, but that's not available for "detached" processes (not children),
> and probably not when using LaunchServices either (also not a child).

Indeed.

> If you want to hack one instead of the other for experimentation purposes, 
> why not, but I don't think it will make it any easier to work on the wrapper
> (which has no access to the QProcess internals).

We might not need access to those if we can override certain things from 
QProcess. To be seen in practice; KCoreAddons *is* a lot less overwhelming to 
start hacking around in of course.

> > There's the issue with LaunchServices delegating to Terminal.app for 
> > certain applications, but one could say that's a side-effect users of 
> > QGuiProcess will have to take into consideration.
> 
> That sounds like an expected result of GuiLaunch or QGuiProcess. You ask for 
> a GUI, you start a commandline exe, you get a GUI terminal running that 
> command.

Yes, agreed. I was thinking about code using the class; it'd probably want to 
assess whether a terminal might be fired and use a regular QProcess in that 
case. That would be much easier to implement if QGuiProcess inherits QProcess, 
no?

> It's rather surprising that the events are not queued until the app processes 
> them.

That may just be how AppleScript implements the feature, but it can also be 
that the applications I tried this with (Qt apps...) simply discard events 
until they're all set up.

R.
// a very simple utility to that allows to launch OS X applications (app bundles) from a command line
// and lets them appear in the foreground. Contrary to OS X's own open command, it does not treat all
// arguments like they're documents to be opened, and so doesn't impose to pass non-file arguments after
// a --args option. It also removes the need to specify full paths for the files to be opened for
// certain applications (KDE app bundles for instance).

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import <sys/types.h>
#import <libgen.h>
#import <mach/clock_types.h>
#import <dispatch/semaphore.h>

// from http://stackoverflow.com/a/4933492

@interface NSWorkspace (MDAdditions)
- (pid_t) launchApplicationAtPath:(NSString *)path
				withArguments:(NSArray *)argv
				 waitFinished:(BOOL)wait
						psn:(ProcessSerialNumber*)psn
					   error:(NSError **)error;
- (void) appTerminated:(NSNotification*)note;
@end

#import <CoreServices/CoreServices.h>

@interface NSString (MDAdditions)
- (BOOL) getFSRef:(FSRef *)anFSRef error:(NSError **)anError;
@end

#import <sys/syslimits.h>

@implementation NSString (MDAdditions)

- (BOOL) getFSRef:(FSRef *)anFSRef error:(NSError **)anError
{
	if( anError ){
		*anError = nil;
	}
	OSStatus status = noErr;
	status = FSPathMakeRef( (const UInt8 *)[self UTF8String], anFSRef, NULL );
	if( status != noErr ){
		if( anError ){
			*anError = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
		}
	}
	return (status == noErr);
}
@end

static dispatch_semaphore_t sema;
static pid_t waitPID;

@implementation NSWorkspace (MDAdditions)

- (void)appTerminated:(NSNotification *)note
{
// 	NSLog(@"terminated %@\n", [[note userInfo] objectForKey:@"NSApplicationProcessIdentifier"]);
	if( [[[note userInfo] objectForKey:@"NSApplicationProcessIdentifier"] unsignedLongLongValue] == (unsigned long long) waitPID ){
		dispatch_semaphore_signal(sema);
	}
}

- (pid_t) launchApplicationAtPath:(NSString *)path withArguments:(NSArray *)argv
				 waitFinished:(BOOL)wait
						psn:(ProcessSerialNumber*)psn
					   error:(NSError **)error
{ pid_t pid = 0;
	if( error ){
		*error = nil;
	}

	if( path ){
		FSRef itemRef;
		if( [path getFSRef:&itemRef error:error] ){
			LSApplicationParameters appParameters = { 0,
				kLSLaunchAndDisplayErrors|kLSLaunchDontAddToRecents, &itemRef, NULL, NULL,
				(argv ? (CFArrayRef)argv : NULL), NULL };
			ProcessSerialNumber PSN;

			OSStatus status = noErr;
			status = LSOpenApplication( &appParameters, &PSN );

			if( status != noErr ){
                    NSLog( @"[%@ %@] LSOpenApplication() returned %d for %@",
					 NSStringFromClass([self class]),
					 NSStringFromSelector(_cmd), (int) status, path );
                    if( error ){
					*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
				}
			}
			else{
				if( psn ){
					*psn = PSN;
				}
				GetProcessPID( &PSN, &pid );
				if( wait ){
				  NSNotificationCenter *center = [self notificationCenter];
					sema = dispatch_semaphore_create(0);
					[center addObserver:self selector:@selector(appTerminated:)
								name:NSWorkspaceDidTerminateApplicationNotification
							   object:NSApp];
					waitPID = pid;
					if( kill( pid, 0 ) ){
						// catch the case where the app we launched exited before we could put the notification centre
						// in place
						dispatch_semaphore_signal(sema);
					}
				}
			}
		}
	}
	else{
		if( error ){
			*error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil];
		}
	}
	return pid;
}
@end



int main( int argc, const char *argv[] )
{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  BOOL success = NO, waitApp = NO;
	if ( argc > 1 ){
		int i = 1;
		NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
		NSMutableArray *theArguments = [[NSMutableArray alloc] init];

		if( strcmp( argv[i], "--help" ) == 0 ){
			fprintf( stderr, "Usage: %s [-W|--wait-app] command [arguments]\n",
				   basename((char*)argv[0]) );
			exit(0);
		}

		if( strcmp( argv[i], "-W" ) == 0 || strcmp( argv[i], "--wait-apps" ) == 0 ){
			waitApp = YES;
			i += 1;
		}

		NSString *command = [NSString stringWithCString:argv[i] encoding:NSUTF8StringEncoding];

		for( ++i ; i < argc ; ++i ){
		  NSString *arg = [NSString stringWithCString:argv[i] encoding:NSUTF8StringEncoding];
			if( argv[i][0] != '/' && access( argv[i], R_OK ) == 0 ){
			  NSString *fullPath = [NSString stringWithFormat:@"%@/%s", cwd, argv[i]];
				if( fullPath ){
// 					NSLog( @"%s@%@ -> %@", argv[i], cwd, fullPath );
					arg = fullPath;
				}
			}
			[theArguments addObject:arg];
		}

		NSError *err = nil;
		ProcessSerialNumber psn;
		[[NSWorkspace sharedWorkspace] launchApplicationAtPath:command withArguments:theArguments
														   waitFinished:waitApp psn:&psn error:&err];
		if( err ){
			NSLog( @"%@", err );
			success = NO;
		}
		else{
			success = YES;
			if( waitApp ){
				// thanks to http://michiganlabs.com/unit-testing-objectivec-asychronous-blockbased-api/
				// and http://stackoverflow.com/questions/4326350/how-do-i-wait-for-an-asynchronously-dispatched-block-to-finish
			  NSTimeInterval TIMEOUT = 5.0;
				while( dispatch_semaphore_wait( sema, DISPATCH_TIME_NOW ) ){
					[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
										beforeDate:[NSDate dateWithTimeIntervalSinceNow:TIMEOUT]];
				}
			}
		}
	}
	[pool release];
	exit( !success );
}
_______________________________________________
Kde-frameworks-devel mailing list
Kde-frameworks-devel@kde.org
https://mail.kde.org/mailman/listinfo/kde-frameworks-devel

Reply via email to