Mac OS X Cocoa:applicationDidBecomeActive未被触发

编程语言 2026-07-12

我打算完全靠代码来实现一个应用程序(因此这不是对 this 的重复提问,因为那个问题的解决方案是在Xcode中配置nib文件),并且我想在我的应用程序成为主窗口时检测到它。

我正在通过以下方式创建它:

@interface MilskoCocoa : NSObject {
  NSApplication *application;
  MilskoCocoaWindow *window;
  NSRect rect;
  MilskoCocoaView *view;
  MwLL parent;
}
+ (MilskoCocoa *)newWithParent:(MwLL)parent
                             x:(int)x
                             y:(int)y
                         width:(int)width
                        height:(int)height;

@end

// ...

@implementation MilskoCocoa
+ (MilskoCocoa *)newWithParent:(MwLL)parent
                             x:(int)x
                             y:(int)y
                         width:(int)width
                        height:(int)height
                        handle:(MwLL)r {
  MilskoCocoa *c = [MilskoCocoa alloc];
  [c retain];

  c->application = [NSApplication sharedApplication];
  c->rect = rectFlip(NSMakeRect(x, y, width, height));

  if (parent == NULL) {
    c->window = [[MilskoCocoaWindow alloc]
        initWithContentRect:c->rect
                  styleMask:(NSTitledWindowMask |
                             NSTexturedBackgroundWindowMask |
                             NSClosableWindowMask | NSMiniaturizableWindowMask |
                             NSResizableWindowMask)
                    backing:NSBackingStoreBuffered
                      defer:NO];
  } else {
    double offset = 0;
    MilskoCocoaWindow *parentWindow = parent->cocoa.real->window;
    offset =
        [parentWindow frameRectForContentRect:[parentWindow frame]]
            .size.height -
        [parentWindow contentRectForFrameRect:[parentWindow frame]].size.height;

    c->rect.origin.x += [parentWindow frame].origin.x;
    c->rect.origin.y -= [parentWindow frame].origin.y - offset;
    c->rect.origin.y -= offset;

    c->window =
        [[MilskoCocoaWindow alloc] initWithContentRect:c->rect
                                             styleMask:NSBorderlessWindowMask
                                               backing:NSBackingStoreBuffered
                                                 defer:NO];
  }
  [c->window
      setDelegate:[[MilskoCocoaWindowDelegate alloc] initWithWin:c->window]];

  [c->window makeKeyAndOrderFront:c->application];
  [c->window retain];

  if (parent != NULL) {
    MilskoCocoa *p = parent->cocoa.real;
    double offset = 0;
    offset = [p->window frameRectForContentRect:[p->window frame]].size.height -
             [p->window contentRectForFrameRect:[p->window frame]].size.height;
    [p->window addChildWindow:c->window ordered:NSWindowAbove];
    [c->window setHasShadow:MwFALSE];
    [c->window setParentWindow:p->window];

    c->rect.origin.x += [p->window frame].origin.x;
    c->rect.origin.y -= [p->window frame].origin.y - offset;
    c->rect.origin.y -= offset;
  } else {
    [c->application setActivationPolicy:NSApplicationActivationPolicyRegular];
    [c->application activateIgnoringOtherApps:true];
    [c->window makeFirstResponder:c->view];
    [c->window makeKeyAndOrderFront:nil];
  }
  [c->application setDelegate:[[MilskoCocoaApplicationDelegate alloc]
                                  initWithAppl:c->application]];
  [c->application retain];

  c->view = [[MilskoCocoaView alloc] initWithFrame:c->rect];
  [c->view retain];

  [c->window setContentView:c->view];

  c->parent = parent;

  [c->application finishLaunching];

  return c;
}

如代码所示,我也有这个应用程序代理:

@implementation MilskoCocoaApplicationDelegate
- (MilskoCocoaApplicationDelegate *)initWithAppl:(NSApplication *)_appl {
  self->appl = _appl;
  return self;
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
  printf("test\n");
}
@end

