Skip to content
Development

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 bootstrap

Then inspect the host, build all three components, and run their test suites:

tools/denial-pc doctor
tools/denial-pc build
tools/denial-pc test

The main x86-64 reference outputs are:

ArtifactLocation
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 bundledart_shell/build/linux/x64/release/bundle
Settings release bundlesettings_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.dart

The 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-session

Log out and select that entry explicitly. Remove it when it is no longer needed:

tools/denial-pc remove-session

The 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 setup

The default setup:

  1. creates ~/DenialUI at the source revision recorded by the package;
  2. prepares the matching JIT bundle with the packaged toolchain;
  3. selects ~/DenialUI/dart_shell as the workspace;
  4. 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 profile

This 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_shell

Keep that command running while profiling. Return to the packaged release shell afterward with:

denialctl ui restore

After 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 restore

Some 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 restore

It 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.