Thank you for your interest in contributing to Conic Launcher! There are many ways to contribute and we appreciate all of them.
Generally it's fine to just work on these kinds of things and put a pull-request out for it. If there is an issue accompanying it make sure to link it in the pull request description so it can be closed afterwards or linked for context.
It's advised to first open an issue for any kind of new feature so the team can tell upfront whether the feature is desirable or not before any implementation work happens. We want to minimize the possibility of someone putting a lot of work into a feature that is then going to waste as we deem it out of scope (be it due to generally not fitting in with Conic Launcher, or just not having the maintenance capacity). If there already is a feature issue open but it is not clear whether it is considered accepted feel free to just drop a comment and ask!
All use of AI in contributions must follow the AI Policy.
Contributions not following the AI Policy will be closed.
Before you start, please read the README and search existing issues. To get a quick overview of the crates and structure of the project take a look at the ARCHITECTURE.md manual. You can also join our Discord server to discuss development-related topics.
To set up your development environment, you'll need Rust installed. If you don't have Rust yet, follow the official installation guide: https://www.rust-lang.org/tools/install.
Once Rust is installed, clone the repository and build the project:
git clone https://github.com/conic-apps/launcher.git
cd launcher
cargo buildTo run the project locally:
cargo runTo run the test suite:
cargo testBefore submitting a pull request, please make sure the code is formatted, passes lints, and builds clean docs:
cargo fmt --all -- --check
slint-lsp format -i $(git ls-files '*.slint') # no check mode; CI diffs the result
cargo check
cargo clippy --all-targets --release -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items
cargo test.slint files are formatted with slint-lsp (cargo binstall slint-lsp), pinned to the slint
version in Cargo.lock; CI fails on any diff. Keep it in step with slint: bumping slint without
bumping slint-lsp can reformat the tree.
Every .rs, .slint, .py and .sh file needs its SPDX header — hawkeye check verifies them
and hawkeye format writes the missing ones. A new dependency must pass the licence allowlist in
deny.toml (cargo deny check licenses), which CI runs on any change to Cargo.toml or
Cargo.lock.
Rust unit tests live beside the code they cover, under app/src/ and in several crates; run
cargo test. There is no UI test framework in use beyond i-slint-backend-testing as a
dev-dependency of app.
There are two translation surfaces, and they are updated differently:
- In-app strings live in
ui/i18n/<locale>/LC_MESSAGES/conic-ui.po, one catalogue per language.tools/update-i18n.pyre-keys them againstslint-tr-extractor; do not hand-edit them. Adding a string to the UI means adding it to all twelve catalogues through that tool. - The READMEs and the desktop entry's
Commentare hand-maintained. The EnglishREADME.mdis the source: when it changes, mirror the same structure intodocs/readme/README.<locale>.md. The desktop entry'sCommentcarries one line per language; seepackaging/linux/README.mdfor which locales need a region suffix.
Machine translation is welcome in both places — see the AI Policy.
The app version is version in app/Cargo.toml — the single source. build.yml reads it with
sed (no toolchain needed in the version job), builds the Linux packages on push to master, and
publishes a release only when the version differs from HEAD~1. Pull requests target dev;
releases are cut from master.
CURSEFORGE_API_KEY is read at build time by crates/curseforge/build.rs; without it the official
CurseForge API is simply unauthenticated.
packaging/ holds the payload each format installs, one directory per platform. The README.md in
each documents its contents and what is easy to break there:
packaging/linux/— the desktop entry and hicolor icon set, the only copy the deb, rpm, AppImage and Arch package install.packaging/macos/— the.icns, theInfo.plisttemplate and the disk image's window background.packaging/windows/— the.wxsand the.ico.packaging/arch/— thePKGBUILDand its.SRCINFO.
The scripts that assemble the artifacts, all writing to <target-dir>/package/:
tools/package-linux.sh # deb + rpm + AppImage
tools/package-linux.sh deb rpm # a subset
tools/package-macos.sh # .app + .dmg, native arch
tools/package-macos.sh --universal # both arches in one binary (local only; CI
# builds each natively instead — see macOS)
pwsh tools/package-windows.ps1 # .exe + .msi, native arch
pwsh tools/package-windows.ps1 msi # a subset| Format | Recipe |
|---|---|
.msi/.exe |
tools/package-windows.ps1, see Windows |
.deb |
[package.metadata.deb] in app/Cargo.toml |
.rpm |
[package.metadata.rpm] + the spec at app/.rpm/conic-launcher.spec |
.AppImage |
the AppDir tools/package-linux.sh assembles, then appimagetool |
.app/.dmg |
the bundle tools/package-macos.sh assembles, then hdiutil, with the window layout from tools/dmg-ds-store.py |
| Arch | packaging/arch/PKGBUILD — makepkg -si |
One rule spans every format: each spells the version differently. 0.1.0-alpha.2 ships as
0.1.0~alpha.2-1 in the deb (cargo-deb maps - to ~; a Debian version may carry only one -),
as Version: 0.1.0 / Release: 0.alpha.2 in the rpm (cargo-rpm splits on the first -, which is
what makes the pre-release sort below the final), as 0.1.0 + a build of 0.1.0-alpha.2 in the
macOS bundle (CFBundleShortVersionString must be dotted integers), and verbatim in the AppImage,
the .dmg and the Arch package. Never hardcode a filename.
tools/package-linux.sh needs Linux: dpkg-deb, rpmbuild, dpkg-shlibdeps and appimagetool
have no counterpart elsewhere, and it says so rather than failing three steps in.
tools/package-windows.ps1 assembles the .msi from the WiX toolset and copies the standalone
.exe beside it; packaging/windows/README.md is that recipe in detail, including the
.msi-specific traps. The script and its CI job carry a few decisions the payload README does not:
- PowerShell, not bash.
wix,msiexecanddotnetare Windows programs, and a bash script driving them spends its length fighting Git-Bash's path rewriting on exactly the strings the installer needs verbatim. - WiX 6 is installed as a pinned .NET tool on both runners, not taken from the image.
windows-2025carries WiX v3.14.1,windows-11-armcarries none, and WiX v3 is EOL — the image's copy would mean two dialects for one.wxs. The version is pinned (6.0.2): an installer that builds one week and not the next is not a reproducible release. WixToolset.UI.wixextis installed explicitly. Without it the MSI builds and installs with no interface at all, so its absence is not an error.- CI builds each architecture natively (
windows-2025,windows-11-arm), for the reasonpackaging/macos/README.mdgives. The script therefore has no--targetand reads its architecture out ofrustc -vV. - The workflow installs and uninstalls the MSI.
wixvalidates XML;msiexecis what refuses a package the processor type does not support, and it is the only check that finds the packaged binary is the one that was built.
tools/package-macos.sh assembles the .app and .dmg itself out of codesign, hdiutil, lipo
and python3; packaging/macos/README.md covers the payload, the native-runner build and the
signing gap. Two things worth knowing before touching the script:
-
The window layout is written, not scripted. Finder cannot be told to lay out a folder that does not exist yet without an Automation grant and a window on screen, so
tools/dmg-ds-store.pywrites the.DS_Storeand copies in the background picture directly — the Buddy allocator and the Carbon Alias record are implemented against the standard library rather than pulled in asdmgbuildords_store, because this script's contract is that a build machine needs nothing beyond macOS. It reads the window's size and the icon placement out ofpackaging/macos/dmg/background.svg, so the artwork and the placement cannot disagree;python3 tools/dmg-ds-store.py --checkprints what it derived. -
x86_64-apple-darwinneeds care here.objc-systypes an Objective-CBOOLasboolon arm64 (where C'sBOOLis_Bool) and asi8on x86_64 (where it issigned char), so code that returns one straight out of an FFI call builds for Apple Silicon and fails for Intel withexpected bool, found i8.app/src/support/native/macos/traffic_lights.rs'sadd_methodis the one place this bites; it goes throughi8to satisfy both. A--universalbuild is what surfaces this.
Not declared anywhere in the build, so a missing one is a link error in the middle of a release build:
pkg-config libfontconfig1-dev libasound2-dev libudev-dev
(fontconfig via fontique, ALSA via cpal, libudev via i-slint-backend-linuxkms). GL, xkbcommon
and X11 are dlopened by glutin, winit and Skia — runtime dependencies of the package, not build
dependencies of the crate. At runtime the app also shells out to xdg-open and dbus-send; file
and folder pickers go through the XDG desktop portal (rfd), whose file chooser
xdg-desktop-portal implements.