feat: IMCore bridge via dylib injection for typing indicators#59
Open
alexrudloffBot wants to merge 7 commits intosteipete:mainfrom
Open
feat: IMCore bridge via dylib injection for typing indicators#59alexrudloffBot wants to merge 7 commits intosteipete:mainfrom
alexrudloffBot wants to merge 7 commits intosteipete:mainfrom
Conversation
Objective-C dylib that gets injected into Messages.app via DYLD_INSERT_LIBRARIES. Provides file-based IPC (JSON command/response files in the Messages.app container) with a 100ms polling timer on the main run loop. Supports commands: typing, read, status, list_chats, ping. Chat resolution tries multiple methods: - existingChatWithGUID: with all known prefixes (iMessage, SMS, any) - existingChatWithChatIdentifier: - Participant matching with phone number normalization Requires SIP disabled for DYLD_INSERT_LIBRARIES to work on system apps.
MessagesLauncher manages the Messages.app lifecycle: - Kills running instance, relaunches with DYLD_INSERT_LIBRARIES - Waits for lock file confirming dylib initialization - Sends commands via file-based IPC (JSON files in container) - Provides async sendCommand() API IMCoreBridge wraps the launcher with a high-level Swift API: - setTyping(for:typing:) - typing indicators - markAsRead(handle:) - read receipts - listChats() / getStatus() - debugging - checkAvailability() - diagnostic messages Both are @unchecked Sendable singletons following imsg's patterns.
- launch: Kills Messages.app, relaunches with dylib injection. Supports --dylib for custom path, --kill-only to just terminate. - status: Shows diagnostic report of feature availability. Reports basic vs advanced feature status with setup instructions. - Both support --json output. - Registered in CommandRouter between typing and rpc.
TypingIndicator.setTyping() now tries the IMCoreBridge first (dylib injected into Messages.app via DYLD_INSERT_LIBRARIES). If the bridge is unavailable or fails, falls back to the existing direct IMCore access via dlopen. This fixes the core issue: standalone CLI processes can't connect to imagent via XPC due to Apple's entitlement wall. The bridge runs inside Messages.app which already has the required entitlements. Uses a thread-safe BridgeResultBox to bridge async/sync boundary in a Swift 6 strict concurrency compatible way.
- build-dylib: Compiles IMsgInjected.m as arm64e dynamic library - imsg target now depends on build-dylib - install target copies both binary and dylib to /usr/local/ - clean removes the built dylib - Suppresses expected performSelector ARC warnings
- IMCoreBridgeTests: Verify shared instance, availability check, error descriptions, and launcher properties - LaunchStatusCommandTests: Verify commands are registered in router, status produces both JSON and text output
|
Heads up, I'm pretty sure this won't work on macOS 26 (Sequoia/Tahoe) due to library-validation enforcement, even with SIP disabled. The IssuemacOS 26 introduced a new code signature flag on Messages.app that blocks dylib injection: $ codesign -dv /System/Applications/Messages.app 2>&1 | grep flags
CodeDirectory v=20400 size=2764 flags=0x2000(library-validation)
^^^^^^^^^^^^^^^^^^^^^^^^^^This flag prevents DYLD_INSERT_LIBRARIES from loading third-party/unsigned dylibs into Messages.app, even with SIP disabled. The injection silently fails at the kernel level. What I've Tested
Why This Happens The library-validation flag requires all loaded libraries to:
Since the injected dylib is ad-hoc signed and Messages.app is Apple-signed, the kernel blocks loading. Potential Solutions?
Has anyone tested this on macOS 26? Or have ideas for alternative approaches that don't require dylib injection? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
imsg typingdoes not work becauseIMChatRegistryis empty when called from a standalone CLI process. Apple's entitlement wall blocks XPC connections toimagent. Documented in #58.Solution
Inject a helper dylib into Messages.app via
DYLD_INSERT_LIBRARIES. Messages.app already has the required entitlements, so IMCore works fully inside its process. The CLI communicates with the injected dylib through file-based IPC (JSON files in the Messages container).Same approach as BlueBubbles and imsg-plus.
Architecture
New files:
Sources/IMsgHelper/IMsgInjected.m— Obj-C injectable dylib (__attribute__((constructor))). Connects to IMDaemon, polls commands via NSTimer.Sources/IMsgCore/MessagesLauncher.swift— Kill/relaunch Messages.app withDYLD_INSERT_LIBRARIES.Sources/IMsgCore/IMCoreBridge.swift— Swift API wrapping file IPC.Modified:
TypingIndicator.swift— Prefers bridge, falls back to direct IMCore.Makefile—build-dylibtarget.New commands:
imsg launch,imsg statusAlso fixes
GUID prefix mismatch (#58): modern macOS uses
any;-;prefix. The dylib tries all prefixes plus participant matching with phone normalization.Requirements
Tested
make build-dylib+swift build— cleanCloses #58