Custom Carbon key event handler fails after mouse events
I'm trying to write a custom NSMenu which will be able to list for key input and intercept the necessary events. This is to provide a simple search-as-you-type functionality for my open source clipboard manager.
It seems like the only way to do this is to install a custom Carbon event handler which will listen for key events and handler them accordingly, but it seems like there is an issue with such a custom handler.
Normally, I can propagate events downwards to other handlers (e.g. system ones) and they should be gracefully handled. This can be done by a simple callback:
let eventHandlerCallback: EventHandlerUPP = { eventHandlerCallRef, eventRef, userData in
let response = CallNextEventHandler(eventHandlerCallRef, eventRef!)
print("Response (response)")
return response
}
This callback works perfectly and prints Response 0
all the time. This response means that the event is handled correctly.
However, things get weird once we send mouse events before keyboard events. In such case, the callback fails and prints Response -9874
. This response means that the event was not handled correctly.
It seems like the event fails to be handled somewhere below my custom view and I don't know where exactly or how to overcome this issue.
To reproduce, I've uploaded the code to Gist which can be added to XCode playground and run. Once you see menu popup, press some keys (preferably arrow keys as they won't close the menu) and observe Response 0
in the console. After that, move cursor inside the menu and press more arrow keys. You should see Response -9874
in the console now.
swift cocoa appkit macos-carbon
|
show 3 more comments
I'm trying to write a custom NSMenu which will be able to list for key input and intercept the necessary events. This is to provide a simple search-as-you-type functionality for my open source clipboard manager.
It seems like the only way to do this is to install a custom Carbon event handler which will listen for key events and handler them accordingly, but it seems like there is an issue with such a custom handler.
Normally, I can propagate events downwards to other handlers (e.g. system ones) and they should be gracefully handled. This can be done by a simple callback:
let eventHandlerCallback: EventHandlerUPP = { eventHandlerCallRef, eventRef, userData in
let response = CallNextEventHandler(eventHandlerCallRef, eventRef!)
print("Response (response)")
return response
}
This callback works perfectly and prints Response 0
all the time. This response means that the event is handled correctly.
However, things get weird once we send mouse events before keyboard events. In such case, the callback fails and prints Response -9874
. This response means that the event was not handled correctly.
It seems like the event fails to be handled somewhere below my custom view and I don't know where exactly or how to overcome this issue.
To reproduce, I've uploaded the code to Gist which can be added to XCode playground and run. Once you see menu popup, press some keys (preferably arrow keys as they won't close the menu) and observe Response 0
in the console. After that, move cursor inside the menu and press more arrow keys. You should see Response -9874
in the console now.
swift cocoa appkit macos-carbon
1
What leads you believe that you have to use a Carbon event handler for this? Have you triedNSEvent.addLocalMonitorForEvents(matching:handler:)
? What else did you consider and discard, and why did you discard it? Also, why are you monitoring key input (other than a keyboard shortcut) from anNSMenu
? Why not some other object, like a controller or custom application class?
– Ken Thomases
Nov 13 '18 at 4:30
I have tried usingNSEvent.addLocalMonitorForEvents(matching:handler:)
but it looks like the menu subsystem doesn't use it at all. I also cannot useNSEvent.addGLobalMonitorForEvents(matching:handler:)
because it doesn't allow to stop event propagation if necessary. Monitoring of key input can be done from anywhere (NSView
in this case), but it doesn't matter since Carbon handlers are installed globally, and it seems like the only way to monitor events and useNSMenu
vs. writing customNSMenu
implementation which can use a normal localNSEvent
monitors or view methods.
– p0deje
Nov 13 '18 at 6:21
Are you sure you need to propagate the event?+
– Aris
Nov 15 '18 at 13:57
Even if I don't explicitly propagate event and let Carbon do it for me, it still fails.
– p0deje
Nov 16 '18 at 6:44
I checked what the -9874 error means. According to the old Carbon Event Manager codes -9874 =eventNotHandledErr
, and this is returned when "This is what you should return from an event handler when your handler has received an event it doesn't currently want to (or isn't able to) handle. If you handle an event, you should return noErr from your event handler." I used to handle and still handle old-style Carbon Events, and is perfectly OK to ignore such an error in some cases. So, what do you exactly mean with "it still fails"?
– jvarela
Nov 19 '18 at 0:55
|
show 3 more comments
I'm trying to write a custom NSMenu which will be able to list for key input and intercept the necessary events. This is to provide a simple search-as-you-type functionality for my open source clipboard manager.
It seems like the only way to do this is to install a custom Carbon event handler which will listen for key events and handler them accordingly, but it seems like there is an issue with such a custom handler.
Normally, I can propagate events downwards to other handlers (e.g. system ones) and they should be gracefully handled. This can be done by a simple callback:
let eventHandlerCallback: EventHandlerUPP = { eventHandlerCallRef, eventRef, userData in
let response = CallNextEventHandler(eventHandlerCallRef, eventRef!)
print("Response (response)")
return response
}
This callback works perfectly and prints Response 0
all the time. This response means that the event is handled correctly.
However, things get weird once we send mouse events before keyboard events. In such case, the callback fails and prints Response -9874
. This response means that the event was not handled correctly.
It seems like the event fails to be handled somewhere below my custom view and I don't know where exactly or how to overcome this issue.
To reproduce, I've uploaded the code to Gist which can be added to XCode playground and run. Once you see menu popup, press some keys (preferably arrow keys as they won't close the menu) and observe Response 0
in the console. After that, move cursor inside the menu and press more arrow keys. You should see Response -9874
in the console now.
swift cocoa appkit macos-carbon
I'm trying to write a custom NSMenu which will be able to list for key input and intercept the necessary events. This is to provide a simple search-as-you-type functionality for my open source clipboard manager.
It seems like the only way to do this is to install a custom Carbon event handler which will listen for key events and handler them accordingly, but it seems like there is an issue with such a custom handler.
Normally, I can propagate events downwards to other handlers (e.g. system ones) and they should be gracefully handled. This can be done by a simple callback:
let eventHandlerCallback: EventHandlerUPP = { eventHandlerCallRef, eventRef, userData in
let response = CallNextEventHandler(eventHandlerCallRef, eventRef!)
print("Response (response)")
return response
}
This callback works perfectly and prints Response 0
all the time. This response means that the event is handled correctly.
However, things get weird once we send mouse events before keyboard events. In such case, the callback fails and prints Response -9874
. This response means that the event was not handled correctly.
It seems like the event fails to be handled somewhere below my custom view and I don't know where exactly or how to overcome this issue.
To reproduce, I've uploaded the code to Gist which can be added to XCode playground and run. Once you see menu popup, press some keys (preferably arrow keys as they won't close the menu) and observe Response 0
in the console. After that, move cursor inside the menu and press more arrow keys. You should see Response -9874
in the console now.
swift cocoa appkit macos-carbon
swift cocoa appkit macos-carbon
edited Nov 13 '18 at 3:06
rmaddy
240k27315379
240k27315379
asked Nov 13 '18 at 3:06
p0dejep0deje
2,8381832
2,8381832
1
What leads you believe that you have to use a Carbon event handler for this? Have you triedNSEvent.addLocalMonitorForEvents(matching:handler:)
? What else did you consider and discard, and why did you discard it? Also, why are you monitoring key input (other than a keyboard shortcut) from anNSMenu
? Why not some other object, like a controller or custom application class?
– Ken Thomases
Nov 13 '18 at 4:30
I have tried usingNSEvent.addLocalMonitorForEvents(matching:handler:)
but it looks like the menu subsystem doesn't use it at all. I also cannot useNSEvent.addGLobalMonitorForEvents(matching:handler:)
because it doesn't allow to stop event propagation if necessary. Monitoring of key input can be done from anywhere (NSView
in this case), but it doesn't matter since Carbon handlers are installed globally, and it seems like the only way to monitor events and useNSMenu
vs. writing customNSMenu
implementation which can use a normal localNSEvent
monitors or view methods.
– p0deje
Nov 13 '18 at 6:21
Are you sure you need to propagate the event?+
– Aris
Nov 15 '18 at 13:57
Even if I don't explicitly propagate event and let Carbon do it for me, it still fails.
– p0deje
Nov 16 '18 at 6:44
I checked what the -9874 error means. According to the old Carbon Event Manager codes -9874 =eventNotHandledErr
, and this is returned when "This is what you should return from an event handler when your handler has received an event it doesn't currently want to (or isn't able to) handle. If you handle an event, you should return noErr from your event handler." I used to handle and still handle old-style Carbon Events, and is perfectly OK to ignore such an error in some cases. So, what do you exactly mean with "it still fails"?
– jvarela
Nov 19 '18 at 0:55
|
show 3 more comments
1
What leads you believe that you have to use a Carbon event handler for this? Have you triedNSEvent.addLocalMonitorForEvents(matching:handler:)
? What else did you consider and discard, and why did you discard it? Also, why are you monitoring key input (other than a keyboard shortcut) from anNSMenu
? Why not some other object, like a controller or custom application class?
– Ken Thomases
Nov 13 '18 at 4:30
I have tried usingNSEvent.addLocalMonitorForEvents(matching:handler:)
but it looks like the menu subsystem doesn't use it at all. I also cannot useNSEvent.addGLobalMonitorForEvents(matching:handler:)
because it doesn't allow to stop event propagation if necessary. Monitoring of key input can be done from anywhere (NSView
in this case), but it doesn't matter since Carbon handlers are installed globally, and it seems like the only way to monitor events and useNSMenu
vs. writing customNSMenu
implementation which can use a normal localNSEvent
monitors or view methods.
– p0deje
Nov 13 '18 at 6:21
Are you sure you need to propagate the event?+
– Aris
Nov 15 '18 at 13:57
Even if I don't explicitly propagate event and let Carbon do it for me, it still fails.
– p0deje
Nov 16 '18 at 6:44
I checked what the -9874 error means. According to the old Carbon Event Manager codes -9874 =eventNotHandledErr
, and this is returned when "This is what you should return from an event handler when your handler has received an event it doesn't currently want to (or isn't able to) handle. If you handle an event, you should return noErr from your event handler." I used to handle and still handle old-style Carbon Events, and is perfectly OK to ignore such an error in some cases. So, what do you exactly mean with "it still fails"?
– jvarela
Nov 19 '18 at 0:55
1
1
What leads you believe that you have to use a Carbon event handler for this? Have you tried
NSEvent.addLocalMonitorForEvents(matching:handler:)
? What else did you consider and discard, and why did you discard it? Also, why are you monitoring key input (other than a keyboard shortcut) from an NSMenu
? Why not some other object, like a controller or custom application class?– Ken Thomases
Nov 13 '18 at 4:30
What leads you believe that you have to use a Carbon event handler for this? Have you tried
NSEvent.addLocalMonitorForEvents(matching:handler:)
? What else did you consider and discard, and why did you discard it? Also, why are you monitoring key input (other than a keyboard shortcut) from an NSMenu
? Why not some other object, like a controller or custom application class?– Ken Thomases
Nov 13 '18 at 4:30
I have tried using
NSEvent.addLocalMonitorForEvents(matching:handler:)
but it looks like the menu subsystem doesn't use it at all. I also cannot use NSEvent.addGLobalMonitorForEvents(matching:handler:)
because it doesn't allow to stop event propagation if necessary. Monitoring of key input can be done from anywhere (NSView
in this case), but it doesn't matter since Carbon handlers are installed globally, and it seems like the only way to monitor events and use NSMenu
vs. writing custom NSMenu
implementation which can use a normal local NSEvent
monitors or view methods.– p0deje
Nov 13 '18 at 6:21
I have tried using
NSEvent.addLocalMonitorForEvents(matching:handler:)
but it looks like the menu subsystem doesn't use it at all. I also cannot use NSEvent.addGLobalMonitorForEvents(matching:handler:)
because it doesn't allow to stop event propagation if necessary. Monitoring of key input can be done from anywhere (NSView
in this case), but it doesn't matter since Carbon handlers are installed globally, and it seems like the only way to monitor events and use NSMenu
vs. writing custom NSMenu
implementation which can use a normal local NSEvent
monitors or view methods.– p0deje
Nov 13 '18 at 6:21
Are you sure you need to propagate the event?+
– Aris
Nov 15 '18 at 13:57
Are you sure you need to propagate the event?+
– Aris
Nov 15 '18 at 13:57
Even if I don't explicitly propagate event and let Carbon do it for me, it still fails.
– p0deje
Nov 16 '18 at 6:44
Even if I don't explicitly propagate event and let Carbon do it for me, it still fails.
– p0deje
Nov 16 '18 at 6:44
I checked what the -9874 error means. According to the old Carbon Event Manager codes -9874 =
eventNotHandledErr
, and this is returned when "This is what you should return from an event handler when your handler has received an event it doesn't currently want to (or isn't able to) handle. If you handle an event, you should return noErr from your event handler." I used to handle and still handle old-style Carbon Events, and is perfectly OK to ignore such an error in some cases. So, what do you exactly mean with "it still fails"?– jvarela
Nov 19 '18 at 0:55
I checked what the -9874 error means. According to the old Carbon Event Manager codes -9874 =
eventNotHandledErr
, and this is returned when "This is what you should return from an event handler when your handler has received an event it doesn't currently want to (or isn't able to) handle. If you handle an event, you should return noErr from your event handler." I used to handle and still handle old-style Carbon Events, and is perfectly OK to ignore such an error in some cases. So, what do you exactly mean with "it still fails"?– jvarela
Nov 19 '18 at 0:55
|
show 3 more comments
1 Answer
1
active
oldest
votes
Unclear if you have an NSTextField
as your menu view, but if you use one then it's easy to setup a delegate for that text field that can get the current contents of the field as the user types (this takes care of them moving backwards with the arrow keys and then deleting characters, using the delete key, etc). Your delegate implements the appropriate delegate method and gets called each time the text changes:
extension CustomMenuItemViewController: NSTextFieldDelegate {
func controlTextDidChange( _ obj: Notification) {
if let postingObject = obj.object as? NSTextField {
let text = postingObject.stringValue
print("the text is now: (text)")
}
}
}
Just to confirm this works as expected, I created the ViewController class for the custom menu item views (label + edit field) in a xib file and then dynamically built a simple test menu with a single menu item that has the custom view controller's view and added it to the menubar inside my app delegate:
func installCustomMenuItem() {
let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let menu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = menuItemVC.view
menu.addItem(subMenuBarItem)
menuBarItem.submenu = menu
NSApp.mainMenu?.addItem(menuBarItem)
}
Looks like this after I typed "hello":
And you can from the console that my handler got called for every character typed:
the text is now: H
the text is now: He
the text is now: Hel
the text is now: Hell
the text is now: Hello
Your situation is probably a little different, but it seems like this approach is very clean and might work for you. If it won't for some reason, add a clarifying comment and we'll see if we can't make it work for you.
Addition:
It occurred to me that you might wish to not use NSTextField
and so I was curious if it was as easy to do this with a custom view and it's relatively easy.
Make a subclass of NSView
:
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
Set the class of your root view in the custom view controller to be of this type of class and then all done as before - view controller loaded in applicationDidFinishLaunching
and menus built and view controller's view (which is now a CustomMenuView
) is set as the menuBarItem.view.
That's it. You now get your keyDown
method called for every key down when the menu is dropped down.
key down with character: Optional("H")
key down with character: Optional("e")
key down with character: Optional("l")
key down with character: Optional("l")
key down with character: Optional("o")
key down with character: Optional(" ")
key down with character: Optional("T")
key down with character: Optional("h")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("c")
key down with character: Optional("o")
key down with character: Optional("o")
key down with character: Optional("l")
:)
Now your custom view (and subviews if you like) can do their own drawing and so on.
Addition with requested sample without the ViewController:
// Simple swift playground test
// The pop-up menu will show up onscreen in the playground at a fixed location.
// Click in the popup and then all key commands will be logged.
// The ViewController in my example above may be taking care of putting the custom view in the responder chain, or the fact that it's in a menubar and being invoked via a MenuItem might be.
// I'd suggest trying it in the actual environment rather than in a playground. In my test app you click the menu name in the menubar to drop down the menu and it is added to the responder chain and works as expected without having to click in the menu first to get the events flowing.
// There is no reason you need to be hooking events either with carbon events or the newer format. If you're in the responder chain of and implement the necessary, method then you'll get the key events you're looking for.
import AppKit
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
func installCustomMenuItem() -> NSMenu {
// let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let resultMenu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = CustomMenuView(frame: NSRect(x: 0, y: 0, width: 40, height: 44))
resultMenu.addItem(subMenuBarItem)
// menuBarItem.submenu = menu
return resultMenu
}
var menu = installCustomMenuItem()
menu.popUp(positioning: nil, at: NSPoint(x:600,y:400), in: nil)
Sorry, but I still can't make this work. I'm not sure if ViewController is required in this chain, but I tried assigningCustomView
directly to menu item view and it couldn't receive any events. Is linking view-controller via xib required? Is it possible for you to adapt original Gist code I used in Playground to make it work?
– p0deje
Dec 2 '18 at 5:20
playground is a rather different execution environment. I'm not sure things will work there the way they do in an actual app.
– Dad
Dec 3 '18 at 4:23
Did a quick test and you can kind of make it work in a playground but the responder chain is a little different so you have to do some manual work to get something in the responder chain that'll send events to your view. Clicking in the menu will do this. I don't have this issue in the sample app with it as a normal menu that drops down when you click on the menu in the menu bar.
– Dad
Dec 3 '18 at 4:55
I wonder if your issues are happening only when you use a keydown hook of some kind to show your menu? If so, then I suspect it's because you have not make your view the first responder manually and you may need to do so.
– Dad
Dec 3 '18 at 4:57
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53273191%2fcustom-carbon-key-event-handler-fails-after-mouse-events%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
Unclear if you have an NSTextField
as your menu view, but if you use one then it's easy to setup a delegate for that text field that can get the current contents of the field as the user types (this takes care of them moving backwards with the arrow keys and then deleting characters, using the delete key, etc). Your delegate implements the appropriate delegate method and gets called each time the text changes:
extension CustomMenuItemViewController: NSTextFieldDelegate {
func controlTextDidChange( _ obj: Notification) {
if let postingObject = obj.object as? NSTextField {
let text = postingObject.stringValue
print("the text is now: (text)")
}
}
}
Just to confirm this works as expected, I created the ViewController class for the custom menu item views (label + edit field) in a xib file and then dynamically built a simple test menu with a single menu item that has the custom view controller's view and added it to the menubar inside my app delegate:
func installCustomMenuItem() {
let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let menu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = menuItemVC.view
menu.addItem(subMenuBarItem)
menuBarItem.submenu = menu
NSApp.mainMenu?.addItem(menuBarItem)
}
Looks like this after I typed "hello":
And you can from the console that my handler got called for every character typed:
the text is now: H
the text is now: He
the text is now: Hel
the text is now: Hell
the text is now: Hello
Your situation is probably a little different, but it seems like this approach is very clean and might work for you. If it won't for some reason, add a clarifying comment and we'll see if we can't make it work for you.
Addition:
It occurred to me that you might wish to not use NSTextField
and so I was curious if it was as easy to do this with a custom view and it's relatively easy.
Make a subclass of NSView
:
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
Set the class of your root view in the custom view controller to be of this type of class and then all done as before - view controller loaded in applicationDidFinishLaunching
and menus built and view controller's view (which is now a CustomMenuView
) is set as the menuBarItem.view.
That's it. You now get your keyDown
method called for every key down when the menu is dropped down.
key down with character: Optional("H")
key down with character: Optional("e")
key down with character: Optional("l")
key down with character: Optional("l")
key down with character: Optional("o")
key down with character: Optional(" ")
key down with character: Optional("T")
key down with character: Optional("h")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("c")
key down with character: Optional("o")
key down with character: Optional("o")
key down with character: Optional("l")
:)
Now your custom view (and subviews if you like) can do their own drawing and so on.
Addition with requested sample without the ViewController:
// Simple swift playground test
// The pop-up menu will show up onscreen in the playground at a fixed location.
// Click in the popup and then all key commands will be logged.
// The ViewController in my example above may be taking care of putting the custom view in the responder chain, or the fact that it's in a menubar and being invoked via a MenuItem might be.
// I'd suggest trying it in the actual environment rather than in a playground. In my test app you click the menu name in the menubar to drop down the menu and it is added to the responder chain and works as expected without having to click in the menu first to get the events flowing.
// There is no reason you need to be hooking events either with carbon events or the newer format. If you're in the responder chain of and implement the necessary, method then you'll get the key events you're looking for.
import AppKit
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
func installCustomMenuItem() -> NSMenu {
// let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let resultMenu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = CustomMenuView(frame: NSRect(x: 0, y: 0, width: 40, height: 44))
resultMenu.addItem(subMenuBarItem)
// menuBarItem.submenu = menu
return resultMenu
}
var menu = installCustomMenuItem()
menu.popUp(positioning: nil, at: NSPoint(x:600,y:400), in: nil)
Sorry, but I still can't make this work. I'm not sure if ViewController is required in this chain, but I tried assigningCustomView
directly to menu item view and it couldn't receive any events. Is linking view-controller via xib required? Is it possible for you to adapt original Gist code I used in Playground to make it work?
– p0deje
Dec 2 '18 at 5:20
playground is a rather different execution environment. I'm not sure things will work there the way they do in an actual app.
– Dad
Dec 3 '18 at 4:23
Did a quick test and you can kind of make it work in a playground but the responder chain is a little different so you have to do some manual work to get something in the responder chain that'll send events to your view. Clicking in the menu will do this. I don't have this issue in the sample app with it as a normal menu that drops down when you click on the menu in the menu bar.
– Dad
Dec 3 '18 at 4:55
I wonder if your issues are happening only when you use a keydown hook of some kind to show your menu? If so, then I suspect it's because you have not make your view the first responder manually and you may need to do so.
– Dad
Dec 3 '18 at 4:57
add a comment |
Unclear if you have an NSTextField
as your menu view, but if you use one then it's easy to setup a delegate for that text field that can get the current contents of the field as the user types (this takes care of them moving backwards with the arrow keys and then deleting characters, using the delete key, etc). Your delegate implements the appropriate delegate method and gets called each time the text changes:
extension CustomMenuItemViewController: NSTextFieldDelegate {
func controlTextDidChange( _ obj: Notification) {
if let postingObject = obj.object as? NSTextField {
let text = postingObject.stringValue
print("the text is now: (text)")
}
}
}
Just to confirm this works as expected, I created the ViewController class for the custom menu item views (label + edit field) in a xib file and then dynamically built a simple test menu with a single menu item that has the custom view controller's view and added it to the menubar inside my app delegate:
func installCustomMenuItem() {
let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let menu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = menuItemVC.view
menu.addItem(subMenuBarItem)
menuBarItem.submenu = menu
NSApp.mainMenu?.addItem(menuBarItem)
}
Looks like this after I typed "hello":
And you can from the console that my handler got called for every character typed:
the text is now: H
the text is now: He
the text is now: Hel
the text is now: Hell
the text is now: Hello
Your situation is probably a little different, but it seems like this approach is very clean and might work for you. If it won't for some reason, add a clarifying comment and we'll see if we can't make it work for you.
Addition:
It occurred to me that you might wish to not use NSTextField
and so I was curious if it was as easy to do this with a custom view and it's relatively easy.
Make a subclass of NSView
:
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
Set the class of your root view in the custom view controller to be of this type of class and then all done as before - view controller loaded in applicationDidFinishLaunching
and menus built and view controller's view (which is now a CustomMenuView
) is set as the menuBarItem.view.
That's it. You now get your keyDown
method called for every key down when the menu is dropped down.
key down with character: Optional("H")
key down with character: Optional("e")
key down with character: Optional("l")
key down with character: Optional("l")
key down with character: Optional("o")
key down with character: Optional(" ")
key down with character: Optional("T")
key down with character: Optional("h")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("c")
key down with character: Optional("o")
key down with character: Optional("o")
key down with character: Optional("l")
:)
Now your custom view (and subviews if you like) can do their own drawing and so on.
Addition with requested sample without the ViewController:
// Simple swift playground test
// The pop-up menu will show up onscreen in the playground at a fixed location.
// Click in the popup and then all key commands will be logged.
// The ViewController in my example above may be taking care of putting the custom view in the responder chain, or the fact that it's in a menubar and being invoked via a MenuItem might be.
// I'd suggest trying it in the actual environment rather than in a playground. In my test app you click the menu name in the menubar to drop down the menu and it is added to the responder chain and works as expected without having to click in the menu first to get the events flowing.
// There is no reason you need to be hooking events either with carbon events or the newer format. If you're in the responder chain of and implement the necessary, method then you'll get the key events you're looking for.
import AppKit
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
func installCustomMenuItem() -> NSMenu {
// let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let resultMenu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = CustomMenuView(frame: NSRect(x: 0, y: 0, width: 40, height: 44))
resultMenu.addItem(subMenuBarItem)
// menuBarItem.submenu = menu
return resultMenu
}
var menu = installCustomMenuItem()
menu.popUp(positioning: nil, at: NSPoint(x:600,y:400), in: nil)
Sorry, but I still can't make this work. I'm not sure if ViewController is required in this chain, but I tried assigningCustomView
directly to menu item view and it couldn't receive any events. Is linking view-controller via xib required? Is it possible for you to adapt original Gist code I used in Playground to make it work?
– p0deje
Dec 2 '18 at 5:20
playground is a rather different execution environment. I'm not sure things will work there the way they do in an actual app.
– Dad
Dec 3 '18 at 4:23
Did a quick test and you can kind of make it work in a playground but the responder chain is a little different so you have to do some manual work to get something in the responder chain that'll send events to your view. Clicking in the menu will do this. I don't have this issue in the sample app with it as a normal menu that drops down when you click on the menu in the menu bar.
– Dad
Dec 3 '18 at 4:55
I wonder if your issues are happening only when you use a keydown hook of some kind to show your menu? If so, then I suspect it's because you have not make your view the first responder manually and you may need to do so.
– Dad
Dec 3 '18 at 4:57
add a comment |
Unclear if you have an NSTextField
as your menu view, but if you use one then it's easy to setup a delegate for that text field that can get the current contents of the field as the user types (this takes care of them moving backwards with the arrow keys and then deleting characters, using the delete key, etc). Your delegate implements the appropriate delegate method and gets called each time the text changes:
extension CustomMenuItemViewController: NSTextFieldDelegate {
func controlTextDidChange( _ obj: Notification) {
if let postingObject = obj.object as? NSTextField {
let text = postingObject.stringValue
print("the text is now: (text)")
}
}
}
Just to confirm this works as expected, I created the ViewController class for the custom menu item views (label + edit field) in a xib file and then dynamically built a simple test menu with a single menu item that has the custom view controller's view and added it to the menubar inside my app delegate:
func installCustomMenuItem() {
let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let menu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = menuItemVC.view
menu.addItem(subMenuBarItem)
menuBarItem.submenu = menu
NSApp.mainMenu?.addItem(menuBarItem)
}
Looks like this after I typed "hello":
And you can from the console that my handler got called for every character typed:
the text is now: H
the text is now: He
the text is now: Hel
the text is now: Hell
the text is now: Hello
Your situation is probably a little different, but it seems like this approach is very clean and might work for you. If it won't for some reason, add a clarifying comment and we'll see if we can't make it work for you.
Addition:
It occurred to me that you might wish to not use NSTextField
and so I was curious if it was as easy to do this with a custom view and it's relatively easy.
Make a subclass of NSView
:
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
Set the class of your root view in the custom view controller to be of this type of class and then all done as before - view controller loaded in applicationDidFinishLaunching
and menus built and view controller's view (which is now a CustomMenuView
) is set as the menuBarItem.view.
That's it. You now get your keyDown
method called for every key down when the menu is dropped down.
key down with character: Optional("H")
key down with character: Optional("e")
key down with character: Optional("l")
key down with character: Optional("l")
key down with character: Optional("o")
key down with character: Optional(" ")
key down with character: Optional("T")
key down with character: Optional("h")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("c")
key down with character: Optional("o")
key down with character: Optional("o")
key down with character: Optional("l")
:)
Now your custom view (and subviews if you like) can do their own drawing and so on.
Addition with requested sample without the ViewController:
// Simple swift playground test
// The pop-up menu will show up onscreen in the playground at a fixed location.
// Click in the popup and then all key commands will be logged.
// The ViewController in my example above may be taking care of putting the custom view in the responder chain, or the fact that it's in a menubar and being invoked via a MenuItem might be.
// I'd suggest trying it in the actual environment rather than in a playground. In my test app you click the menu name in the menubar to drop down the menu and it is added to the responder chain and works as expected without having to click in the menu first to get the events flowing.
// There is no reason you need to be hooking events either with carbon events or the newer format. If you're in the responder chain of and implement the necessary, method then you'll get the key events you're looking for.
import AppKit
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
func installCustomMenuItem() -> NSMenu {
// let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let resultMenu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = CustomMenuView(frame: NSRect(x: 0, y: 0, width: 40, height: 44))
resultMenu.addItem(subMenuBarItem)
// menuBarItem.submenu = menu
return resultMenu
}
var menu = installCustomMenuItem()
menu.popUp(positioning: nil, at: NSPoint(x:600,y:400), in: nil)
Unclear if you have an NSTextField
as your menu view, but if you use one then it's easy to setup a delegate for that text field that can get the current contents of the field as the user types (this takes care of them moving backwards with the arrow keys and then deleting characters, using the delete key, etc). Your delegate implements the appropriate delegate method and gets called each time the text changes:
extension CustomMenuItemViewController: NSTextFieldDelegate {
func controlTextDidChange( _ obj: Notification) {
if let postingObject = obj.object as? NSTextField {
let text = postingObject.stringValue
print("the text is now: (text)")
}
}
}
Just to confirm this works as expected, I created the ViewController class for the custom menu item views (label + edit field) in a xib file and then dynamically built a simple test menu with a single menu item that has the custom view controller's view and added it to the menubar inside my app delegate:
func installCustomMenuItem() {
let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let menu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = menuItemVC.view
menu.addItem(subMenuBarItem)
menuBarItem.submenu = menu
NSApp.mainMenu?.addItem(menuBarItem)
}
Looks like this after I typed "hello":
And you can from the console that my handler got called for every character typed:
the text is now: H
the text is now: He
the text is now: Hel
the text is now: Hell
the text is now: Hello
Your situation is probably a little different, but it seems like this approach is very clean and might work for you. If it won't for some reason, add a clarifying comment and we'll see if we can't make it work for you.
Addition:
It occurred to me that you might wish to not use NSTextField
and so I was curious if it was as easy to do this with a custom view and it's relatively easy.
Make a subclass of NSView
:
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
Set the class of your root view in the custom view controller to be of this type of class and then all done as before - view controller loaded in applicationDidFinishLaunching
and menus built and view controller's view (which is now a CustomMenuView
) is set as the menuBarItem.view.
That's it. You now get your keyDown
method called for every key down when the menu is dropped down.
key down with character: Optional("H")
key down with character: Optional("e")
key down with character: Optional("l")
key down with character: Optional("l")
key down with character: Optional("o")
key down with character: Optional(" ")
key down with character: Optional("T")
key down with character: Optional("h")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("i")
key down with character: Optional("s")
key down with character: Optional(" ")
key down with character: Optional("c")
key down with character: Optional("o")
key down with character: Optional("o")
key down with character: Optional("l")
:)
Now your custom view (and subviews if you like) can do their own drawing and so on.
Addition with requested sample without the ViewController:
// Simple swift playground test
// The pop-up menu will show up onscreen in the playground at a fixed location.
// Click in the popup and then all key commands will be logged.
// The ViewController in my example above may be taking care of putting the custom view in the responder chain, or the fact that it's in a menubar and being invoked via a MenuItem might be.
// I'd suggest trying it in the actual environment rather than in a playground. In my test app you click the menu name in the menubar to drop down the menu and it is added to the responder chain and works as expected without having to click in the menu first to get the events flowing.
// There is no reason you need to be hooking events either with carbon events or the newer format. If you're in the responder chain of and implement the necessary, method then you'll get the key events you're looking for.
import AppKit
class CustomMenuView: NSView {
override var acceptsFirstResponder: Bool {
return true
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func keyDown(with event: NSEvent) {
print("key down with character: (String(describing: event.characters)) " )
}
}
func installCustomMenuItem() -> NSMenu {
// let menuBarItem = NSMenuItem(title: "Test", action: nil, keyEquivalent: "")
let resultMenu = NSMenu(title: "TestMenu" )
let subMenuBarItem = NSMenuItem(title: "Custom View", action: nil, keyEquivalent: "")
subMenuBarItem.view = CustomMenuView(frame: NSRect(x: 0, y: 0, width: 40, height: 44))
resultMenu.addItem(subMenuBarItem)
// menuBarItem.submenu = menu
return resultMenu
}
var menu = installCustomMenuItem()
menu.popUp(positioning: nil, at: NSPoint(x:600,y:400), in: nil)
edited Dec 3 '18 at 4:52
answered Nov 28 '18 at 3:04
DadDad
4,40812028
4,40812028
Sorry, but I still can't make this work. I'm not sure if ViewController is required in this chain, but I tried assigningCustomView
directly to menu item view and it couldn't receive any events. Is linking view-controller via xib required? Is it possible for you to adapt original Gist code I used in Playground to make it work?
– p0deje
Dec 2 '18 at 5:20
playground is a rather different execution environment. I'm not sure things will work there the way they do in an actual app.
– Dad
Dec 3 '18 at 4:23
Did a quick test and you can kind of make it work in a playground but the responder chain is a little different so you have to do some manual work to get something in the responder chain that'll send events to your view. Clicking in the menu will do this. I don't have this issue in the sample app with it as a normal menu that drops down when you click on the menu in the menu bar.
– Dad
Dec 3 '18 at 4:55
I wonder if your issues are happening only when you use a keydown hook of some kind to show your menu? If so, then I suspect it's because you have not make your view the first responder manually and you may need to do so.
– Dad
Dec 3 '18 at 4:57
add a comment |
Sorry, but I still can't make this work. I'm not sure if ViewController is required in this chain, but I tried assigningCustomView
directly to menu item view and it couldn't receive any events. Is linking view-controller via xib required? Is it possible for you to adapt original Gist code I used in Playground to make it work?
– p0deje
Dec 2 '18 at 5:20
playground is a rather different execution environment. I'm not sure things will work there the way they do in an actual app.
– Dad
Dec 3 '18 at 4:23
Did a quick test and you can kind of make it work in a playground but the responder chain is a little different so you have to do some manual work to get something in the responder chain that'll send events to your view. Clicking in the menu will do this. I don't have this issue in the sample app with it as a normal menu that drops down when you click on the menu in the menu bar.
– Dad
Dec 3 '18 at 4:55
I wonder if your issues are happening only when you use a keydown hook of some kind to show your menu? If so, then I suspect it's because you have not make your view the first responder manually and you may need to do so.
– Dad
Dec 3 '18 at 4:57
Sorry, but I still can't make this work. I'm not sure if ViewController is required in this chain, but I tried assigning
CustomView
directly to menu item view and it couldn't receive any events. Is linking view-controller via xib required? Is it possible for you to adapt original Gist code I used in Playground to make it work?– p0deje
Dec 2 '18 at 5:20
Sorry, but I still can't make this work. I'm not sure if ViewController is required in this chain, but I tried assigning
CustomView
directly to menu item view and it couldn't receive any events. Is linking view-controller via xib required? Is it possible for you to adapt original Gist code I used in Playground to make it work?– p0deje
Dec 2 '18 at 5:20
playground is a rather different execution environment. I'm not sure things will work there the way they do in an actual app.
– Dad
Dec 3 '18 at 4:23
playground is a rather different execution environment. I'm not sure things will work there the way they do in an actual app.
– Dad
Dec 3 '18 at 4:23
Did a quick test and you can kind of make it work in a playground but the responder chain is a little different so you have to do some manual work to get something in the responder chain that'll send events to your view. Clicking in the menu will do this. I don't have this issue in the sample app with it as a normal menu that drops down when you click on the menu in the menu bar.
– Dad
Dec 3 '18 at 4:55
Did a quick test and you can kind of make it work in a playground but the responder chain is a little different so you have to do some manual work to get something in the responder chain that'll send events to your view. Clicking in the menu will do this. I don't have this issue in the sample app with it as a normal menu that drops down when you click on the menu in the menu bar.
– Dad
Dec 3 '18 at 4:55
I wonder if your issues are happening only when you use a keydown hook of some kind to show your menu? If so, then I suspect it's because you have not make your view the first responder manually and you may need to do so.
– Dad
Dec 3 '18 at 4:57
I wonder if your issues are happening only when you use a keydown hook of some kind to show your menu? If so, then I suspect it's because you have not make your view the first responder manually and you may need to do so.
– Dad
Dec 3 '18 at 4:57
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53273191%2fcustom-carbon-key-event-handler-fails-after-mouse-events%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
What leads you believe that you have to use a Carbon event handler for this? Have you tried
NSEvent.addLocalMonitorForEvents(matching:handler:)
? What else did you consider and discard, and why did you discard it? Also, why are you monitoring key input (other than a keyboard shortcut) from anNSMenu
? Why not some other object, like a controller or custom application class?– Ken Thomases
Nov 13 '18 at 4:30
I have tried using
NSEvent.addLocalMonitorForEvents(matching:handler:)
but it looks like the menu subsystem doesn't use it at all. I also cannot useNSEvent.addGLobalMonitorForEvents(matching:handler:)
because it doesn't allow to stop event propagation if necessary. Monitoring of key input can be done from anywhere (NSView
in this case), but it doesn't matter since Carbon handlers are installed globally, and it seems like the only way to monitor events and useNSMenu
vs. writing customNSMenu
implementation which can use a normal localNSEvent
monitors or view methods.– p0deje
Nov 13 '18 at 6:21
Are you sure you need to propagate the event?+
– Aris
Nov 15 '18 at 13:57
Even if I don't explicitly propagate event and let Carbon do it for me, it still fails.
– p0deje
Nov 16 '18 at 6:44
I checked what the -9874 error means. According to the old Carbon Event Manager codes -9874 =
eventNotHandledErr
, and this is returned when "This is what you should return from an event handler when your handler has received an event it doesn't currently want to (or isn't able to) handle. If you handle an event, you should return noErr from your event handler." I used to handle and still handle old-style Carbon Events, and is perfectly OK to ignore such an error in some cases. So, what do you exactly mean with "it still fails"?– jvarela
Nov 19 '18 at 0:55