{"record":{"id":"9fb3eb3475e2fed4","repo":"wailsapp/wails","slug":"faild-to-create-solid-color-brush","errorCode":null,"errorMessage":"Faild to create solid color brush","messagePattern":"Faild to create solid color brush","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v2/internal/frontend/desktop/windows/winc/brush.go","lineNumber":25,"sourceCode":"\npackage winc\n\nimport (\n\t\"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32\"\n)\n\nvar DefaultBackgroundBrush = NewSystemColorBrush(w32.COLOR_BTNFACE)\n\ntype Brush struct {\n\thBrush   w32.HBRUSH\n\tlogBrush w32.LOGBRUSH\n}\n\nfunc NewSolidColorBrush(color Color) *Brush {\n\tlb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(color)}\n\thBrush := w32.CreateBrushIndirect(&lb)\n\tif hBrush == 0 {\n\t\tpanic(\"Faild to create solid color brush\")\n\t}\n\n\treturn &Brush{hBrush, lb}\n}\n\nfunc NewSystemColorBrush(colorIndex int) *Brush {\n\t//lb := w32.LOGBRUSH{LbStyle: w32.BS_SOLID, LbColor: w32.COLORREF(colorIndex)}\n\tlb := w32.LOGBRUSH{LbStyle: w32.BS_NULL}\n\thBrush := w32.GetSysColorBrush(colorIndex)\n\tif hBrush == 0 {\n\t\tpanic(\"GetSysColorBrush failed\")\n\t}\n\treturn &Brush{hBrush, lb}\n}\n\nfunc NewHatchedColorBrush(color Color) *Brush {\n\tlb := w32.LOGBRUSH{LbStyle: w32.BS_HATCHED, LbColor: w32.COLORREF(color)}\n\thBrush := w32.CreateBrushIndirect(&lb)","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/wailsapp/wails/blob/0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3/v2/internal/frontend/desktop/windows/winc/brush.go#L7-L43","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (leaks a brush every paint)\nfunc (c *MyControl) WndProc(msg uint32, w, l uintptr) uintptr {\n    if msg == w32.WM_ERASEBKGND {\n        br := winc.NewSolidColorBrush(winc.RGB(255, 255, 255)) // panics once GDI handles run out\n        defer w32.DeleteObject(br.GetHBRUSH())\n    }\n    return c.ControlBase.WndProc(msg, w, l)\n}\n\n// after (one brush for the control lifetime)\ntype MyControl struct {\n    winc.ControlBase\n    bg *winc.Brush\n}\n\nfunc NewMyControl(parent winc.Controller) *MyControl {\n    c := new(MyControl)\n    c.InitWindow(\"MyControl\", parent, 0, 0)\n    c.bg = winc.NewSolidColorBrush(winc.RGB(255, 255, 255))\n    return c\n}","handlingStrategy":"validation","validationCode":"// winc panics on failure, so guard the conditions that cause it:\n// reuse brushes instead of creating them repeatedly.\nvar (\n    bgBrush     = winc.NewSolidColorBrush(winc.RGB(240, 240, 240))\n    accentBrush = winc.NewSolidColorBrush(winc.RGB(0, 120, 215))\n)\n// If you must create one dynamically, verify GDI budget first (Windows caps at 10000):\n// watch `GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS)` and recycle brushes when it grows.","typeGuard":null,"tryCatchPattern":"// Last-resort isolation around third-party code that creates brushes per call:\nfunc safeBrush(c winc.Color) (br *winc.Brush, ok bool) {\n    ok = w32.GetGuiResources(w32.GetCurrentProcess(), 0) < 9000 // GR_GDIOBJECTS headroom\n    if !ok { return nil, false }\n    defer func() { if r := recover(); r != nil { br, ok = nil, false } }()\n    return winc.NewSolidColorBrush(c), true\n}","preventionTips":["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"],"tags":["windows","gdi","resource-leak","winc","panic"],"backgroundTag":null,"analyzedSha":"0e754b1b40ba9044c2a1460e23b7c3de20fc5cf3","analyzedAt":"2026-08-15T14:17:36.034Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}