Slide 1

Slide 1 text

Using Private APIs Getting Started with
 Making macOS Utility App Yoshimasa Niwa 9/8/2019 — TIME SHARING ࢛୩
 macOS native symposium #05

Slide 2

Slide 2 text

@niw
 Yoshimasa Niwa

Slide 3

Slide 3 text

Do you like it? Touch Bar

Slide 4

Slide 4 text

A simple utility application to trigger haptic feedback when tapping Touch Bar Haptic Key

Slide 5

Slide 5 text

github.com/niw/HapticKey

Slide 6

Slide 6 text

Today’s presentation Topics 1. We have no windows 2. We launch app when login 3. We watches Touch Bar events 4. We use track pad actuator

Slide 7

Slide 7 text

No, no, we don’t need ! No windows

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

statusItemWithLength: NSStatusBarItem • applicationDidFinishLaunching: is still application entry point • Create NSStatusBarItem from NSStatusBar • Use NSStatusItemBehaviorRemovalAllowed to let users to arrange items in status bar.
 The position is persistent in NSUserDefaults

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

LSUIElement is YES Hide dock icon • In Info.plist, set Application is agent (UIElement) to YES • That’s it

Slide 13

Slide 13 text

A few additional things Caveats • Provide a way to quit app, or user can’t quit app • Status bar menu is the main user interface to communicate with users. Provide simple, intuitive menu items • Status bar icon may represent application state, such as enabled, disabled

Slide 14

Slide 14 text

But we have status bar item and menu No main window • Use NSStatusBar and NSStatusBarItem • Set LSUIElement to YES • Status bar menu is the main user interface

Slide 15

Slide 15 text

Login item, you’re so… ☹ Launch app when login

Slide 16

Slide 16 text

You might see this Start on Login

Slide 17

Slide 17 text

You might know this preference Login Items

Slide 18

Slide 18 text

You might find this API SMLoginItemSetEnabled • Enable a helper application located in the main application bundle’s “Contents/Library/LoginItems” directory

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Useless, unless you need to put your app in App Store SMLoginItemSetEnabled • It never work if you have two copy of application bundles that have same bundle identifier … and you will always have the two or more of them in the derived data and the archives • This is not adding Login Item to the account preferences. Completely different logic and more similar to /Library/LaunchAgents

Slide 21

Slide 21 text

Slide 22

Slide 22 text

This is the one you need LSSharedFileList • Deprecated but still supported • You can add, remove Login Item • You can observe Login Item changes

Slide 23

Slide 23 text

Slide 24

Slide 24 text

