MacroSIP
EnglishРусский中文

Contributing

MacroSIP is GPLv2 or later (PJSIP is GPLv2+, ADR 0007); any contribution is accepted under that license, and any bundled third-party code must be GPL-compatible. Bugs and feature requests go to https://github.com/HSMM/MacroSIP/issues. Before touching code read docs/DEVELOPMENT.md (the engineering notes), docs/ROADMAP.md (what is shipped and what comes next), docs/GLOSSARY.md (use its terms in code and docs), the ADRs in docs/adr/ and the UI spec docs/design/SCREENS.md with docs/design/mockups.html.

Workflow (ADR 0008)

Work is organised by the milestones in docs/ROADMAP.md. Each task starts from a written brief: goal, boundaries, files, done criteria and the facts already known; briefs are self-contained. Every milestone gets an independent review by someone who did not write the code (bugs, races, leaks, Keychain and TLS handling), then verification: build, tests, the loopback self-test and a call on a real SIP account. UI code starts only after the mockups are approved. Settled decisions are not reopened silently: to change one, add a new ADR (docs/adr/README.md: Context, Decision, Consequences; never edited after acceptance except for a "Superseded by" line). Do not add dependencies without an ADR.

Open a pull request against main; CI must pass on both the arm64 and the Intel job (see Testing). Keep docs/design/SCREENS.md, mockups.html and docs/DEVELOPMENT.md in sync with the code you change, in the same pull request.

Coding rules

Add a string

  1. Add a static var or func to L with a dotted key and an English default, like the existing static var tabsContacts: String { loc("tabs.contacts", "Contacts") }. Interpolations become %@ for text and %lld for integers; with two or more arguments use %1$@, %2$@ in both files.
  2. Add the key to Resources/en.lproj/Localizable.strings and Resources/ru.lproj/Localizable.strings.
  3. Counts (contacts, calls, entries) go to Localizable.stringsdict instead, with one/other for English and one/few/many/other for Russian; never the same key in both files.
  4. Run scripts/check-strings.sh.

Product, codec and protocol names (SIP, SRTP, TLS, STUN, ICE, DTMF, REGISTER), the diagnostics report and log lines are not translated. Check the layout in Russian: dist/MacroSIP.app/Contents/MacOS/MacroSIP --demo-state contacts -AppleLanguages "(ru)".

Add a setting

  1. Add the field to the right struct in Sources/MacroSIPCore/Model/AppSettings.swift (GeneralSettings, AudioSettings, RecordingSettings, AutoAnswerSettings, NetworkSettings, CallOptions, CodecOptions, QuickKeysSettings, DirectorySettings) with a default and a tolerant init(from:), and a value test in SettingsCompatTests that an old settings.json still decodes.
  2. Apply it: engine-side options go through AppModel+Engine.swift (applyCallOptions, applyCodecOptions, scheduleNetworkApply; a network field that needs a restart is listed in NetworkSettings.needsRestart(from:)).
  3. Add the control to the pane in Views/Settings/ (each pane is a PaneContainer, 680 pt wide) using model.updateSettings { $0... = ... }.
  4. Strings in L and both languages; describe the control and its states in docs/design/SCREENS.md, and update mockups.html if the pane layout changes.
  5. A demo state in Model/DemoState.swift if the control has a state worth a screenshot.

Add a codec

  1. third_party/pjsip/config_site.h: set PJMEDIA_HAS_<NAME>_CODEC 1 (and, if pjproject's configure has a --disable-<name>-codec flag, make sure scripts/build-pjsip.sh does not pass it; the script already fails when configure disables one of the expected codecs or a codec archive is missing). Rerun scripts/build-pjsip.sh: PJSIP is rebuilt for both architectures, OpenSSL and Opus stay cached.
  2. Package.swift: add the archive to pjsipLibraries, keeping it in sync with third_party/pjsip/LINKFLAGS; then rm -rf .build and swift build.
  3. Engine.optionalCodecs in MacroSIPCore (the ids shown after G.711, left at priority 0 until enabled) and CodecCatalog in Views/Settings/CodecsPane.swift (all, onByDefault; it appends ids a stored order lacks).
  4. Tests: an id listing test and a loopback call that negotiates the codec (Engine262Tests shows the pattern), plus the self-test "codecs" step.

No G.729 (bcg729 stays disabled); see ADR 0002 and ADR 0012 for the build.

Add a directory source kind

From docs/DIRECTORY.md:

  1. Add the case to DirectorySourceKind and, if it has settings, a Codable config struct with a tolerant init(from:) (every key optional) and a field on DirectorySource decoded with try?, so a damaged config does not drop the settings file.
  2. Implement DirectoryProvider (an actor when it keeps a cache, a struct otherwise): search returns at most limit entries, lookup uses DirectoryNumber.matches, test returns .connected(entries:), allEntries everything for import. Throw DirectoryError; .signInRequired puts the source in "Sign in required". For a local list build a DirectoryIndex.
  3. Return it from DirectoryService.defaultProvider. Secrets go through DirectoryCredentials, never into settings.json.
  4. Tests in a Directory*Tests.swift file; StubDirectoryProvider in DirectoryServiceTests.swift covers the service side.
  5. UI: the type popup and fields in Views/Directory/DirectorySourceSheet.swift, strings, SCREENS.md section 13, and a directory-sheet-<kind> demo state.

Commit messages

Plain English, present tense, what and why. The first line names the area and the change (Engine: retry REGISTER with backoff after a transport error, Settings: Network pane with the RTP port range); the body explains the reason when it is not obvious. No trailers. Commit each finished piece, including the docs that go with it.

See also Architecture, Building, Testing, Releasing.