我的理解是applicationDidBecomeActive应该会在我的应用程序成为主窗口时被调用(当我在它的任意区域点击,除了标题栏)。然而这并没有发生,我放置的日志也从未被触发。是不是应该调用其他的函数?

值得一提的是,由于我正在移植的库的设计,我并不使用任何回调,相反我的主循环包含手动调用 nextEventMatchingMask,根据产生的NSEvent的类型来执行代码,然后再执行 [NSWindow sendEvent:{event}]。这样做在过去也带来过麻烦,因为相应的回调函数也不会被触发。

编辑:由于在调用 [c->application run] 时它确实会被激活,下面是前述自定义事件循环。

/* Check whether there are pending events. Yes, this is hacky and unreliable, but the rest of the library REALLY doesn't like us just always returning 1 from this function so we have to do Something. */
- (int)pending {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  MwBool isPending = MwFALSE;
  if (_forceRender) {
    _forceRender = MwFALSE;
    [pool release];
    return 1;
  }
  self->lastEvent = [self->window nextEventMatchingMask:NSAnyEventMask
                                              untilDate:[NSDate distantPast]
                                                 inMode:NSDefaultRunLoopMode
                                                dequeue:YES];

  isPending = self->lastEvent != NULL;
  [pool release];
  return isPending;
};

/* Actually process the event assuming there are any pending */
- (void)getNextEvent {
  [self eventProcess:self->lastEvent];
  while (true) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSEvent *ev = [self->window nextEventMatchingMask:NSAnyEventMask
                                            untilDate:[NSDate distantPast]
                                               inMode:NSDefaultRunLoopMode
                                              dequeue:YES];
    if (!ev) {
      [pool release];
      break;
    }
    [self eventProcess:ev];
    [pool release];
  }

  [self sendClipboardEvent];

  [self->application updateWindows];
  [self->application runModalForWindow:self->window];
};

- (void)eventProcess:(NSEvent *)ev {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSWindow *win = [ev window];
  MwLL h;
  MwBool doSendEvent = MwTRUE;

  if (!win) {
    [pool release];
    return;
  }

  switch ([ev type]) {
       /* snip */
  }
  if (doSendEvent) {
    [win sendEvent:ev];
  }

  [pool release];
}

解决方案

编辑:发帖后我意识到实际需要做的事情,但为了防止有人遇到这个问题时第一种方法不是他们的解决方案,我就把两种方案都保留着。

正确的方案:模态会话

在创建我的窗口时,我需要使用 beginModalSessionForWindow 来创建一个“模态会话”,然后在待处理函数中使用 runModalSession 运行该模态会话。

- (int)pending {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  MwBool isPending = MwFALSE;

  self->lastEvent = [self->window nextEventMatchingMask:NSAnyEventMask
                                              untilDate:[NSDate distantPast]
                                                 inMode:NSDefaultRunLoopMode
                                                dequeue:YES];
  [self->lastEvent retain];

  /* only run the modal session if we don't have a pending event, as otherwise
   * it consumes a bunch of our events */
  if (!self->lastEvent) {
    [NSApp runModalSession:self->modalSession];
  }

  isPending = self->lastEvent != NULL;
  [pool release];
  return isPending;
};

笨拙、不正确,但可行的方案:重复 [NSApplication run] 调用

我的原始方案是只使用 [NSApplication run],再搭配一个立即停止它的函数,从而以某种方式推动事件,以便让MacOS愿意接受它。然而,这也会导致CPU使用率飙升(除非在每次循环后执行 usleep(1000)),在一些鲜为人知的地方还引发了奇怪的段错误。我发现换一种做法反而解决了更多的问题。

- (int)pending {
  [MilskoCocoa performSelectorOnMainThread:@selector(eventCanceller:)
                                withObject:self
                             waitUntilDone:NO];
  [self->application run];
  return 1;
};
+ (void)eventCanceller:(MilskoCocoa *)this {
  this->lastEvent = [this->application currentEvent];
  [this eventProcess:this->lastEvent];

  [[NSApplication sharedApplication] stop:nil];
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章