1

I have written a small productivity tool that does a few string manipulations via the clipboard.

It is currently registering a hot key, where it pulls in the clipboard text, processes it, and dumps the result back on the clipboard.

I have this installed on CMD+SHIFT+V

currently what you need to do from another apppiclation is copy (CMD+C) and then activate my hothandler (CMD+SHIFT+V), and then you have to paste it back into the orginal app with (CMD+V).

I'd like to eliminate the third step if possible, so my hothandler somehow tells whatever is the active application to paste.

Any suggestions how to do this?

My code (minus the actual boring text replacement stuff) is this:

Please note this needs carbon framework for hotkey handlers

[EDIT:] I borrowed code from this answer on stack overflow for the hotkey handler code.

AppDelegate.h

#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#import <Carbon/Carbon.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>  {

    EventHotKeyRef  hotKeyRef;
}

@property (assign) IBOutlet NSWindow *window;
-(IBAction) checkClipboard:(id) sender ;

@end

AppDelegate.m

#import "AppDelegate.h"
//#import "NSString+cppMacros.h" // not relevant to question




OSStatus _AppDelegateHotKeyHandler(EventHandlerCallRef nextHandler, EventRef event, void *userData) {
    AppDelegate *appDel = [[NSApplication sharedApplication] delegate];
    [appDel checkClipboard:nil];
    return noErr;
}

@implementation AppDelegate



- (void)installHotkey {

    if (!hotKeyRef) {
        EventHotKeyID   hotKeyId;
        EventTypeSpec   eventType;

        eventType.eventClass    = kEventClassKeyboard;
        eventType.eventKind     = kEventHotKeyPressed;

        InstallApplicationEventHandler(&_AppDelegateHotKeyHandler, 1, &eventType, NULL, NULL);

        hotKeyId.signature  = 'hotk';
        hotKeyId.id         = 1337;

        RegisterEventHotKey(kVK_ANSI_V, cmdKey + shiftKey, hotKeyId, GetApplicationEventTarget(), 0, &hotKeyRef);
        NSLog(@"_AppDelegateHotKeyHandler installed");

    }
}

-(void) uninstallHotkey {
    if (hotKeyRef) {
        UnregisterEventHotKey(hotKeyRef);
        hotKeyRef = nil;
        NSLog(@"_AppDelegateHotKeyHandler uninstalled");
    }

}



-(IBAction) checkClipboard:(id) sender {

    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];


    NSArray *types = [NSArray arrayWithObjects:NSStringPboardType, nil];



    NSString *text = [pasteboard  stringForType:NSPasteboardTypeString];

    NSLog(@"clipboard input:%@",text);

    /* actual code is this: (not relevant to question)
     NSString *newText = [text isMacroEncoded] ? [text macroDecodedString] : [text macroEncodedString];
     */
    // demo code for question

    NSString *newText = [@"Pasted:" stringByAppendingString:text];


    [pasteboard declareTypes:types owner:self];

    [pasteboard setString:newText forType:NSStringPboardType];

    NSLog(@"clipboard output:%@",newText);

}


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
    hotKeyRef = nil;
    [self installHotkey];
}

-(void) applicationWillTerminate:(NSNotification *)notification {
    [self uninstallHotkey];
}



@end
Community
  • 1
  • 1
unsynchronized
  • 4,828
  • 2
  • 31
  • 43

3 Answers3

4

You don't want to use a global hot key for this. You want to write your application so that it provides a "service". See the Services Implementation Guide.

Your service can be activated by a keyboard shortcut, either the default configured in your app's Info.plist or one configured by the user in System Preferences.

Cocoa takes care of both copying from the active app and pasting into it after you've done the transformation, so this will take care of the first and third steps.

Services use a separate pasteboard for transferring data, so this also doesn't interfere with the contents of the general (copy and paste) pasteboard.

It's a superior approach on every level.


Edited to add other benefits:

A service won't steal a keyboard shortcut from the active app.

A service is also available via the application menu and context menu. That makes it discoverable.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • interesting theory. one wonders why a generalPasteboard exists then? perhaps for "general use"? oh no that would confuse people. :) – unsynchronized Nov 24 '13 at 04:25
  • 2
    It's clearly documented that the general pasteboard is for cut, copy, and paste operations. And if you don't see the benefit to the user to both have a service transform the selected text but also leave the last-copied text on the general pasteboard alone, then I'm not sure what to tell you. Also, please don't edit answers to put words into people's mouths. If you object to my telling you what you do or don't want, then say so in a comment. – Ken Thomases Nov 24 '13 at 04:41
  • so you putting thoughts in my mind is ok, but me putting words in your mouth is not? interesting when shoe is on the other foot isn't it. i take your point about the services, however, I humbly suggest that if you can't answer a question as asked, then don't. this is not a forum to practice mind control – unsynchronized Nov 24 '13 at 05:57
  • 3
    My statement "You don't want..." doesn't actually put thoughts in your mind or control it. It's just a statement. If it's wrong you can say so. What you did was to make it appear as though *I* said something which I didn't. There's a huge difference. Nobody would have confused my statement for your actual state of mind. If you prefer, you can imagine an "I believe..." or "I think..." in front of everybody's declarative statements. It is routine on SO and similar forums for people to suggest that the question is misguided because a different approach would be better. I'll keep doing so. – Ken Thomases Nov 24 '13 at 06:06
  • well it's great to know that us plebeians have people like you to tell us what we want. where would we be without you? – unsynchronized Nov 29 '13 at 14:16
2

It turns out there is a way to do what I want.

void pasteCurrent() {

    CGEventRef commandDown = CGEventCreateKeyboardEvent(NULL, kVK_Command, YES);
    CGEventRef VDown = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_V, YES);

    CGEventRef VUp = CGEventCreateKeyboardEvent(NULL, kVK_ANSI_V, NO);
    CGEventRef commandUp = CGEventCreateKeyboardEvent(NULL, kVK_Command, NO);

    CGEventSetFlags(VDown,kCGEventFlagMaskCommand);
    CGEventSetFlags(VUp,kCGEventFlagMaskCommand);

    CGEventPost(kCGHIDEventTap, commandDown);
    CGEventPost(kCGHIDEventTap, VDown);
    CGEventPost(kCGHIDEventTap, VUp);
    CGEventPost(kCGHIDEventTap, commandUp);

    CFRelease(commandDown);
    CFRelease(VDown);
    CFRelease(VUp);
    CFRelease(commandUp);

}
unsynchronized
  • 4,828
  • 2
  • 31
  • 43
0

@unsynchronized, your solution works for me thanks!

I like to translate your code Objective-C to Swift

    let commandDown = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(UInt32(kVK_Command)), keyDown: true)
    let VDown = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(UInt32(kVK_ANSI_V)), keyDown: true)

    let VUp = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(UInt32(kVK_ANSI_V)), keyDown: false)
    let commandUp = CGEvent(keyboardEventSource: nil, virtualKey: CGKeyCode(UInt32(kVK_Command)), keyDown: false)

    VDown!.flags = CGEventFlags.maskCommand
    VUp!.flags = CGEventFlags.maskCommand

    let valuee = UInt32(0)
    let loc: CGEventTapLocation = CGEventTapLocation(rawValue: valuee)!

    commandDown!.post(tap: loc)
    VDown!.post(tap: loc)
    VUp!.post(tap: loc)
    commandUp!.post(tap: loc)
Ashvin
  • 8,227
  • 3
  • 36
  • 53