Skip to content

Compose UI for native Linux #2027

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: jb-main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/ComposePlatforms.kt
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ enum class ComposePlatforms(vararg val alternativeNames: String) {
val GENERATE_KLIB = WEB + LINUX_NATIVE + WINDOWS_NATIVE + DARWIN

val SKIKO_SUPPORT =
EnumSet.of(KotlinMultiplatform) + JVM_BASED + UI_KIT + MACOS_NATIVE + WEB
EnumSet.of(KotlinMultiplatform) + JVM_BASED + UI_KIT + MACOS_NATIVE + LINUX_NATIVE + WEB

val ALL = EnumSet.allOf(ComposePlatforms::class.java) - IOS
val ALL_AOSP = EnumSet.allOf(ComposePlatforms::class.java) - UI_KIT
Expand Down
1 change: 1 addition & 0 deletions compose/animation/animation-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down
1 change: 1 addition & 0 deletions compose/animation/animation-graphics/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down
1 change: 1 addition & 0 deletions compose/animation/animation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down
2 changes: 2 additions & 0 deletions compose/foundation/foundation-layout/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()
}

kotlin {
Expand Down Expand Up @@ -122,6 +123,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
desktopMain.dependsOn(notMobileMain)
macosMain.dependsOn(notMobileMain)
jsWasmMain.dependsOn(notMobileMain)
linuxMain.dependsOn(notMobileMain)

jsMain {
dependsOn(jsWasmMain)
Expand Down
1 change: 1 addition & 0 deletions compose/foundation/foundation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ if (AndroidXComposePlugin.isMultiplatformEnabled(project)) {
darwin()
js()
wasm()
linux()

configureDarwinFlags()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation

// TODO: b/168524931 - should this depend on the input device?
internal actual val TapIndicationDelay: Long = 100L
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalAccessorScope

@Composable
internal actual fun rememberPlatformOverscrollEffect(): OverscrollEffect? =
null

internal actual fun CompositionLocalAccessorScope.defaultOverscrollFactory(): OverscrollFactory? =
null
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation

internal actual fun isRequestFocusOnClickEnabled(): Boolean = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.gestures

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastFold
import kotlin.math.sqrt

internal actual fun CompositionLocalConsumerModifierNode.platformScrollConfig(): ScrollConfig =
LinuxScrollConfig

private object LinuxScrollConfig : ScrollConfig {
// See https://developer.apple.com/documentation/appkit/nsevent/1535387-scrollingdeltay
override fun Density.calculateMouseWheelScroll(event: PointerEvent, bounds: IntSize): Offset {
// 64 dp value is taken from ViewConfiguration.java, replace with better solution

val verticalScrollFactor = -64.dp.toPx()

val horizontalScrollFactor = -64.dp.toPx()

return event.changes
.fastFold(Offset.Zero) { acc, c -> acc + c.scrollDelta }
.let { Offset(it.x * horizontalScrollFactor, it.y * verticalScrollFactor) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.gestures

import androidx.compose.animation.SplineBasedFloatDecayAnimationSpec
import androidx.compose.animation.core.generateDecayAnimationSpec
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember

internal actual fun platformDefaultFlingBehavior(): ScrollableDefaultFlingBehavior =
DefaultFlingBehavior(
SplineBasedFloatDecayAnimationSpec(UnityDensity).generateDecayAnimationSpec()
)

@Composable
internal actual fun rememberPlatformDefaultFlingBehavior(): FlingBehavior {
val flingSpec = rememberSplineBasedDecay<Float>()
return remember(flingSpec) {
DefaultFlingBehavior(flingSpec)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:OptIn(ExperimentalComposeUiApi::class)

package androidx.compose.foundation.internal

import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.text.AnnotatedString


internal actual suspend fun ClipEntry.readText(): String? {
return null
Copy link
Author

@Thomas-Vos Thomas-Vos Apr 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many features in this PR are not implemented (this is just one random example). I am afraid that using wayland code here could break it for non-wayland apps.

My use case is using Compose on an embedded system which is not using wayland but manufacturer specific APIs for most (windowing) features. It is called HarmonyOS Next. I need to implement some of these functions with manufacturer-specific code, which of course can never be merged into compose.

As this is an expect/actual (like almost everywhere), I do not know of an easy way to pull out the wayland code into a separate module. Ideally a user like me can provide their own implementation for the windowing features. Or maybe multiple separate modules (one for wayland, etc). Any ideas what would work best?

}

internal actual suspend fun ClipEntry.readAnnotatedString(): AnnotatedString? {
return null
}

internal actual fun AnnotatedString?.toClipEntry(): ClipEntry? {
return null
}

internal actual fun ClipEntry?.hasText(): Boolean = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.lazy

import androidx.compose.runtime.Composable

@Composable internal actual fun defaultLazyListBeyondBoundsItemCount(): Int = 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.text

internal actual val platformDefaultKeyMapping: KeyMapping = defaultSkikoKeyMapping
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.text

import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.isMetaPressed
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.key.utf16CodePoint

actual val KeyEvent.isTypedEvent: Boolean
get() = type == KeyEventType.KeyDown &&
!isISOControl(utf16CodePoint) &&
!isMetaPressed &&
!isCtrlPressed

private fun isISOControl(codePoint: Int): Boolean =
codePoint in 0x00..0x1F ||
codePoint in 0x7F..0x9F

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.foundation.text

import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.text.selection.TextFieldSelectionManager
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.text.input.OffsetMapping

@Composable
internal actual fun Modifier.textFieldPointer(
manager: TextFieldSelectionManager,
enabled: Boolean,
interactionSource: MutableInteractionSource?,
state: LegacyTextFieldState,
focusRequester: FocusRequester,
readOnly: Boolean,
offsetMapping: OffsetMapping
): Modifier = Modifier.defaultTextFieldPointer(
manager,
enabled,
interactionSource,
state,
focusRequester,
readOnly,
offsetMapping,
)
Loading