commit e62626f7fbdc10490347e5365e04f733f039ea3d
parent 3a205f0693374301a6950a50d853acdeaf1c9e7f
Author: triesap <tyson@radroots.org>
Date: Fri, 14 Aug 2026 17:33:51 +0000
desktop: provide macOS window chrome geometry
- Install fail-fast host chrome state per application window
- Reserve a conservative physical macOS traffic-light region
- Keep non-macOS windows free of host chrome exclusions
- Verify desktop chrome policy and environment integration
Diffstat:
3 files changed, 70 insertions(+), 7 deletions(-)
diff --git a/app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleWindowChrome.kt b/app/design_system/src/commonMain/kotlin/org/harvestcircle/designsystem/layout/HarvestCircleWindowChrome.kt
@@ -1,6 +1,10 @@
package org.harvestcircle.designsystem.layout
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isSpecified
@@ -45,6 +49,31 @@ public data class HarvestCircleWindowChromeClearance(
public val right: Dp,
)
+private val LocalHarvestCircleWindowChromeExclusion =
+ staticCompositionLocalOf<HarvestCircleWindowChromeExclusion> {
+ error("HarvestCircleWindowChromeEnvironment is missing from the composition")
+ }
+
+/** Read-only access to the host-owned exclusion for the current window. */
+public object HarvestCircleWindowChrome {
+ public val exclusion: HarvestCircleWindowChromeExclusion
+ @Composable
+ @ReadOnlyComposable
+ get() = LocalHarvestCircleWindowChromeExclusion.current
+}
+
+/** Installs immutable host chrome geometry for exactly one application window. */
+@Composable
+public fun HarvestCircleWindowChromeEnvironment(
+ exclusion: HarvestCircleWindowChromeExclusion,
+ content: @Composable () -> Unit,
+) {
+ CompositionLocalProvider(
+ LocalHarvestCircleWindowChromeExclusion provides exclusion,
+ content = content,
+ )
+}
+
/**
* Resolves the portion of [exclusion] intersecting a top-edge region.
*
diff --git a/app/desktop/src/main/kotlin/org/harvestcircle/desktop/Main.kt b/app/desktop/src/main/kotlin/org/harvestcircle/desktop/Main.kt
@@ -16,6 +16,9 @@ import org.harvestcircle.application.HarvestCircleApplication
import org.harvestcircle.application.NativeHarvestCircleRuntime
import org.harvestcircle.application.desktopRuntimeOpenConfiguration
import org.harvestcircle.application.verifyNativeCompatibility
+import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEdge
+import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEnvironment
+import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeExclusion
import org.harvestcircle.ffi.HarvestCircleException
import org.harvestcircle.ffi.compatibilityDescriptor
import org.harvestcircle.ui.shell.StartupFailureScreen
@@ -42,6 +45,8 @@ internal const val INITIAL_WINDOW_WIDTH = 1280
internal const val INITIAL_WINDOW_HEIGHT = 800
internal const val MINIMUM_WINDOW_WIDTH = 1100
internal const val MINIMUM_WINDOW_HEIGHT = 720
+internal const val MACOS_WINDOW_CHROME_EXCLUSION_WIDTH = 112
+internal const val MACOS_WINDOW_CHROME_EXCLUSION_HEIGHT = 40
internal const val HEALTH_CHECK_ARGUMENT = "--health-check"
internal const val HEALTH_READY_EVIDENCE = "HARVESTCIRCLE_HEALTH_READY"
internal const val HEALTH_CLOSED_EVIDENCE = "HARVESTCIRCLE_HEALTH_CLOSED"
@@ -85,13 +90,15 @@ fun main(args: Array<String>) {
onDispose { }
}
- if (nativeStartupProblem == null) {
- HarvestCircleApplication(
- closeRequested = closeRequested,
- onExitApproved = ::exitApplication,
- )
- } else {
- StartupFailureScreen(nativeStartupProblem)
+ HarvestCircleWindowChromeEnvironment(desktopWindowChromeExclusion(isMacOs)) {
+ if (nativeStartupProblem == null) {
+ HarvestCircleApplication(
+ closeRequested = closeRequested,
+ onExitApproved = ::exitApplication,
+ )
+ } else {
+ StartupFailureScreen(nativeStartupProblem)
+ }
}
}
}
@@ -99,6 +106,17 @@ fun main(args: Array<String>) {
internal fun desktopWindowTitle(macOs: Boolean): String = if (macOs) "" else APPLICATION_NAME
+internal fun desktopWindowChromeExclusion(macOs: Boolean): HarvestCircleWindowChromeExclusion =
+ if (macOs) {
+ HarvestCircleWindowChromeExclusion(
+ edge = HarvestCircleWindowChromeEdge.Left,
+ width = MACOS_WINDOW_CHROME_EXCLUSION_WIDTH.dp,
+ height = MACOS_WINDOW_CHROME_EXCLUSION_HEIGHT.dp,
+ )
+ } else {
+ HarvestCircleWindowChromeExclusion.None
+ }
+
internal fun configureMacOsWindowChrome(
rootPane: JRootPane,
windowAccessibility: AccessibleContext,
diff --git a/app/desktop/src/test/kotlin/org/harvestcircle/desktop/MainTest.kt b/app/desktop/src/test/kotlin/org/harvestcircle/desktop/MainTest.kt
@@ -1,8 +1,11 @@
package org.harvestcircle.desktop
+import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.harvestcircle.application.ApplicationLifecycle
+import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeEdge
+import org.harvestcircle.designsystem.layout.HarvestCircleWindowChromeExclusion
import java.io.ByteArrayInputStream
import java.nio.file.Files
import javax.swing.JRootPane
@@ -37,6 +40,19 @@ class MainTest {
}
@Test
+ fun macOsWindowChromeReservesAConservativePhysicalExclusion() {
+ assertEquals(
+ HarvestCircleWindowChromeExclusion(
+ edge = HarvestCircleWindowChromeEdge.Left,
+ width = 112.dp,
+ height = 40.dp,
+ ),
+ desktopWindowChromeExclusion(macOs = true),
+ )
+ assertEquals(HarvestCircleWindowChromeExclusion.None, desktopWindowChromeExclusion(macOs = false))
+ }
+
+ @Test
fun missingOrInvalidRuntimeIconFailsSafely() {
assertNull(loadRuntimeIcon { null })
assertNull(loadRuntimeIcon { ByteArrayInputStream("not an image".encodeToByteArray()) })