Custom Carbon key event handler fails after mouse events












6















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.










share|improve this question




















  • 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











  • 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











  • 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


















6















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.










share|improve this question




















  • 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











  • 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











  • 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
















6












6








6








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.










share|improve this question
















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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











  • 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





    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











  • 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














1 Answer
1






active

oldest

votes


















0














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":



enter image description here



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)





share|improve this answer


























  • 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











  • 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











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
});


}
});














draft saved

draft discarded


















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









0














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":



enter image description here



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)





share|improve this answer


























  • 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











  • 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
















0














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":



enter image description here



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)





share|improve this answer


























  • 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











  • 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














0












0








0







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":



enter image description here



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)





share|improve this answer















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":



enter image description here



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)






share|improve this answer














share|improve this answer



share|improve this answer








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











  • 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











  • 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


















draft saved

draft discarded




















































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.




draft saved


draft discarded














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





















































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







Popular posts from this blog

Full-time equivalent

さくらももこ

13 indicted, 8 arrested in Calif. drug cartel investigation