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

  1. 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)
  2. 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()
  3. Monitor GDI objects in Task Manager (Details tab, add 'GDI objects' column) while reproducing; a count climbing toward 10000 confirms the leak
  4. 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

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


AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15). Data as JSON: /api/errors/9fb3eb3475e2fed4. Report an issue: GitHub.