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
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;
}
}
}