Three changes that came out of making the shell's back button work across Eydos the way it does on Android. The first is the feature; the other two are gaps it exposed. Back is no longer Escape. forge turns its back gesture and button into the `XF86Back` key for the focused client, and ltk treated that key as an alias of `Escape`: close transient UI, else drop the focus and reach `on_key_with_modifiers`. That is "cancel", and it is all an app could get: nothing navigated, and nothing ever left the app. `XF86Back` is now a back request with its own order, one layer per request, top-most first: transient UI the runtime owns (open xdg-popup overlays, the built-in context menu, a text selection, the top-most pressable carrying an `on_escape`, i.e. a dialog's cancel), then text-input focus, then the new `App::on_back`, then `on_key_with_modifiers( XF86_Back )` so an app that bound the key itself keeps receiving it (crustace does, for its launcher), then `App::back_at_root`. The first step is what Escape already did; it moves out of `handle_key_escape` into `AppData::dismiss_transient` in the new `input/keyboard/back.rs`, which both paths call, so Escape behaves exactly as before. Text focus comes before the app on purpose: with a keyboard covering half the screen, back means "put the keyboard away", and navigating instead would discard what the user was typing. A compositor that folds its keyboard itself, as forge now does, only lets the next request through, which makes this step the fallback for the ones that do not. `App::on_back` returns a `BackResponse`: `Message( msg )` with the message that undoes the top-most piece of navigation state, `Unhandled` at the root, or `Consumed` for a host whose back stack lives elsewhere and cannot be inspected ahead of time. `Consumed` exists for rustdroid: an Android app keeps its activities, fragments and back callbacks in its own code, so the host can only hand the request over. `App::back_at_root` then picks `Minimize` (the default), `Close` (through `on_close_requested`, which can still veto) or `Ignore`. Minimising is what a root activity does on Android since version 12 and what suits an app the user comes back to: a browser keeps its tabs, a call goes on. It is also the single point where the two stacks meet: the navigation stack belongs to the app's model, the task stack to the compositor, and `xdg_toplevel.set_minimized` is the one standard request between them, so no custom protocol carries navigation state out of the process and clients that are not ltk apps keep working. Only `ShellMode::Window` surfaces react at the root; a panel or a lock screen that minimised itself would be a bug. Three consequences of back being a navigation request rather than input: the key leaves the default `App::key_repeats` set, since a held button would empty the whole stack; it is no longer reported through `App::on_raw_key`, so a host forwarding raw keys into an embedded web view or guest toolkit stops leaking it as a keystroke; and the mouse's back button (`BTN_SIDE` / `BTN_BACK`) enters through the same `handle_back`. `focused_text_input` becomes `pub( crate )` for the new module. The answer is derived, not recorded, and that is the design decision worth writing down. The runtime keeps no navigation stack of its own. Its entries would have to be messages, which are not serialisable (the trait puts no serde bound on `Message`, by design), so it could not survive a relaunch; and a list of "things to undo" drifts the first time the user leaves a screen some other way, through its own close button or a link, leaving a back press that does nothing visible. An answer computed from the model cannot disagree with it. This is also what ties back to the session persistence added earlier: navigation state is both what `on_back` reads and what `save_state` should persist, and keeping the two the same is what makes back retrace the same places after a session restore or a crash, with nothing extra to serialise. Persist less and a restored app opens on the right screen with nothing behind it; persist more and it restores a menu the user had closed. `NavStack<R>` packages that rule for the common case, an app made of screens. It is a plain value the app keeps in its model, never seen by the runtime: `push`, `pop`, `replace` (the sideways move back should not retrace), `pop_to_root`, `reset`, `current`, `current_mut`, `iter`, `depth`, `can_pop`, and `back( msg )`, which answers `on_back` directly. It is stored as a root plus the routes above it rather than as a `Vec`, so "never empty" holds by construction, including after deserialising an old or damaged state file, and it derives serde whenever `R` does. Six unit tests cover it, a serde round trip among them, and its rustdoc example is a complete `App` where one object answers `on_back`, `save_state` and `restore_state`. Documentation, since the point is for app authors to take navigation state into account. The new `docs/navigation.md` is the reference: transient UI versus navigation state and the test that separates them, the back order and the reason for each step, why the answer is derived, how it lines up with session state, `NavStack`, apps that embed a web view (whose page history is navigation state too, to be persisted with ltk-webkit's `session_state`), hosts with a foreign back stack, the root, who owns which stack, and an author's checklist. `architecture.md` gains a group in the trait surface and a paragraph in the state patterns, `onboarding.md` and the README index link to it, and in the cookbook the multi-screen recipe now uses `NavStack` with `on_back` and persists the stack without serde, which keeps it compiled by the Markdown doctest harness (that harness links `ltk` alone, so the serde example in `navigation.md` is marked `ignore` and compiled as `NavStack`'s own doctest instead). `BackResponse`, `BackAtRoot` and `NavStack` are exported at the crate root and in the `ltk::window` facade. Behaviour change for existing apps, also recorded under Changed: a window app that implements neither `on_back` nor the key is now minimised on back at its root, where nothing happened before; `BackAtRoot::Ignore` restores that. With a text input focused, back only drops the focus and the app sees the next request, where it used to get the key in the same press. Renew the text-input enable on a press. Moving the focus between two text fields, or pressing the one already focused, only committed the refreshed content type on the existing `zwp_text_input_v3`. `activate_text_input` now sends `enable` first, as GTK does when a focused entry is clicked. A compositor that folded its on-screen keyboard while the field kept the focus, which is what back now does, has no other way to learn that the user wants it again: the text-input never stopped being active, so there was nothing for it to observe. ltk sends neither surrounding text nor a cursor rectangle, so the only state `enable` resets is the content type set right after it. Fit an oversized `window_size_hint` to the screen. The hint is pinned as `min_size == max_size` before the first commit, when no output is known yet, so an app asking for a phone-shaped 720x1440 window on a 1080p desktop got a toplevel taller than the screen; rustdroid carried a dead branch that tried to avoid this from a screen size nothing could give it. The first configure is the earliest point the hint can be checked, and the protocol has the right input for it: `xdg_toplevel.configure_bounds`, exposed by sctk as `WindowConfigure::suggested_bounds` and ignored until now. The new `event_loop/window_fit.rs` holds `fit_within`, which scales a size down keeping its aspect ratio until it fits and leaves one that fits untouched (four unit tests), and `AppData::fit_size_hint`, run once behind the new `pending_size_hint_fit` latch: it uses the compositor's bounds when sent and otherwise 90 % of the first output's logical size, since panels and decorations are unknown from the client side. When the hint has to shrink, `min_size` (and `max_size` for a non-resizable window) is re-pinned to the fitted size, which is kept in `fitted_size_hint` and replaces the app's hint as the fallback for later configures that carry no size; without that the window would grow back on the next one. On that one configure the fitted size wins over the compositor's `new_size`, which was derived from the oversized pin. `App::window_size_hint` documents the rule with the worked example (720x1440 opens as 486x972 on a 1080p output). The back order needs a live Wayland connection to exercise and has no test; the unit tests cover `NavStack` and `fit_within`.
433 lines
16 KiB
Markdown
433 lines
16 KiB
Markdown
# ltk
|
|
|
|
`ltk` is a public Rust UI toolkit for Wayland applications.
|
|
|
|
It is developed by Liberux as part of the Eydos stack, where it powers
|
|
shell and application surfaces, but it is published as a reusable library for
|
|
third-party developers building their own Wayland software.
|
|
|
|
Being written in Rust is also part of the project's value proposition:
|
|
|
|
- memory safety without a garbage collector
|
|
- predictable resource lifetimes through ownership and borrowing
|
|
- good control over allocations and data movement in rendering-heavy code
|
|
- a strong fit for low-level UI, graphics, and system-integration work
|
|
|
|
For a Wayland toolkit, that combination is useful in practice: it reduces an
|
|
entire class of memory-management bugs common in lower-level UI stacks while
|
|
still allowing tight control over performance-sensitive paths.
|
|
|
|
## What It Is
|
|
|
|
`ltk` is a lightweight, declarative toolkit with an Elm-shaped model:
|
|
|
|
- implement `App`
|
|
- return an `Element<Msg>` tree from `view()`
|
|
- update your state in `update()`
|
|
- run the event loop with `ltk::run(app)`
|
|
|
|
The runtime handles layout, drawing, input dispatch, focus, overlays, and
|
|
backend selection between GLES and software rendering.
|
|
|
|
## What It Is Not
|
|
|
|
`ltk` is not:
|
|
|
|
- a browser UI toolkit
|
|
- a cross-platform desktop toolkit
|
|
- a general-purpose web-style framework
|
|
|
|
Today it is specifically a Wayland toolkit. If you are building native Wayland
|
|
applications, panels, launchers, lock screens, or other shell-adjacent
|
|
surfaces, it is in scope. If you need Windows, macOS, or browser targets, it
|
|
is not.
|
|
|
|
## Project Status
|
|
|
|
`ltk` is a public library intended for third-party use, but it is still shaped
|
|
by real production needs inside the Liberux / Eydos ecosystem.
|
|
|
|
That means:
|
|
|
|
- the API is usable for external applications today
|
|
- the project is optimized first for native Wayland workloads
|
|
- some advanced APIs are still more shell-oriented than app-oriented
|
|
- public documentation and examples are present, but the project is not trying
|
|
to present itself as a cross-platform beginner toolkit
|
|
|
|
If you are evaluating `ltk` for a third-party application, the right mental
|
|
model is "public Wayland toolkit with production consumers" rather than
|
|
"experimental demo crate".
|
|
|
|
## Why Third Parties Might Use It
|
|
|
|
`ltk` is designed around a few practical goals:
|
|
|
|
- low idle wakeups and event-driven redraws
|
|
- partial redraws and damage tracking
|
|
- a simple declarative tree instead of retained widgets
|
|
- direct support for normal windows, layer-shell, and ext-session-lock surfaces
|
|
- a runtime-free core (`ltk::core::UiSurface`) for embedding layout and drawing
|
|
without `ltk::run()`
|
|
|
|
This makes it especially relevant for:
|
|
|
|
- Wayland applications
|
|
- mobile-first Linux shells
|
|
- launchers and dashboards
|
|
- greeters and lock screens
|
|
- compositor-side or embedded UI surfaces
|
|
|
|
## Quick Start
|
|
|
|
Add `ltk` to your `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies]
|
|
ltk = { path = "../ltk" }
|
|
```
|
|
|
|
If you consume `ltk` from outside this workspace, replace the `path` dependency with a versioned one.
|
|
|
|
Minimal app:
|
|
|
|
```rust
|
|
use ltk::{ App, Element, button, column, spacer, text };
|
|
|
|
#[derive(Clone)]
|
|
enum Msg
|
|
{
|
|
Increment,
|
|
}
|
|
|
|
struct CounterApp
|
|
{
|
|
value: u32,
|
|
}
|
|
|
|
impl App for CounterApp
|
|
{
|
|
type Message = Msg;
|
|
|
|
fn app_id( &self ) -> &str { "net.example.Counter" }
|
|
fn save_state( &self ) -> Option<Vec<u8>> { None }
|
|
fn restore_state( &mut self, _state: Vec<u8> ) {}
|
|
|
|
fn view( &self ) -> Element<Msg>
|
|
{
|
|
column::<Msg>()
|
|
.padding( 32.0 )
|
|
.spacing( 16.0 )
|
|
.center_y( true )
|
|
.push( text( "Hello from ltk" ).size( 28.0 ) )
|
|
.push( text( format!( "Count: {}", self.value ) ).size( 18.0 ) )
|
|
.push( spacer() )
|
|
.push( button( "Increment" ).on_press( Msg::Increment ) )
|
|
.into()
|
|
}
|
|
|
|
fn update( &mut self, msg: Msg )
|
|
{
|
|
match msg
|
|
{
|
|
Msg::Increment => self.value += 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn main()
|
|
{
|
|
ltk::run( CounterApp { value: 0 } );
|
|
}
|
|
```
|
|
|
|
`app_id` names the window and the `$XDG_STATE_HOME/<app_id>/` session
|
|
directory; `save_state` / `restore_state` are the opaque bytes ltk persists
|
|
for session restore and crash recovery — return `None` when you have nothing
|
|
to keep.
|
|
|
|
## Requirements
|
|
|
|
`ltk` currently assumes:
|
|
|
|
- **Rust 1.85** or newer (the toolchain shipped with Debian stable; declared
|
|
as `rust-version` in `Cargo.toml`).
|
|
- A running **Wayland** session — there is no X11 backend.
|
|
- System headers for `libwayland`, `libegl` and `libxkbcommon` at compile
|
|
time. On Debian / Ubuntu:
|
|
```bash
|
|
sudo apt-get install libwayland-dev libegl-dev libxkbcommon-dev pkg-config
|
|
```
|
|
- A usable system font (`fonts-sora`, `fonts-liberation`, `fonts-dejavu`,
|
|
…). If none is installed `ltk` falls back to an embedded Sora Regular
|
|
build with a stderr warning.
|
|
- A theme named `default`, installed system-wide (the
|
|
`ltk-theme-default` Debian package drops it under
|
|
`/usr/share/ltk/themes/default/`) or exposed through
|
|
`LTK_THEMES_DIR` for development.
|
|
|
|
Rendering backend selection is automatic:
|
|
|
|
- **GLES** when EGL is available (every modern Wayland compositor).
|
|
- **Software** fallback otherwise.
|
|
- Set `LTK_FORCE_SOFTWARE=1` to force the software path even when EGL is
|
|
available — useful for headless test runs and for diagnosing
|
|
driver-specific bugs.
|
|
|
|
For development inside this repository:
|
|
|
|
```bash
|
|
export LTK_THEMES_DIR="$PWD/themes"
|
|
cargo run --example showcase
|
|
```
|
|
|
|
## Examples
|
|
|
|
Useful entry points in this repository:
|
|
|
|
- `cargo run --example showcase`
|
|
- `cargo run --example responsive`
|
|
- `cargo run --example widgets`
|
|
- `cargo run --example inputs`
|
|
- `cargo run --example scroll`
|
|
- `cargo run --example combo`
|
|
- `cargo run --example dialog`
|
|
- `cargo run --example sliders`
|
|
- `cargo run --example pickers`
|
|
- `cargo run --example carousel`
|
|
- `cargo run --example clip_path`
|
|
- `cargo run --example mini_shell`
|
|
|
|
In general:
|
|
|
|
- start with `showcase` for a regular app window
|
|
- use `responsive` to see the fluid vs physical modes on stock widgets
|
|
- use `widgets` to see the core controls
|
|
- use `mini_shell` if you need overlays, theme switching, or shell-style
|
|
composition
|
|
|
|
## Public API Overview
|
|
|
|
Most applications should start with this subset:
|
|
|
|
- `App`
|
|
- `Element<Msg>`
|
|
- widgets such as `button`, `text`, `text_edit`, `img_widget`
|
|
- layouts such as `column`, `row`, `stack`, `grid`, `spacer`
|
|
- `Color`
|
|
- `run`
|
|
|
|
More advanced APIs are available when needed:
|
|
|
|
- `overlays()`
|
|
- `shell_mode()` and layer-shell controls
|
|
- `set_channel_sender()` and `poll_external()`
|
|
- gesture hooks such as `on_swipe_*`
|
|
- `core::UiSurface`
|
|
- runtime theme APIs
|
|
|
|
## Responsive Design
|
|
|
|
`ltk` offers **two** first-class ways to make an interface adapt to the
|
|
display, chosen per value or per process:
|
|
|
|
- **Fluid** — sizes are a fraction of the surface, tracking the short side
|
|
(width in portrait, height in landscape). Best for full-screen system
|
|
surfaces. Written with `Length::vmin` / `orient` / `fluid` and bounded with
|
|
`.clamp`.
|
|
- **Physical** — sizes stay a constant real-world size across displays (the
|
|
mainstream HiDPI `dp` model). Best for conventional windowed apps. Written
|
|
with `Length::dp` plus `set_density`.
|
|
|
|
Stock widgets follow the process-wide mode (`set_widget_scaling`, fluid by
|
|
default); an explicit `Length` on a widget always overrides it. The full
|
|
mechanics live in [`docs/architecture.md`](docs/architecture.md#responsive-sizing),
|
|
a walkthrough in [`docs/onboarding.md`](docs/onboarding.md), and the per-item
|
|
reference in the `Length` / `WidgetScaling` rustdoc.
|
|
|
|
## Windows and Shell Surfaces
|
|
|
|
By default, `ltk` creates a regular `xdg-shell` window.
|
|
|
|
That is the right starting point for:
|
|
|
|
- normal applications
|
|
- internal tools
|
|
- prototypes
|
|
|
|
Switch to layer-shell only when you are building shell surfaces such as:
|
|
|
|
- top bars
|
|
- docks
|
|
- homescreens
|
|
- notifications
|
|
- greeters
|
|
- lock screens
|
|
|
|
For a screen locker, use `ShellMode::SessionLock` instead of layer-shell: it
|
|
presents an `ext-session-lock-v1` surface that the compositor keeps on top of
|
|
everything until the app returns `true` from `requested_exit()`, which makes
|
|
the runtime call `unlock` and lift the lock.
|
|
|
|
## Performance Notes
|
|
|
|
`ltk` is designed to sleep when idle and redraw only on real work.
|
|
|
|
The main rules for downstream applications are:
|
|
|
|
- keep `view()` pure and cheap
|
|
- do not perform I/O inside `view()`
|
|
- use `poll_interval()` sparingly
|
|
- return `true` from `is_animating()` only while something is actually moving — note that on the software backend animation redraws are capped at ~30 Hz by default (override with `App::cap_software_animation`)
|
|
- cache decoded images and expensive derived state in your app
|
|
|
|
Set `LTK_PERF_WARN=1` during development for opt-in diagnostics: it warns about stuck animations, sustained software-render animation, and low `poll_interval` values.
|
|
|
|
The library already provides:
|
|
|
|
- event-driven redraw scheduling
|
|
- per-surface invalidation
|
|
- partial redraws for interaction-only changes
|
|
- GPU and software backends behind the same widget API
|
|
|
|
## Backend Differences
|
|
|
|
The public API is the same across backends, but visual parity is not perfect
|
|
yet. The widget tree, layout, hit-testing, text, images, fills, strokes and
|
|
single-rect / path clipping all paint identically on both paths. The gaps are in
|
|
gradients, the shadow / backdrop pipeline, and multi-rect clipping: the GLES
|
|
backend installs the bounding-box union of the rects as its `glScissor` — a
|
|
coarse clip that does not cull pixels between disjoint rects — while the
|
|
software backend clips each rect exactly.
|
|
|
|
Effects that currently render only on the **GLES** backend, and degrade on the
|
|
**Software** backend:
|
|
|
|
- **Gradients** (linear and radial, via `Canvas::fill_paint_rect`) — rendered with
|
|
dedicated shaders on GLES; on software they collapse to a flat fill from the
|
|
first stop (tiny-skia can render gradients natively, but that is not wired up
|
|
yet).
|
|
- **Outer drop shadows** (`Canvas::fill_shadow_outer`) — themed surfaces that
|
|
declare a `Shadow` slot show the soft halo on GLES and a flat fill on
|
|
software.
|
|
- **Inner / inset shadows** (`Canvas::fill_shadow_inset`) — `InsetShadow`
|
|
slots paint nothing on software.
|
|
- **Inset shadow blend modes** — `PlusLighter`, `Multiply`, `Screen` and
|
|
`Overlay` are GLES-only; the GLES `Overlay` path snapshots the framebuffer
|
|
and computes the CSS Overlay formula in-shader, which has no software
|
|
equivalent today.
|
|
|
|
Calls to these APIs are safe on both backends — they simply produce a flatter
|
|
appearance under software. No widget panics, returns an error, or skips
|
|
unrelated drawing.
|
|
|
|
If your application leans heavily on shadows or inset effects, validate both
|
|
rendering paths before shipping. Force the software path with:
|
|
|
|
```bash
|
|
LTK_FORCE_SOFTWARE=1 cargo run --example showcase
|
|
```
|
|
|
|
Closing this gap (porting the shadow / inset-shadow pipeline to tiny-skia) is
|
|
on the roadmap.
|
|
|
|
## Documentation
|
|
|
|
| File | When to read it |
|
|
| --- | --- |
|
|
| [`docs/onboarding.md`](docs/onboarding.md) | First hour with the library — environment, first app, what to ignore at first. |
|
|
| [`docs/architecture.md`](docs/architecture.md) | Runtime model, overlays, animation, theming, performance and where the cost of a frame lives. |
|
|
| [`docs/widgets.md`](docs/widgets.md) | Per-widget catalogue: what each one is, when to use it, minimal example, see-also. |
|
|
| [`docs/theming.md`](docs/theming.md) | JSON theme schema, slot conventions, runtime APIs. |
|
|
| [`docs/backends.md`](docs/backends.md) | Software/GLES capability matrix — what renders identically, what degrades, what is GPU-only. |
|
|
| [`docs/navigation.md`](docs/navigation.md) | Navigation state and the back button: what back closes and in which order, why the answer is derived from the model, how it lines up with session state, `NavStack`. |
|
|
| [`docs/cookbook.md`](docs/cookbook.md) | Concrete recipes — slide-in panels, password fields, runtime theme toggle, channel-driven state, embedding without `ltk::run`. |
|
|
| `cargo doc --open` | Per-item rustdoc for the public API. |
|
|
| [`CHANGELOG.md`](CHANGELOG.md) | What changed in each release, and what is pending unreleased. |
|
|
| [`code_style_guide.md`](code_style_guide.md) | The Modified Allman style rules the codebase follows. |
|
|
| [`SECURITY.md`](SECURITY.md) | How to report a vulnerability and what is in / out of scope. |
|
|
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Build, test, code style, patch shape. |
|
|
|
|
Recommended reading order for a new contributor:
|
|
|
|
1. run `examples/showcase.rs`
|
|
2. read `docs/onboarding.md`
|
|
3. browse `docs/widgets.md` for the catalogue
|
|
4. dip into `docs/cookbook.md` when you hit a specific shape
|
|
5. open `docs/architecture.md` once you need overlays, animations, or
|
|
runtime theming.
|
|
|
|
## Relationship to Liberux and Eydos
|
|
|
|
Liberux is the promoter and primary maintainer of `ltk`.
|
|
|
|
The project exists because Eydos needs a native Wayland toolkit for its
|
|
own shell and application stack, but `ltk` is intentionally published as a
|
|
public library rather than kept as a private internal component. Third-party
|
|
developers are part of the intended audience.
|
|
|
|
That origin matters because it explains the current priorities:
|
|
|
|
- strong Wayland focus
|
|
- support for layer-shell and shell-style overlays
|
|
- attention to mobile power usage
|
|
- theming and runtime surfaces that fit an operating system environment
|
|
|
|
## License
|
|
|
|
This project is licensed under `LGPL-2.1-only`.
|
|
|
|
That means third parties can use `ltk` in their own applications, including
|
|
proprietary ones, subject to the obligations of the GNU Lesser General Public
|
|
License v2.1. If you are planning a commercial or closed-source product, read
|
|
the license text carefully and make sure your distribution model complies with
|
|
it.
|
|
|
|
See [LICENSE](./LICENSE).
|
|
|
|
## Third-party assets
|
|
|
|
`ltk`'s default theme bundles two third-party asset sets that travel under
|
|
their own licences. Anyone redistributing the toolkit (or a binary that
|
|
embeds the default theme) must propagate the attributions below.
|
|
|
|
- **Symbolic icons** under `themes/default/icons/catalogue/` come from
|
|
Streamline's *Core Line Free* set, distributed under
|
|
[Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/)
|
|
(CC BY 4.0). © Streamline. Some files have been modified for the
|
|
symbolic-tinting pipeline; details in
|
|
[`themes/default/icons/catalogue/LICENSE.md`](./themes/default/icons/catalogue/LICENSE.md).
|
|
Upstream: <https://www.streamlinehq.com/icons/core-line-free>.
|
|
- **Sora Regular** (`src/theme/fallback/Sora-Regular.otf`) is the
|
|
embedded font fallback, distributed under
|
|
[SIL Open Font Licence 1.1](https://scripts.sil.org/OFL).
|
|
© 2019-2020 The Sora Project Authors, Jonathan Barnbrook, Julián Moncada.
|
|
Upstream: <https://github.com/sora-xor/sora-font>.
|
|
|
|
The remaining artwork in the default theme — wallpapers, lockscreens,
|
|
launcher logo, brand-mark variants and per-application icons — is
|
|
original to Liberux Labs and travels under the toolkit's own
|
|
`LGPL-2.1-only` licence.
|
|
|
|
The full Debian-style declaration of every asset and its licence lives
|
|
in [`debian/copyright`](./debian/copyright); that is the file the `.deb`
|
|
ships under `/usr/share/doc/libltk*/copyright`.
|
|
|
|
## Contributing
|
|
|
|
Patches and bug reports are welcome. Read
|
|
[`CONTRIBUTING.md`](CONTRIBUTING.md) for the practical mechanics: build
|
|
prerequisites, how to run tests, the project's Modified Allman code
|
|
style, and what shape a pull request should take.
|
|
|
|
For security-sensitive issues see [`SECURITY.md`](SECURITY.md) — please
|
|
do not file those through the public issue tracker.
|
|
|
|
If you are evaluating `ltk` for a third-party product and are unsure
|
|
whether your use case is in scope, open a discussion before writing
|
|
code. That is especially useful when you are:
|
|
|
|
- missing an app-facing example,
|
|
- blocked by a shell-oriented assumption in the API,
|
|
- trying to understand whether a given platform target is realistic.
|