【问题标题】:Why would my application fail to "unhide" (show)为什么我的应用程序无法“取消隐藏”(显示)
【发布时间】:2013-03-08 18:07:24
【问题描述】:

我正在调查 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_throw 上放置一个断点(以防万一)时,当我隐藏或“显示”应用程序时它不会中断.

然后我认为我需要证明NSApplicationWillUnhideNotificationNSApplicationDidUnhideNotification 正在被发送出去。它们是当我从 Xcode 或终端启动时,但如果我从 Finder 启动,它们不是。

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

-[NSNotificationCenter postNotificationName:object:userInfo] 

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

我在 StackOverflow 的 answer posted here, 中找到的。

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

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

NSApplicationWillUnhideNotification是在这种情况下发帖的。

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

NSApplicationWillUpdateNotification, NSWindowDidUpdateNotification, NSApplicationDidUpdateNotification, NSApplicationWillBecomeActiveNotification, ...

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

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

我有一个空闲事件处理程序,因此我在隐藏和“显示”应用程序时打印了 [[NSApplication sharedApplicaton] isHidden] 的值。

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

进入应用Exposé模式可以看到文档窗口,点击文档窗口会出现窗口,但是dock菜单项还是“Show”,isHidden还是YES

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


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

我让应用程序使用 [[NSProcessInfo processInfo] environment] 记录环境变量,我能看到的唯一真正区别是终端应用程序的变量中存在 PWD:我在我们的代码中看不到任何充分利用它。

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

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

【问题讨论】:

  • 关于赏金,我的意思是说第一个做#1或#2的人。
  • 如果您想知道,“psn”是ProcessSerialNumber,它是一个进程管理器类型。 developer.apple.com/legacy/mac/library/documentation/Carbon/…
  • 理论/疯狂猜测:您(或您使用的任何第三方库)是否安装了 Carbon 事件管理器标准处理程序?也许kEventAppShown 有一个标准处理程序,它在AppKit 自己的处理程序前面潜水。 developer.apple.com/legacy/mac/library/documentation/Carbon/…
  • 谢谢彼得。我们对 Carbon 事件处理程序的使用仅限于一个必须访问才能安装它们的功能 - 即便如此,它们也不查看该事件。然后我(简要地)想,也许我们有旧的 WaitNextEvent 或 GetNextEvent,但它也被剥离了。
  • 您的窗口是 NSWindow 还是碳窗口?无论如何,我可能会建议将碳事件附加到窗口或将 NSWindow 子类化并观察 -orderOut 等方法,以查看它们是否被调用。确定窗口本身是否被告知显示可能很有用。如果它被告知要显示,那么您可以开始寻找其他地方。

标签: objective-c macos cocoa


【解决方案1】:

在 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 的核心进程部分有自己的内部队列,用于跟踪显示和隐藏以及其他类型的事件类型,以便它不会发送重复的消息。 (我会调查这个flush是否真的有必要)

问题是当discardEventsMatchingMask:NSAnyEventMask每个事件上被调用时,它会清除应用程序的事件,但不响应核心进程的显示 event 等核心进程认为不需要再次发送 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];

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

特别感谢 Apple 的 KF!

【讨论】:

  • 非常有帮助的答案。但我怀疑在创建掩码时,应该是 NSPeriodicMask 等,而不是 NSPeriodic 等,或者更现代的 NSEventMaskPeriodic 等。
【解决方案2】:

此技术不适合发表评论,但我可能会建议与 DTrace 近距离接触。我在上面的 cmets 中建议将 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 的内容显然并非易事,但可能值得付出努力。我确实有一个问题herehere 想看看是否有人知道做这些事情中的任何一件。 (如果可以得到窗口的标题,可以设置窗口地址到字符串的映射。)

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

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

【讨论】:

  • 我承诺我会将赏金奖励给给我其他探索途径的人。我还没有探索过这个,但也许它会引导我找到答案。谢谢埃里克。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-04
  • 2021-09-27
相关资源
最近更新 更多