Intercept keydown events to NSWindow given to a plugin - objective-c

I am writing an audio plugin (VST) in Objective-C on OSX. My plugin gets loaded into an application and is given an NSWindow in which to add my own NSView. I need to be able to intercept keyboard events on the NSWindow which I can partly do, but not fully.
Here's what I have tried so far:
Make sure my view is the first response and handle keyDown events. This works for most keyDown events, but not carriage return or special keys like cut/copy/paste.
Use addLocalMonitorForEventsMatchingMask. This doesn't provide anything more useful than keyDown.
The NSWindow I'm given has a menu with some key equivalents for cut/copy/paste. I occasionally need to intercept these if the user has something selected in my NSView. I also occasionally need to intercept carriage return if the user is entering some data.
My UI is rendered using OpenGL so I'm not using standard Cocoa UI components apart from NSView to host my OpenGL surface.
I don't want the user to have to enable anything special to do this, like accessibility.
In my keyDown handler I have something like this:
- (void)keyDown:(NSEvent*)event
{
NSString* s = event.charactersIgnoringModifiers;
unichar modified_key = 0;
if (s && [s length] > 0)
{
modified_key = [s characterAtIndex:0];
}
if (modified_key == NSCarriageReturnCharacter)
{
// carriage return
}
}
This works in a stand alone application, but fails when it's hosted as an audio plugin. The problem I think is that the application hosting the plugin is intercepting events before they reach my event handlers.

You seem to be handling the event in a strange way. Process the NSEvent keyCode directly. You can use modifierFlags to get the modifiers.
This won't work for certain "system" key combinations (like Command + Space) for which you will need accessibility access.
- (void)keyDown:(NSEvent *)event
{
if (event.keyCode == 36)
NSLog(#"you pressed return!");
if (event.modifierFlags & NSEventModifierFlagCommand)
{
if (event.keyCode == 8)
NSLog(#"you pressed command+c!");
}
}

Related

"[GCController controllers]" does not contain any controllers that were connected prior to application launch

"[GCController controllers]" does not contain any controllers that were connected prior to application launch
TLDR;
I am trying to implement gamepad input on macOS using the Game Controller Framework. When invoked in my code, [GameController controllers] always returns an empty list until new controllers are connected. It never reflects gamepads connected to macOS prior to application launch, except if you disconnect them and reconnect them while the app is running. Does anyone know what I need to do to make controllers populate with pre-launch connections?
Full question
Now that Apple has added support for Xbox and Playstation controllers to the GameController framework, I'm trying to use it for gamepad input on a C++ game engine I'm developing. I'm using the framework instead of IOKit in order to "future-proof" my games to support additional controller types in the future, as well as to simplify my own input handling code.
Like many other game engines, I've foregone using NSApplicationMain() and nib files in favor of implementing my own event loop and setting up my game window programmatically. While my "Windows style" event loop appears to be working correctly, I've discovered that [GCController controllers] does not. The array it returns is always empty at launch, and will only ever reflect controllers that are connected while the game is running. Disconnecting a pre-connected controller does not trigger my GCControllerDidDisconnectNotification callback.
Here is a simplified version of my event loop:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// Create application
[NSApplication sharedApplication];
// Set up custom app delegate
CustomAppDelegate * delegate = [[CustomAppDelegate alloc] init];
[NSApp setDelegate:delegate];
// Activate and launch app
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[NSApp activateIgnoringOtherApps:YES]; // Strictly speaking, not necessary
[NSApp finishLaunching]; // NSMenu is set up at this point in applicationWillFinishLaunching:.
// Initialize game engine (window is created here)
GenericEngineCode_Init(); // <-- Where I want to call [GCController controllers]
NSEvent *e;
do
{
do
{
// Pump messages
e = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: nil
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (e)
{
[NSApp sendEvent: e];
[NSApp updateWindows];
}
} while (e);
} while (GenericEngineCode_Run()); // Steps the engine, returns false when quitting.
GenericEngineCode_Cleanup();
}
return 0;
}
I've confirmed that even when using [NSApp run] instead of [NSApp finishLaunching], the behavior is the same. As best as I can tell, the problem is that there's something NSApplicationMain() does that I'm not doing, but that function is a black box -- I can't identify what I need to do to get controllers to populate correctly. Does anyone know what I'm missing?
The closest thing I could find to an explanation of this problem is this answer, which suggests that my app isn't getting didBecomeActive notifications, or that at the least, the private _GCControllerManager isn't getting a CBApplicationDidBecomeActive message. I'm not a professional macOS developer, though: I don't know if this actually applies to my situation, or how I'd go about correcting the problem if it does.
After a huge amount of time searching, I found the answer on my own. It turns out that my code wasn't the problem -- the problem was that my Info.plist file was having its CFBundleIdentifier value stripped out due to a problem with my build system. It appears that the Game Controller Framework needs the bundle identifier to correctly populate [GCController controllers] at launch. While a missing CFBundleIdentifier would have been a problem anyway, as a Windows person it didn't occur to me that the identifier might be used for things besides the App Store, so I let it slide until now.
If someone else has this problem, make sure that CFBundleIdentifier isn't missing or empty in Info.plist in your assembled app bundle. In my case with Premake, I had to manually set PRODUCT_BUNDLE_IDENTIFIER with xcodebuildsettings so that $(PRODUCT_BUNDLE_IDENTIFIER) would get properly replaced in Info.plist.

Simulating mouse-down event on another window from CGWindowListCreate

You can list the currently open windows using CGWindowListCreate, e.g:
CFArrayRef windowListArray = CGWindowListCreate(kCGWindowListOptionOnScreenOnly|kCGWindowListExcludeDesktopElements, kCGNullWindowID);
NSArray *windows = CFBridgingRelease(CGWindowListCreateDescriptionFromArray(windowListArray));
// stuff here with windows array
CFRelease(windowListArray);
With this you can get a specific window, e.g. a window from Chrome that's somewhere in the background of the current workspace, but not minimized. I also found that you can simulate mouse-clicks anywhere on-screen using CGEventCreateMouseEvent:
CGEventRef click_down = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, point, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, click_down);
Instead of sending this event to the front-most window under this point on the screen, can I send this to a window in background? Or is the only way to temporarily switch to that window (bring it to front), click, and switch back to the previous frontmost window?
This post suggests the latter is possible:
Cocoa switch focus to application and then switch it back
Although I'm very interested in seeing whether this can be avoided, whether we can send mouse-clicks directly to a specific window in background without bringing it into view. I can consider any Objective-C, C, or C++ options for this.
Finally got this working after finding an alternative to the deprecated GetPSNForPID function:
NSEvent *customEvent = [NSEvent mouseEventWithType: NSEventTypeLeftMouseDown
location: point
modifierFlags: NSEventModifierFlagCommand
timestamp:[NSDate timeIntervalSinceReferenceDate]
windowNumber:[self.windowID intValue]
context: nil
eventNumber: 0
clickCount: 1
pressure: 0];
CGEvent = [customEvent CGEvent];
CGEventPostToPid(PID, CGEvent);
Didn't realise there was a CGEventPostToPid method instead of CGEventPostToPSN. No need for the ProcessSerialNumber struct.

DDHidLib - Implementing a joystick (Logitech 3D Extreme PRO)

