Skip to content

[feature] Native Dark Mode Window Handling #349

@cinderblock

Description

@cinderblock

On Windows, in Dark Mode, Window headers use the default white/grey background. I see #116 references making it invisible, but I don't want to recreate the title bar. The OS provided one is great, as long as DWMWA_USE_IMMERSIVE_DARK_MODE can be set when desired. Would you consider making this automatic or enabling programmatic access to this flag?

Workaround

Claude found this workaround for me that seems to be working reasonably well...

import { dlopen, FFIType, ptr } from "bun:ffi";

async function setTitlebarDarkMode(dark boolean): Promise<void> {
  const dwmapi = dlopen("dwmapi.dll", {
    DwmSetWindowAttribute: {
      args: [FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.u32],
    returns: FFIType.i32,
    },
  });

  const DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
  const mode = new Int32Array([dark ? 1 : 0]);

  dwmapi.symbols.DwmSetWindowAttribute(
    // BrowserWindow.ptr is the HWND
    myWindow.ptr,
    DWMWA_USE_IMMERSIVE_DARK_MODE,
    ptr(mode),
    4,
  );
}

async function isDark(): Promise<boolean> {
  const proc = Bun.spawn(
    ["reg", "query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", "/v", "AppsUseLightTheme"],
    { stdout: "pipe" },
  );
  const output = await new Response(proc.stdout).text();
  // AppsUseLightTheme = 0 means dark
  return /0x0+$/.test(output.trim());
}

let last = await isDark();
if (last) setTitlebarDarkMode(true);
setInterval(async () => {
  const dark = await isDark();
  if (last == dark) return;
  last = dark;
  setTitlebarDarkMode(last);
}, 2000);

Listening for system theme changes

By Opus 4.6

The proper way to detect theme changes at runtime is to handle WM_SETTINGCHANGE in the window's WndProc — Windows broadcasts this message with lParam pointing to the string "ImmersiveColorSet" whenever the user toggles dark/light mode in Settings. Since Electrobun owns the WndProc, this would need to be handled internally (or exposed as a window event like "theme-change").

Without that, the only option from userland is polling the registry, which works but isn't ideal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions