【问题标题】:What is the 'correct' way to identify the currently active application in OSX 10.6+?在 OSX 10.6+ 中识别当前活动应用程序的“正确”方法是什么?
【发布时间】:2011-12-01 21:36:15
【问题描述】:

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

[[NSWorkspace sharedWorkspace] activeApplication]

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

Apple 开发人员文档指出,这应该通过 NSRunningApplication 对象的“活动”属性来完成。我认为解决此问题的一种方法可能是通过

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

然后循环,检查每个应用程序的“活动”属性。但是,以下测试代码的行为与我预期的不同:从 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 代码 --- 这是我第一次尝试 Objective C,所以我知道它对于受过训练的眼睛来说可能非常丑陋!请原谅我!:) 欢迎任何建议.)

【问题讨论】:

  • 嗨乔希---不;我实际上是在 10.6 上编码,而 ownsMenuBar 是 10.7。不过谢谢你的建议。
  • 使用NSRunningApplication.current

标签: objective-c cocoa


【解决方案1】:

您的问题是您的应用程序无法从系统接收任何通知当前应用程序已更改的事件,因此它永远不会更新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];
}

【讨论】:

  • 嗨凯文,非常感谢您的回复。您能否指出有关如何使我的应用程序接收此类事件的任何文档的方向?谢谢!
  • @duncanm:重要的一点是你需要运行主NSRunLoop,并且你需要让控制返回到运行循环。我刚刚用一个正是这样做的例子更新了我的答案。它使用NSTimer 每秒调用该方法,并且该方法本身仅迭代应用程序一次并返回。这样控制就返回给runloop,这样它就可以接收事件了,1秒后它再次调用该方法。
  • 啊。杰出的。我本来想用一个 NSTimer ,然后变得懒惰并使用了一个 while 循环!如果我一开始就这样,我想不会有问题。谢谢!
  • 这篇帖子已经有4年了,但是NSWorkspace的管理还是一样的。请注意:看在上帝的份上,您无需循环查找活动应用程序。使用[[NSWorkspace sharedWorkspace] menuBarOwningApplication][[NSWorkspace sharedWorkspace] frontmostApplication]。如果您想收到更改通知,则无需轮询;订阅NSWorkspaceDidActivateApplicationNotification
【解决方案2】:

每隔一秒左右轮询一次以找出当前的应用程序效率低下,而且是错误的方法。一种更好的方法是简单地设置您的流程以接收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;
}

【讨论】:

  • 我相信 currentApplication 返回的 NSRunningApplication 代表运行代码的应用程序,而不是 UI 中最重要的应用程序。
  • 嗨 NSGod - 我确实尝试过,但它似乎也没有工作(可能与 Kevin 在下面建议的原因相同 --- 即我的应用程序无法接收事件?) - -- 事实上,如果我打印出“[[NSRunningApplication currentApplication]localizedName]”,我得到的是“null”,而不是“Terminal.app”,当我激活不同的应用程序时这不会改变)
  • @NSGod:重复调用-performSelector:withObject:afterDelay: 是一种相当愚蠢的定时器实现方式。
  • @KevinBallard:使用计时器反复轮询活动应用程序是跟踪活动应用程序的愚蠢方法。
  • @NSGod:我同意,但我的印象是 OP 并没有尝试跟踪活动应用程序的更改,而只是想弄清楚如何获取活跃的应用程序。他的循环只是试图查看活动状态的变化,以确保其正常工作。至少,我是这么解释的。
【解决方案3】:

截至 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
    }); 
}

【讨论】:

  • 这应该是公认的答案!干净、简单且有效。
【解决方案4】:

NSWorkspace 的activeApplication 的注释说:

特殊注意事项

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

您可能应该在那里编写一组 10.6 及更新的代码和一组 10.5.X 及更旧的代码。

顺便说一句,NSWorkspace 方法仅在 10.7 中被标记为弃用,但 NSRunningApplication 在 10.6 中出现。

哦,如果您包含应用程序服务框架,这是 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;
}

【讨论】:

  • 嗨,迈克尔,感谢您的回复。我确实尝试过使用“currentApplication”,但它返回了从中进行调用的应用程序(即,在我的情况下,localizedName 为“null”,因为我正在运行命令行进程)。出于您建议的原因,我想避免使用 activeApplication ---即它已被弃用。但是,使用“active”似乎不起作用。
【解决方案5】:
- (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]);

}

【讨论】:

    猜你喜欢
    • 2015-09-13
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 2010-11-10
    • 1970-01-01
    • 1970-01-01
    • 2014-09-01
    相关资源
    最近更新 更多