Skip to content
Open
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
14 changes: 14 additions & 0 deletions composeApp/src/desktopMain/kotlin/com/nuvio/app/DesktopApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.nuvio.app.desktop.DesktopRuntimeLog
import com.nuvio.app.desktop.DesktopSingleInstanceManager
import com.nuvio.app.desktop.DesktopUriHandler
import com.nuvio.app.desktop.DesktopWindowStateStore
import com.nuvio.app.desktop.WindowsChromePolish
import com.nuvio.app.desktop.WindowsNativeBootstrap
import com.nuvio.app.desktop.WindowsUrlProtocolRegistrar
import com.nuvio.app.features.notifications.WindowsToastHelper
Expand All @@ -56,6 +57,14 @@ private fun configureMacOsNativeAppearance() {
System.setProperty("apple.awt.application.appearance", "NSAppearanceNameDarkAqua")
}

/**
* Dark native-caption styling is Windows-only (DWM immersive dark mode); macOS/Linux keep their
* native title bars unchanged. The window stays decorated on every platform, so native fullscreen,
* Aero Snap, resize and maximize are untouched.
*/
private val isWindowsOs: Boolean
get() = System.getProperty("os.name")?.contains("Windows", ignoreCase = true) == true

private fun computeStartupWindowSize(): DpSize {
val displayBounds = GraphicsEnvironment.getLocalGraphicsEnvironment()
.defaultScreenDevice
Expand Down Expand Up @@ -259,6 +268,11 @@ fun main(args: Array<String>) {
window.contentPane.background = DesktopWindowBackground
window.rootPane.background = DesktopWindowBackground
devStreamMode?.startDiagnostics { desktopMainWindow }
// Recolor the native caption to dark (keeps the window decorated so native
// fullscreen / Aero Snap / resize all keep working).
if (isWindowsOs) {
WindowsChromePolish.apply(window)
}
onDispose { desktopMainWindow = null }
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.nuvio.app.desktop

import androidx.compose.ui.awt.ComposeWindow
import com.sun.jna.Native
import com.sun.jna.Pointer
import com.sun.jna.ptr.IntByReference
import com.sun.jna.win32.StdCallLibrary

/**
* Darkens the **native** Windows title bar via DWM (immersive dark mode) so it matches Nuvio's
* near-black UI instead of the default white caption.
*
* Deliberately minimal: it sets ONLY the dark-mode caption attribute and keeps the window
* *decorated*. It does NOT set rounded-corner or border-color attributes — those persist into the
* native borderless-fullscreen window state and black out fullscreen video (DWM can no longer do
* direct fullscreen presentation). Window shape is left to the OS default (Win11 rounds decorated
* windows natively). Dark mode is a non-client/caption attribute, so it can't affect the video
* surface. Aero Snap / resize / maximize all keep working because the native frame is untouched.
*
* Best-effort: guarded and HRESULT-logged; no-ops on non-Windows / older OS.
*/
object WindowsChromePolish {
private const val S_OK = 0
// Dark caption attribute: index 20 on Win10 2004+/Win11, 19 on older Win10 (1809–1909).
private const val DWMWA_USE_IMMERSIVE_DARK_MODE = 20
private const val DWMWA_USE_IMMERSIVE_DARK_MODE_PRE_20H1 = 19

private interface Dwmapi : StdCallLibrary {
fun DwmSetWindowAttribute(
hwnd: Pointer,
dwAttribute: Int,
pvAttribute: IntByReference,
cbAttribute: Int,
): Int
}

fun apply(window: ComposeWindow) {
if (System.getProperty("os.name")?.contains("Windows", ignoreCase = true) != true) return
runCatching {
val hwnd = Native.getWindowPointer(window) ?: run {
DesktopRuntimeLog.warn("window chrome polish skipped: null HWND")
return
}
val dwm = Native.load("dwmapi", Dwmapi::class.java)

// ONLY the dark-caption hint — NOT corner-preference or border-color, which persist into
// the borderless-fullscreen popup window and black out fullscreen video.
var darkHr = dwm.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, IntByReference(1), 4)
if (darkHr != S_OK) {
darkHr = dwm.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_PRE_20H1, IntByReference(1), 4)
}
DesktopRuntimeLog.info("window chrome polish hr: darkMode=$darkHr (0=S_OK)")
}.onFailure {
DesktopRuntimeLog.warn("window chrome polish failed ${it::class.simpleName}:${it.message}")
}
}
}