8

I'm investigating an issue on Mac OS X 10.8, and I am at the end of my wits. I'm not sure what to do next.

The application is 32 bit and has some Carbon calls in it.

Here is the problem: when I right-click the application icon in the dock, select the menu item "Hide", then, after the application has hidden, I select the "Show" menu item from the dock, and the problem occurs: the main document window does not appear (the palettes and menu do appear).

At this point, the "Show" menu item does not change to "Hide" even though the palettes have become visible.

I expect that the main document window becomes visible when I select "Show" from the application dock menu. Just like other Mac applications.

When it fails, I can make the main document window visible again if I use the App Exposé gesture on the Trackpad to show the document windows and select the main document window.

It works fine if I launch the application from the Terminal or from Xcode. The document window shows and the dock menu item for my application changes to "Hide" as expected. I launch the app from the terminal by navigating to the parent directory of the *.app, and typing ./MyApp.app/Contents/MacOS/MyApp.

It fails when I have launched by double-clicking the application icon in the Finder.

My log messages from the application delegate's unhide functions appear when the application is launched from the Terminal and Xcode, but not when launched from the Finder.

– applicationWillUnhide:
– applicationDidUnhide:

I have looked in the Console.app for any exceptions thrown (or any other messages). There are none.


Update:

To try and debug this, I launched from the Finder, and used Xcode to attach to the process.

I had suspected that an exception was being thrown and when I tested it using Xcode's "Exception Breakpoint" as well as putting a breakpoint on objc_exception_throw (just in case), it dd not break when I hide or "show" the application.

I then thought that I needed to prove that the NSApplicationWillUnhideNotification and NSApplicationDidUnhideNotification were being sent out. They are when I launch from Xcode or from the Terminal, but if I launch from the Finder, they are not.

I verified this by, after attaching Xcode to the application, putting a breakpoint via "Add Symbolic Breakpoint" for:

-[NSNotificationCenter postNotificationName:object:userInfo] 

And then, I added a Debugger Command: "po * (id*) ($esp+12)" to print out the first parameter to that selector (the notification name).

Which I found in an answer posted here, in StackOverflow.

Using that, I can see the notifications that are posted after I choose the "Show" menu item. When I launch from Xcode/Terminal, I see the following notifications posted:

NSApplicationWillUpdateNotification, NSWindowDidUpdateNotification, NSApplicationDidUpdateNotification, ** NSApplicationWillUnhideNotification **, ..., ** NSApplicationDidUnhideNotification **, ..., NSApplicationWillBecomeActiveNotification, ...

NSApplicationWillUnhideNotification is posted in this situation.

When I launch from the Finder, I see the following notifications are posted:

NSApplicationWillUpdateNotification, NSWindowDidUpdateNotification, NSApplicationDidUpdateNotification, NSApplicationWillBecomeActiveNotification, ...

It does not send the NSApplicationWillUnhideNotification. Also, when I select "Show" from the Xcode-launched version, I see -[NSApplication _doUnhideWithoutActivation] in the backtrace. Putting a breakpoint for that function when I attach to the Finder-launched version does not result in a break when I select "Show".

Then, I thought to myself, perhaps the application thinks that it is not hidden.

I have a idle event handler, so from there I printed out the value of [[NSApplication sharedApplicaton] isHidden] while I Hide and "Show" the application.

For the problem situation, when the application is not hidden, it prints out NO for isHidden. When the application becomes hidden, it prints out YES for isHidden. When I select "Show" from the dock menu, it continues to print out NO for isHidden. It knows that it is hidden, but part of the application has been activated: the NSPanels and the NSMenuBar appear.

I can see the document window by entering into the application Exposé mode, and clicking the document window will make the window appear, but the dock menu item is still "Show" and isHidden is still YES.

The unhide mechanism works fine for a sample application, so I'm pretty sure that our code is doing something to shut this off.


I wonder what would be different between an application launched from the Terminal compared to an application launched from the Finder?

I had the application log the environment variables using [[NSProcessInfo processInfo] environment] and the only real difference I could see is that PWD exists in the variables for the Terminal application: I cannot see anything in our code that makes use of that.

I had the application log the command-line arguments via [[NSProcessInfo processInfo] arguments], and I do see something different in the Finder-launched version. Both the Terminal and Finder launched versions list the path of the binary as the first argument; the Finder also lists a second paramter, "-psn_0_89445704". I have read online that it is something that Mac OS X adds to command-line arguments for GUI applications and I see it added to the command-line arguments for other applications that Hide and Show properly from the Dock menu.

