如何解决使用热键切换 NSStatusItem 的菜单打开/关闭 - 代码执行排队/阻止
我正在编辑这个问题,因为我认为我可能过于简化了状态项菜单的打开方式。这么简单的函数,复杂的离谱!
我的状态项支持左键和右键单击操作。用户可以更改每次点击类型会发生什么。此外,由于 a macOS bug,当连接 2 个或更多屏幕/显示器并且它们垂直排列时,我必须做一些额外的特殊工作。
我正在使用 MASShortcut 通过系统范围的热键(例如“⌘ ⌥ M”)打开 NsstatusItem 的菜单,我发现一旦打开菜单,它就不是可以用热键关闭它。我正在尝试将菜单从关闭切换到打开,反之亦然。但是,当菜单打开时,代码执行会被阻止。有没有办法解决这个问题?我发现 this question 似乎是一个类似的问题,但遗憾的是没有找到答案。
在此先感谢您的帮助!
更新:Sample Project Demonstrating Issue
[[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey: kShowMenuHotkey toAction: ^
{
if (!self.statusMenuOpen)
{
[self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
}
else
{
[self.statusMenu cancelTracking];
}
}];
这是其他相关代码:
- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{
// CREATE AND CONfigURE THE STATUS ITEM
self.statusItem = [[NsstatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength];
[self.statusItem.button sendActionOn:(NSLeftMouseUpMask|NSRightMouseUpMask)];
[self.statusItem.button setAction: @selector(statusItemClicked:)];
self.statusMenu.delegate = self;
}
- (IBAction) statusItemClicked: (id) sender
{
// Logic exists here to determine if the status item click was a left or right click
// and whether the menu should show based on user prefs and click type
if (menuShouldShow)
{
[self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
}
}
- (IBAction) showStatusMenu: (id) sender
{
// macOS 10.15 introduced an issue with some status item menus not appearing
// properly when two or more screens/displays are arranged vertically
// Logic exists here to determine if this issue is present on the current system
if (@available(*,macOS 10.15))
{
if (verticalScreensIssuePresent)
{
[self performSelector:@selector(popUpStatusItemmenu) withObject:nil afterDelay:0.05];
}
else // vertical screens issues not present
{
// disPLAY THE MENU norMALLY
self.statusItem.menu = self.statusMenu;
[self.statusItem.button performClick:nil];
}
}
else // not macOS 10.15+
{
// disPLAY THE MENU norMALLY
self.statusItem.menu = self.statusMenu;
[self.statusItem.button performClick:nil];
}
}
- (void) popUpStatusItemmenu
{
// Logic exists here to determine how wide the menu is
// If the menu is too wide to fit on the right,display
// it on the left side of the status item
// menu is too wide for screen,need to open left side
if (pt.x + menuWidth >= NSMaxX(currentScreen.frame))
{
[self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
atLocation:CGPointMake((-menuWidth + self.statusItem.button.superview.frame.size.width),-5)
inView:[self.statusItem.button superview]];
}
else // not too wide
{
[self.statusMenu popUpMenuPositioningItem:[self.statusMenu itemAtIndex:0]
atLocation:CGPointMake(0,-5)
inView:[self.statusItem.button superview]];
}
}
解决方法
我可以确认你的观察
我正在尝试将菜单从关闭切换到打开,反之亦然。 当菜单打开时,代码执行被阻止
原因是 NSMenu
当打开时通过标准 NSEvent
队列接管应用程序的 __NSHLTBMenuEventProc
处理(它是内部 [NSApplication run]
处理)。
最终将实际触发快捷方式处理的事件是 NSEventTypeSystemDefined
子类型 6(9 是以下 keyUp,这里并不真正相关)。
打开菜单时根本不会触发那些 NSEventTypeSystemDefined
。某些机制正在推迟它们的触发,直到取消菜单并且应用返回到 [NSApplication run]
队列。 A 尝试了很多技巧和技巧来规避这一点,但都无济于事。
MASShortcut 使用旧版 Carbon API 来安装此自定义事件处理程序。我能够将它插入 NSMenu 内部事件调度程序(它在菜单未打开时工作)但它没有解决问题,因为前面提到的 NSEvent
没有首先被触发(直到菜单消失)。
我有根据的猜测是 MacOS WindowServer 管理着这个(因为它知道诸如按下的控制键之类的事情)。
无论如何,我很高兴您找到了解决方法。
如果有人想调试这些事件(我想这是我能提供的最好的起点)这里是我使用的代码:
int main(int argc,const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
Class clazz = NSApplication.class;
SEL selectorNextEventMatchingEventMask = NSSelectorFromString(@"_nextEventMatchingEventMask:untilDate:inMode:dequeue:");
Method method = class_getInstanceMethod(clazz,selectorNextEventMatchingEventMask);
const char *typesSelectorNextEventMatchingMask = method_getTypeEncoding(method);
IMP genuineSelectorNextEventMatchingMask = method_getImplementation(method);
IMP test = class_replaceMethod(clazz,selectorNextEventMatchingEventMask,imp_implementationWithBlock(^(__unsafe_unretained NSApplication* self,NSEventMask mask,NSDate* expiration,NSRunLoopMode mode,BOOL deqFlag) {
NSEvent* (*genuineSelectorNextEventMatchingMaskTyped)(id,SEL,NSEventMask,NSDate*,NSRunLoopMode,BOOL) = (void *)genuineSelectorNextEventMatchingMask;
NSEvent* event = genuineSelectorNextEventMatchingMaskTyped(self,mask,expiration,mode,deqFlag);
if (event.type == NSEventTypeSystemDefined) {
if (event.subtype == 6l) {
NSLog(@"⚪️ %@ %i %@",event,mode);
}
else if (event.subtype == 9l) {
NSLog(@"⚪️⚪️ %@ %i %@",mode);
}
else if (event.subtype == 7l) {
NSLog(@"? UNKNOWN %@ %i %@",mode);
}
else {
NSLog(@"? %@ %i %@",mode);
}
} else if (event == NULL && [mode isEqualToString:NSEventTrackingRunLoopMode]) {
//NSMenu "null" events happening here
NSLog(@"⚪️⚪️⚪️ %@ %i %@",mode);
} else if (event == NULL) {
NSLog(@"⭐️ %@ %i %@",mode);
} else {
NSLog(@"? %@ %i %@",mode);
}
return event;
}),typesSelectorNextEventMatchingMask);
return NSApplicationMain(argc,argv);
}
可以注意到 NSMenu 触发的事件将在 NSEventTrackingRunLoopMode
中运行,但这对于解决任何问题都不是特别有用。
我最终通过以编程方式将 NSMenuItem 的 keyEquivalent 分配为与 MASShortcut 热键值相同的热键来解决此问题。这允许用户使用相同的热键来执行不同的功能(关闭 NSMenu。)
设置热键时:
{this.state.Book.filter(Book => Book.id >= 8 && Book.id <= 12).map(Book =>
<Link to={Book.path} className="hpsurah">
<p className="hpid">{Book.id}</p>
<div>
<h2 className="hpsurahsq">{Book.surah}</h2>
<h1 className="hpsurahen">{Book.surahsq}</h1>
<h3 className="hpnumber">{Book.verses}</h3>
</div>
</Link>)}
然后,当菜单关闭时:
-(void) setupOpenCloseMenuHotKey
{
[[MASShortcutBinder sharedBinder] bindShortcutWithDefaultsKey: kShowMenuHotkey toAction: ^
{
// UNHIDES THE NEW "CLOSE MENU" MENU ITEM
self.closeMenuItem.hidden = NO;
// SET THE NEW "CLOSE MENU" MENU ITEM'S KEY EQUIVALENT TO BE THE SAME
// AS THE MASSHORTCUT VALUE
[self.closeMenuItem setKeyEquivalentModifierMask: self.showMenu.shortcutValue.modifierFlags];
[self.closeMenuItem setKeyEquivalent:self.showMenu.shortcutValue.keyCodeString];
self.showMenuTemp = [self.showMenu.shortcutValue copy];
self.showMenu.shortcutValue = nil;
dispatch_async(dispatch_get_main_queue(),^{
[self performSelector:@selector(showStatusMenu:) withObject:self afterDelay:0.01];
});
}];
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。