fyne
fyne copied to clipboard
Sometimes fyne fails to setup dark mode
Checklist
- [X] I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
- [X] This issue only relates to a single bug. I will open new issues for any other problems.
Describe the bug
Happens randomly, with error code 0x800703e6.
Dark mode, however, is still set successfully.
How to reproduce
Use go run .
until it appears. Depends on your luck I guess.
Screenshots
No response
Example code
func main() {
a := app.New()
w := a.NewWindow("Hello")
hello := widget.NewLabel("Hello Fyne!")
w.SetContent(container.NewVBox(
hello,
widget.NewButton("Hi!", func() {
hello.SetText("Welcome :)")
}),
))
w.ShowAndRun()
}
Fyne version
2.4.4
Go compiler version
1.22.0
Operating system and version
Windows 10
Additional Information
Modified internal/driver/glfw/window_windows.go
to show the error code.
Sometimes this also happens here. Even though the theme is correctly set in the 'fyne elements', the Windows' own window top bar remains in light theme, as in the print below (exactly when it occurred). It throws the same error 0x800703e6.
The right behavior for the window would be:
I read the error HRESULT =0x800703e6 represents - ERROR_NOACCESS. A description of the code reveals the message invalid access to memory location.
Perhaps a possibility is that the window fyne tries to set to dark mode isn't fully initialized yet?
I take this opportunity to ask you: Were you using the SystemTray or SystemTrayMenu API when this occurred?
I were using none of them. At least on my machine the theme is still applied successfully, and I get the following error description: "The operation completed successfully."
While developing, happened to me too:
Any error message logged?
The operation completed successfully
OMG windows.
I wonder if there is something in the thread handling where we set that up... possibly we knocked it into a goroutine?
OMG windows.
I wonder if there is something in the thread handling where we set that up... possibly we knocked it into a goroutine?
Seems not:
runOnMain(func() {
w.setDarkMode()
Perhaps there is something in our windows call that is not correct for all systems or at all times?
dwm := syscall.NewLazyDLL("dwmapi.dll")
setAtt := dwm.NewProc("DwmSetWindowAttribute")
ret, _, err := setAtt.Call(uintptr(unsafe.Pointer(hwnd)), // window handle
20, // DWMWA_USE_IMMERSIVE_DARK_MODE
uintptr(unsafe.Pointer(&dark)), // on or off
8) // sizeof(darkMode)
I have a vague recollection of this being a thing on old versions of Windows 10 (or at least something similar). Is the system updated to the latest version?
In my case I get this error randomly as well but I am not even setting dark mode in the app anywhere.
To add some more info, I am using windows 10. My windows version is only a single patch out of date, but of course it is still Windows 10, not 11.
Edition Windows 10 Pro
Version 22H2
Installed on 12/16/2022
OS build 19045.4894
Experience Windows Feature Experience Pack 1000.19060.1000.0
I debugged the issue and found the return code was: 0x800703e6
More info, that error code is this:
Err_6.4.5.exe 0x800703e6
# No results found for hex 0x800703e6 / decimal -2147023898
# as an HRESULT: Severity: FAILURE (1), FACILITY_WIN32 (0x7), Code 0x3e6
# for hex 0x3e6 / decimal 998
ERROR_NOACCESS winerror.h
# Invalid access to memory location.
# 1 matches found for "0x800703e6"
So, looking at the original code I noticed it is passing Bool unsafe with a size of 8. I think there are a few issues with this as I explain here in comments:
func (w *window) setDarkMode() {
if runtime.GOOS == "windows" {
hwnd := w.view().GetWin32Window()
dark := isDark() // <-----returns go bool, one byte
dwm := syscall.NewLazyDLL("dwmapi.dll")
setAtt := dwm.NewProc("DwmSetWindowAttribute")
ret, _, err := setAtt.Call(uintptr(unsafe.Pointer(hwnd)), // window handle
20, // DWMWA_USE_IMMERSIVE_DARK_MODE
uintptr(unsafe.Pointer(&dark)), // on or off
8) // <---- 8 is too big, it will access invalid memory depending where isDark return is allocated.
// explains the random nature. Also explains why other people sometimes see the title bar
// the wrong color, it is because it is reading other data that make the bool true in windows
// api call because the byte that is actually used under the convers is 0 even if dark == true
if ret != 0 && ret != 0x80070057 { // err is always non-nil, we check return value (except erroneous code)
fyne.LogError(fmt.Sprintf("Failed to set dark mode, ret 0x%x", ret), err) <--- what I used to get the return code
}
}
}
So, based on my reasoning above this is the code I came up with.
func (w *window) setDarkMode() {
if runtime.GOOS == "windows" {
hwnd := w.view().GetWin32Window()
dark := isDark()
// cannot use a go bool.
var winBool int32
if dark {
winBool = 1
}
dwm := syscall.NewLazyDLL("dwmapi.dll")
setAtt := dwm.NewProc("DwmSetWindowAttribute")
ret, _, err := setAtt.Call(uintptr(unsafe.Pointer(hwnd)), // window handle
20, // DWMWA_USE_IMMERSIVE_DARK_MODE
uintptr(unsafe.Pointer(&winBool)), // on or off
4) // sizeof(bool for windows)
if ret != 0 && ret != 0x80070057 { // err is always non-nil, we check return value (except erroneous code)
fyne.LogError("Failed to set dark mode", err)
}
}
}
The above code works for my machine, windows 10. I think there is some out of date document implying DWMWA_USE_IMMERSIVE_DARK_MODE only works starting windows 11, but I think they added this in a newer windows 10 patch, I am just unsure when. However, the check for 0x80070057 catches the case for versions where windows 10 isn't supported.
Anyway, I think these are the correct changes that should work on windows 10 and window 11 and shouldn't randomly give these errors or make the title bar the wrong color (it could be white or black with the opposite theme with this bug).
Thanks to @steampoweredtaco this is fixed on develop, and I will pick it onto release branch