I have recently decided to start a project which requires some kind of direct input to control my application and, in my case, I decided to use my Logitech 3D Extreme PRO joystick.
Therefore I started documenting myself online using the HID Class Device Interface Guide provided by Apple (Even the one dealing with the new HID Manager for OS X 10.5). I was able to implement the very main methods but unfortunately none of them worked.
After few days of search I have discovered a great Library developed by Daij-Djan : DDHidLib which helps a lot when dealing with direct inputs, providing great methods for discovering button presses and stick toggles, queues and lot more.
Even though this lib is a bit odd (2007), I decided to import it and give it a try..
I imported it into my project and started implementing some of it's methods which apparently seem very easy.
For instance, inside the DDHidJoystick sub-class, I found:
- (void)ddhidJoystick:(DDHidJoystick *)joystick buttonDown:(unsigned)buttonNumber
This method returns the number of the button that have been pressed.
Now, after this long introduction, let me explain my problem:
I coded a tested implementation of this class but without success (At least with my hardware).
Apparently with no reason the method reported above gets called only if I press one button on my joystick (Number 7).
Therefore I get some kind of notification {NSLog()} just in that case, even though the library is deigned to retrive any kind of input from the device.
But the most weird thing is that I am able to retrive all the other buttons/povs/sticks values only by pressing that specific button (N. 7) at the same time.
So let's say I want to get input from button 8, I will have to press button 8, than, at the same time, button number 7. Now i got a notification for both inputs.
If I want to get x-axis value, I need to:
Move x-stick
Press button n. 7
Then I see one notification both for button n.7 and x-axis moved at the same time.
To clarify, let me introduce this method:
- (void)ddhidJoystick:(DDHidJoystick *)joystick
stick:(unsigned)stick
xChanged:(int)value
As you can imagine, this method should be triggered whenever I move my X-Axis stick, however it doesn't.
Instead it gets triggered only if I press button number 7 and then, at the same time,I move my stick !
I tested out the joystick with X-Plane 10 and works just fine, so my guess is that there should be something else different from my app acquiring the input and hiding it.
I'm expecting to move my axis-sticks and se a NSLog, but that is not happening.
I'm not requesting a specific response on how to achieve my task using this lib, any other working approach will really be appreciated.
I really hope that this question is not too specific and could be helpful to somebody else in the future since nobody (apparently) tried to implement such input.
Thanks a lot to anyone who will reply to this post.
For the most curious:
I am building my own quad-copter using Arduino/Raspberry and lot of other electronics. I got a TX/RX Module operating at 2.4GHz which allows communication between 2 boards: one on the quad, and the other one plugged to the pc. I developed a lib (in C) reading POSIX documentation to read/write to serial ports and therefore I am able to send data over usb to my board, which than sends it to the quad. Finally I'am developing an OS X app to control the copter using the mentioned hardware/software and it is not far from being finished.
However for my purposes I want to use my joystick, and this is difficult.
In the end I will have a live video from onboard (FPV-Like) on the screen with telemetry all controlled by my Logitech 3D Extreme.
EDIT - I FOUND A SOLUTION
I found a solution and it seems to work pretty good!
Basically I had to edit a bit one method of the lib, adding support for the engine slider the joystick has.
Open up DDHidJoystick.m
Locate the method - (BOOL) addElement: (DDHidElement *) element;
Add the case statement case kHIDUsage_GD_Slider:
Set the action to [mStickElements addObject: element];
I will post the code here just in case somebody needs it in the future:
- (BOOL) addElement: (DDHidElement *) element;
{
DDHidUsage * usage = [element usage];
if ([usage usagePage] != kHIDPage_GenericDesktop)
return NO;
BOOL elementAdded = YES;
switch ([usage usageId])
{
case kHIDUsage_GD_X:
if (mXAxisElement == nil)
mXAxisElement = [element retain];
else
[mStickElements addObject: element];
break;
case kHIDUsage_GD_Y:
if (mYAxisElement == nil)
mYAxisElement = [element retain];
else
[mStickElements addObject: element];
break;
case kHIDUsage_GD_Z:
case kHIDUsage_GD_Rx:
case kHIDUsage_GD_Ry:
case kHIDUsage_GD_Rz:
[mStickElements addObject: element];
break;
case kHIDUsage_GD_Hatswitch:
[mPovElements addObject: element];
break;
/* EDIT HERE */
case kHIDUsage_GD_Slider:
[mStickElements addObject: element];
default:
elementAdded = NO;
}
return elementAdded;
}
Under this line you can find my whole implementation, and an image of the joystick.
(Developing on OS X 10.10 - Alberto Bellini)
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self startWatchingJoysticks];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)startWatchingJoysticks
{
//get an array of all joystick objects
joySticks = [[DDHidJoystick allJoysticks] retain];
//become the delegate of all available joystick objects
[joySticks makeObjectsPerformSelector:#selector(setDelegate:) withObject:self];
[joySticks makeObjectsPerformSelector:#selector(startListening) withObject:nil];
}
- (void)dealloc
{
[joySticks release];
[super dealloc];
}
//these are the DDHidLib joystick delegate methods related to buttons
- (void)ddhidJoystick:(DDHidJoystick *)joystick buttonDown:(unsigned)buttonNumber
{
NSLog(#"button down: %d", buttonNumber);
//Works only number 7
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick
stick:(unsigned)stick
xChanged:(int)value
{
NSLog(#"x axis %d",value);
//Works ONCE only if presing button number 7 and moving stick
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick
stick:(unsigned)stick
yChanged:(int)value
{
NSLog(#"y axis %d",value);
//Works ONCE only if presing button number 7 and moving stick
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick
stick:(unsigned)stick
otherAxis:(unsigned)otherAxis
valueChanged:(int)value
{
NSLog(#"z axis %d",value);
//Works ONCE only if presing button number 7 and moving stick
}
- (void)ddhidJoystick:(DDHidJoystick *)joystick
stick:(unsigned)stick
povNumber:(unsigned)povNumber
valueChanged:(int)value
{
NSLog(#"Pov changed");
//Works ONCE only if presing button number 7 and moving stick
}
#end
Maybe I found the problem with your joystick.
Logitech Extreme 3D has no standard data packet for joysticks. It has different HID report descriptor and I can not found HID report descriptor parser in DDHidLib. I think DDHidLib just assume standard data packet.
Check this link: http://www.circuitsathome.com/mcu/using-logitech-extreme-3d-pro-joystick-with-arduino-hid-library
Unfortunately, I can not help you more because I don't know nothing about objetive-c nor OSX nor HID.
Maybe you can modify data packet structure in DDHidLib, create a HID report descriptor parser for DDHidLib or get a new joystick with standard data packet. ;)

keyDown press failure

I want to put together a ping pong game in Xcode with Sprite Kit. But I want to do it for Mac OS X, there are no tutorials on fixing my code, and I want to make a keydown event to move the paddle. Here's my code.
-(void)keyDown:(NSEvent *)theEvent {
/* Called when a keyPress occurs */
// inside code
}
EDIT:
So I downloaded a pong game that is an Xcode project then I looked at it and saw this:
- (void)handleKeyEvent:(NSEvent*)keyEvent keyDown:(BOOL)isKeyDown {
if ([keyEvent keyCode] == LED_PONG_MOVE_UP || [keyEvent keyCode] == LED_PONG_MOVE_UP_ALT) {
self.moveUp = isKeyDown;
} else if ([keyEvent keyCode] == LED_PONG_MOVE_DOWN || [keyEvent keyCode] == LED_PONG_MOVE_DOWN_ALT) {
self.moveDown = isKeyDown;
}
}
and this:
#define LED_PONG_MOVE_UP 13 // W
#define LED_PONG_MOVE_UP_ALT 126 // Arrow Up
#define LED_PONG_MOVE_DOWN 1 // S
#define LED_PONG_MOVE_DOWN_ALT 125 // Arrow Down
So that about solves it for anyone in my place, fellow iOS haters, and Mac OS X lovers.
Notify me for more info if you need help.
It sounds like you simply need to make certain that your SKScene is designated as the first responder, which means that all events come in through your SKScene first.
So when your game (or scene) starts up, make certain that your NSWindow calls "makeFirstResponder:" with your SKScene object as the parameter.

Disable cut and/or paste in ObjC

I'd like to disable cut and/or paste in the menubar at runtime, in my ObjC app. I know that that's possible in iOS using
-(BOOL)canPerformAction:(SEL)aSelector withSender:(id)sender
Is there anything similar for MacOS?
Thank you
There is the NSUserInterfaceValidations Protocol, a generic protocol for validating items. You simply implement the validateUserInterfaceItem: method and return NO to disable the action.
- (BOOL)validateUserInterfaceItem:(id < NSValidatedUserInterfaceItem >)anItem {
if([anItem action] == #selector(cut:) ||
[anItem action] == #selector(copy:) ||
[anItem action] == #selector(paste:)) return NO;
return [self respondsToSelector:[anItem action]];
}
There is also a NSMenuValidation Protocol, which performs the same function but is used only to validate menu items, instead of all interface items. If you don't implement it, the system will fall back to the standard validation instead.