Text entry on a phone comes from `zwp_text_input_v3`: a focused field enables the protocol and the compositor's input method, squeekboard today, draws a keyboard and commits text through it. Eydos wants to drop that process, so the keyboard has to come from somewhere else, and the natural place is the toolkit that already draws everything else and knows which field has the focus and what it is for. This commit puts one in ltk, as data files plus a panel any host can drive: ltk's runtime shows it under a focused text field when no input method is there, and forge will draw the same panel in its chrome and deliver the keys to the focused client, which is the second half and is not in this commit. Along the way the demo that exercises it exposed two gaps in the widgets, fixed here too.
Layouts are data. `keyboards/<language>.json`, one file per layout, searched in `LTK_KEYBOARDS_DIR` when set, then `$XDG_DATA_HOME/ltk/keyboards` or `~/.local/share/ltk/keyboards`, then `/usr/share/ltk/keyboards`; `osk::layout::search_paths()`, `available()` and `load( name )` are the API, and the English layout is compiled in as the last resort. A file has `name`, a `label` for the layout-switch key, `layers` and `variants`. A layer is a list of rows; a row a list of keys; a key is a bare string for the text it types, or an object with an `action` (`shift`, `backspace`, `enter`, `space`, `layer`, `layout`, `hide`), the target `layer`, a `label`, and a `width` in key units, `1` by default, which share the row's width so a row of nine keys comes out centred under one of ten. `base` is mandatory; a missing `shift` layer is derived from it by uppercasing. `variants` maps a key's text to what a long press offers, and the shifted form of a key uses its base's, uppercased. A key may also be a named key, `{ "key": "Escape" }`, `Tab`, the arrows, `Home`, `End`, `Page_Up`, `Page_Down`, `Delete`, `Insert`, `F1` to `F12`, or a sticky modifier, `{ "modifier": "ctrl" }` or `alt`. `en.json` and `es.json` ship, with symbol layers and the accented variants each language needs. The files go into the registry copy through `make install` and into `ltk-theme-default` under `/usr/share/ltk/keyboards`.
The terminal row. A terminal needs Escape, Tab, Ctrl, Alt, the arrows and the shell's symbols, and no other application does, so those are not a layout but a row laid above any language's layout: `keyboards/bar-terminal.json`, made of the named keys and modifiers above plus `-`, `/`, `|`, `\` and `~` with their variants. Files named `bar-*.json` are rows, not layouts, and `available()` leaves them out, so the layout-switch key does not cycle onto one; the first version listed it and a tap on that key made the keyboard vanish behind a duplicated bar. Which applications get the row is decided by what the field says it is for: `TextEdit::purpose( InputPurpose::Terminal )`, snapshotted into the handlers like `secure`, which the built-in panel reads through `set_purpose` and which also goes out as the `terminal` content purpose of `zwp_text_input_v3`, the same one VTE-based terminals declare, so a compositor hosting the panel can pick the row from the protocol; for a client that declares nothing it can decide by `app_id`.
`osk::Panel` is a state machine a host drives. It holds the loaded layouts, the current layer, shift as off, one-shot or locked, the sticky modifiers, the purpose and its extra row, the geometry of all of it inside the rectangle the host gave it with `set_rect( rect, scale )`, and the press in flight. The host feeds it `press_down`, `press_move`, `press_up` and, from its own timer, `tick`, each returning an `Event`: `Text`, `Backspace`, `Enter`, `Hide`, `Key( keysym, modifiers )` for what text cannot carry, an arrow, Escape, Tab, or a character while Ctrl or Alt is held, or `Changed` when only the panel's look changed. A key types on release; a press held 450 ms opens its variants, which the finger slides onto and the release picks; backspace fires on press and repeats every 60 ms after the same delay; shift is one-shot and locks on a double tap within 350 ms; a modifier stays down for the next key and a character typed under it goes out as a `Key`, since text cannot carry Ctrl. `next_deadline()` says when `tick` next has work, `height( scale )` what the panel needs, `press_down` answers `None` for a press outside it so the host treats that as its own. `draw( canvas )` paints it with the active palette; the action keys show theme icons rather than glyphs, `keyboard/shift`, `keyboard/backspace`, `keyboard/enter`, `general/down-simple` for hide, `accessibility/languages` for the layout key, the `general` arrows for the terminal row, rasterised once per size and tint, because the first version drew `⇧` and `🌐` and the fallback chain has no glyph for the latter, which showed as a question mark; a layout key with no label of its own shows the active layout's. Nothing in `Panel` knows about text fields or Wayland, which is what lets a compositor host it. Fifteen tests cover the layout parser, the derived shift layer, variants following case, the bar filtering, and the panel's taps, one-shot and locked shift, variants, repeat, layer and layout switches and the terminal row.
The runtime shows it. `event_loop/osk.rs` puts a `Panel` on a surface when a text field takes the focus and `builtin_osk` holds: the compositor advertised no `zwp_text_input_manager_v3`, or `LTK_BUILTIN_OSK=1` is set, which is how to try it on a desktop that has an input method, or the app returns `true` from the new `App::builtin_keyboard()`. The panel is placed at the bottom of the surface by both renderers, and the widget tree is laid out in what is left above it, so the focused field is never covered. Presses, motion and releases from the pointer and from touch are offered to the panel before the gesture machine, a press on the already focused field brings a hidden panel back, and the run loop's timeout now takes the minimum of the long-press, tooltip and panel wakeups so variants and repeat fire on time. `Text` goes through the same insertion as a physical key, `Key` through the same dispatch, with Ctrl applied for its duration, `Enter` submits or inserts a newline in a multiline field, `Hide` keeps the focus and takes the panel away; losing the focus removes it. The layout is picked from `$LANG`. `ltk::InputPurpose` is exported, and `docs/architecture.md` gains the section that says all of this, with the compositor's part sketched.
The demo. `examples/inputs` becomes a notebook with the login page it had and a Shell page: one multiline field that holds a transcript, prompt included, declared a terminal, so the row appears; Enter runs the line under the last prompt (`echo`, `ls`, `pwd`, `clear`), the arrows move through the transcript, and a Ctrl combination interrupts the line the way a terminal shows it. Escape quits only from the login page, because a key the app answers never reaches the field, which is what first made the arrows dead.
Two widget gaps the demo found. `Notebook` gave its page the page's preferred height, so a page that fills its space, a scrolled list or a flex, got none and showed nothing: the active page is now wrapped in `flex` and gets the height under the strip. And the multiline field had no way to scroll: it chose its first visible line from the cursor alone on every draw, so the transcript ran under the keyboard and the wheel did nothing. `text_edit/scroll.rs` gives it a `TextScroll` the runtime keeps per field beside the cursor and the selection, in the frame state, the draw context and the `core` runtime alike: the first line asked for by hand and the cursor at that moment. The view holds there while the cursor stays put and, once it moves, follows it by the least it has to, as an editor does; without a request the result is exactly the old rule. `Window::resolve` computes the visible lines once for drawing, hit testing and the wheel, so they cannot disagree, and replaces the copy each of the first two had. In `on_pointer_axis` the vertical motion is offered to the multiline field under the pointer first, three lines per notch from `value120` or `discrete`, touchpad pixels accumulated until they add up to a line, and only when the field cannot move that way does it go to a `scroll` around it. `TextScroll` is public but opaque, since `Element::draw` is public and needs it; `Default` is the cursor-following behaviour. Six tests cover the hold, the follow, the clamping, the notch and the accumulation. A read-only field is not interactive and does not take the wheel; touch drags inside a field do not scroll it yet.
Not done. Forge does not host the panel yet, so on the phone squeekboard is still what appears. The panel's look is a first pass, sized in logical units with the theme's palette, to be restyled from mockups. Only `en` and `es` ship.
434 lines
16 KiB
Markdown
434 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
|
|
- Linux shells that adapt to any screen, from phones to desktops
|
|
- 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
|
|
- `osk::Panel` and its layout files, for a host that draws the on-screen keyboard itself
|
|
|
|
## 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 power usage on battery-powered devices
|
|
- 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.
|