Repository: nevyn/SPMediaKeyTap Branch: master Commit: 338bebf5a813 Files: 18 Total size: 194.6 KB Directory structure: gitextract__jjcsyjq/ ├── Example/ │ ├── English.lproj/ │ │ ├── InfoPlist.strings │ │ └── MainMenu.xib │ ├── SPMediaKeyTapExample-Info.plist │ ├── SPMediaKeyTapExample.xcodeproj/ │ │ └── project.pbxproj │ ├── SPMediaKeyTapExampleAppDelegate.h │ ├── SPMediaKeyTapExampleAppDelegate.m │ ├── SPMediaKeyTapExample_Prefix.pch │ └── main.m ├── LICENSE ├── README.md ├── SPInvocationGrabbing/ │ ├── NSObject+SPInvocationGrabbing.h │ ├── NSObject+SPInvocationGrabbing.m │ ├── gistfile3.m │ ├── gistfile4.m │ └── main.m ├── SPMediaKeyTap.h ├── SPMediaKeyTap.m └── SPMediaKeyTapDelegate.m ================================================ FILE CONTENTS ================================================ ================================================ FILE: Example/English.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Example/English.lproj/MainMenu.xib ================================================ 1060 10J869 823 1038.35 461.00 com.apple.InterfaceBuilder.CocoaPlugin 823 YES YES com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion YES NSApplication FirstResponder NSApplication AMainMenu YES SPMediaKeyTapExample 1048576 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState submenuAction: SPMediaKeyTapExample YES About SPMediaKeyTapExample 2147483647 YES YES 1048576 2147483647 Preferences… , 1048576 2147483647 YES YES 1048576 2147483647 Services 1048576 2147483647 submenuAction: Services YES _NSServicesMenu YES YES 1048576 2147483647 Hide SPMediaKeyTapExample h 1048576 2147483647 Hide Others h 1572864 2147483647 Show All 1048576 2147483647 YES YES 1048576 2147483647 Quit SPMediaKeyTapExample q 1048576 2147483647 _NSAppleMenu File 1048576 2147483647 submenuAction: File YES New n 1048576 2147483647 Open… o 1048576 2147483647 Open Recent 1048576 2147483647 submenuAction: Open Recent YES Clear Menu 1048576 2147483647 _NSRecentDocumentsMenu YES YES 1048576 2147483647 Close w 1048576 2147483647 Save s 1048576 2147483647 Save As… S 1179648 2147483647 Revert to Saved 2147483647 YES YES 1048576 2147483647 Page Setup... P 1179648 2147483647 Print… p 1048576 2147483647 Edit 1048576 2147483647 submenuAction: Edit YES Undo z 1048576 2147483647 Redo Z 1179648 2147483647 YES YES 1048576 2147483647 Cut x 1048576 2147483647 Copy c 1048576 2147483647 Paste v 1048576 2147483647 Paste and Match Style V 1572864 2147483647 Delete 1048576 2147483647 Select All a 1048576 2147483647 YES YES 1048576 2147483647 Find 1048576 2147483647 submenuAction: Find YES Find… f 1048576 2147483647 1 Find Next g 1048576 2147483647 2 Find Previous G 1179648 2147483647 3 Use Selection for Find e 1048576 2147483647 7 Jump to Selection j 1048576 2147483647 Spelling and Grammar 1048576 2147483647 submenuAction: Spelling and Grammar YES Show Spelling and Grammar : 1048576 2147483647 Check Document Now ; 1048576 2147483647 YES YES 2147483647 Check Spelling While Typing 1048576 2147483647 Check Grammar With Spelling 1048576 2147483647 Correct Spelling Automatically 2147483647 Substitutions 1048576 2147483647 submenuAction: Substitutions YES Show Substitutions 2147483647 YES YES 2147483647 Smart Copy/Paste f 1048576 2147483647 1 Smart Quotes g 1048576 2147483647 2 Smart Dashes 2147483647 Smart Links G 1179648 2147483647 3 Text Replacement 2147483647 Transformations 2147483647 submenuAction: Transformations YES Make Upper Case 2147483647 Make Lower Case 2147483647 Capitalize 2147483647 Speech 1048576 2147483647 submenuAction: Speech YES Start Speaking 1048576 2147483647 Stop Speaking 1048576 2147483647 Format 2147483647 submenuAction: Format YES Font 2147483647 submenuAction: Font YES Show Fonts t 1048576 2147483647 Bold b 1048576 2147483647 2 Italic i 1048576 2147483647 1 Underline u 1048576 2147483647 YES YES 2147483647 Bigger + 1048576 2147483647 3 Smaller - 1048576 2147483647 4 YES YES 2147483647 Kern 2147483647 submenuAction: Kern YES Use Default 2147483647 Use None 2147483647 Tighten 2147483647 Loosen 2147483647 Ligature 2147483647 submenuAction: Ligature YES Use Default 2147483647 Use None 2147483647 Use All 2147483647 Baseline 2147483647 submenuAction: Baseline YES Use Default 2147483647 Superscript 2147483647 Subscript 2147483647 Raise 2147483647 Lower 2147483647 YES YES 2147483647 Show Colors C 1048576 2147483647 YES YES 2147483647 Copy Style c 1572864 2147483647 Paste Style v 1572864 2147483647 _NSFontMenu Text 2147483647 submenuAction: Text YES Align Left { 1048576 2147483647 Center | 1048576 2147483647 Justify 2147483647 Align Right } 1048576 2147483647 YES YES 2147483647 Writing Direction 2147483647 submenuAction: Writing Direction YES YES Paragraph 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 YES Selection 2147483647 CURlZmF1bHQ 2147483647 CUxlZnQgdG8gUmlnaHQ 2147483647 CVJpZ2h0IHRvIExlZnQ 2147483647 YES YES 2147483647 Show Ruler 2147483647 Copy Ruler c 1310720 2147483647 Paste Ruler v 1310720 2147483647 View 1048576 2147483647 submenuAction: View YES Show Toolbar t 1572864 2147483647 Customize Toolbar… 1048576 2147483647 Window 1048576 2147483647 submenuAction: Window YES Minimize m 1048576 2147483647 Zoom 1048576 2147483647 YES YES 1048576 2147483647 Bring All to Front 1048576 2147483647 _NSWindowsMenu Help 2147483647 submenuAction: Help YES SPMediaKeyTapExample Help ? 1048576 2147483647 _NSHelpMenu _NSMainMenu 15 2 {{335, 693}, {258, 57}} 1954021376 SPMediaKeyTapExample NSWindow {9999, 57} {258, 57} 256 YES 266 {{17, 20}, {224, 17}} YES 68288064 272630784 No input received yet. LucidaGrande 13 1044 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 3 MAA {258, 57} {{0, 0}, {1920, 1178}} {258, 79} {9999, 79} SPMediaKeyTapExampleAppDelegate NSFontManager YES performMiniaturize: 37 arrangeInFront: 39 print: 86 runPageLayout: 87 clearRecentDocuments: 127 orderFrontStandardAboutPanel: 142 performClose: 193 toggleContinuousSpellChecking: 222 undo: 223 copy: 224 checkSpelling: 225 paste: 226 stopSpeaking: 227 cut: 228 showGuessPanel: 230 redo: 231 selectAll: 232 startSpeaking: 233 delete: 235 performZoom: 240 performFindPanelAction: 241 centerSelectionInVisibleArea: 245 toggleGrammarChecking: 347 toggleSmartInsertDelete: 355 toggleAutomaticQuoteSubstitution: 356 toggleAutomaticLinkDetection: 357 saveDocument: 362 saveDocumentAs: 363 revertDocumentToSaved: 364 runToolbarCustomizationPalette: 365 toggleToolbarShown: 366 hide: 367 hideOtherApplications: 368 unhideAllApplications: 370 newDocument: 373 openDocument: 374 addFontTrait: 421 addFontTrait: 422 modifyFont: 423 orderFrontFontPanel: 424 modifyFont: 425 raiseBaseline: 426 lowerBaseline: 427 copyFont: 428 subscript: 429 superscript: 430 tightenKerning: 431 underline: 432 orderFrontColorPanel: 433 useAllLigatures: 434 loosenKerning: 435 pasteFont: 436 unscript: 437 useStandardKerning: 438 useStandardLigatures: 439 turnOffLigatures: 440 turnOffKerning: 441 terminate: 449 toggleAutomaticSpellingCorrection: 456 orderFrontSubstitutionsPanel: 458 toggleAutomaticDashSubstitution: 461 toggleAutomaticTextReplacement: 463 uppercaseWord: 464 capitalizeWord: 467 lowercaseWord: 468 pasteAsPlainText: 486 performFindPanelAction: 487 performFindPanelAction: 488 performFindPanelAction: 489 showHelp: 493 delegate 495 alignCenter: 518 pasteRuler: 519 toggleRuler: 520 alignRight: 521 copyRuler: 522 alignJustified: 523 alignLeft: 524 makeBaseWritingDirectionNatural: 525 makeBaseWritingDirectionLeftToRight: 526 makeBaseWritingDirectionRightToLeft: 527 makeTextWritingDirectionNatural: 528 makeTextWritingDirectionLeftToRight: 529 makeTextWritingDirectionRightToLeft: 530 window 532 debugLabel 535 YES 0 YES -2 File's Owner -1 First Responder -3 Application 29 YES 19 YES 56 YES 217 YES 83 YES 81 YES 75 80 78 72 82 124 YES 77 73 79 112 74 125 YES 126 205 YES 202 198 207 214 199 203 197 206 215 218 YES 216 YES 200 YES 219 201 204 220 YES 213 210 221 208 209 57 YES 58 134 150 136 144 129 143 236 131 YES 149 145 130 24 YES 92 5 239 23 295 YES 296 YES 297 298 211 YES 212 YES 195 196 346 348 YES 349 YES 350 351 354 371 YES 372 YES 375 YES 376 YES 377 YES 388 YES 389 390 391 392 393 394 395 396 397 YES 398 YES 399 YES 400 401 402 403 404 405 YES 406 407 408 409 410 411 YES 412 413 414 415 YES 416 417 418 419 420 450 YES 451 YES 452 453 454 457 459 460 462 465 466 485 490 YES 491 YES 492 494 496 YES 497 YES 498 499 500 501 502 503 YES 504 505 506 507 508 YES 509 510 511 512 513 514 515 516 517 533 YES 534 YES YES -3.IBPluginDependency 112.IBPluginDependency 112.ImportedFromIB2 124.IBPluginDependency 124.ImportedFromIB2 125.IBPluginDependency 125.ImportedFromIB2 125.editorWindowContentRectSynchronizationRect 126.IBPluginDependency 126.ImportedFromIB2 129.IBPluginDependency 129.ImportedFromIB2 130.IBPluginDependency 130.ImportedFromIB2 130.editorWindowContentRectSynchronizationRect 131.IBPluginDependency 131.ImportedFromIB2 134.IBPluginDependency 134.ImportedFromIB2 136.IBPluginDependency 136.ImportedFromIB2 143.IBPluginDependency 143.ImportedFromIB2 144.IBPluginDependency 144.ImportedFromIB2 145.IBPluginDependency 145.ImportedFromIB2 149.IBPluginDependency 149.ImportedFromIB2 150.IBPluginDependency 150.ImportedFromIB2 19.IBPluginDependency 19.ImportedFromIB2 195.IBPluginDependency 195.ImportedFromIB2 196.IBPluginDependency 196.ImportedFromIB2 197.IBPluginDependency 197.ImportedFromIB2 198.IBPluginDependency 198.ImportedFromIB2 199.IBPluginDependency 199.ImportedFromIB2 200.IBEditorWindowLastContentRect 200.IBPluginDependency 200.ImportedFromIB2 200.editorWindowContentRectSynchronizationRect 201.IBPluginDependency 201.ImportedFromIB2 202.IBPluginDependency 202.ImportedFromIB2 203.IBPluginDependency 203.ImportedFromIB2 204.IBPluginDependency 204.ImportedFromIB2 205.IBEditorWindowLastContentRect 205.IBPluginDependency 205.ImportedFromIB2 205.editorWindowContentRectSynchronizationRect 206.IBPluginDependency 206.ImportedFromIB2 207.IBPluginDependency 207.ImportedFromIB2 208.IBPluginDependency 208.ImportedFromIB2 209.IBPluginDependency 209.ImportedFromIB2 210.IBPluginDependency 210.ImportedFromIB2 211.IBPluginDependency 211.ImportedFromIB2 212.IBPluginDependency 212.ImportedFromIB2 212.editorWindowContentRectSynchronizationRect 213.IBPluginDependency 213.ImportedFromIB2 214.IBPluginDependency 214.ImportedFromIB2 215.IBPluginDependency 215.ImportedFromIB2 216.IBPluginDependency 216.ImportedFromIB2 217.IBPluginDependency 217.ImportedFromIB2 218.IBPluginDependency 218.ImportedFromIB2 219.IBPluginDependency 219.ImportedFromIB2 220.IBEditorWindowLastContentRect 220.IBPluginDependency 220.ImportedFromIB2 220.editorWindowContentRectSynchronizationRect 221.IBPluginDependency 221.ImportedFromIB2 23.IBPluginDependency 23.ImportedFromIB2 236.IBPluginDependency 236.ImportedFromIB2 239.IBPluginDependency 239.ImportedFromIB2 24.IBEditorWindowLastContentRect 24.IBPluginDependency 24.ImportedFromIB2 24.editorWindowContentRectSynchronizationRect 29.IBEditorWindowLastContentRect 29.IBPluginDependency 29.ImportedFromIB2 29.WindowOrigin 29.editorWindowContentRectSynchronizationRect 295.IBPluginDependency 296.IBEditorWindowLastContentRect 296.IBPluginDependency 296.editorWindowContentRectSynchronizationRect 297.IBPluginDependency 298.IBPluginDependency 346.IBPluginDependency 346.ImportedFromIB2 348.IBPluginDependency 348.ImportedFromIB2 349.IBEditorWindowLastContentRect 349.IBPluginDependency 349.ImportedFromIB2 349.editorWindowContentRectSynchronizationRect 350.IBPluginDependency 350.ImportedFromIB2 351.IBPluginDependency 351.ImportedFromIB2 354.IBPluginDependency 354.ImportedFromIB2 371.IBEditorWindowLastContentRect 371.IBPluginDependency 371.IBWindowTemplateEditedContentRect 371.NSWindowTemplate.visibleAtLaunch 371.editorWindowContentRectSynchronizationRect 371.windowTemplate.hasMaxSize 371.windowTemplate.hasMinSize 371.windowTemplate.maxSize 371.windowTemplate.minSize 372.IBPluginDependency 375.IBPluginDependency 376.IBEditorWindowLastContentRect 376.IBPluginDependency 377.IBPluginDependency 388.IBEditorWindowLastContentRect 388.IBPluginDependency 389.IBPluginDependency 390.IBPluginDependency 391.IBPluginDependency 392.IBPluginDependency 393.IBPluginDependency 394.IBPluginDependency 395.IBPluginDependency 396.IBPluginDependency 397.IBPluginDependency 398.IBPluginDependency 399.IBPluginDependency 400.IBPluginDependency 401.IBPluginDependency 402.IBPluginDependency 403.IBPluginDependency 404.IBPluginDependency 405.IBPluginDependency 406.IBPluginDependency 407.IBPluginDependency 408.IBPluginDependency 409.IBPluginDependency 410.IBPluginDependency 411.IBPluginDependency 412.IBPluginDependency 413.IBPluginDependency 414.IBPluginDependency 415.IBPluginDependency 416.IBPluginDependency 417.IBPluginDependency 418.IBPluginDependency 419.IBPluginDependency 450.IBPluginDependency 451.IBEditorWindowLastContentRect 451.IBPluginDependency 452.IBPluginDependency 453.IBPluginDependency 454.IBPluginDependency 457.IBPluginDependency 459.IBPluginDependency 460.IBPluginDependency 462.IBPluginDependency 465.IBPluginDependency 466.IBPluginDependency 485.IBPluginDependency 490.IBPluginDependency 491.IBEditorWindowLastContentRect 491.IBPluginDependency 492.IBPluginDependency 496.IBPluginDependency 497.IBEditorWindowLastContentRect 497.IBPluginDependency 498.IBPluginDependency 499.IBPluginDependency 5.IBPluginDependency 5.ImportedFromIB2 500.IBPluginDependency 501.IBPluginDependency 502.IBPluginDependency 503.IBPluginDependency 504.IBPluginDependency 505.IBPluginDependency 506.IBPluginDependency 507.IBPluginDependency 508.IBEditorWindowLastContentRect 508.IBPluginDependency 509.IBPluginDependency 510.IBPluginDependency 511.IBPluginDependency 512.IBPluginDependency 513.IBPluginDependency 514.IBPluginDependency 515.IBPluginDependency 516.IBPluginDependency 517.IBPluginDependency 533.IBPluginDependency 533.IBViewBoundsToFrameTransform 534.IBPluginDependency 56.IBPluginDependency 56.ImportedFromIB2 57.IBEditorWindowLastContentRect 57.IBPluginDependency 57.ImportedFromIB2 57.editorWindowContentRectSynchronizationRect 58.IBPluginDependency 58.ImportedFromIB2 72.IBPluginDependency 72.ImportedFromIB2 73.IBPluginDependency 73.ImportedFromIB2 74.IBPluginDependency 74.ImportedFromIB2 75.IBPluginDependency 75.ImportedFromIB2 77.IBPluginDependency 77.ImportedFromIB2 78.IBPluginDependency 78.ImportedFromIB2 79.IBPluginDependency 79.ImportedFromIB2 80.IBPluginDependency 80.ImportedFromIB2 81.IBEditorWindowLastContentRect 81.IBPluginDependency 81.ImportedFromIB2 81.editorWindowContentRectSynchronizationRect 82.IBPluginDependency 82.ImportedFromIB2 83.IBPluginDependency 83.ImportedFromIB2 92.IBPluginDependency 92.ImportedFromIB2 YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{522, 812}, {146, 23}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{436, 809}, {64, 6}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{753, 187}, {275, 113}} com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {275, 83}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{547, 180}, {254, 283}} com.apple.InterfaceBuilder.CocoaPlugin {{187, 434}, {243, 243}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {167, 43}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{753, 217}, {238, 103}} com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {241, 103}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{654, 239}, {194, 73}} com.apple.InterfaceBuilder.CocoaPlugin {{525, 802}, {197, 73}} {{380, 836}, {538, 20}} com.apple.InterfaceBuilder.CocoaPlugin {74, 862} {{6, 978}, {478, 20}} com.apple.InterfaceBuilder.CocoaPlugin {{604, 269}, {231, 43}} com.apple.InterfaceBuilder.CocoaPlugin {{475, 832}, {234, 43}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{746, 287}, {220, 133}} com.apple.InterfaceBuilder.CocoaPlugin {{608, 612}, {215, 63}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{359, 631}, {258, 57}} com.apple.InterfaceBuilder.CocoaPlugin {{359, 631}, {258, 57}} {{33, 99}, {480, 360}} {9999, 57} {258, 57} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{591, 420}, {83, 43}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{523, 2}, {178, 283}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{753, 197}, {170, 63}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{725, 289}, {246, 23}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{674, 260}, {204, 183}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{878, 180}, {164, 173}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin P4AAAL+AAABDWQAAw48AAA com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{286, 129}, {275, 183}} com.apple.InterfaceBuilder.CocoaPlugin {{23, 794}, {245, 183}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{452, 109}, {196, 203}} com.apple.InterfaceBuilder.CocoaPlugin {{145, 474}, {199, 203}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES YES YES YES 535 YES NSObject IBProjectSource ../SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h NSObject IBProjectSource ../SPMediaKeyTap.h SPMediaKeyTapExampleAppDelegate NSObject YES YES debugLabel window YES NSTextField NSWindow YES YES debugLabel window YES debugLabel NSTextField window NSWindow IBProjectSource SPMediaKeyTapExampleAppDelegate.h 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES ../SPMediaKeyTapExample.xcodeproj 3 YES YES NSMenuCheckmark NSMenuMixedState YES {9, 8} {7, 2} ================================================ FILE: Example/SPMediaKeyTapExample-Info.plist ================================================ CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier com.yourcompany.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSMainNibFile MainMenu NSPrincipalClass MediaKeyExampleApp ================================================ FILE: Example/SPMediaKeyTapExample.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 05E15189136015D000F84972 /* SPMediaKeyTap.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E15188136015D000F84972 /* SPMediaKeyTap.m */; }; 05E151901360172A00F84972 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E1518F1360172A00F84972 /* Carbon.framework */; }; 05E151981360173A00F84972 /* NSObject+SPInvocationGrabbing.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E151971360173A00F84972 /* NSObject+SPInvocationGrabbing.m */; }; 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; 256AC3DA0F4B6AC300CF3369 /* SPMediaKeyTapExampleAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 256AC3D90F4B6AC300CF3369 /* SPMediaKeyTapExampleAppDelegate.m */; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 05E15187136015D000F84972 /* SPMediaKeyTap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMediaKeyTap.h; path = ../SPMediaKeyTap.h; sourceTree = SOURCE_ROOT; }; 05E15188136015D000F84972 /* SPMediaKeyTap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMediaKeyTap.m; path = ../SPMediaKeyTap.m; sourceTree = SOURCE_ROOT; }; 05E1518F1360172A00F84972 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 05E151961360173A00F84972 /* NSObject+SPInvocationGrabbing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject+SPInvocationGrabbing.h"; path = "../SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h"; sourceTree = SOURCE_ROOT; }; 05E151971360173A00F84972 /* NSObject+SPInvocationGrabbing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+SPInvocationGrabbing.m"; path = "../SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m"; sourceTree = SOURCE_ROOT; }; 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; 256AC3D80F4B6AC300CF3369 /* SPMediaKeyTapExampleAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMediaKeyTapExampleAppDelegate.h; sourceTree = ""; }; 256AC3D90F4B6AC300CF3369 /* SPMediaKeyTapExampleAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMediaKeyTapExampleAppDelegate.m; sourceTree = ""; }; 256AC3F00F4B6AF500CF3369 /* SPMediaKeyTapExample_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMediaKeyTapExample_Prefix.pch; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 8D1107310486CEB800E47090 /* SPMediaKeyTapExample-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "SPMediaKeyTapExample-Info.plist"; sourceTree = ""; }; 8D1107320486CEB800E47090 /* SPMediaKeyTapExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SPMediaKeyTapExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8D11072E0486CEB800E47090 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 05E151901360172A00F84972 /* Carbon.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 080E96DDFE201D6D7F000001 /* Classes */ = { isa = PBXGroup; children = ( 05E15187136015D000F84972 /* SPMediaKeyTap.h */, 05E15188136015D000F84972 /* SPMediaKeyTap.m */, 05E151961360173A00F84972 /* NSObject+SPInvocationGrabbing.h */, 05E151971360173A00F84972 /* NSObject+SPInvocationGrabbing.m */, 256AC3D80F4B6AC300CF3369 /* SPMediaKeyTapExampleAppDelegate.h */, 256AC3D90F4B6AC300CF3369 /* SPMediaKeyTapExampleAppDelegate.m */, ); name = Classes; sourceTree = ""; }; 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* SPMediaKeyTapExample.app */, ); name = Products; sourceTree = ""; }; 29B97314FDCFA39411CA2CEA /* SPMediaKeyTapExample */ = { isa = PBXGroup; children = ( 080E96DDFE201D6D7F000001 /* Classes */, 29B97315FDCFA39411CA2CEA /* Other Sources */, 29B97317FDCFA39411CA2CEA /* Resources */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); name = SPMediaKeyTapExample; sourceTree = ""; }; 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( 256AC3F00F4B6AF500CF3369 /* SPMediaKeyTapExample_Prefix.pch */, 29B97316FDCFA39411CA2CEA /* main.m */, ); name = "Other Sources"; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( 8D1107310486CEB800E47090 /* SPMediaKeyTapExample-Info.plist */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, ); name = Resources; sourceTree = ""; }; 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 05E1518F1360172A00F84972 /* Carbon.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8D1107260486CEB800E47090 /* SPMediaKeyTapExample */ = { isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "SPMediaKeyTapExample" */; buildPhases = ( 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, 8D11072E0486CEB800E47090 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = SPMediaKeyTapExample; productInstallPath = "$(HOME)/Applications"; productName = SPMediaKeyTapExample; productReference = 8D1107320486CEB800E47090 /* SPMediaKeyTapExample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0510; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SPMediaKeyTapExample" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 1; knownRegions = ( English, Japanese, French, German, ); mainGroup = 29B97314FDCFA39411CA2CEA /* SPMediaKeyTapExample */; projectDirPath = ""; projectRoot = ""; targets = ( 8D1107260486CEB800E47090 /* SPMediaKeyTapExample */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8D1107290486CEB800E47090 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8D11072C0486CEB800E47090 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8D11072D0486CEB800E47090 /* main.m in Sources */, 256AC3DA0F4B6AC300CF3369 /* SPMediaKeyTapExampleAppDelegate.m in Sources */, 05E15189136015D000F84972 /* SPMediaKeyTap.m in Sources */, 05E151981360173A00F84972 /* NSObject+SPInvocationGrabbing.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 089C165DFE840E0CC02AAC07 /* English */, ); name = InfoPlist.strings; sourceTree = ""; }; 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 1DDD58150DA1D0A300B32029 /* English */, ); name = MainMenu.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = SPMediaKeyTapExample_Prefix.pch; GCC_PREPROCESSOR_DEFINITIONS = _DEBUG; INFOPLIST_FILE = "SPMediaKeyTapExample-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; PRODUCT_NAME = SPMediaKeyTapExample; }; name = Debug; }; C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = SPMediaKeyTapExample_Prefix.pch; INFOPLIST_FILE = "SPMediaKeyTapExample-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; PRODUCT_NAME = SPMediaKeyTapExample; }; name = Release; }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; PREBINDING = NO; SDKROOT = macosx; }; name = Debug; }; C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; SDKROOT = macosx; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "SPMediaKeyTapExample" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4B08A954540054247B /* Debug */, C01FCF4C08A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SPMediaKeyTapExample" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } ================================================ FILE: Example/SPMediaKeyTapExampleAppDelegate.h ================================================ #import #import "../SPMediaKeyTap.h" @interface MediaKeyExampleApp : NSApplication @end @interface SPMediaKeyTapExampleAppDelegate : NSObject { NSWindow *window; SPMediaKeyTap *keyTap; IBOutlet NSTextField *debugLabel; } @property (assign) IBOutlet NSWindow *window; @end ================================================ FILE: Example/SPMediaKeyTapExampleAppDelegate.m ================================================ #import "SPMediaKeyTapExampleAppDelegate.h" @implementation MediaKeyExampleApp - (void)sendEvent:(NSEvent *)theEvent { // If event tap is not installed, handle events that reach the app instead BOOL shouldHandleMediaKeyEventLocally = ![SPMediaKeyTap usesGlobalMediaKeyTap]; if(shouldHandleMediaKeyEventLocally && [theEvent type] == NSSystemDefined && [theEvent subtype] == SPSystemDefinedEventMediaKeys) { [(id)[self delegate] mediaKeyTap:nil receivedMediaKeyEvent:theEvent]; } [super sendEvent:theEvent]; } @end @implementation SPMediaKeyTapExampleAppDelegate @synthesize window; +(void)initialize; { if([self class] != [SPMediaKeyTapExampleAppDelegate class]) return; // Register defaults for the whitelist of apps that want to use media keys [[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys: [SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers], kMediaKeyUsingBundleIdentifiersDefaultsKey, nil]]; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self]; if([SPMediaKeyTap usesGlobalMediaKeyTap]) [keyTap startWatchingMediaKeys]; else NSLog(@"Media key monitoring disabled"); } -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; { NSAssert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys, @"Unexpected NSEvent in mediaKeyTap:receivedMediaKeyEvent:"); // here be dragons... int keyCode = (([event data1] & 0xFFFF0000) >> 16); int keyFlags = ([event data1] & 0x0000FFFF); BOOL keyIsPressed = (((keyFlags & 0xFF00) >> 8)) == 0xA; int keyRepeat = (keyFlags & 0x1); if (keyIsPressed) { NSString *debugString = [NSString stringWithFormat:@"%@", keyRepeat?@", repeated.":@"."]; switch (keyCode) { case NX_KEYTYPE_PLAY: debugString = [@"Play/pause pressed" stringByAppendingString:debugString]; break; case NX_KEYTYPE_FAST: debugString = [@"Ffwd pressed" stringByAppendingString:debugString]; break; case NX_KEYTYPE_REWIND: debugString = [@"Rewind pressed" stringByAppendingString:debugString]; break; default: debugString = [NSString stringWithFormat:@"Key %d pressed%@", keyCode, debugString]; break; // More cases defined in hidsystem/ev_keymap.h } [debugLabel setStringValue:debugString]; } } @end ================================================ FILE: Example/SPMediaKeyTapExample_Prefix.pch ================================================ // // Prefix header for all source files of the 'SPMediaKeyTapExample' target in the 'SPMediaKeyTapExample' project // #ifdef __OBJC__ #import #endif ================================================ FILE: Example/main.m ================================================ // // main.m // SPMediaKeyTapExample // // Created by Joachim Bengtsson on 2011-04-21. // Copyright 2011 Spotify AB. All rights reserved. // #import int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **) argv); } ================================================ FILE: LICENSE ================================================ Copyright (c) 2011, Joachim Bengtsson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Neither the name of the organization nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ SPMediaKeyTap ============= `SPMediaKeyTap` abstracts a `CGEventHook` and other nastiness in order to give you a relatively simple API to receive media key events (prev/next/playpause, on F7 to F9 on modern MacBook Pros) exclusively, without them reaching other applications like iTunes. `SPMediaKeyTap` is clever enough to resign its exclusive lock on media keys by looking for which application was active most recently: if that application is in `SPMediaKeyTap`'s whitelist, it will resign the keys. This is similar to the behavior of Apple's applications collaborating on media key handling exclusivity, but unfortunately, Apple is not exposing any APIs allowing third-parties to join in on this collaboration. For now, the whitelist is just a hardcoded array in `+[SPMediaKeyTap defaultMediaKeyUserBundleIdentifiers]`. If your app starts using `SPMediaKeyTap`, please [mail me](mailto:nevyn@spotify.com) your bundle ID, and I'll include it in the canonical repository. This is a bad solution; a better solution would be to use distributed notifications to collaborate in creating this whitelist at runtime. Hopefully someone'll have the time and energy to write this soon. In `Example/SPMediaKeyTapExampleAppDelegate.m` is an example of both how you use `SPMediaKeyTap`, and how you handle the semi-private `NSEvent` subtypes involved in media keys, including on how to fall back to non-event tap handling of these events. `SPMediaKeyTap` and other `CGEventHook`s on the event type `NSSystemDefined` is known to interfere with each other and applications doing weird stuff with mouse input, because mouse clicks are also part of the `NSSystemDefined` category. The single issue we have had reported here at Spotify is Adobe Fireworks, in which item selection stops working with `SPMediaKeyTap` is active. `SPMediaKeyTap` requires 10.5 to work, and disables itself on 10.4. ================================================ FILE: SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h ================================================ #import @interface SPInvocationGrabber : NSObject { id _object; NSInvocation *_invocation; int frameCount; char **frameStrings; BOOL backgroundAfterForward; BOOL onMainAfterForward; BOOL waitUntilDone; } -(id)initWithObject:(id)obj; -(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack; @property (readonly, retain, nonatomic) id object; @property (readonly, retain, nonatomic) NSInvocation *invocation; @property BOOL backgroundAfterForward; @property BOOL onMainAfterForward; @property BOOL waitUntilDone; -(void)invoke; // will release object and invocation -(void)printBacktrace; -(void)saveBacktrace; @end @interface NSObject (SPInvocationGrabbing) -(id)grab; -(id)invokeAfter:(NSTimeInterval)delta; -(id)nextRunloop; -(id)inBackground; -(id)onMainAsync:(BOOL)async; @end ================================================ FILE: SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m ================================================ #import "NSObject+SPInvocationGrabbing.h" #import #pragma mark Invocation grabbing @interface SPInvocationGrabber () @property (readwrite, retain, nonatomic) id object; @property (readwrite, retain, nonatomic) NSInvocation *invocation; @end @implementation SPInvocationGrabber - (id)initWithObject:(id)obj; { return [self initWithObject:obj stacktraceSaving:YES]; } -(id)initWithObject:(id)obj stacktraceSaving:(BOOL)saveStack; { self.object = obj; if(saveStack) [self saveBacktrace]; return self; } -(void)dealloc; { free(frameStrings); self.object = nil; self.invocation = nil; [super dealloc]; } @synthesize invocation = _invocation, object = _object; @synthesize backgroundAfterForward, onMainAfterForward, waitUntilDone; - (void)runInBackground; { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; @try { [self invoke]; } @finally { [pool drain]; } } - (void)forwardInvocation:(NSInvocation *)anInvocation { [anInvocation retainArguments]; anInvocation.target = _object; self.invocation = anInvocation; if(backgroundAfterForward) [NSThread detachNewThreadSelector:@selector(runInBackground) toTarget:self withObject:nil]; else if(onMainAfterForward) [self performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:waitUntilDone]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)inSelector { NSMethodSignature *signature = [super methodSignatureForSelector:inSelector]; if (signature == NULL) signature = [_object methodSignatureForSelector:inSelector]; return signature; } - (void)invoke; { @try { [_invocation invoke]; } @catch (NSException * e) { NSLog(@"SPInvocationGrabber's target raised %@:\n\t%@\nInvocation was originally scheduled at:", e.name, e); [self printBacktrace]; printf("\n"); [e raise]; } self.invocation = nil; self.object = nil; } -(void)saveBacktrace; { void *backtraceFrames[128]; frameCount = backtrace(&backtraceFrames[0], 128); frameStrings = backtrace_symbols(&backtraceFrames[0], frameCount); } -(void)printBacktrace; { for(int x = 3; x < frameCount; x++) { if(frameStrings[x] == NULL) { break; } printf("%s\n", frameStrings[x]); } } @end @implementation NSObject (SPInvocationGrabbing) -(id)grab; { return [[[SPInvocationGrabber alloc] initWithObject:self] autorelease]; } -(id)invokeAfter:(NSTimeInterval)delta; { id grabber = [self grab]; [NSTimer scheduledTimerWithTimeInterval:delta target:grabber selector:@selector(invoke) userInfo:nil repeats:NO]; return grabber; } - (id)nextRunloop; { return [self invokeAfter:0]; } -(id)inBackground; { SPInvocationGrabber *grabber = [self grab]; grabber.backgroundAfterForward = YES; return grabber; } -(id)onMainAsync:(BOOL)async; { SPInvocationGrabber *grabber = [self grab]; grabber.onMainAfterForward = YES; grabber.waitUntilDone = !async; return grabber; } @end ================================================ FILE: SPInvocationGrabbing/gistfile3.m ================================================ // A +(UIView*)flashAt:(CGRect)r in:(UIView*)parent color:(UIColor*)color; { float duration = 0.5; UIView *flash = [[[UIView alloc] initWithFrame:r] autorelease]; flash.backgroundColor = color; [parent addSubview:flash]; [[flash invokeAfter:duration+0.1] removeFromSuperview]; [UIView beginAnimations:@"SPFlash" context:NULL]; [UIView setAnimationDuration:duration]; flash.alpha = 0.0; [UIView commitAnimations]; return flash; } // B - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // Force the animation to happen by calling this method again after a small // delay - see http://blog.instapaper.com/post/53568356 [[self nextRunloop] delayedTableViewDidSelectRowAtIndexPath: indexPath]; } // C [[tableView invokeAfter:0.15] selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; [[tableView invokeAfter:0.30] deselectRowAtIndexPath:indexPath animated:YES]; [[tableView invokeAfter:0.45] selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; ================================================ FILE: SPInvocationGrabbing/gistfile4.m ================================================ @interface MyClass : NSObject -(BOOL)areTheNewViewersGoneYet:(Duck*)duck; @end ... MyClass *myInstance = [[MyClass alloc] init]; id invocationGrabber = [[[SPInvocationGrabber alloc] initWithTarget:myInstance] autorelease]; [invocationGrabber areTheNewViewersGoneYet:[Duck yellowDuck]]; // line 9 NSInvocation *invocationForAreTheNewViewersGoneYet = [invocationGrabber invocation]; ================================================ FILE: SPInvocationGrabbing/main.m ================================================ #import #import "NSObject+SPInvocationGrabbing.h" @interface Foo : NSObject { int a; } -(void)startIt; -(void)theBackgroundStuff; -(void)theForegroundStuff; @end @implementation Foo -(void)startIt; { NSLog(@"Starting out on the main thread..."); a = 3; [[self inBackground] theBackgroundStuff]; } -(void)theBackgroundStuff; { NSLog(@"Woah, this is a background thread!"); a += 6; [[self onMainAsync:YES] theForegroundStuff]; } -(void)theForegroundStuff; { NSLog(@"Hey presto: %d", a); } @end int main() { NSAutoreleasePool *pool = [NSAutoreleasePool new]; Foo *foo = [Foo new]; [foo startIt]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; [pool release]; return 0; } ================================================ FILE: SPMediaKeyTap.h ================================================ #include #import #import // http://overooped.com/post/2593597587/mediakeys #define SPSystemDefinedEventMediaKeys 8 @interface SPMediaKeyTap : NSObject { EventHandlerRef _app_switching_ref; EventHandlerRef _app_terminating_ref; CFMachPortRef _eventPort; CFRunLoopSourceRef _eventPortSource; CFRunLoopRef _tapThreadRL; BOOL _shouldInterceptMediaKeyEvents; id _delegate; // The app that is frontmost in this list owns media keys NSMutableArray *_mediaKeyAppList; } + (NSArray*)defaultMediaKeyUserBundleIdentifiers; -(id)initWithDelegate:(id)delegate; +(BOOL)usesGlobalMediaKeyTap; -(void)startWatchingMediaKeys; -(void)stopWatchingMediaKeys; -(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event; @end @interface NSObject (SPMediaKeyTapDelegate) -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; @end #ifdef __cplusplus extern "C" { #endif extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey; extern NSString *kIgnoreMediaKeysDefaultsKey; #ifdef __cplusplus } #endif ================================================ FILE: SPMediaKeyTap.m ================================================ // Copyright (c) 2010 Spotify AB #import "SPMediaKeyTap.h" #import "SPInvocationGrabbing/NSObject+SPInvocationGrabbing.h" // https://gist.github.com/511181, in submodule @interface SPMediaKeyTap () -(BOOL)shouldInterceptMediaKeyEvents; -(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting; -(void)startWatchingAppSwitching; -(void)stopWatchingAppSwitching; -(void)eventTapThread; @end static SPMediaKeyTap *singleton = nil; static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData); static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData); static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon); // Inspired by http://gist.github.com/546311 @implementation SPMediaKeyTap #pragma mark - #pragma mark Setup and teardown -(id)initWithDelegate:(id)delegate; { _delegate = delegate; [self startWatchingAppSwitching]; singleton = self; _mediaKeyAppList = [NSMutableArray new]; _tapThreadRL=nil; _eventPort=nil; _eventPortSource=nil; return self; } -(void)dealloc; { [self stopWatchingMediaKeys]; [self stopWatchingAppSwitching]; [_mediaKeyAppList release]; [super dealloc]; } -(void)startWatchingAppSwitching; { // Listen to "app switched" event, so that we don't intercept media keys if we // weren't the last "media key listening" app to be active EventTypeSpec eventType = { kEventClassApplication, kEventAppFrontSwitched }; OSStatus err = InstallApplicationEventHandler(NewEventHandlerUPP(appSwitched), 1, &eventType, self, &_app_switching_ref); assert(err == noErr); eventType.eventKind = kEventAppTerminated; err = InstallApplicationEventHandler(NewEventHandlerUPP(appTerminated), 1, &eventType, self, &_app_terminating_ref); assert(err == noErr); } -(void)stopWatchingAppSwitching; { if(!_app_switching_ref) return; RemoveEventHandler(_app_switching_ref); _app_switching_ref = NULL; } -(void)startWatchingMediaKeys;{ // Prevent having multiple mediaKeys threads [self stopWatchingMediaKeys]; [self setShouldInterceptMediaKeyEvents:YES]; // Add an event tap to intercept the system defined media key events _eventPort = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(NX_SYSDEFINED), tapEventCallback, self); assert(_eventPort != NULL); _eventPortSource = CFMachPortCreateRunLoopSource(kCFAllocatorSystemDefault, _eventPort, 0); assert(_eventPortSource != NULL); // Let's do this in a separate thread so that a slow app doesn't lag the event tap [NSThread detachNewThreadSelector:@selector(eventTapThread) toTarget:self withObject:nil]; } -(void)stopWatchingMediaKeys; { // TODO: Shut down thread, remove event tap port and source if(_tapThreadRL){ CFRunLoopStop(_tapThreadRL); _tapThreadRL=nil; } if(_eventPort){ CFMachPortInvalidate(_eventPort); CFRelease(_eventPort); _eventPort=nil; } if(_eventPortSource){ CFRelease(_eventPortSource); _eventPortSource=nil; } } #pragma mark - #pragma mark Accessors +(BOOL)usesGlobalMediaKeyTap { #ifdef _DEBUG // breaking in gdb with a key tap inserted sometimes locks up all mouse and keyboard input forever, forcing reboot return NO; #else // XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy. return ![[NSUserDefaults standardUserDefaults] boolForKey:kIgnoreMediaKeysDefaultsKey] && floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/; #endif } + (NSArray*)defaultMediaKeyUserBundleIdentifiers; { return [NSArray arrayWithObjects: [[NSBundle mainBundle] bundleIdentifier], // your app @"com.spotify.client", @"com.apple.iTunes", @"com.apple.QuickTimePlayerX", @"com.apple.quicktimeplayer", @"com.apple.iWork.Keynote", @"com.apple.iPhoto", @"org.videolan.vlc", @"com.apple.Aperture", @"com.plexsquared.Plex", @"com.soundcloud.desktop", @"org.niltsh.MPlayerX", @"com.ilabs.PandorasHelper", @"com.mahasoftware.pandabar", @"com.bitcartel.pandorajam", @"org.clementine-player.clementine", @"fm.last.Last.fm", @"fm.last.Scrobbler", @"com.beatport.BeatportPro", @"com.Timenut.SongKey", @"com.macromedia.fireworks", // the tap messes up their mouse input @"at.justp.Theremin", @"ru.ya.themblsha.YandexMusic", @"com.jriver.MediaCenter18", @"com.jriver.MediaCenter19", @"com.jriver.MediaCenter20", @"co.rackit.mate", @"com.ttitt.b-music", @"com.beardedspice.BeardedSpice", @"com.plug.Plug", @"com.plug.Plug2", @"com.netease.163music", @"org.quodlibet.quodlibet", nil ]; } -(BOOL)shouldInterceptMediaKeyEvents; { BOOL shouldIntercept = NO; @synchronized(self) { shouldIntercept = _shouldInterceptMediaKeyEvents; } return shouldIntercept; } -(void)pauseTapOnTapThread:(BOOL)yeahno; { CGEventTapEnable(self->_eventPort, yeahno); } -(void)setShouldInterceptMediaKeyEvents:(BOOL)newSetting; { BOOL oldSetting; @synchronized(self) { oldSetting = _shouldInterceptMediaKeyEvents; _shouldInterceptMediaKeyEvents = newSetting; } if(_tapThreadRL && oldSetting != newSetting) { id grab = [self grab]; [grab pauseTapOnTapThread:newSetting]; NSTimer *timer = [NSTimer timerWithTimeInterval:0 invocation:[grab invocation] repeats:NO]; CFRunLoopAddTimer(_tapThreadRL, (CFRunLoopTimerRef)timer, kCFRunLoopCommonModes); } } #pragma mark #pragma mark - #pragma mark Event tap callbacks // Note: method called on background thread static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { SPMediaKeyTap *self = refcon; if(type == kCGEventTapDisabledByTimeout) { NSLog(@"Media key event tap was disabled by timeout"); CGEventTapEnable(self->_eventPort, TRUE); return event; } else if(type == kCGEventTapDisabledByUserInput) { // Was disabled manually by -[pauseTapOnTapThread] return event; } NSEvent *nsEvent = nil; @try { nsEvent = [NSEvent eventWithCGEvent:event]; } @catch (NSException * e) { NSLog(@"Strange CGEventType: %d: %@", type, e); assert(0); return event; } if (type != NX_SYSDEFINED || [nsEvent subtype] != SPSystemDefinedEventMediaKeys) return event; int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16); if (keyCode != NX_KEYTYPE_PLAY && keyCode != NX_KEYTYPE_FAST && keyCode != NX_KEYTYPE_REWIND && keyCode != NX_KEYTYPE_PREVIOUS && keyCode != NX_KEYTYPE_NEXT) return event; if (![self shouldInterceptMediaKeyEvents]) return event; [nsEvent retain]; // matched in handleAndReleaseMediaKeyEvent: [self performSelectorOnMainThread:@selector(handleAndReleaseMediaKeyEvent:) withObject:nsEvent waitUntilDone:NO]; return NULL; } static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; CGEventRef ret = tapEventCallback2(proxy, type, event, refcon); [pool drain]; return ret; } // event will have been retained in the other thread -(void)handleAndReleaseMediaKeyEvent:(NSEvent *)event { [event autorelease]; [_delegate mediaKeyTap:self receivedMediaKeyEvent:event]; } -(void)eventTapThread; { _tapThreadRL = CFRunLoopGetCurrent(); CFRunLoopAddSource(_tapThreadRL, _eventPortSource, kCFRunLoopCommonModes); CFRunLoopRun(); } #pragma mark Task switching callbacks NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys"; NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys"; -(void)mediaKeyAppListChanged; { if([_mediaKeyAppList count] == 0) return; /*NSLog(@"--"); int i = 0; for (NSValue *psnv in _mediaKeyAppList) { ProcessSerialNumber psn; [psnv getValue:&psn]; NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary( &psn, kProcessDictionaryIncludeAllInformationMask ) autorelease]; NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey]; NSLog(@"%d: %@", i++, bundleIdentifier); }*/ ProcessSerialNumber mySerial, topSerial; GetCurrentProcess(&mySerial); [[_mediaKeyAppList objectAtIndex:0] getValue:&topSerial]; Boolean same; OSErr err = SameProcess(&mySerial, &topSerial, &same); [self setShouldInterceptMediaKeyEvents:(err == noErr && same)]; } -(void)appIsNowFrontmost:(ProcessSerialNumber)psn; { NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)]; NSDictionary *processInfo = [(id)ProcessInformationCopyDictionary( &psn, kProcessDictionaryIncludeAllInformationMask ) autorelease]; NSString *bundleIdentifier = [processInfo objectForKey:(id)kCFBundleIdentifierKey]; NSArray *whitelistIdentifiers = [[NSUserDefaults standardUserDefaults] arrayForKey:kMediaKeyUsingBundleIdentifiersDefaultsKey]; if(![whitelistIdentifiers containsObject:bundleIdentifier]) return; [_mediaKeyAppList removeObject:psnv]; [_mediaKeyAppList insertObject:psnv atIndex:0]; [self mediaKeyAppListChanged]; } -(void)appTerminated:(ProcessSerialNumber)psn; { NSValue *psnv = [NSValue valueWithBytes:&psn objCType:@encode(ProcessSerialNumber)]; [_mediaKeyAppList removeObject:psnv]; [self mediaKeyAppListChanged]; } static pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef evt, void* userData) { SPMediaKeyTap *self = (id)userData; ProcessSerialNumber newSerial; GetFrontProcess(&newSerial); [self appIsNowFrontmost:newSerial]; return CallNextEventHandler(nextHandler, evt); } static pascal OSStatus appTerminated (EventHandlerCallRef nextHandler, EventRef evt, void* userData) { SPMediaKeyTap *self = (id)userData; ProcessSerialNumber deadPSN; GetEventParameter( evt, kEventParamProcessID, typeProcessSerialNumber, NULL, sizeof(deadPSN), NULL, &deadPSN ); [self appTerminated:deadPSN]; return CallNextEventHandler(nextHandler, evt); } @end ================================================ FILE: SPMediaKeyTapDelegate.m ================================================ -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; { assert([event type] == NSSystemDefined && [event subtype] == SPSystemDefinedEventMediaKeys); int keyCode = (([event data1] & 0xFFFF0000) >> 16); int keyFlags = ([event data1] & 0x0000FFFF); int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA; int keyRepeat = (keyFlags & 0x1); if (keyState == 1 && windowController != NULL) { switch (keyCode) { case NX_KEYTYPE_PLAY: ... return; case NX_KEYTYPE_FAST: ... return; case NX_KEYTYPE_REWIND: ... return; } } }