Getting Started

This walks through a tweak that does nothing but prove it loaded. Once that works, everything else is detail.

Before you start

TweakInject needs System Integrity Protection disabled, because injecting into a running process requires task_for_pid, which SIP blocks. The app's Station Board reports what is missing and what to do about it. See Installation for the host setup.

The tweak

MyTweak.m
#import <Foundation/Foundation.h>

__attribute__((constructor))
static void init(void) {
    NSLog(@"[MyTweak] loaded into %@",
          [[NSProcessInfo processInfo] processName]);
}

A constructor runs when the library is loaded, which is the moment the loader has put you inside the host process.

The filter

Without one, your tweak loads into nothing. This targets the Dock:

Filter.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Bundles</key>
    <array><string>com.apple.dock</string></array>
</dict>
</plist>

Building

Build
clang -dynamiclib -arch arm64e \
    -isysroot "$(xcrun --show-sdk-path --sdk macosx)" \
    -framework Foundation \
    -install_name /Library/TweakInject/Tweaks/DynamicLibraries/MyTweak.dylib \
    -o MyTweak.dylib MyTweak.m
codesign -f -s - MyTweak.dylib

The signature is ad-hoc. Library validation is disabled system-wide, which is the only reason anything not signed by Apple loads into these processes at all.

Installing it

Drag the .dylib and its .plist onto the Installed Tweaks page, or package them as a .deb and install that. See Packaging.

Restart the Dock and watch the log. Tweaks are injected at process start, so anything already running has to be restarted before it picks one up.

Next