Contributing
Bug fixes, documentation improvements, performance work, focused features, and thoughtful ideas are welcome. This guide covers building OmniWM and submitting a change. For larger changes, open an issue or discussion first so we can agree on direction.
Documentation-only contributions do not require building the app. For website changes, follow the website development guide.
Quick Start
Section titled “Quick Start”1. Install Xcode
Section titled “1. Install Xcode”Building OmniWM requires an Apple Silicon Mac and Xcode 27 with Swift 6.4. Xcode 26.6 includes Swift 6.3 and cannot build this checkout. Xcode 27 requires macOS 26.6 or later, even though the released OmniWM app supports macOS 26.0 or later. Check Apple’s Xcode requirements before downloading Xcode.
Install Xcode from Apple Developer Downloads, open it, and finish its first-launch setup. In Xcode → Settings → Locations → Command Line Tools, select Xcode 27. Confirm the compiler in Terminal:
xcrun swift --versionThe result must report Swift 6.4. The standalone Command Line Tools package is not a substitute for this Xcode installation.
2. Fork, Clone, and Create a Branch
Section titled “2. Fork, Clone, and Create a Branch”Click Fork on the OmniWM repository, then clone your fork. Replace YOUR-USERNAME with your GitHub username:
git clone https://github.com/YOUR-USERNAME/OmniWM.gitcd OmniWMgit remote add upstream https://github.com/OmniNull/OmniWM.gitgit fetch upstreamgit switch -c my-change upstream/mainUse a descriptive branch name in place of my-change. Each contribution should start from main and focus on one problem.
3. Set Up Dependencies
Section titled “3. Set Up Dependencies”make setupThis downloads the pinned, prebuilt GhosttyKit into Frameworks/GhosttyKit.xcframework and checks its checksum. You do not need to compile GhosttyKit. It also installs the pinned SwiftFormat and SwiftLint tools into the repository’s ignored local tool cache; no manual Homebrew installation is required. Valid existing dependencies are reused.
Tool versions and download checksums are recorded in Scripts/dev-tools.env; Ghostty’s internal archive pin remains in Scripts/build-metadata.env. Setup preserves an existing framework that does not match the pin and explains the mismatch instead of overwriting it. Setup does not launch OmniWM or change privacy permissions, login items, or CLI links.
4. Optionally Create a Signing Certificate
Section titled “4. Optionally Create a Signing Certificate”For repeated development, create the local signing certificate below. A stable signing identity helps macOS retain the Dev app’s permissions across rebuilds. It needs no paid Apple Developer membership.
You can skip this and run Dev immediately. Without the certificate, the build uses ad-hoc signing and warns that you may need to grant permissions again after rebuilding.
5. Build and Run Dev
Section titled “5. Build and Run Dev”make runThis builds your checked-out code, packages and signs OmniWM Dev.app, installs it at ~/Applications/OmniWM Dev.app, and opens it. It builds before quitting the running OmniWM copy; if that copy cannot quit, installation stops with an error. Your normal OmniWM app remains installed. Only one copy runs at a time.
On first launch, grant Accessibility and Input Monitoring to OmniWM Dev in the permissions window. Screen Recording is optional for capture-derived visuals such as Overview thumbnails. Dev has its own permissions, separate from your normal app. Follow any restart prompt after granting permissions, then return to the permissions window and click Start OmniWM or Continue Without Screen Recording.
Edit code in your preferred editor, then run make run again to rebuild. You can also launch the installed Dev app from Finder.
6. Verify and Open a Pull Request
Section titled “6. Verify and Open a Pull Request”For app code changes, run:
make verifyswift testmake verify checks formatting, lint, and the build. It does not run tests. See verification for runtime changes and website checks.
Commit your change, push your branch to your fork, and open a pull request targeting OmniNull/OmniWM:main. A draft PR is welcome when you want early feedback. Explain the problem, the resulting behavior, and what you verified; say what you could not check and why.
Everyday Commands
Section titled “Everyday Commands”| Command | Result |
|---|---|
make setup |
Check prerequisites and obtain the pinned dependencies. |
make doctor |
Diagnose tools, dependencies, signing, and app paths without changing them. |
make build |
Build the app without installing or launching it. |
make run |
Build, sign, install, and switch to Dev; alias for make dev-install. |
make use-dev |
Switch to the installed Dev app without rebuilding. |
make use-release |
Switch back to the normal app at /Applications/OmniWM.app. |
If your normal app is elsewhere, set its path when switching:
OMNIWM_RELEASE_APP="$HOME/Applications/OmniWM.app" make use-releaseThe development commands do not install the normal release. If you need it, follow the installation guide.
Separate Settings and State
Section titled “Separate Settings and State”The installed Dev app has the fixed identity com.barut.OmniWM.dev, which selects its own storage directories even when launched from Finder:
| Data | Normal app | Dev app |
|---|---|---|
| Settings | ~/.config/omniwm/settings.toml |
~/.config/omniwm-dev/settings.toml |
| Saved state | ~/.local/state/omniwm/ |
~/.local/state/omniwm-dev/ |
| OmniWM diagnostics | ~/.local/state/omniwm/diagnostics/ |
~/.local/state/omniwm-dev/diagnostics/ |
On first installation, the helper copies your normal settings.toml into an independent Dev file. Existing Dev settings are preserved, and later edits are never synchronized. If no normal settings file exists, Dev uses its defaults. Saved state and clipboard history start fresh; they are not copied.
Absolute XDG_CONFIG_HOME and XDG_STATE_HOME values replace the corresponding base directories, with omniwm or omniwm-dev appended. Relative values are ignored. These variables must be available to the app process: a variable set only in a Terminal session is not automatically available to a Finder launch.
Keep Start at Login disabled in Dev. The helper does not change login registration. Dev can still display normal release-update notifications; installing a stable update does not rebuild your development code. Use make run to rebuild Dev.
Testing the CLI
Section titled “Testing the CLI”Both copies use the same IPC socket, so an existing omniwmctl command talks to whichever copy is running. Enable IPC in that copy’s settings when testing CLI commands.
To test the CLI built from your changes, invoke it directly:
"$HOME/Applications/OmniWM Dev.app/Contents/MacOS/omniwmctl" --helpSetup and switching do not change CLI links. Dev’s Install CLI setting can create a link to its bundled CLI when no conflicting link exists; use the embedded path above to leave your normal CLI setup alone.
Optional Signing Certificate
Section titled “Optional Signing Certificate”Create one certificate and reuse it for future builds:
- Open Keychain Access using Spotlight and select the login keychain.
- Choose Keychain Access → Certificate Assistant → Create a Certificate.
- Set Name to
OmniWM Dev, Identity Type to Self Signed Root, and Certificate Type to Code Signing. - Click Create, accept the self-signed certificate prompt, and click Done. Keep the certificate and its private key in your login keychain.
- Double-click the certificate, expand Trust, and set Code Signing to Always Trust. Close the window and confirm the change if prompted.
- Run
make doctor, then rebuild withmake run.
See Apple’s certificate creation guide and code-signing instructions for background. If macOS asks whether codesign may access this certificate’s private key while building, allow that access.
To use an existing local code-signing identity with another name:
OMNIWM_SIGNING_IDENTITY="Your Certificate Name" make runVerification
Section titled “Verification”Run make format to apply formatting and the required license headers. Run make verify afterward to check formatting, lint, localization catalogs, and an arm64 debug build. The commands use the versions installed by make setup.
Localization checks and synchronization generate compiler metadata with the selected Xcode installation’s default Swift toolchain in .build/localization. This separate incremental build supports localization extraction even when swift on your PATH selects a standalone toolchain that does not emit the metadata.
Every Swift source and test file starts with the two-line GPL-2.0 header enforced by SwiftFormat. Preserve that header. Package.swift keeps its swift-tools-version directive on line one. Keep contributions in Swift, and avoid additional source comments; use clear names and structure.
Use focused regression tests for changed behavior. Runtime changes require the full serial swift test suite, and changes affecting concurrency also require swift test --parallel. Environment-dependent live tests remain opt-in. For motion, focus, layout, and other visible behavior, also describe the manual checks you performed.
For changes to setup, packaging, development installation, or related tooling, also run make test-dev-tools. This runs the Python development-tooling tests and is included in CI’s Verify job.
Website changes use the checks in Website Development: npm run check and npm run build from website/. Small documentation-only changes do not need app builds or Swift tests.
GitHub’s OmniWM CI workflow reports Verify (make verify) and Tests (the serial Swift suite) separately. Tests are initially advisory while the hosted environment is established; a failing test still needs an explanation. On a first contribution, a maintainer may need to approve the workflow run before checks start. A pending approval does not mean you did something wrong.
Troubleshooting
Section titled “Troubleshooting”Start with make doctor and the first error reported by the failing command.
- Wrong Swift version: select Xcode 27 in its Locations settings, complete its first-launch setup, and check
xcrun swift --versionagain. - Missing dependencies: run
make setup. If an existing GhosttyKit fails its checksum, move your custom framework aside before running setup again; the helper will not replace it for you. - Permissions requested again: check the signing identity with
make doctor, rebuild with the same valid certificate, and grant Dev’s permissions again when prompted. Stable and Dev permissions are separate. - Switching stops because OmniWM is still running: quit that copy from its menu, then retry. The helper does not force-kill it or install over a running copy.
- Normal app not found: install the release or supply
OMNIWM_RELEASE_APPas shown above.
Pull Request Expectations
Section titled “Pull Request Expectations”- Keep changes focused. Explain the current problem and why your approach solves it; refactors need a concrete reason.
- Include verification results, including any checks you could not run. Add screenshots, recordings, or CLI examples when useful.
- Update documentation when behavior, configuration, workflows, or interfaces change.
- Create your change branch from
mainand targetmain. There is no permanentdevelopbranch. - Contributor PRs are normally integrated with merge commits so their commit history and authorship remain intact. You do not need to squash or perfect your history before asking for review.
Reviewed development lands on main; published releases come from version tags. Merging a PR does not itself update users’ installed apps.
Maintainer CI Rollout
Section titled “Maintainer CI Rollout”The Main branch protection ruleset blocks branch deletion and force-pushes, with repository-admin bypass for local merges and releases. GitHub uses merge commits and automatically deletes merged branches in this repository.
After publishing the CI workflow, confirm that Verify passes on GitHub, then add Verify from GitHub Actions as a required status check in that existing ruleset. Keep the admin bypass and leave Tests non-required until several hosted runs establish that the serial suite works reliably there.
For an existing PR, use Actions → OmniWM CI → Run workflow and enter its PR number to test its merge with the base branch. A manual run provides logs; it does not replace the PR’s required check. Updating the PR branch triggers its normal PR checks. Approve first-time fork runs when needed.
Trace Files
Section titled “Trace Files”Include a trace file when useful, especially for bug reports. Open Settings → Troubleshooting, click Start Recording, reproduce the bug, then click Stop & Save Recording and attach the saved .log file.
Report a Bug… in the status-bar menu opens the in-app report form. Recording or selecting trace and crash evidence is optional; on submit OmniWM prepares a fresh diagnostic .log with whatever you selected, reveals it for attaching, and opens a pre-filled GitHub issue.
Before attaching a diagnostic, review it: the file can contain OmniWM settings, application and window titles, and title-based App Rule matchers.
With IPC enabled, captures can also be scripted using the CLI: capture start trace, capture stop, and capture status.
Improving the Issue-Report Prompt
Section titled “Improving the Issue-Report Prompt”The prompt that rewrites bug reports into GitHub issues lives in plain Markdown. See docs/issue-report-prompt.md for the files, constraints, and verification steps.
If you are unsure about something, open an issue or ask in your pull request. Questions and early feedback are welcome.