Files
ltk/CONTRIBUTING.md

5.1 KiB

Contributing to ltk

Thanks for considering a contribution. This document covers the practical mechanics: how to set up your environment, how to run tests, what shape a patch should take, and where to send it.

For background on the toolkit itself, read docs/onboarding.md and docs/architecture.md first.

Reporting bugs and proposing features

Open an issue on the project repository before sending a non-trivial patch. We want to align on scope before you spend time on an implementation. For security-relevant issues see SECURITY.md — those should not go through the public issue tracker.

When reporting a bug, include:

  • the ltk version (commit hash if you built from source),
  • the Rust toolchain (rustc --version),
  • the compositor and OS,
  • a minimal reproducer (Rust source preferred over screenshots),
  • whether the issue happens on both the GLES and the software backend (set LTK_FORCE_SOFTWARE=1 to force software).

Building and testing

The project requires the Rust toolchain shipped with Debian stable (currently 1.85). On Debian / Ubuntu:

sudo apt-get install \
    libwayland-dev libegl-dev libxkbcommon-dev pkg-config

git clone <repo>
cd ltk
cargo build
cargo test

The Makefile wraps the common targets:

make all          # cargo build --release
make test         # cargo test
make audit        # cargo audit (installs cargo-audit on first run)
make doc          # cargo doc --no-deps
make example      # run every example under examples/ in turn
make clean

Running the examples requires a Wayland session and the default theme on disk:

export LTK_THEMES_DIR="$PWD/themes"
cargo run --example showcase

Code style

ltk uses a custom Modified Allman style. rustfmt's default settings do not match it; do not run cargo fmt. The full rules live in code_style_guide.md, but the headline points are:

  • tabs for indentation (never spaces),
  • opening { on its own new line for fn, impl, struct, enum, mod, if, for, while, match, loop,
  • } else { and } else if … { on the same line as the closing brace (compact else),
  • spaces inside non-empty parentheses: fn foo( x: i32 ), bar( arg ), Some( x ), Ok( v ),
  • spaces inside non-empty attribute brackets: #[ derive( Clone, Debug ) ],
  • no spaces inside <> generics: Vec<String>, Option<i32>,
  • comments in English — never another language.

Match the surrounding code when in doubt.

Patch shape

  • Keep changes focused: one logical change per pull request. A bug fix and a refactor go in separate PRs.
  • Add tests. The repository has ~400 tests covering the existing surface, and a contribution that adds behaviour without test coverage will get review pushback. See tests/ for examples of integration tests via UiSurface, and the inline #[cfg(test)] mod tests blocks under src/ for unit tests.
  • Document new public API. Every pub item exported from the crate root is expected to have a /// rustdoc comment with at minimum a one-paragraph description and an example. Module-level //! comments are required for new submodules.
  • Don't break the public API surface without coordinating. Until the crate hits 1.0, breaking changes go in minor versions (0.x.0 → 0.(x+1).0); patch versions (0.x.y → 0.x.(y+1)) keep source compatibility.
  • Run cargo test and make audit before sending. CI will run them again, but it is faster for both of us if your local run is clean.

Architectural decisions worth knowing

A few patterns recur across the codebase:

  • Builder methods consume self (pub fn padding( mut self, p: f32 ) -> Self). Chaining works because every builder returns Self. Don't introduce setters that take &mut self.
  • Layouts and widgets share Element<Msg>. Anything that converts to Element can be pushed into any layout. The split between [crate::layout] and [crate::widget] is documentation, not architecture.
  • The runtime is single-threaded. Use RefCell for caches inside App state, never Mutex. Cross-thread communication goes through ChannelSender.
  • view() must be pure. No I/O, no allocation-heavy work, no state mutation. Cache derived data on the app struct (behind RefCell if needed) and look it up.
  • Theming is process-global. There is no per-app theme; the active document and mode live in a RwLock<Arc<…>>. view() reads the state, never writes it. Mode flips and document swaps go through [crate::set_active_mode] and [crate::set_active_document].
  • Per-frame allocations are fine. Building the Element tree on every render is the supported model. Don't try to retain widgets across frames.

License

By submitting a patch you agree it is licensed under LGPL-2.1-only, the same as the rest of the project. Do not add Co-Authored-By lines or other AI attribution to commit messages.

Code of conduct

Be respectful. Disagreements are welcome; personal attacks are not. Maintainers will moderate threads that go off the rails.