wailsapp/wails · error
Faild to create solid color brush
Error message
Faild to create solid color brush
What it means
NewSolidColorBrush calls the Win32 CreateBrushIndirect API with a BS_SOLID LOGBRUSH and panics when the returned handle is 0. CreateBrushIndirect virtually never fails for a solid brush unless the GDI handle table (10000 handles per process) is exhausted or the process is terminating. This panic fires from library-internal initialization paths (e.g. DefaultBackgroundBrush-style setup), so it usually indicates a GDI leak rather than a bad argument.
Source
Thrown at v2/internal/frontend/desktop/windows/winc/brush.go:25
package winc
import (
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
)
var DefaultBackgroundBrush = NewSystemColorBrush(w32.COLOR_BTNFACE)
type Brush struct {
hBrush w32.HBRUSH
logBrush w32.LOGBRUSH
}
func NewSolidColorBrush(color Color) *Brush {
lb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(color)}
hBrush := w32.CreateBrushIndirect(&lb)
if hBrush == 0 {
panic("Faild to create solid color brush")
}
return &Brush{hBrush, lb}
}
func NewSystemColorBrush(colorIndex int) *Brush {
//lb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(colorIndex)}
lb := w32.LOGBRUSH{LbStyle: w32.BS_NULL}
hBrush := w32.GetSysColorBrush(colorIndex)
if hBrush == 0 {
panic("GetSysColorBrush failed")
}
return &Brush{hBrush, lb}
}
func NewHatchedColorBrush(color Color) *Brush {
lb := w32.LOGBRUSH{LbStyle: w32.BS_HATCHED, LbColor: w32.COLORREF(color)}
hBrush := w32.CreateBrushIndirect(&lb)View on GitHub (pinned to 0e754b1b40)
Solutions
- Audit the app for Brush creation inside paint/size/event callbacks and hoist brushes to package-level or control-lifetime singletons (winc itself uses shared vars like DefaultBackgroundBrush)
- Ensure every dynamically created Brush is released via w32.DeleteObject when the control is destroyed; winc controls that own brushes should delete them in Dispose()
- Monitor GDI objects in Task Manager (Details tab, add 'GDI objects' column) while reproducing; a count climbing toward 10000 confirms the leak
- If the panic happens at startup with no leak, check for DLL injection / hook software that corrupts the GDI handle table
Example fix
// before (leaks a brush every paint)
func (c *MyControl) WndProc(msg uint32, w, l uintptr) uintptr {
if msg == w32.WM_ERASEBKGND {
br := winc.NewSolidColorBrush(winc.RGB(255, 255, 255)) // panics once GDI handles run out
defer w32.DeleteObject(br.GetHBRUSH())
}
return c.ControlBase.WndProc(msg, w, l)
}
// after (one brush for the control lifetime)
type MyControl struct {
winc.ControlBase
bg *winc.Brush
}
func NewMyControl(parent winc.Controller) *MyControl {
c := new(MyControl)
c.InitWindow("MyControl", parent, 0, 0)
c.bg = winc.NewSolidColorBrush(winc.RGB(255, 255, 255))
return c
} Defensive patterns
Strategy: validation
Validate before calling
// winc panics on failure, so guard the conditions that cause it:
// reuse brushes instead of creating them repeatedly.
var (
bgBrush = winc.NewSolidColorBrush(winc.RGB(240, 240, 240))
accentBrush = winc.NewSolidColorBrush(winc.RGB(0, 120, 215))
)
// If you must create one dynamically, verify GDI budget first (Windows caps at 10000):
// watch `GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS)` and recycle brushes when it grows. Try / catch
// Last-resort isolation around third-party code that creates brushes per call:
func safeBrush(c winc.Color) (br *winc.Brush, ok bool) {
ok = w32.GetGuiResources(w32.GetCurrentProcess(), 0) < 9000 // GR_GDIOBJECTS headroom
if !ok { return nil, false }
defer func() { if r := recover(); r != nil { br, ok = nil, false } }()
return winc.NewSolidColorBrush(c), true
} Prevention
- Create brushes once at package/control scope; never inside WM_PAINT or per-event callbacks
- Pair every dynamic brush with w32.DeleteObject on control disposal
- Track GDI objects in Task Manager during development to catch leaks early
When it happens
Trigger: Calling winc.NewSolidColorBrush(color) when the process has leaked GDI brushes/pens (each NewSolidColorBrush allocates an HBRUSH that must be freed with DeleteObject); calling it during process shutdown after the GDI subsystem is torn down; a color value itself is never the cause since any COLORREF is accepted.
Common situations: Apps that create brushes per-paint (e.g. in WM_CTLCOLOR or draw handlers) without disposing them; long-running Wails v2 Windows apps whose custom controls allocate Brush objects in event loops; Task Manager shows steady GDI-object growth.
Related errors
- Failed to create null brush
- Create canvas from %v failed.
- CreateFontIndirect failed
- GetSysColorBrush failed
- Cannot create canvas from invalid HDC.
AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15).
Data as JSON: /api/errors/9fb3eb3475e2fed4.
Report an issue: GitHub.