ITADN
linux-compose-desktop-native/compose-desktop-linux
linux-compose-desktop-native/compose-desktop-linux · 文件
文件最后提交记录最后更新时间
README.md

Compose Desktop for Linux/Native

Compose Multiplatform running as a native Linux executable — no JVM, no AWT. SDL2 provides the window, input and OpenGL context; Skia renders through Ganesh.

// build.gradle.kts
plugins {
    id("dev.composenative.desktop") version "0.1.0"
}

// src/linuxX64Main/kotlin/Main.kt
fun main() = composeDesktopApplication(title = "My App") {
    MyContent()
}

The plugin configures the linuxX64 target, the Compose compiler, the native link flags and the library dependency. It is not sugar: Kotlin/Native does not propagate a published cinterop KLIB's linker options to whoever links against it, so without it an application fails with unresolved SDL2 and OpenGL symbols.

composeDesktop { } adjusts the defaults:

composeDesktop {
    executableName.set("my-app")   // defaults to the project name
    entryPoint.set("com.example.main")
    libraryVersion.set("0.1.0")
    addRepositories.set(false)     // if you declare repositories in settings.gradle.kts
}

Status

Early. The rendering, input, clipboard, cursor and text-input paths work and are covered by tests, but this is not a supported JetBrains configuration and the API will change.

Currently linuxX64 only.

Why this exists

JetBrains publishes Skiko for Kotlin/Native on Apple targets and Compose Desktop for the JVM, but nothing for Kotlin/Native on Linux. Making Compose run natively here needs three pieces:

PieceWhere
Skia bindings for linuxX64skiko fork (linux-native)
Compose UI KLIBs for linuxX64compose-multiplatform-core fork (linux-native)
Window, GL context, scene hosting, event translationthis repository

The forks are submodules under third_party/. Both carry a small number of patches, kept as separate commits so they can be rebased onto upstream.

Requirements

  • JDK 21
  • Kotlin 2.3.21 — not negotiable, KLIB ABI is locked to the compiler version
  • SDL2, OpenGL, fontconfig and freetype development packages
# Arch
sudo pacman -S --needed sdl2 mesa libglvnd fontconfig freetype2 pkgconf
# Debian/Ubuntu
sudo apt install libsdl2-dev libgl-dev libfontconfig-dev libfreetype-dev pkg-config

Building

git clone --recurse-submodules https://github.com/linux-compose-desktop-native/compose-desktop-linux
cd compose-desktop-linux

# Neither upstream ships Kotlin/Native Linux artifacts, so the forks must be
# built first. This takes a while the first time.
tools/publish-forks.sh

./gradlew :library:linuxTest
./gradlew :examples:demo:runDemo

Builds are memory-hungry; tools/capped.sh runs any command inside a 10 GB cgroup if you need a ceiling:

tools/capped.sh ./gradlew :library:linuxTest

Compositing foreign OpenGL content

Anything that renders with its own OpenGL commands — a video player, a game view — can be composited into the scene by implementing ExternalGlTexture:

class MyRenderer : ExternalGlTexture {
    override val textureId: Int get() = texture
    override val width: Int get() = ...
    override val height: Int get() = ...

    override fun render(width: Int, height: Int) {
        // Draw into your own framebuffer, at the size this node was laid out to.
        // GL state need not be restored.
    }
}

Place it with the ExternalTexture composable. It is an ordinary layout node, so it is sized and clipped like any other content, any number can coexist, and Compose composites above or below it in tree order:

composeDesktopApplication(title = "Player") {
    Layout(contents = listOf(
        { ExternalTexture(video) },   // sized by the layout below
        { ExternalTexture(preview) }, // a second, independent source
        { Controls() },               // drawn over both
    )) { (v, p, c), constraints -> /* place them */ }
}

Skia borrows the texture and will never delete it; equally it must stay alive while a frame referencing it is in flight. Skia's cached GL state is reset after the foreign draws, so an implementation is free to leave state dirty — libmpv, the case this was designed against, makes no guarantees about what it leaves behind. A fixed baseline is also restored before each source draws (default framebuffer bound; scissor, blend, depth and stencil disabled; texture unit 0 active), so one source cannot corrupt the next.

Layout

library/         the host layer, published as a KLIB
gradle-plugin/   consumer build configuration and native link flags
examples/demo/   sample application, built against project(":library")
examples/hello/  standalone build that consumes the published artifacts
third_party/     the Skiko and Compose UI forks
tools/           build and publish scripts

examples/hello is deliberately outside the main Gradle build: it names only the plugin id and resolves everything else from a repository, so it is the check that the published contract actually works. tools/verify-consumer.sh publishes the library and plugin locally, builds it, runs it headlessly and confirms a frame was rendered.

See DESIGN.md for the architecture and the decisions behind it.

Licence

Apache 2.0, matching Compose Multiplatform and Skiko.