300 lines
13 KiB
Markdown
300 lines
13 KiB
Markdown
# Keluna
|
||
|
||
**Kotlin Embedded Local UI for Native Applications**
|
||
|
||
An initial Linux x86-64 Kotlin/Native framework prototype: Kotlin owns the UI tree,
|
||
state and click handlers; the system WebView renders HTML and CSS. There is no
|
||
HTTP server, bundled browser, Node dependency, or JVM in the resulting application.
|
||
A JDK is required to run Gradle during development.
|
||
|
||
## License
|
||
|
||
Keluna, including its original examples and documentation, is licensed under the
|
||
[Apache License 2.0](LICENSE). See also [NOTICE](NOTICE).
|
||
|
||
Third-party code, dependencies, and tools retain their respective licenses;
|
||
existing third-party notices take precedence for those files. See
|
||
[third-party code policy](docs/third-party-code.md) before copying or porting code.
|
||
|
||
## Install a development JDK with SDKMAN!
|
||
|
||
**Recommended: Eclipse Temurin JDK 25 (LTS).** The project's Gradle 9.3.0 wrapper
|
||
supports running on Java 25. Java 17 is only the minimum Gradle runtime version;
|
||
use JDK 25 for a new development setup. Java is needed for the build tools, not
|
||
for the compiled Keluna application.
|
||
|
||
The repository pins the JDK in `.sdkmanrc`. With SDKMAN! already installed, run:
|
||
|
||
```sh
|
||
source "${SDKMAN_DIR:-$HOME/.sdkman}/bin/sdkman-init.sh"
|
||
cd ~/Projekte/keluna
|
||
sdk env install
|
||
sdk env
|
||
java -version
|
||
./gradlew --version
|
||
./gradlew check
|
||
```
|
||
|
||
`sdk env install` installs the recorded JDK if necessary. `sdk env` selects it for
|
||
the current shell. The global Java default does not need to change.
|
||
|
||
`25.0.4-tem` was available in SDKMAN!'s Linux x86-64 catalog on 2026-09-16.
|
||
For later patch updates, use `sdk list java` to find a Temurin **25.x** identifier
|
||
and update the `java=` entry in `.sdkmanrc`. This is the single source of truth
|
||
for both manual SDKMAN! activation and the included direnv configuration.
|
||
|
||
### Automatic activation with direnv
|
||
|
||
The included `.envrc` loads SDKMAN!, watches `.sdkmanrc` for changes, and runs
|
||
`sdk env`. It activates an installed JDK; it does not install missing versions.
|
||
Install prerequisites once with `sdk env install` as shown above.
|
||
|
||
If direnv is not installed, on Ubuntu/Debian run `sudo apt install direnv`.
|
||
Enable its shell hook once in your personal shell configuration:
|
||
|
||
```sh
|
||
# Bash: add to ~/.bashrc after SDKMAN! initialization
|
||
eval "$(direnv hook bash)"
|
||
# Zsh alternative: add to ~/.zshrc after SDKMAN! initialization
|
||
eval "$(direnv hook zsh)"
|
||
```
|
||
|
||
Use only the line matching your shell, then open a new terminal. Authorize the
|
||
repository's `.envrc` locally:
|
||
|
||
```sh
|
||
cd ~/Projekte/keluna
|
||
direnv allow
|
||
java -version
|
||
```
|
||
|
||
Entering the repository now selects its JDK; leaving restores the previous
|
||
environment. The shell hook and `direnv allow` are personal machine setup and
|
||
cannot be enabled by checking in repository files. After an `.envrc` change,
|
||
review it and run `direnv allow` again.
|
||
|
||
Without direnv, use `sdk env` manually, or enable `sdkman_auto_env=true` in your
|
||
personal `~/.sdkman/etc/config`. Choose one automatic activation mechanism;
|
||
SDKMAN!'s auto-env is unnecessary when using direnv.
|
||
|
||
In IntelliJ IDEA, also select this JDK as the project's **Gradle JVM**.
|
||
Gradle itself is supplied by `./gradlew`; a separate SDKMAN! Gradle installation
|
||
is unnecessary.
|
||
|
||
See the [Gradle 9.3.0 Java compatibility matrix](https://docs.gradle.org/9.3.0/userguide/compatibility.html#java_runtime),
|
||
[Temurin support roadmap](https://adoptium.net/support), and
|
||
[SDKMAN! usage documentation](https://sdkman.io/usage/), and
|
||
[direnv shell setup](https://direnv.net/docs/hook.html).
|
||
|
||
## Build and test the native core
|
||
|
||
Requires the development JDK above, an Internet connection on the first build,
|
||
and a Linux x86-64 host. Gradle downloads Kotlin/Native and its toolchain.
|
||
|
||
```sh
|
||
./gradlew check
|
||
./gradlew :examples:counter:runDebugExecutableLinuxX64
|
||
```
|
||
|
||
Optional JavaScript runtime contract tests (Node.js 18+ for development only):
|
||
|
||
```sh
|
||
node --test keluna-web-runtime/runtime.test.cjs
|
||
```
|
||
|
||
The headless counter prints the initial DOM batch, then only the patches caused
|
||
by a simulated button click. Tests also run as native executables.
|
||
|
||
## Run the desktop counter
|
||
|
||
On Ubuntu 24.04 / Debian with suitable packages:
|
||
|
||
```sh
|
||
sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev
|
||
./gradlew -Pwebview=true :keluna-platform-linux:runDebugExecutableLinuxX64
|
||
```
|
||
|
||
Run from a graphical desktop session. Desktop configuration is opt-in so GTK
|
||
headers are not required for framework development. Build a release executable:
|
||
|
||
```sh
|
||
./gradlew -Pwebview=true :keluna-platform-linux:linkReleaseExecutableLinuxX64
|
||
```
|
||
|
||
Output: `keluna-platform-linux/build/bin/linuxX64/releaseExecutable/keluna-platform-linux.kexe`.
|
||
The HTML, framework JavaScript and CSS are embedded into the native executable.
|
||
GTK 3 and WebKitGTK 4.1 remain system runtime dependencies on the target machine.
|
||
|
||
## Play Snake
|
||
|
||
Snake is a native Keluna example with its game rules, state, scoring and timer
|
||
callbacks in Kotlin. CSS controls presentation; the framework's browser runtime
|
||
only updates DOM cells and forwards input. It uses the same Linux dependencies
|
||
as the desktop counter.
|
||
|
||
```sh
|
||
./gradlew -Pwebview=true :keluna-platform-linux:runSnakeDebugExecutableLinuxX64
|
||
```
|
||
|
||
Controls: **arrow keys / W A S D** steer, **Space / Enter** start or pause, and
|
||
**R** starts a new game. Buttons provide the same actions. Losing window focus
|
||
pauses a running game automatically. Collect food for 10 points, avoid walls and
|
||
your own body, and fill the 20 × 20 board to win. The best score lasts for the
|
||
current application session. The game moves eight times per second.
|
||
|
||
Build an optimized executable once, then launch it directly without Gradle:
|
||
|
||
```sh
|
||
./gradlew -Pwebview=true :keluna-platform-linux:linkSnakeReleaseExecutableLinuxX64
|
||
./keluna-platform-linux/build/bin/linuxX64/snakeReleaseExecutable/snake.kexe
|
||
```
|
||
|
||
The debug executable can also be launched directly:
|
||
|
||
```sh
|
||
./keluna-platform-linux/build/bin/linuxX64/snakeDebugExecutable/snake.kexe
|
||
```
|
||
|
||
Game logic and UI live in `examples/snake/src/commonMain/kotlin/dev/keluna/snake/`:
|
||
`SnakeGame.kt`, `SnakeView.kt` and adjacent `Snake.css`. The Linux entry point is
|
||
`keluna-platform-linux/src/linuxX64Main/kotlin/dev/keluna/snake/Main.kt`.
|
||
Snake and Counter share the window host and native adapter. GTK schedules ticks
|
||
on the UI thread and removes the timer when the window closes. The DOM grid is
|
||
created once; ticks send only changed cell classes and labels.
|
||
|
||
The eight game/view tests cover movement, pause, growth, walls, self-collision,
|
||
turn buffering, moving into the vacating tail, winning and incremental patches.
|
||
They require no graphical session or GTK development packages:
|
||
|
||
```sh
|
||
./gradlew :examples:snake:linuxX64Test
|
||
```
|
||
|
||
## Workshop registration form
|
||
|
||
This example uses the new **Vaadin-oriented component API**: `Component`, typed
|
||
fields, `HasValue<T>`, `HasValidation`, value-change events and a buffered `Binder`.
|
||
See [the API guide](docs/component-api.md) for semantics, examples and current
|
||
limits. This direction is also recorded in `AGENTS.md` for future development.
|
||
|
||
```sh
|
||
./gradlew -Pwebview=true :keluna-platform-linux:runWorkshopDebugExecutableLinuxX64
|
||
```
|
||
|
||
Or build once and run the optimized native application directly:
|
||
|
||
```sh
|
||
./gradlew -Pwebview=true :keluna-platform-linux:linkWorkshopReleaseExecutableLinuxX64
|
||
./keluna-platform-linux/build/bin/linuxX64/workshopReleaseExecutable/workshop.kexe
|
||
```
|
||
|
||
The form demonstrates text, email, telephone, date, time, integer, select,
|
||
radio-button, slider, textarea and checkbox inputs. Required fields, ranges,
|
||
conversion errors and custom rules are validated in Kotlin with field-level
|
||
messages. Catering is visible/required only for on-site attendance. A valid
|
||
submission displays a typed local summary and total price; editing invalidates
|
||
that preview. No booking is sent or stored. Reset restores defaults, and
|
||
“Beispiel ausfüllen” loads sample data while leaving consent unchecked.
|
||
|
||
Application code is in `examples/workshop/src/commonMain/kotlin/dev/keluna/workshop/`:
|
||
`WorkshopForm.kt`, `WorkshopRegistration.kt`, and adjacent `WorkshopForm.css`.
|
||
It uses no HTML tags, raw attributes or JavaScript. Components can be composed
|
||
before `UiTree.mount(view)`. Input synchronization preserves DOM identity, caret
|
||
position and IME composition, and prevents stale updates from replacing newer
|
||
edits.
|
||
|
||
Run native component/binding/form tests without GTK development packages:
|
||
|
||
```sh
|
||
./gradlew :keluna-ui:linuxX64Test :examples:workshop:linuxX64Test
|
||
node keluna-web-runtime/runtime.test.cjs
|
||
```
|
||
|
||
## Project layout
|
||
|
||
- `keluna-core`: UI-thread-confined elements, stable internal IDs, change queue and JSON patches.
|
||
- `keluna-ui`: typed components, field values, buffered Binder and Kotlin DSL helpers.
|
||
- `keluna-runtime`: click dispatch and incremental synchronization.
|
||
- `keluna-web-runtime`: internal DOM interpreter and framework CSS defaults.
|
||
- `keluna-platform-linux`: small C interop adapter for GTK/WebKitGTK and desktop entry point.
|
||
- `examples/shared`: shared Kotlin counter and adjacent component CSS, plus the application theme.
|
||
- `examples/counter`: headless native counter executable.
|
||
- `examples/snake`: pure Kotlin Snake game model, Keluna UI, colocated CSS and native tests.
|
||
- `examples/workshop`: Vaadin-oriented registration form, data model, CSS and native tests.
|
||
- `docs`: original concept documents; proposals, not claims of completed functionality.
|
||
|
||
## Styling
|
||
|
||
CSS loads in this order: framework defaults, application theme, component CSS.
|
||
Use semantic classes, explicit DOM IDs for accessibility, and CSS custom properties
|
||
for theming. `data-keluna-id` is reserved for internal synchronization.
|
||
`Counter.css` uses explicit `data-keluna-scope` selectors. Automatic CSS selector
|
||
rewriting and a reusable resource/Gradle plugin are future work; the prototype's
|
||
resource task embeds each demo's known stylesheets without a JavaScript bundler.
|
||
|
||
## Validation
|
||
|
||
With Temurin JDK 25.0.4 on Ubuntu 24.04, all 34 Kotlin/Native tests and all
|
||
nine JavaScript runtime tests passed. The Counter and Snake debug executables
|
||
and Workshop debug/release executables built successfully. `ldd -r` found no
|
||
missing libraries or unresolved symbols in the Workshop release executable.
|
||
|
||
Native-window checks in an isolated X11 display verified Workshop field errors,
|
||
editing within text without moving the caret, valid submission previews, online
|
||
attendance with hidden catering, and reset. The release executable also passed
|
||
the sample-data/consent/validation workflow. The demo keeps all data local.
|
||
|
||
Regression checks verified Snake's movement, keyboard turns, pause/resume,
|
||
wall collision, restart and automatic pause when focus moves to another window.
|
||
Clicking Increment in Counter still changes the value from 0 to 1.
|
||
|
||
### Linux linking and Java warnings
|
||
|
||
Kotlin/Native invokes the linker directly. The Linux build retains system library
|
||
paths from `pkg-config --keep-system-libs --libs`, translates `-pthread` into
|
||
`-lpthread`, and unwraps compiler-driver `-Wl,` options before passing them on.
|
||
C compilation continues to use the original `pkg-config --cflags` output.
|
||
|
||
The host's GTK/WebKitGTK shared libraries may require newer glibc symbols than
|
||
Kotlin/Native's bundled sysroot provides. `--allow-shlib-undefined` lets those
|
||
shared-library imports resolve against the host at runtime; unresolved symbols
|
||
in application object files remain link errors. This does not provide compatibility
|
||
with older Linux distributions: the target system must satisfy the installed
|
||
GTK/WebKitGTK libraries' ABI requirements. Verify dependencies on each target:
|
||
|
||
```sh
|
||
ldd -r keluna-platform-linux/build/bin/linuxX64/debugExecutable/keluna-platform-linux.kexe
|
||
```
|
||
|
||
Kotlin/Native 2.3.21's interop tools can emit `sun.misc.Unsafe` and restricted
|
||
`System.load` warnings under JDK 25. These warnings did not prevent the verified
|
||
build and are separate from linker errors. Disabling compiler caches is not
|
||
required for this linking fix.
|
||
|
||
## Prototype boundaries and next steps
|
||
|
||
Currently implemented: one Linux window, local content, initial mount, text,
|
||
attributes, DOM properties, click/key/focus/input/change events, typed components,
|
||
field validation, buffered form binding, and a UI-thread timer with
|
||
incremental updates. State and callbacks are confined to the GTK main thread. The inbound event wire format is
|
||
`click:<node id>`, `key:<registered key>`, `blur` and revisioned field values;
|
||
see the API guide for the value envelope. Outbound updates are JSON arrays.
|
||
Text uses `textContent`.
|
||
Navigation away from the local document is blocked.
|
||
|
||
Not yet implemented: removal/reparenting and lifecycle disposal, dynamic data
|
||
providers, reactive state/coroutines, multiple windows, native dialogs, packaging,
|
||
macOS WKWebView and Windows WebView2 adapters. No cross-platform support or size,
|
||
startup-time or memory claims have been measured yet. Next steps are lifecycle
|
||
and richer components, packaging, and additional platform adapters.
|
||
|
||
Toolchain: Kotlin 2.3.21, Gradle 9.3.0. Kotlin's 2.3.20 line documents Gradle 9.3
|
||
compatibility: https://kotlinlang.org/docs/whatsnew2320.html#compatibility-with-gradle-9-3-0
|
||
WebKitGTK bridge API: https://webkitgtk.org/reference/webkit2gtk/stable/signal.UserContentManager.script-message-received.html
|
||
|
||
## CI desktop downloads
|
||
|
||
Werkator builds the AMD64 desktop examples as the `samples-linux-amd64` follow-up after the tests pass.
|
||
Download the sample archive from the [Keluna build history](https://werkator.javagil.de/repos/keluna/history).
|
||
See [the sample guide](docs/samples-linux-amd64.md) for runtime requirements, usage, and the build environment.
|