Moshe
Moshe

Reputation: 58097

Make my Cocoa app respond to the keyboard play/pause key?

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?

Upvotes: 13

Views: 5094

Answers (4)

Joshua Nozzi
Joshua Nozzi

Reputation: 61228

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
    }

}

Upvotes: 17

J. Perkins
J. Perkins

Reputation: 4276

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.

Upvotes: -1

Tatarasanu Victor
Tatarasanu Victor

Reputation: 654

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

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

Upvotes: -1

cobbal
cobbal

Reputation: 70775

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

Upvotes: 3

Related Questions