13

Is there a way to make my app respond to the play/pause button on Mac?

EDIT:

Using the suggested code,I get this console message:

Could not connect the action buttonPressed: to target of class NSApplication

Why would that be?

Moshe
  • 57,511
  • 78
  • 272
  • 425

4 Answers4

17

I accomplished this in my own application by subclassing NSApplication (and setting the app's principal class to this subclass). It catches seek and play/pause keys and translates them to specific actions in my app delegate.

Relevant lines:

#import <IOKit/hidsystem/ev_keymap.h>

- (void)sendEvent:(NSEvent *)event
{
    // Catch media key events
    if ([event type] == NSSystemDefined && [event subtype] == 8)
    {
        int keyCode = (([event data1] & 0xFFFF0000) >> 16);
        int keyFlags = ([event data1] & 0x0000FFFF);
        int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA;

        // Process the media key event and return
        [self mediaKeyEvent:keyCode state:keyState];
        return;
    }

    // Continue on to super
    [super sendEvent:event];
}

- (void)mediaKeyEvent:(int)key state:(BOOL)state
{
    switch (key)
    {
        // Play pressed
        case NX_KEYTYPE_PLAY:
            if (state == NO)
                [(TSAppController *)[self delegate] togglePlayPause:self];
            break;

        // Rewind
        case NX_KEYTYPE_FAST:
            if (state == YES)
                [(TSAppController *)[self delegate] seekForward:self];
            break;

        // Previous
        case NX_KEYTYPE_REWIND:
            if (state == YES)
                [(TSAppController *)[self delegate] seekBack:self];
            break;
    }
}

Edit:

Swift 4:

override func sendEvent(_ event: NSEvent)
{
    if  event.type == .systemDefined &&
        event.subtype == .screenChanged
    {
        let keyCode : Int32 = (Int32((event.data1 & 0xFFFF0000) >> 16))
        let keyFlags = (event.data1 & 0x0000FFFF)
        let keyState = ((keyFlags & 0xFF00) >> 8) == 0xA

        self.mediaKeyEvent(withKeyCode: keyCode, andState: keyState)
        return
    }

    super.sendEvent(event)
}

private func mediaKeyEvent(withKeyCode keyCode : Int32, andState state : Bool)
{
    guard let delegate = self.delegate as? AppDelegate else { return }

    switch keyCode
    {
        // Play pressed
        case NX_KEYTYPE_PLAY:
            if state == false
            {
                delegate.musicPlayerWC.handleUserPressedPlayButton()
            }
            break
        // Rewind
        case NX_KEYTYPE_FAST:
            if state == true
            {
                delegate.musicPlayerWC.handleUserPressedNextSongButton()
            }
            break

        // Previous
        case NX_KEYTYPE_REWIND:
            if state == true
            {
                delegate.musicPlayerWC.handleUserPressedPreviousSongButton()
            }

            break
        default:
            break
    }

}
Will
  • 1,697
  • 17
  • 34
Joshua Nozzi
  • 60,946
  • 14
  • 140
  • 135
  • I'm getting this weird console message. Any clue why? Also, I'm streaming music and I just want it to catch the play/pause button. Volume can be done on the regular OS X GUI. – Moshe Oct 07 '10 at 20:29
  • What weird console message? Also, if you want to catch only play/pause, then only respond to NX_KEYTYPE_PLAY (remove the other cases). – Joshua Nozzi Oct 07 '10 at 20:38
  • @Joshua Nozzi - Actually, it has to do with something else. Thanks for the code. +1 – Moshe Oct 07 '10 at 20:50
  • 1
    What's the specific issue you're having? I've implemented this in a shipping app that's been out a few years now. – Joshua Nozzi Oct 08 '10 at 18:50
  • @Joshua - I'me having problems "overriding" parts of my app. I'll look into it a bit more. This code seems to work, my placement of it doesn't. – Moshe Oct 11 '10 at 23:51
  • Subclass NSApplication, put this code in it. In your target's info, set the principal class to the name of your NSApplication subclass. Done. See the screenshot I posted here: http://jnozzi.me/mediakeys – Joshua Nozzi Oct 12 '10 at 01:24
  • 8
    I was able to get this working, but I still have a problem where iTunes launches and still gets played/paused as I press the keys. Is there a solution to this? – Kyle Jan 25 '12 at 12:14
  • 1
    The same issue with running iTunes ( Hot to prevent iTunes from Launching? Spotify has handled this. And even more: it exclusively takes touches. My application with code from this answer also doesn't receive events for these buttons when Spotify is running. – yas375 May 03 '13 at 08:05
  • 2
    Just to make the post more current: same code is available in Swift (and compatible with Swift 2, tested) [here](http://stackoverflow.com/questions/32499676/capture-osx-media-control-buttons-in-swift) – Arc676 Oct 09 '15 at 09:29
  • @kyle I am also waiting for iTunes launch solution. – AJit Jul 01 '16 at 21:08
  • 1
    To handle iTunes launch on media key press. https://github.com/nevyn/SPMediaKeyTap – AJit Jul 01 '16 at 22:36
  • Works just great until I minimize the app. Any clue? – Sunil Chauhan Jul 11 '16 at 19:02
  • @SunilChauhan The app must be active to receive these events as these aren't registered system shortcuts. They're just app-level keyboard events (which is only handed to the active app by the system). – Joshua Nozzi Jul 12 '16 at 02:36
  • @JoshuaNozzi: Actually that was not an issue. My app works fine now, even when minimized, (iTunes, VLC, etc. does respond to media keys when minimized), the issue @ my side was I was firing NSNotification to respond to the events!!! – Sunil Chauhan Jul 12 '16 at 11:37
  • Dude can you make this more simple, what do you mean by subclassing NSApplication (and setting the app's principal class to this subclass). I don't understand – YeaTheMans Aug 11 '17 at 07:25
  • @Lucasware You have some reading to do. 1) Read Apple’s NSApplication API reference, 2) Read the Objective-C guide’s subclassing section, 3) Read Xcode’s documentation on “Principal Class” setting found in your build target’s Info.plist. – Joshua Nozzi Aug 11 '17 at 13:49
3

Here's a great article on the subject: http://www.rogueamoeba.com/utm/2007/09/29/

cobbal
  • 69,903
  • 20
  • 143
  • 156
-1

In case anyone comes looking, there is sample code to do this here. This approach does allow you to "eat" the events they do not reach iTunes or other media-key-aware apps.

But be warned that event taps are not allowed by the sandbox, so this won't work in the App Store. If anyone has a workaround for that I'd love to hear it.

Community
  • 1
  • 1
J. Perkins
  • 4,156
  • 2
  • 34
  • 48
-1

Check this: https://gist.github.com/gauravk92/546311 Works perfectly.

As example, this repo uses it: https://github.com/onepill/PauseIt

Tatarasanu Victor
  • 656
  • 1
  • 7
  • 19
  • But this doesn't work in App Sandbox or is there something you can do about this? – plaetzchen Jul 28 '14 at 10:05
  • Yes. You need to use another work around with a helper app (that will eventually not be sandboxed). Check this: https://github.com/tredds/MagicKeys . E.g apps: https://itunes.apple.com/app/vox/id461369673?mt=12&ign-mpt=uo%3D4 with http://coppertino.com/addon/ – Tatarasanu Victor Jul 28 '14 at 12:25