I'm working on an OpenGL game in Cocoa, and need to capture when the user attempts to copy or paste (via command+c, or command+v).
So far, I have an NSView<NSTextInputClient> as the first-respondant of my NSWindow. It is successful in allowing non-ASCII characters to be typed into my game (中文維基百科, for example) but I'm lost trying to capture Copy&Paste.
I think I might be able to get a working solution using flagsChanged combined with keyDown, but this feels like a hack, and I'm sure someone out there knows a better solution. :)
Edit: For clarity, I'm looking mainly for the existence of a predefined Copy event or keycode. The rationale being that if I manually define Copy as "Command+C" then that might break if a user has remapped his/her keys or is using some accessibility tool.
You can use the NSEvent class method: + (id)addGlobalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(void (^)(NSEvent*))block
or you can implement - (void)keyDown:(NSEvent *)theEvent
- (void)keyDown:(NSEvent *)theEvent {
if ([theEvent modifierFlags] & NSCommandKeyMask) {
NSString *character = [theEvent charactersIgnoringModifiers];
if ([character isEqualToString:#"c"]) {
NSLog(#"Capture Copy&Paste Key");
}
}
[super keyDown:theEvent];
}
Related
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!");
}
}
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. ;)
I'm trying to figure out how to limit my NSDocument based application to one open document at a time. It is quickly becoming a mess.
Has anyone been able to do this in a straightforward & reliable way?
////EDIT////
I would like to be able to prompt the user to save an existing open document and close it before creating/opening a new document.
////EDIT 2
I'm now trying to just return an error with an appropriate message if any documents are opening -- however, the error message is not displaying my NSLocalizedKeyDescription. This is in my NSDocumentController subclass.
-(id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError{
if([self.documents count]){
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObject:#"Only one document can be open at a time. Please close your document." forKey:NSLocalizedDescriptionKey];
*outError = [NSError errorWithDomain:#"Error" code:192 userInfo:dict];
return nil;
}
return [super openUntitledDocumentAndDisplay:displayDocument error:outError];
}
It won't be an easy solution, since it's a pretty complex class, but I would suggest that you subclass NSDocumentController and register your own which disables opening beyond a certain number of documents. This will allow you to prevent things like opening files by dropping them on the application's icon in the dock or opening in the finder, both of which bypass the Open menu item.
You will still need to override the GUI/menu activation code to prevent Open... from being available when you have a document open already, but that's just to make sure you don't confuse the user.
Your document controller needs to be created before any other document controllers, but that's easy to do by placing a DocumentController instance in your MainMenu.xib and making sure the class is set to your subclass. (This will cause it to call -sharedDocumentController, which will create an instance of yours.)
In your document controller, then, you will need to override:
- makeDocumentForURL:withContentsOfURL:ofType:error:
- makeUntitledDocumentOfType:error:
- makeDocumentWithContentsOfURL:ofType:error:
to check and see if a document is already open and return nil, setting the error pointer to a newly created error that shows an appropriate message (NSLocalizedDescriptionKey).
That should take care of cases of drag-and-drop, applescript,etc.
EDIT
As for your additional request of the close/save prompt on an opening event, that's a nastier problem. You could:
Save off the information (basically the arguments for the make requests)
Send the -closeAllDocumentsWithDelegate:didCloseAllSelector:contextInfo: with self as a delegate and a newly-created routine as the selector
When you receive the selector, then either clear out the saved arguments, or re-execute the commands with the arguments you saved.
Note that step 2 and 3 might need to be done on delay with performSelector
I haven't tried this myself (the rest I've done before), but it seems like it should work.
Here's the solution I ended up with. All of this is in a NSDocumentController subclass.
- (NSInteger)runModalOpenPanel:(NSOpenPanel *)openPanel forTypes:(NSArray *)extensions{
[openPanel setAllowsMultipleSelection:NO];
return [super runModalOpenPanel:openPanel forTypes:extensions];
}
-(NSUInteger)maximumRecentDocumentCount{
return 0;
}
-(void)newDocument:(id)sender{
if ([self.documents count]) {
[super closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(newDocument:didCloseAll:contextInfo:) contextInfo:(void*)sender];
}
else{
[super newDocument:sender];
}
}
- (void)newDocument:(NSDocumentController *)docController didCloseAll: (BOOL)didCloseAll contextInfo:(void *)contextInfo{
if([self.documents count])return;
else [super newDocument:(__bridge id)contextInfo];
}
-(void)openDocument:(id)sender{
if ([self.documents count]) {
[super closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(openDocument:didCloseAll:contextInfo:) contextInfo:(void*)sender];
}
else{
[super openDocument:sender];
}
}
- (void)openDocument:(NSDocumentController *)docController didCloseAll: (BOOL)didCloseAll contextInfo:(void *)contextInfo{
if([self.documents count])return;
else [super openDocument:(__bridge id)contextInfo];
}
Also, I unfortunately needed to remove the "Open Recent" option from the Main Menu. I haven't figured out how to get around that situation.
On OSX, I am using XCode to make a desktop app with a webview in it. The webview loads ok, and I can dynamically load content into it - but when I click on links inside the webview, they are not followed. They change color, but no new page is loaded. If I code my links with javascript like this - then they work.
link there
Is there an Objective-C one liner that allows links to be followed inside web-views?
Is there another issue I am not aware of here?
The links at the top of Gmail open in a new window. To make them work you have to implement at least the WebUIDelegate methods webView:createWebViewWithRequest: and webViewShow:. If you simply want to open all links in the same web view, you could return it from webView:createWebViewWithRequest: instead of creating a new one.
Turns out it was because I had code I copied form the web with some custom function to ignore WebPolicyDecisionListener...
Sorry for asking question without giving all the details - all this objective-c is new to me, I don't know which bits do what yet. I do some pointing and clicking, and then some coding - I don't know exactly how that all links up. With other languages, you have the whole program in one place - it takes a bit of a learning curve to get used to... but I digress.
I fixed by adding some comments - see code below...
- (void)webView:(WebView *)aWebView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
frame:(WebFrame *)frame
decisionListener:(id < WebPolicyDecisionListener >)listener
{
if ([self requestIsLinkClick:actionInformation]) {
if ([#"method" isEqual:[[request URL] scheme]]) {
SEL selector = NSSelectorFromString([[request URL] resourceSpecifier]);
if ([prototypeDelegate respondsToSelector:selector]) {
[prototypeDelegate performSelector:selector];
}
}
// [listener ignore];
} // else {
[listener use];
//}
}
I just experimented with the addLocalMonitorForEventsMatchingMask:handler: method in NSEvent and came across the following question: How do I find out if only certain modifiers were pressed?
A short example to set this question into context: I wanted to listen for the shortcut "⌘+W". Therefore I wrote the following code:
[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *theEvent) {
if ([theEvent modifierFlags] & NSCommandKeyMask && [theEvent keyCode] == 13) {
[self.window performClose:self];
}
return theEvent;
}];
This works well, however the shortcut will be triggered, even if more modifier keys are pressed, e.g. "⌃+⌘+W" or "⇧+⌃+⌥+⌘+W". Is there a way to circumvent this?
A simple solution would be to check for all other modifier keys and ensure they are not pressed. This seems tedious and error prone - besides it's ugly enough as it is now with the unary "&". In addition you may get into trouble if (for some reason) another modifier key is added to keyboard layouts.
As always I'm thankful for any recommendations.
I think this'll do it:
// Mask out everything but the key flags
NSUInteger flags = [theEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
if( flags == NSCommandKeyMask ){
// Got it!
}
Hat tip to SpaceDog for pointing out the deprecation of the original mask name, NSDeviceIndependentModifierFlagsMask.
Swift 5 version
if event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .command {
// Got it!
}
#JoshCaswell answer has been outdated thanks to Apple, because NSDeviceIndependentModifierFlagsMask has been deprecated since 10.12.
His answer updated to the new syntax is
// Mask out everything but the key flags
NSUInteger flags = [theEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
if( flags == NSCommandKeyMask ){
// Got it!
}
NSDeviceIndependentModifierFlagsMask has been replaced with NSEventModifierFlagDeviceIndependentFlagsMask because it makes a world of difference...
Maybe even better...
if event.modifierFlags.contains(.shift){
// do it
}