代码之家  ›  专栏  ›  技术社区  ›  Community wiki

为什么我的应用程序无法“取消隐藏”(显示)

  •  8
  • Community wiki  · 技术社区  · 1 年前

    我正在调查Mac OS X 10.8上的一个问题,我已经无计可施了。我不知道下一步该做什么。

    该应用程序是32位的,其中包含一些Carbon调用。

    问题是:当我右键单击dock中的应用程序图标,选择菜单项“隐藏”,然后,在应用程序隐藏后,我从dock中选择“显示”菜单项,问题就出现了:主文档窗口不会出现(调色板和菜单会出现)。

    此时,“显示”菜单项不会更改为“隐藏”,即使调色板已变为可见。

    当我从应用程序停靠菜单中选择“显示”时,我希望主文档窗口变得可见。就像其他Mac应用程序一样。

    当它失败时,如果我在触控板上使用App Expos手势来显示文档窗口并选择主文档窗口,我可以使主文档窗口再次可见。

    如果我从终端或Xcode启动应用程序,效果会很好。此时将显示文档窗口,我的应用程序的停靠菜单项将按预期更改为“隐藏”。我通过导航到*.app的父目录并键入 ./MyApp.app/Contents/MacOS/MyApp .

    当我双击Finder中的应用程序图标启动时,它会失败。

    当从终端和Xcode启动应用程序时,应用程序代理的取消隐藏功能会显示我的日志消息,但从Finder启动时不会显示。

    – applicationWillUnhide:
    – applicationDidUnhide:
    

    我已经在Console.app中查找了抛出的任何异常(或任何其他消息)。没有。


    使现代化 :

    为了尝试调试这一点,我从Finder启动,并使用Xcode连接到进程。

    我曾怀疑有人抛出了异常,当我使用Xcode的“异常断点”测试它,并在objc_exception_show上设置断点(以防万一)时,当我隐藏或“显示”应用程序时,它不会中断。

    然后我想我需要证明 NSApplicationWillUnhideNotification NSApplicationDidUnhideNotification 正在发送。当我从Xcode或终端启动时,它们就是,但如果我从Finder启动,它们就不是。

    在将Xcode附加到应用程序后,我通过“添加符号断点”设置断点来验证这一点:

    -[NSNotificationCenter postNotificationName:object:userInfo] 
    

    然后,我添加了一个调试器命令:“po*(id*)($esp+12)”,以打印出该选择器的第一个参数(通知名称)。

    我在一个 answer posted here, 在StackOverflow中。

    使用它,我可以看到在我选择“显示”菜单项后发布的通知。当我从Xcode/Terminal启动时,我看到发布了以下通知:

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

    NSApplicationWillUn隐藏通知 在这种情况下发布。

    当我从Finder启动时,我看到发布了以下通知:

    NSApplicationWillUpdateNotification, NSWindowDidUpdateNotification, NSApplicationDidUpdateNotification, NSApplicationWillBecomeActiveNotification, ...
    

    它不会发送 NSApplicationWillUn隐藏通知 此外,当我从Xcode启动的版本中选择“显示”时,我看到 -[NSApplication _doUnhideWithoutActivation] 在回溯中。当我连接到Finder启动的版本时,为该函数设置断点不会导致我选择“显示”时的中断。

    然后,我心想,也许应用程序认为它没有被隐藏。

    我有一个空闲的事件处理程序,所以我从那里打印出的值 [[NSApplication sharedApplicaton] isHidden] 而我隐藏并“显示”应用程序。

    对于出现问题的情况,当应用程序未被隐藏时,它会打印出来 NO 对于 isHidden 。当应用程序被隐藏时,它会打印出来 YES 对于 is隐藏 。当我从停靠菜单中选择“显示”时,它会继续打印出来 对于 is隐藏 。它知道它是隐藏的,但部分应用程序已被激活: NSPanels 以及 NSMenuBar 显得

    我可以通过进入应用程序Expos模式来查看文档窗口,单击文档窗口将显示该窗口,但dock菜单项仍然是“Show”和 is隐藏 静止不动 .

    取消隐藏机制对于一个示例应用程序来说很好,所以我很确定我们的代码正在做一些事情来关闭它。


    我想知道从终端启动的应用程序与从Finder启动的应用软件有什么不同?

    我让应用程序使用 [[NSProcessInfo processInfo] environment] 我能看到的唯一真正的区别是 压水堆 存在于终端应用程序的变量中:我在我们的代码中看不到任何使用它的东西。

    我让应用程序通过 [[NSProcessInfo processInfo] arguments] ,我确实在Finder推出的版本中看到了一些不同的东西。终端和Finder启动的版本都将二进制文件的路径列为第一个参数;Finder还列出了第二个参数“-psn_0_89445704”。我在网上读到,它是Mac OS X添加到GUI应用程序的命令行参数中的东西,我看到它添加到其他应用程序的行参数中,这些应用程序可以从Dock菜单中正确隐藏和显示。

    你还有其他想法可以引导我进一步解开这个谜团吗?感谢您的帮助或建议!

    2 回复  |  直到 7 年前
        1
  •  3
  •   Lyndsey Ferguson    11 年前

    在与苹果工程师在AppKit中合作后,找到了一个解决方案。

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

    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系统在启动时向应用程序发送“显示”事件。我们的 脸红 函数在启动时被调用,它有效地将该事件从队列中删除,但Mac OS X的核心进程部分有自己的内部队列,可以跟踪显示和隐藏以及其他类型的事件类型,这样它就不会发送重复的消息。(我会调查是否真的需要冲洗)

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

    这个特殊问题的解决方案是在清除事件时更具选择性。对于我的新实现,我不清楚将由核心流程发送的事件。

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

    由于“显示”事件在启动时未被清除,请立即显示和隐藏工作!

    特别感谢苹果的KF!

        2
  •  0
  •   sfg    7 年前

    这种技术不适合发表评论,但我可能建议近距离接触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的内容显然不是一件小事,但这可能是值得的。我有个问题 here here 看看是否有人知道做这两件事中的任何一件。(如果你能得到窗口的标题,你可以设置一个从窗口地址到字符串的映射。)

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

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