Skip to content

Enable linuxX64 native target for Compose Multiplatform Core libraries#3189

Draft
Aaron Chancey (hi-mod) wants to merge 16 commits into
JetBrains:jb-mainfrom
hi-mod:linux-x64
Draft

Enable linuxX64 native target for Compose Multiplatform Core libraries#3189
Aaron Chancey (hi-mod) wants to merge 16 commits into
JetBrains:jb-mainfrom
hi-mod:linux-x64

Conversation

@hi-mod

@hi-mod Aaron Chancey (hi-mod) commented Jul 4, 2026

Copy link
Copy Markdown

Proposed Changes

This pull request enables the linuxX64 compilation and publication target for the Compose Multiplatform Core libraries (ui, ui-text, ui-backhandler, foundation, foundation-layout, animation, animation-core, material, material-ripple, and material3).

The implementation connects the Compose UI rendering runtime to the native Linux platform via Skiko:

  • ComposeWindow.linux.kt: Connects an X11 window to Skiko's SkiaLayer using a CanvasLayersComposeScene. It contains an X11 event pump that polls for window input events, handles window resize signals, and invokes redrawing on the canvas.
  • Key Mapping: Translates X11 key symbols (keysyms) to androidx key codes in Key.linux.kt.
  • Main Dispatcher Integration: Routes tasks in PostDelayedDispatcher to SkikoDispatchers.Main to handle the lack of Dispatchers.Main in Kotlin/Native on Linux.
  • Platform Services: Adds basic actuals for locale matching, POSIX calendar date formatting, (X11 CLIPBOARD selection: TARGETS/UTF8_STRING/TEXT/STRING (X11 CLIPBOARD selection: TARGETS/UTF8_STRING/TEXT/STRING serving, XConvertSelection paste with STRING fallback, INCR not supported, and URI handling via xdg-open using POSIX system() calls.
  • Multiplatform Demo Support: Added the :compose:mpp:linux-demo sample project configured for linuxX64 target. Added a native Linux entry point at compose/mpp/linux-demo/src/linuxMain/kotlin/Main.kt that creates a ComposeWindow to render a shared Material3 counter app.
  • Build Infrastructure Resilience: Replaced getByName("iosSimulatorArm64") with a safe findByName("iosSimulatorArm64") check inside AndroidXForkTargetsExtensions.kt (for both buildSrc and buildSrc-fork).
  • Conditional Demo Targets: Updated compose/mpp/demo/build.gradle.kts to conditionally register JS, Wasm, macOS, and iOS targets using platform-enable checks (enableJs(), enableWasmJs(), enableMac()) and configured their source sets only if they are active. This prevents dependency resolution errors (such as missing target variants of :lifecycle:lifecycle-common) when building/syncing with restricted target platforms.

Testing

Local Compilation and Publishing

  1. Compiled the local repository targets using the multiplatform compile tasks:
    ./gradlew :compose:ui:ui:compileKotlinLinuxX64
    ./gradlew :compose:foundation:foundation:compileKotlinLinuxX64
    ./gradlew :compose:material3:material3:compileKotlinLinuxX64
  2. Published the compiled artifacts to the local Maven repository:
    ./gradlew publishComposeJbToMavenLocal -Pcompose.platforms=linuxX64

End-to-End Verification

Built and ran the demo application in the consumer repository against the local maven artifacts:

  1. Compiled the demo app using:
    ./gradlew linkDebugExecutableLinuxX64
  2. Launched the native binary:
    ./build/bin/linuxX64/debugExecutable/linux-kotlin-native-desktop.kexe
  3. Verified the interface using manual inspection and automated scripting:
    • Captured the screen visual output using xwd and converted to PNG to verify rendering correctness.
    • Tested pointer click interaction using python-xlib xtest.fake_input (simulating button press and release events) to verify target click response.
    • Verified clean exit (status code 0) after sending a WM_DELETE_WINDOW client message to close the application window.

Standalone Demo Verification

Verified compiling and running the built-in multiplatform demo within this repository:

  1. Compiled and linked the demo executable:
    ./gradlew :compose:mpp:linux-demo:linkDebugExecutableLinuxX64
  2. Ran the executable directly:
    ./out/compose-multiplatform-core/compose/mpp/linux-demo/build/bin/linuxX64/debugExecutable/linux-demo.kexe

QA Verification

This change should be tested by QA. Verification steps:

  1. Compile and link the built-in demo module:
    ./gradlew :compose:mpp:linux-demo:linkDebugExecutableLinuxX64
  2. Run the resulting executable on a Linux desktop system under a standard X11 session:
    ./out/compose-multiplatform-core/compose/mpp/linux-demo/build/bin/linuxX64/debugExecutable/linux-demo.kexe
  3. Confirm that the window opens, UI widgets (buttons, dividers, text fields) render, interactive events function, and the application terminates with exit code 0 when closing the window.

Local Development Tips

Gradle Sync with Local Skiko Snapshots

When syncing the project in IntelliJ or via Gradle against a locally built Skiko snapshot (which typically only contains target variants for JVM and Linux), Gradle dependency resolution will try to download the snapshot versions for all targets (Wasm, JS, macOS, iOS).
To prevent these resolution errors, restrict the active target platforms by adding the following property to your user-level Gradle properties file (~/.gradle/gradle.properties):

androidx.enabled.kmp.target.platforms=-wasm,-js,-mac,-windows,-android_native

Release Notes

Features - Desktop

  • Added experimental native linuxX64 target support for Compose Multiplatform Core libraries.
    • Enabled the linuxX64 target in the core multiplatform Gradle configurations.
    • Implemented ComposeWindow using a native X11 window hosted on Skiko's SkiaLayer.
    • Integrated X11 key symbol mapping, event loops, i(X11 CLIPBOARD selection: TARGETS/UTF8_STRING/TEXT/STRING serving, and UriHandlers.
    • Added a new native Linux demo project (:compose:mpp:linux-demo) showing Material3 integration.
    • Added build resilience for developers building/syncing with a subset of KMP platforms.

Aaron Chancey added 10 commits July 3, 2026 22:29
Skiko now builds a native linuxX64 artifact, so LinuxX64 moves into
ComposePlatforms.SKIKO_SUPPORT. The linux() target helper is narrowed
to linuxX64 for now: skiko-linuxarm64 klibs are built, but the compose
modules have only been compiled and tested for linuxX64.

buildSrc and buildSrc-fork carry the same change because the fork
build consumes the -fork copies.
Local development configuration, not intended for upstream: the Linux
native SkiaLayer lives in a skiko fork published to mavenLocal as
0.0.0-SNAPSHOT, so the version catalogs point at it and repos.gradle
adds mavenLocal(). Drop this commit once a skiko release ships the
Linux native support.
The published org.jetbrains.androidx.navigationevent artifacts (1.1.0)
ship no linuxx64 variant, so ui and ui-backhandler cannot resolve them
when the linux target is enabled. Include :navigationevent:* in the
fork settings, enable linuxX64 in navigationevent-compose with a
nonAndroidMain/linuxMain source-set chain, and swap the published
dependency for the project one in ui and ui-backhandler.
These modules need no macos-style platform sources; nativeMain and
skikoMain already cover them. The only new actuals are the no-op
trace/traceValue in ui-util. Everything else is the linux() target
declaration.

Test: ./gradlew :compose:ui:ui-graphics:compileKotlinLinuxX64
linuxMain actuals mirror the darwin ones in shape but stay minimal:
the default PlatformResolveInterceptor (font loading itself goes
through skikoMain's FontMgr.default, backed by fontconfig), platform
locale from the posix locale, and a string delegate using the Kotlin
stdlib case functions, which ignore the locale argument for now.

Test: ./gradlew :compose:ui:ui-text:compileKotlinLinuxX64
The core of the port. linuxMain provides:

- window/ComposeWindow.linux.kt: X11 window plus skiko SkiaLayer
  hosting a CanvasLayersComposeScene, an X11 event loop that pumps
  events, drains the skiko main-dispatcher queue, and renders on
  demand, and the Window() entry point.
- input/key/Key.linux.kt: X11 keysym to androidx Key map.
- platform/: in-memory clipboard stub, UriHandler via xdg-open.
- No-op actuals for drag-and-drop, pointer icons, interop views, and
  focusability, mirroring the macos port's level of support.

PostDelayedDispatcher moves from nonJvmMain to per-platform actuals:
kotlinx-coroutines has no Dispatchers.Main on linuxX64, so the native
actual routes Linux to skiko's SkikoDispatchers.Main, whose queue the
X11 event loop drains.

Test: ./gradlew :compose:ui:ui:compileKotlinLinuxX64, plus a material3
demo app on linuxX64 (screenshot, synthetic clicks, clean shutdown).
foundation's linuxMain actuals mirror the macos ones: platform text
selection, context menus, text field internals, lazy list prefetch,
scroll config, and clipboard conversion helpers, each at the same
stub-or-minimal level the macos port uses.

Test: ./gradlew :compose:foundation:foundation:compileKotlinLinuxX64
animation-core needs a single linuxMain actual (Expect.linux.kt);
animation is target declaration only.

Test: ./gradlew :compose:animation:animation:compileKotlinLinuxX64
material3 gets linuxMain actuals for CalendarLocale and
PlatformDateFormat built on the posix locale; material and
material-ripple are target declaration only.

Test: ./gradlew :compose:material3:material3:compileKotlinLinuxX64
In AndroidXForkTargetsExtensions.kt (both in buildSrc and buildSrc-fork ), the script was unconditionally trying to
fetch iosSimulatorArm64  from  testableTargets  via getByName. When iOS compilation target groups are disabled, this would crash configuration.
  - I replaced  getByName  with  findByName(...)  and safely configure iOS instrumented tests only if the target is actually enabled/registered.
@google-cla

google-cla Bot commented Jul 4, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Aaron Chancey added 6 commits July 4, 2026 13:19
Key identity now resolves from the effective keysym with an unshifted
fallback, covering punctuation, navigation, function, numpad, and
modifier keys; unmapped keysyms fall through as Key.Unknown so typed
characters still reach text fields. X11 state masks translate to
PointerKeyboardModifiers on key and pointer events, and
WindowInfo.keyboardModifiers tracks them.
Mirrors MacosTextInputService: tracks the active input session so text
fields get a real PlatformTextInputService, with XIM/ibus composition
left as a follow-up.
Work scheduled by one event (gesture coroutines, focus moves) now runs
before the next event is handled, matching AWT/AppKit loop ordering. A
click-then-type burst previously delivered every key before the tap
gesture focused the text field, dropping the typed characters.
:compose:ui:ui:linuxX64Test cannot run them yet: the shared skikoTest
sources need ui-test, which is not enabled for linuxx64. The file
compiles clean; enabling ui-test for linux is tracked as follow-up.
Replaces the in-memory clipboard stub with a real X11 CLIPBOARD selection
backend shared by Clipboard and ClipboardManager. Copy claims selection
ownership and serves SelectionRequest events (TARGETS, UTF8_STRING, TEXT,
STRING) from the window event loop; paste issues XConvertSelection and
waits on the display connection for SelectionNotify, preferring UTF8_STRING
with a Latin-1 STRING fallback, and short-circuits to in-process text when
a window of this process owns the selection. SelectionClear drops stale
ownership. INCR (multi-chunk) transfers are not supported yet.
@JakeWharton

Copy link
Copy Markdown

Seems like a mistake to build anything new on X11. I also don't think we should assume any compositor for the majority of UI.

@hi-mod

Copy link
Copy Markdown
Author

That makes sense. I will work on supporting Wayland and being compositor agnostic. I think I chose X11 because Mint defaults to that. I tried using Wayland and my screen just blanks so I will need to figure that out.

@hi-mod
Aaron Chancey (hi-mod) marked this pull request as draft July 10, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants