在OSX 10.6+中识别当前活动应用程序的“正确”方法是什么?

时间:2011-12-01 21:36:16

标签: objective-c cocoa

我正在尝试确定哪个OSX应用程序当前处于活动状态。据我所知,在OSX 10.5中,可以通过以下方式完成:

[[NSWorkspace sharedWorkspace] activeApplication]

但是,这已在10.6 +中弃用。

apple developers文档指出,这应该通过NSRunningApplication对象的“active”属性来完成。我认为解决这个问题的一种方法可能是通过

获取所有正在运行的应用程序的列表
[[NSWorkspace sharedWorkspace] runningApplications]

然后循环,检查每个应用程序的“active”属性。但是,以下测试代码的行为与我的预期不同:从Terminal.app编译和运行时,无论是否选择其他应用程序,只有“终端”应用程序被标记为活动状态。

#import <Foundation/Foundation.h>
#import <AppKit/NSRunningApplication.h>
#import <AppKit/NSWorkspace.h>

int main(int argc, char *argv[]) {
  while(1){
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSString *currApp;
    NSArray *runningApps;
    runningApps = [[NSWorkspace sharedWorkspace] runningApplications];
    for (id currApp in runningApps) {
      if ([currApp isActive])
          NSLog(@"* %@", [currApp localizedName]);
      else
          NSLog(@"  %@", [currApp localizedName]);
    }
    sleep(1);
    [pool release];
  }

  return 0;
}

我做错了什么?我误解了“主动”财产的运作方式吗?

(另外,请随意批评我的Objective C代码---这是我在目标C上的第一次尝试,所以我知道它可能会让受过训练的眼睛难看!请原谅我!:)欢迎任何建议。)

5 个答案:

答案 0 :(得分:18)

您的问题是您的应用程序无法从系统接收任何事件,通知它当前应用程序已更改,因此它永远不会更新NSRunningApplication实例上的活动属性。如果我使用完全相同的代码,但是当我开始运行代码时,另一个应用程序处于活动状态,则会报告该应用程序。

如果您更改代码以运行主线程的NSRunLoop并使用1秒计时器,它应该可以工作。

这是一个简单的例子:

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

@interface Foo : NSObject
- (void)run;
@end

@implementation Foo
- (void)run {
    for (NSRunningApplication *currApp in [[NSWorkspace sharedWorkspace] runningApplications]) {
        if ([currApp isActive]) {
            NSLog(@"* %@", [currApp localizedName]);
        } else {
            NSLog(@"  %@", [currApp localizedName]);
        }
    }
    NSLog(@"---");
}
@end

int main(int argc, char *argv[]) {
    NSAutoreleasePool *p = [NSAutoreleasePool new];

    Foo *foo = [[Foo new] autorelease];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
                                                      target:foo
                                                    selector:@selector(run)
                                                    userInfo:nil
                                                     repeats:YES];
    [[NSRunLoop mainRunLoop] run];

    [p release];
}

答案 1 :(得分:17)

每隔一秒左右进行轮询以找出当前的应用程序是低效的,并且这是错误的方法。更好的方法是简单地设置您的流程以接收NSWorkspaceDidActivateApplicationNotification通知。

@interface MDAppController : NSObject <NSApplicationDelegate> {
    NSRunningApplication    *currentApp;
}
@property (retain) NSRunningApplication *currentApp;
@end

@implementation MDAppController 
@synthesize currentApp;

- (id)init {
    if ((self = [super init])) {
        [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                      selector:@selector(activeAppDidChange:)
               name:NSWorkspaceDidActivateApplicationNotification object:nil];
    }
    return self;
}
- (void)dealloc {
    [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
    [super dealloc];
}
- (void)activeAppDidChange:(NSNotification *)notification {
    self.currentApp = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
    NSLog(@"currentApp == %@", currentApp);
}
@end

int main(int argc, const char * argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [NSApplication sharedApplication];
    MDAppController *appController = [[MDAppController alloc] init];
    [NSApp setDelegate:appController];
    [NSApp run];
    [pool release];
    return 0;
}

答案 2 :(得分:12)

从OS X 10.7开始NSWorkspace也有方便的方法:

- (NSRunningApplication *)frontmostApplication;

此外,您现在可以使用Grand Central调度调用来进行重复调用。

这样的事情:

- (void) checkFrontmostApp {

    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSRunningApplication* runningApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
        //do something
        NSLog(@"frontmost app: %@", runningApp.bundleIdentifier);
        [self checkFrontmostApp]; //'recursive' call
    }); 
}

答案 3 :(得分:1)

NSWorkspace的activeApplication注释说:

  

特别注意事项

     

强烈建议您使用NSRunningApplication   班   currentApplication   或activeMthods在目标应用程序中检索此信息   适用于Mac OS X v10.6及更高版本。

你应该做10.6&amp;更新的代码集和10.5.X及更旧的代码集。

B.T.W。,从10.7开始,NSWorkspace方法仅被标记为已弃用,但NSRunningApplication从10.6开始提供。

哦,如果您包含Application Services框架,这里有64位兼容的替代方案:

int main (int argc, const char * argv[])
{
    // insert code here...
    CFShow(CFSTR("Hello, World!\n"));
    ProcessSerialNumber psn;

    OSErr err = GetFrontProcess(&psn);
    if(err == noErr)
    {
        ProcessInfoRec info;
        StringPtr processName = malloc(64);

        if(processName)
        {
            bzero(processName, 64);
            info.processInfoLength = sizeof(ProcessInfoRec);
            info.processName = processName;
            err = GetProcessInformation( &psn, &info);
            if(err == noErr)
            {
                fprintf(stdout, "front most process name is %s", processName+1 );
            }
            free(processName);
        }
    }
    return 0;
}

答案 4 :(得分:0)

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

    NSNotificationCenter *allApplicationsNotificationCenter;

    allApplicationsNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];

    [allApplicationsNotificationCenter addObserver:self selector:@selector(applicationActivated:) name:NSWorkspaceDidActivateApplicationNotification object:nil];
}

- (void)applicationActivated:(NSNotification *)aNotification {

    NSLog(@"%@",[[[NSWorkspace sharedWorkspace] menuBarOwningApplication] localizedName]);

}