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!

Community
  • 1
  • 1
Lyndsey Ferguson
  • 5,306
  • 1
  • 29
  • 46
  • With regards to the bounty, I meant to say the first person who does either #1 OR #2. – Lyndsey Ferguson Mar 15 '13 at 13:52
  • 1
    In case you're wondering, “psn” is a `ProcessSerialNumber`, which is a Process Manager type. http://developer.apple.com/legacy/mac/library/documentation/Carbon/Reference/Process_Manager/ – Peter Hosey Mar 15 '13 at 21:03
  • 1
    Theory/wild guess: Are you (or any third-party library you use) installing the Carbon Event Manager standard handlers? Maybe there's a standard handler for `kEventAppShown` that's diving in front of AppKit's own handler. http://developer.apple.com/legacy/mac/library/documentation/Carbon/Reference/Carbon_Event_Manager_Ref/Reference/reference.html#//apple_ref/doc/c_ref/kEventAppShown – Peter Hosey Mar 15 '13 at 21:06
  • Thanks Peter. Our use of Carbon Event handlers is limited to one feature that one has to access for them to be installed - even so, they don't look at that event. Then I thought (briefly) that perhaps we had the old WaitNextEvent or GetNextEvent, but that has been stripped out too. – Lyndsey Ferguson Mar 15 '13 at 22:13
  • 1
    Is your window a NSWindow or a carbon one? Regardless, I might suggest either attaching carbon events to the window or subclassing the NSWindow and watching methods like -orderOut, etc. to see if they are getting called. It might be useful to determine whether or not the window itself is being told to show or not. If it is being told to display, then you can start to look elsewhere. – ericg Mar 19 '13 at 20:26
  • Hey Eric! Thanks, These are NSWindow objects. I just tried all of the orderX calls and none of them are called when the application is hidden. I tried in a pure Cocoa sample and observed the same behavior. Conversely, the selector, orderWindow:relativeTo: is called when the application is "Shown". – Lyndsey Ferguson Mar 21 '13 at 15:19
  • There may be some issues around access to the access to the window server when an app is launched from terminal vs the GUI. You're not launching your app as a non-gui user are you? – nielsbot Mar 21 '13 at 17:44
  • No, I'm a logged in user launching it from the Terminal by typing ./MyApp.app/Contents/MacOS/MyApp to launch it one way, or by double clicking the application icon from the Finder window. – Lyndsey Ferguson Mar 21 '13 at 17:55
  • Well, if the -orderWindow:relativeTo: method is being called on your window, that would likely tell me that someone has told your window to display itself and someone has decided to block it. What are the parameters passed into the method and do you know what they mean (DTrace will be able to print that information as well)? Do you have a delegate assigned to the window? There may be a 'should' method that is returning NO or a 'will' or 'did' method that is doing the wrong thing. There are always notifications that could be generated and the response to them might be wrong. – ericg Mar 22 '13 at 02:38
  • Are you using `LSUIElement`, `-[NSApplication setActivationPolicy:]`, or `TransformProcessType()`? Are you using a custom subclass of `NSApplication`? Does the app unhide any more reliably if you simply click its Dock icon or Command-Tab to it, rather than selecting the Show menu item? What about Show All from another app? – Ken Thomases Mar 22 '13 at 06:37
  • Sorry Eric, I meant to state that `orderWindow:relativeTo` is called for both the sample application, and our application. I will use DTrace to watch what is going on in other areas to see if I can figure out something. – Lyndsey Ferguson Mar 22 '13 at 21:40
  • Hi Ken, we do not use `LSUIElement`, `-[NSApplication setActivationPolicy:]`, nor `TransformProcessType()`. We did not subclass `NSApplication` either. I'll look into the other methods of hiding and unhiding. It does unhide properly from the Application menu as we created our own menus and hooked up those methods the same as a sample Cocoa application did. – Lyndsey Ferguson Mar 22 '13 at 21:43
  • I had another thought. You mentioned that you had developed a sample application and couldn't reproduce the problem. What if, in the real application, when the problematic NSWindow is created, you create another plain one. Does the plain one come back properly? If so (and I'm guessing it will), you can then try to slowly turn it into your problematic NSWindow until it no longer comes back correctly. This would take awhile and be a bit painful, but it would eventually allow you to pin and resolve the problem. – ericg Mar 23 '13 at 22:20
  • The NSWindow does come back properly. The menu item in the dock continues to display "Show" but that probably is due to something our problematic NSWindow is doing. – Lyndsey Ferguson Mar 25 '13 at 16:01

2 Answers2

3

After working with Apple Engineers in AppKit, a solution has been found.

In our application, we "flush" the event queue for various reasons via this method:

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];

The Mac OS X systems sends a "Show" event to the application on launch. Our flush function, which is called upon launch, effectively removes that event from the queue, but the core process part of Mac OS X has its own internal queue that keeps track of show and hide and other types of event types so that it doesn't send repeated messages. (I'll be investigating if this flush is really necessary)

The problem is that when discardEventsMatchingMask:NSAnyEventMask is called on every event, it clears out the events for the application, but doesn't respond to the core process's show event and so core process thinks that it doesn't need to send the show event again.

The solution to this particular problem is to be more selective in which events are cleared. With my new implementation, I do not clear events that will be sent by core process.

/* 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];

As the "show" event is not cleared on launch, show and hide work now!

A special thanks to KF of Apple!

Lyndsey Ferguson
  • 5,306
  • 1
  • 29
  • 46
  • Very helpful answer. But I suspect when creating the mask, instead of NSPeriodic etc., it should be NSPeriodicMask etc., or the more modern NSEventMaskPeriodic etc. – Bob Murphy Sep 15 '18 at 19:23
0

This technique won't fit in a comment, but I might suggest getting up-close-and-personal with DTrace. I suggested in the comments above to subclass NSWindow and put NSLog statements in the -orderOut:, etc. methods. However, using DTrace for this would likely be far more effective - although, as you will see, it will still be useful to know the address of the objects you will be observing - the upside is that you won't litter your code with a bunch of NSLog statements.

The simplest script might be:

#pragma D option quiet

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

and would be called with the process id of the application by doing something like:

sudo dtrace -s dtrace_window.d -p9434

In this particular case, arg0 will contain the address of the window. Unfortunately, it is apparently non-trivial to obtain the title of the window or even the contents of a NSString from within DTrace, but may be worth the effort. I do have a question here and here to see if anyone knows to do either one of these things. (If you can get the title of window, you can set up a map from the address of the window to a string.)

It would be easy to attach probes to any and all methods, functions, etc. you think might be involved so you can try to "follow the event" to resolve this problem.

So, ultimately, I would suggest to just keep adding DTrace probes until something provides the required hint resolve this problem.

Community
  • 1
  • 1
ericg
  • 8,413
  • 9
  • 43
  • 77
  • I promised that I would award the bounty to someone that gave me other avenues to explore. I haven't explored this one yet, but maybe it leads me to an answer. Thanks Eric. – Lyndsey Ferguson Mar 22 '13 at 13:31