Author: harald.oehlmann@elmicron.de
State: Withdrawn
Type: Project
Created: 02-Apr-2026
Tcl-Version: 9.1
Tk-Branch: tip-749-mswin-dark-toplevel
Keywords: Tk, MS-WIndows, toplevel
Vote: Pending
Vote-Summary:
Votes-For:
Votes-Against:
Votes-Present:
Abstract
This TIP proposes adding a switch to change a toplevels decoration to dark mode on MS-Windows.
This TIP was integrated in TIP 750 and withdrawn.
Rationale
When the windows content is dark, a dark toplevel decoration is beautiful.
To see an example, do the following on MS-Windows 11: Open the start menu and type "cmd". The cmd.exe shell window is dark (independent on the theme) and the window decoration is dark.
Compatibility to the MacOS implementation
TIP655 (implemented) and TIP750 (proposed) proposes the following commands:
On MacOS:
wm attributes $toplevel attributes -appearance auto|dak|light
The values may be abbreviated. See the discussion part below. It is announced to change this to:
wm attributes $toplevel attributes -appearance auto|light|dark
On MacOS and MS-Win:
winfo isdark $toplevel
Which returns a boolean to indicate dark mode.
Specification
The new attribute "-appearance" is added to "wm attribute" for the MS-Windows platform. The new attribute manages the dark mode of the Window decoration.
The permitted values:
- "light": light Window decoration. This is the default value.
- "dark": dark Window decoration.
Set mode
The appearance may be set by:
wm attributes $toplevel -appearance light|dark
Abbreviations for light or dark are allowed.
The command may fail if there are issues with the given toplevel window status.
Query mode
The appearance mode may be queried by one of the two following commands:
wm attributes $toplevel -appearance
wm attributes $toplevel
Reference Implementation
The implementation is in Tk branch tip-749-mswin-dark-toplevel.
The current implementation requires an "update idletasks" between toplevel creation and appearance setting:
toplevel .t
update idletasks
wm attributes .t -appearance dark
Any solution to avoid this is appreciated.
Credits
This proposal is by Alexandru. The solution sketch is by Emiliano. The MacOS version is authored by Marc. Details are in Tk ticket a2125a1b.
And the whole Tk community has to help, as the coordinator is again approaching something he has no idea about.
Backwards Compatibility
The shortcut for
wm attribute $t -a $color
for -alpha does not work any more.
Discussion
Black toplevel menu
The first level of an eventual menu does not change the color with the -darkmode switch. The issue is, that an eventual color "-bg black" is not honored on MS-Windows on the top menu. Tk Bug 11bd4a03 addresses this point.
Compatibility to MacOS
Emiliano
On windows, win32 apps get light window decorations by default. To check whether dark mode is active for the more modern APIs, this code can be used:
package require registry
proc isdarkmodeactive {} {
set path [join {
HKEY_CURRENT_USER
SOFTWARE
Microsoft
Windows
CurrentVersion
Themes
Personalize
} \\]
if {[catch {registry get $path AppsUseLightTheme} value]} {
# key not found
return 0
}
return [expr {$value == 0}]
}
Support more colors (Mason McParlane)
If anyone is interested in a script solution to dark mode I added it to TkCon http://github.com/bohagan1/TkCon/blob/a879ff32af1b836112894ba638e90d66802a9ef7/tkcon.tcl#L418. See below for code snippets. This detection solution works for macOS, Linux, and Windows and a routine for Windows that uses CFFI https://github.com/apnadkarni/tcl-cffi allows users to set the color. Having a single dark-mode toggle doesn't seem to be enough for Windows where controlling the specific color can really enhance the feel of apps.
## ::tkcon::DarkModeSetting - detects dark mode
# Outputs: true if dark mode is enabled, otherwise false
##
proc ::tkcon::DarkModeSetting {} {
variable PRIV
set darkmode 0
catch {
if {$PRIV(WIN32)} {
package require registry
set keypath {HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize}
set darkmode [expr {[registry get $keypath AppsUseLightTheme] == 0}]
} elseif {$PRIV(AQUA)} {
set istyle [exec defaults read -g AppleInterfaceStyle]
set darkmode [expr {$istyle eq "Dark"}]
} else {
set colorscheme_query {qdbus org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop
org.freedesktop.portal.Settings.Read "org.freedesktop.appearance" "color-scheme"
}
set darkmode [expr {1 == [exec {*}$colorscheme_query]}]
}
}
return $darkmode
}
proc ::tkcon::HexToBGR {color} {
if {[scan $color "#%2x%2x%2x" r g b] != 3} {
return -code error "Invalid hex color format: $color"
}
return [expr {($b << 16) | ($g << 8) | $r}]
}
proc ::tkcon::SetWindowColor {window color} {
variable PRIV
if {!$PRIV(WIN32) || [catch {package require cffi}]} {
return
}
cffi::alias load win32
cffi::Wrapper create dwmapi [file join $::env(windir) system32 dwmapi.dll]
cffi::Wrapper create user32 [file join $::env(windir) system32 user32.dll]
cffi::alias define HRESULT {long nonnegative winerror}
dwmapi stdcall DwmSetWindowAttribute HRESULT {
hwnd pointer.HWND dwAttribute DWORD pvAttribute pointer cbAttribute DWORD
}
user32 stdcall GetParent pointer.HWND {
hwnd pointer.HWND
}
proc ::tkcon::SetWindowColor {window color} {
set DWMWA_CAPTION_COLOR 35
set hwndptr [cffi::pointer make [winfo id $window] HWND]
cffi::pointer safe $hwndptr
set parentptr [GetParent $hwndptr]
set colorptr [cffi::arena pushframe DWORD]
cffi::memory set $colorptr DWORD [HexToBGR $color]
set size [cffi::type size DWORD]
DwmSetWindowAttribute $parentptr $DWMWA_CAPTION_COLOR $colorptr $size
cffi::arena popframe
cffi::pointer dispose $hwndptr
cffi::pointer dispose $parentptr
}
tailcall ::tkcon::SetWindowColor $window $color
}
Support auto mode
An auto-Mode with the same syntax as MacOS may be added as follows:
wm attribute . -appearance auto
Will check the registry for the current mode and sets the mode
Here is the TCL equivalent:
proc isdarkmodeactive {} {
set path [join {
HKEY_CURRENT_USER
SOFTWARE
Microsoft
Windows
CurrentVersion
Themes
Personalize
} \\]
if {[catch {registry get $path AppsUseLightTheme} value]} {
# key not found
return 0
}
return [expr {$value == 0}]
}
Emiliano: According to what I've read online (docs are lacking) there is a Windows message sent to toplevels when the theme changes; it seem to be WM_SETTINGCHANGE, which Tk handles in the function WmProc(), in win/tkWinWM.c. While is not entirely clear what the message payload is when the dark mode is set, a bit of experimentation could lead to a solution.
Kevin: On initialization on Win10 or Win11, you can determine whether the system is in dark mode by querying the registry key HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionThemesPersonalizeAppsUseLightTheme. If it does not exist or is nonzero, the light theme should be used.
Of note, if SystemParametersInfo(SPI_GETHIGHCONTRAST, ...) indicates that the high contrast feature is on, then the high contrast request takes precedence over the Windows theme.
The app is responsible for making its title bar follow the theme.
There is a function in
The system colors whose names are found in the 'Windows' section of colors should follow the OS theme. I don't recall without sourcediving whether Tk uses Windows control messages (e.g., WM_CTLCOLORSTATIC, WM_CTLCOLORDLG, etc.) to manage these, or if Tk uses its own messaging (I'm not sure all of this can really handle theme changes without first making Tk support named colors, a feature that I've wanted for a long time but never had time to explore implementing).
Emiliano is correct that the basic notification for changes to the theme is WM_SETTINGCHANGE. Typically, the lParam is a string that is the name of the leaf node in the registry (so "AppsUseLightTheme" in this case). But apps that send WM_SETTINGCHANGE often get it wrong, so apps that handle WM_SETTINGCHANGE typically check and reload all the Windows system parameter settings that the app uses. (I've also seen that the WM_SETTINGCHANGE for the theme will set lParam to "ImmersiveColorSet".)
There's also a message, WM_THEMECHANGED. I'm not sure of the context in which it appears. When it arrives, the code should use OpenThemeData() to get the current theme, and then GetThemeColor to retrieve the colors. (I think this is where Tk's 'system*' colors come from on Windows, but I'm far from positive, and again, no time to sourcedive.)
Another possible approach to getting notified of the 'light/dark' change is to call RegNotifyChangeKeyValue with fAsynchronous set and an hEvent provided. Changes will be reported through the message pump, with the given hEvent.
Pre-Windows-10, only the light theme exists (although high contrast still needs to be handled, that goes all the way back to WinXP).
Copyright
This document has been placed in the public domain.