Skip to content

Conversation

@kohoj
Copy link

@kohoj kohoj commented Feb 6, 2026

Summary

Implements typing indicators for outgoing messages (closes #22) using runtime dynamic loading of Apple's IMCore private framework. This is the only way to programmatically send typing indicators — AppleScript has no equivalent capability.

What's included

1. IMsgCoreTypingIndicator struct

  • startTyping(chatIdentifier:) — show the "..." typing bubble on the recipient's device
  • stopTyping(chatIdentifier:) — dismiss the typing bubble
  • typeForDuration(chatIdentifier:duration:) — show for N seconds, then auto-stop
  • Uses dlopen to load /System/Library/PrivateFrameworks/IMCore.framework at runtime
  • Connects to IMDaemonController, resolves chat via IMChatRegistry, then calls [IMChat setLocalUserIsTyping:]

2. CLI — imsg typing command

# Start typing indicator (stays until timeout or --stop)
imsg typing --to +14155551212

# Show for a specific duration
imsg typing --to +14155551212 --duration 5s

# Stop typing indicator
imsg typing --to +14155551212 --stop true

# Target by chat identifier directly
imsg typing --chat-identifier "iMessage;-;+14155551212"

# JSON output
imsg typing --to +14155551212 --json

Supports all the same chat targeting as send: --to, --chat-id, --chat-identifier, --chat-guid.

3. RPC — typing.start / typing.stop methods

{"jsonrpc":"2.0","id":1,"method":"typing.start","params":{"to":"+14155551212"}}
{"jsonrpc":"2.0","id":2,"method":"typing.stop","params":{"to":"+14155551212"}}

Also supports chat_identifier, chat_guid, chat_id, and service params.

How it works

The implementation uses the same approach as BlueBubbles — loading IMCore at runtime via dlopen/objc_getClass/sel_registerName. The core call is:

[chat setLocalUserIsTyping:YES];  // start
[chat setLocalUserIsTyping:NO];   // stop

No new dependencies are required. The private framework is available on all macOS versions that support Messages.app.

Files changed

File Change
Sources/IMsgCore/TypingIndicator.swift New — core typing indicator logic
Sources/IMsgCore/Errors.swift Add typingIndicatorFailed error case
Sources/imsg/Commands/TypingCommand.swift New — CLI command
Sources/imsg/CommandRouter.swift Register typing command
Sources/imsg/RPCServer.swift Add typing.start/typing.stop RPC methods

Caveats

  • Private API: Uses IMCore private framework. Not suitable for Mac App Store distribution, but fine for CLI tools and side-loaded apps.
  • Requires active conversation: The chat must already exist in Messages.app (same as send).
  • Daemon connection: First call has ~500ms latency for IMDaemonController connection setup; subsequent calls are fast.

Testing

  • swift build passes with zero errors and zero warnings
  • Manual testing: imsg typing --to <number> shows the typing bubble on the recipient's iPhone
  • Existing tests are unaffected (pre-existing Testing framework issue on non-Xcode-16 environments)

Implements typing indicators for outgoing messages using runtime
dynamic loading of Apple's IMCore private framework. This is the
only way to programmatically send typing indicators — AppleScript
has no equivalent capability.

Closes steipete#22

Changes:
- IMsgCore: Add TypingIndicator struct with start/stop/duration APIs
- CLI: Add 'imsg typing' command with --to, --duration, --stop flags
- RPC: Add typing.start and typing.stop methods
- Errors: Add typingIndicatorFailed error case

Usage:
  imsg typing --to +14155551212
  imsg typing --to +14155551212 --duration 5s
  imsg typing --to +14155551212 --stop true
  imsg typing --chat-identifier "iMessage;-;+14155551212"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: typing indicators for outgoing messages

1 participant