Do you have any other thoughts that may lead me further towards solving this mystery? Thanks for any help or suggestions!

4

2 回答 2

3

在与 AppKit 中的 Apple 工程师合作后,找到了解决方案。

在我们的应用程序中,出于各种原因,我们通过此方法“刷新”事件队列:

NSEvent* lastEvent = [NSEvent otherEventWithType:NSPeriodic
                                            location:NSMakePoint(0.0, 0.0)
                                            modifierFlags:0
                                           timestamp:[NSDate timeIntervalSinceReferenceDate]
                                        windowNumber:1
                                             context:NULL
                                             subtype:0
                                               data1:0
                                               data2:0];

    [[NSApplication sharedApplication] discardEventsMatchingMask:NSAnyEventMask beforeEvent:lastEvent];

Mac OS X 系统在启动时向应用程序发送“显示”事件。我们的flush函数在启动时被调用,它有效地从队列中删除了那个事件,但是Mac OS X的核心进程部分有它自己的内部队列,它跟踪显示和隐藏以及其他类型的事件类型,所以它不会' t 发送重复消息。(我会调查这个冲洗是否真的有必要)

问题是,当在每个discardEventsMatchingMask:NSAnyEventMask事件上调用时,它会清除应用程序的事件,但不响应核心进程的显示事件,因此核心进程认为它不需要再次发送显示事件。

这个特定问题的解决方案是在清除事件时更具选择性。在我的新实现中,我不会清除核心进程将发送的事件。

/* a bug in Apple's Core Process group forces me to isolate which events should be cleared as
        show|hide|activate|deactivate messages get sent by Core Process, but are not _marked_ as
     handled and so Core Process thinks that the "Show" event is still pending and will not send
     another */

    NSEvent* lastEvent = [NSEvent otherEventWithType:NSPeriodic
                                            location:NSMakePoint(0.0, 0.0)
                                            modifierFlags:0
                                           timestamp:[NSDate timeIntervalSinceReferenceDate]
                                        windowNumber:1
                                             context:NULL
                                             subtype:0
                                               data1:0
                                               data2:0];


    NSEventMask maskForEventsToDiscard = (NSPeriodic |
                                          NSLeftMouseDown |
                                          NSLeftMouseUp |
                                          NSMouseMoved |
                                          NSLeftMouseDragged |
                                          NSRightMouseDragged |
                                          NSMouseEntered |
                                          NSMouseExited |
                                          NSKeyDown |
                                          NSOtherMouseDown |
                                          NSOtherMouseUp |
                                          NSOtherMouseDragged);

    [[NSApplication sharedApplication] discardEventsMatchingMask:maskForEventsToDiscard
                                                     beforeEvent:lastEvent];

由于“显示”事件在发布时未清除,现在显示和隐藏工作!

特别感谢 Apple 的 KF!

于 2013-06-14T16:33:37.843 回答
0

这种技术不适合发表评论,但我可能会建议与 DTrace 近距离接触。我在上面的评论中建议将 NSWindow 子类化并将 NSLog 语句放在 -orderOut: 等方法中。但是,为此使用 DTrace 可能会更有效——尽管正如您将看到的,知道您将要观察的对象的地址仍然很有用 – 好处是您不会乱扔代码一堆 NSLog 语句。

最简单的脚本可能是:

#pragma D option quiet

objc$target:NSWindow:-orderOut?:entry
{
    printf( "%30s %10s %x %x\n", probemod, probefunc, arg0, arg1 );
}

并将通过执行以下操作使用应用程序的进程 ID 调用:

sudo dtrace -s dtrace_window.d -p9434

在这种特殊情况下,arg0 将包含窗口的地址。不幸的是,从 DTrace 中获取窗口的标题或什至 NSString 的内容显然并非易事,但可能值得付出努力。我在这里这里确实有一个问题,看看是否有人知道做这些事情中的任何一件。(如果你能得到窗口的标题,你可以设置一个从窗口地址到字符串的映射。)

将探测器附加到您认为可能涉及的任何和所有方法、函数等将很容易,因此您可以尝试“跟踪事件”来解决此问题。

因此,最终,我建议继续添加 DTrace 探针,直到提供所需的提示来解决此问题。

于 2013-03-22T02:31:36.277 回答