Example code seems working LSSharedFileList LSSharedFileListRef fileList = LSSharedFileListCreate(NULL,
 kLSSharedFileListSessionLoginItems, NULL); LSSharedFileListAddObserver(fileList, ..., callback, ...); void callback(LSSharedFileListRef fileList, void *context) { NSArray *snapshot = (__bridge NSArray *)
 LSSharedFileListCopySnapshot(fileList, ...); 
 for (item in snapshot) { CFURLRef url; LSSharedFileListItemResolve(item, ..., &url); if ([(__bridge NSURL *)url isEqualTo:applicationBundlerURL]) { // changed! } } }

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

NSDistributedNotificationCenter stops working LSUIElement app is always inactive • LSSharedFileList is using NSDistributedNotificationCenter • LSUIElement app is always inactive • NSDistributedNotificationCenter will not deliver any notifications if app is inactive

Slide 27

Slide 27 text

NSNotificationSuspensionBehaviorDeliverImmediately Listen com.apple.private.BackgroundItemsChang • LSSharedFileList listens com.apple.private.BackgroundItemsChan geNotification • If the application process listens that notification as well with NSNotificationSuspensionBehaviorDeliv erImmediately option, LSSharedFileList can also listen it

Slide 28

Slide 28 text

https://github.com/niw/HapticKey/blob/master/ HapticKey/Classes/HTKLoginItem.m

Slide 29

Slide 29 text

• SMLoginItemSetEnabled is not what we want • LSSharedFileList is the one • LSUIElement app also need to listen com.apple.private.BackgroundItemsChan geNotification LSSharedFileList is the one but requires some tricks Launch app when login

Slide 30

Slide 30 text

We’re watching you Touch Bar events

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

https://docs.google.com/presentation/d/ 1nEaiPUduh1vjks0rDVRTcJaEULbSWWh1tVdG2HF_XSU/edit?usp=sharing

Slide 34

Slide 34 text

Overview ● macOS provides several APIs for event observing. ○ [NSView keyDown] ○ [NSApplication sendEvent] ○ CGEventTapCreate, [NSEvent addGlobalMonitorForEvents] ○ IOHIDQueueRegisterValueAvailableCallback ○ etc. ● macOS Catalina (10.15) will require a user approval of input monitoring for security. (Congratulation!) https://docs.google.com/presentation/d/1nEaiPUduh1vjks0rDVRTcJaEULbSWWh1tVdG2HF_XSU/edit?usp=sharing

Slide 35

Slide 35 text

We can listen system event • CGEventTapCreate with event masks to listen specific events • We can listen keyboard events such as Fn keys • We can listen gesture events, which includes taps on Touch Bar CGEventTap

Slide 36

Slide 36 text

Listener Feedback Key event, Tap event, … Haptic, Sound, Screen, … Event ✔

Slide 37

Slide 37 text

Don’t hide it or we will find it Private API

Slide 38

Slide 38 text

API is a set of functions that many application can use What is API? • macOS API is provided in shared libraries • In form of C or Objective-C or Swift • Newer API may not be implemented in Objective-C • Application are using shared libraries through dyld(1)

Slide 39

Slide 39 text

Public API is pubic because it is public Why public API is public? • Public API is public because it is documented • Public API is public because its name is visible • Public API is public because it is allowed to access from the application

Slide 40

Slide 40 text

Private API is private because it’s not public Why private API is private? • Private API is private because it is not documented • Private API is private because its name is not visible • Private API is private because it is not allowed to access from the application

Slide 41

Slide 41 text

Private API is private because it’s not public Why private API is private? • Private API is private because it is not documented • Private API is private because its name is not visible • Private API is private because it is not allowed to access from the application

Slide 42

Slide 42 text

A container contains all related resources Framework Bundle • API is in shared library, which is lay outed in .framework bundle format • Public one is in /System/Library/Frameworks • Private our is in /System/Library/ PrivateFrameworks

Slide 43

Slide 43 text

/System/Library/PrivateFrameworks/MultitouchSupport.framework Use MultitouchSupport • A private framework that provides multitouch track pad related functions on macOS • This framework also provides track pad vibration

Slide 44

Slide 44 text

Well, I was too lazy to make slides… Hands-on

Slide 45

Slide 45 text

Command line tools Hands-on tools • Use nm -m dylib to find exported function names • Use otool -t -V -p function dylib to disassemble function • Use clang -iframework /System/ Library/PrivateFrameworks -framework framework to link private framework

Slide 46

Slide 46 text

Example code @import Foundation; @import IOKit; CF_EXPORT CFTypeRef MTActuatorCreateFromDeviceID(UInt64 deviceID); CF_EXPORT IOReturn MTActuatorOpen(CFTypeRef actuatorRef); CF_EXPORT IOReturn MTActuatorClose(CFTypeRef actuatorRef); CF_EXPORT IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 unknown1, Float32 unknown2, Float32 unknown3); int main() { IOReturn error; CFTypeRef actuatorRef = MTActuatorCreateFromDeviceID(0x200000001000000 );
 if (!actuatorRef) {
 return -1;
 } error = MTActuatorOpen(actuatorRef);
 if (error != kIOReturnSuccess) {
 CFRelease(actuatorRef);
 return -1;
 } error = MTActuatorActuate(actuatorRef, 6, 0, 0, 0.2); error = MTActuatorClose(actuatorRef); CFRelease(actuatorRef);
 actuatorRef = NULL; return 0; } Hands-on sample

Slide 47

Slide 47 text

https://github.com/niw/HapticKey/blob/master/ HapticKey/Classes/HTKMultitouchActuator.m

Slide 48

Slide 48 text

Disassembling, researching and guessing. Private API • nm, otool, and also lldb • Hopper Disassembler is the tool helps your research • Always guess how API is designed and works

Slide 49

Slide 49 text

Questions? Q&A