Skip to content

Instantly share code, notes, and snippets.

@jerblack
Last active February 22, 2024 06:01
Show Gist options
  • Save jerblack/1d05bbcebb50ad55c312e4d7cf1bc909 to your computer and use it in GitHub Desktop.
Save jerblack/1d05bbcebb50ad55c312e4d7cf1bc909 to your computer and use it in GitHub Desktop.
Golang: Detect dark mode change in Windows 10

I wanted to be able to detect when Dark mode was turned on or off in my Go program so I can swap icons in my tray app on Windows so I wrote this.
When Dark Mode is turned on or off, it writes to a pair of registry keys, both of which Windows allows you to set independently in Settings > Personalization > Colors.

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize  

SystemUsesLightTheme  DWORD 0 or 1 // Changes the taskbar and system tray color  
AppsUseLightTheme     DWORD 0 or 1 // Changes app colors  

Mine is a tray app, so I am monitoring the first one. I did not want to just periodically poll the registry key and I wanted to be able to react immediately when the registry key is changed, so I am using the win32 function RegNotifyChangeKeyValue in advapi32.dll to tell the OS to notify my app so I can swap out the icon for a color-appropriate one.
I start the monitor function in main, which loads the RegNotifyChangeKeyValue function from advapi32 and starts a goroutine that calls that function with a pointer to the registry key I want to monitor. When it detects a change, the registry value is read and if it is actually different, a function that was passed to the monitor function is called with a boolean representing the current state of dark mode.

package main
import (
`fmt`
`golang.org/x/sys/windows/registry`
`log`
`syscall`
)
const (
regKey = `Software\Microsoft\Windows\CurrentVersion\Themes\Personalize` // in HKCU
regName = `SystemUsesLightTheme` // <- For taskbar & tray. Use AppsUseLightTheme for apps
)
func main() {
fmt.Println("Dark Mode on:", isDark())
monitor(react)
}
// react to the change
func react(isDark bool) {
if isDark {
fmt.Println("Dark Mode ON")
} else {
fmt.Println("Dark Mode OFF")
}
}
func isDark() bool {
k, err := registry.OpenKey(registry.CURRENT_USER, regKey, registry.QUERY_VALUE)
if err != nil {
log.Fatal(err)
}
defer k.Close()
val, _, err := k.GetIntegerValue(regName)
if err != nil {
log.Fatal(err)
}
return val == 0
}
func monitor(fn func(bool)) {
var regNotifyChangeKeyValue *syscall.Proc
changed := make(chan bool)
if advapi32, err := syscall.LoadDLL("Advapi32.dll"); err == nil {
if p, err := advapi32.FindProc("RegNotifyChangeKeyValue"); err == nil {
regNotifyChangeKeyValue = p
} else {
log.Fatal("Could not find function RegNotifyChangeKeyValue in Advapi32.dll")
}
}
if regNotifyChangeKeyValue != nil {
go func() {
k, err := registry.OpenKey(registry.CURRENT_USER, regKey, syscall.KEY_NOTIFY|registry.QUERY_VALUE)
if err != nil {
log.Fatal(err)
}
var wasDark uint64
for {
regNotifyChangeKeyValue.Call(uintptr(k), 0, 0x00000001|0x00000004, 0, 0)
val, _, err := k.GetIntegerValue(regName)
if err != nil {
log.Fatal(err)
}
if val != wasDark {
wasDark = val
changed <- val == 0
}
}
}()
}
for {
val := <-changed
fn(val)
}
}
// MIT License
// Copyright 2022 Jeremy Black
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@zwass
Copy link

zwass commented Aug 5, 2022

This looks awesome! Any chance you'd add a permissive license (eg. MIT, Apache2) for use in other projects?

@jerblack
Copy link
Author

jerblack commented Aug 5, 2022

Sure thing. I added the MIT license text to the bottom of the .go file. Hope it helps.

@zwass
Copy link

zwass commented Aug 5, 2022

Awesome thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment