Development
The source tree contains three runtime components:
- the Rust compositor, control client, and portal in
compositor/; - the embedded Flutter shell in
dart_shell/; - the standalone Flutter Settings application in
settings_app/.
They are built and versioned together.
Build from source
Denial supports source builds on x86-64 and ARM64. The commands and output paths below describe the current turnkey x86-64 reference build on an Arch development host. ARM64 uses the same locked Denial, Flutter, and Skia sources with an architecture-matched Flutter engine and shell bundle; first-party ARM64 packages are not published yet.
Bootstrap the pinned toolchain and Rust dependencies once:
tools/denial-pc bootstrapThen inspect the host, build all three components, and run their test suites:
tools/denial-pc doctor
tools/denial-pc build
tools/denial-pc testThe main x86-64 reference outputs are:
| Artifact | Location |
|---|---|
| Compositor | $XDG_CACHE_HOME/denial/pc-build/rust/release/deniald |
| Native control client | $XDG_CACHE_HOME/denial/pc-build/rust/release/denialctl |
| Denial portal | $XDG_CACHE_HOME/denial/pc-build/rust/release/denial-portal |
| Flutter release bundle | dart_shell/build/linux/x64/release/bundle |
| Settings release bundle | settings_app/build/linux/x64/release/bundle |
The bootstrap is networked. Later builds reuse the pinned cache. Run
tools/denial-pc doctor rather than guessing which Smithay, DRM, GBM, EGL,
libinput, udev, or Xwayland development dependency is missing.
Build only the Settings client, or run the lock-matched Flutter tests with
arguments forwarded to flutter test:
tools/denial-pc settings
tools/denial-pc flutter-test
tools/denial-pc flutter-test test/settings/settings_application_test.dartThe current coupled generation is Flutter 3.44.7 and Dart 3.12.2. The exact
Flutter, Skia, engine, and ABI revisions are recorded in
prebuilt/flutter-engine/SOURCE_LOCK.json; do not mix bundles or engine
artifacts from another generation or architecture.
Test a local session
Install a separate development session entry:
tools/denial-pc install-sessionLog out and select that entry explicitly. Remove it when it is no longer needed:
tools/denial-pc remove-sessionThe development entry is separate from the packaged Denial session and does not replace a running compositor.
Live-edit the Flutter shell
Live editing is optional and does not require rebuilding the Rust compositor. Install the version-matched development environment:
sudo pacman -S denial-ui-development
denialctl ui setupThe default setup:
- creates
~/DenialUIat the source revision recorded by the package; - prepares the matching JIT bundle with the packaged toolchain;
- selects
~/DenialUI/dart_shellas the workspace; - replaces the optimized shell with the live runtime.
Pass another absolute destination to denialctl ui setup if preferred.
Open the created dart_shell directory in VSCodium and start Attach to
Denial live UI. Saving a changed Dart file requests hot reload, and Flutter
Inspector remains available.
This attach profile deliberately does not support debugger pause, breakpoints, stepping, or expression evaluation: suspending the shell isolate would also suspend the interactive desktop. Changes to native Rust code or the Flutter engine still require a normal build and session restart.
Profile the Flutter shell
For representative performance measurements, build and activate the optimized AOT profile shell from a Denial checkout:
denial-ui prepare-profile /absolute/path/to/denial/dart_shell
denialctl ui workspace /absolute/path/to/denial/dart_shell
denialctl ui profileThis keeps optimized AOT application code while enabling Flutter’s VM service, timeline events, CPU profiling, and DevTools. It is distinct from the JIT live editing mode above.
Start browser DevTools with:
denial-ui attach-profile /absolute/path/to/denial/dart_shellKeep that command running while profiling. Return to the packaged release shell afterward with:
denialctl ui restoreAfter installing or replacing denial-ui-development, restart the Denial
session once before activating profile mode so the matching native engine is
loaded cleanly.
Runtime control
Useful development commands are:
denialctl ui status
denialctl ui setup [PATH]
denialctl ui workspace /absolute/path/to/dart_shell
denialctl ui live on
denialctl ui reload
denialctl ui restart
denialctl ui profile
denialctl ui restoreSome reserved actions may report that their native capability is not yet implemented. A rejected command returns a clear error rather than pretending to succeed.
Always keep the recovery command available:
denialctl ui restoreIt returns to the packaged optimized shell without ending the Wayland session.
Build a custom shell
The public framework entry point is:
import 'package:denial_dart_shell/denial.dart';
import 'package:flutter/widgets.dart';
void main() {
runDenialShell(
shell: const DenialShell(
mobile: DenialShellScene(content: MyMobileShell()),
desktop: DenialShellScene(content: MyDesktopShell()),
),
);
}runDenialShell initializes the native bridge, lifecycle, localization,
theme, secure lock, input publication, cursor, software keyboard, screenshot
selection, and overlay ordering. A custom shell supplies feature scenes and
may use the exported shell models, actions, surface hosts, window builders,
and optional local applications. Do not import lib/src from code outside
the package; only package:denial_dart_shell/denial.dart is the supported
framework boundary.
The checked dart_shell/example/custom_shell.dart entry point is the
smallest complete example. Prepare and activate its workspace with the same
denialctl ui workspace and profile/live commands described above.
Caution
A custom Flutter shell is trusted session code. It can observe compositor state and call every native action exposed to the official shell. Do not run untrusted shell sources.