Каков "правильный" способ определения активного приложения в OSX 10.6+?
Я пытаюсь определить, какое приложение OSX активно. Я понимаю, что в OSX 10.5 это можно сделать с помощью:
[[NSWorkspace sharedWorkspace] activeApplication]
однако это устарело в 10.6 +.
Документация разработчиков Apple заявляет, что это должно быть сделано через свойство "active" объекта 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 --- это моя первая попытка объективного C, поэтому я знаю, что это может быть ужасно уродливым для обученного глаза! Пожалуйста, простите меня!:) Любые предложения приветствуются.)
Ответы
Ответ 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];
}
Ответ 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;
}
Ответ 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
или activemethods для извлечения этой информации в целевых приложениях для Mac OS X версии 10.6 и более поздних версий.
Вероятно, вы должны сделать 10.6 и более новый набор кода и 10.5.X и более старый набор кода.
B.T.W., метод 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;
}