//go:build windows package platform import ( "fmt" "strconv" "strings" "sync" "unsafe" "github.com/stevec/fancywin/internal/config" "github.com/stevec/fancywin/internal/layout" "golang.org/x/sys/windows" ) const ( wmPaint = 0x000F wmEraseBackground = 0x0014 wmNcHitTest = 0x0084 wsPopup = 0x80000000 wsExTopmost = 0x00000008 wsExTransparent = 0x00000020 wsExToolWindow = 0x00000080 wsExLayered = 0x00080000 wsExNoActivate = 0x08000000 lwaColorKey = 0x00000001 lwaAlpha = 0x00000002 hwndTopmost = ^uintptr(0) swpShowWindow = 0x0040 psSolid = 0 nullBrush = 5 bkModeTransparent = 1 dtCenter = 0x00000001 dtVCenter = 0x00000004 dtSingleLine = 0x00000020 dtEndEllipsis = 0x00008000 fontWeightBold = 700 nonAntialiased = 3 overlayColorKey = 0x00010001 // RGB(1,0,1), made fully transparent. ) var ( gdi32 = windows.NewLazySystemDLL("gdi32.dll") procRegisterClassEx = user32.NewProc("RegisterClassExW") procCreateWindowEx = user32.NewProc("CreateWindowExW") procDestroyWindow = user32.NewProc("DestroyWindow") procDefWindowProc = user32.NewProc("DefWindowProcW") procBeginPaint = user32.NewProc("BeginPaint") procEndPaint = user32.NewProc("EndPaint") procGetClientRect = user32.NewProc("GetClientRect") procFillRect = user32.NewProc("FillRect") procDrawText = user32.NewProc("DrawTextW") procInvalidateRect = user32.NewProc("InvalidateRect") procUpdateWindow = user32.NewProc("UpdateWindow") procSetLayeredWindowAttributes = user32.NewProc("SetLayeredWindowAttributes") procGetModuleHandle = kernel32.NewProc("GetModuleHandleW") procCreateSolidBrush = gdi32.NewProc("CreateSolidBrush") procCreatePen = gdi32.NewProc("CreatePen") procSelectObject = gdi32.NewProc("SelectObject") procDeleteObject = gdi32.NewProc("DeleteObject") procRectangle = gdi32.NewProc("Rectangle") procSetBkMode = gdi32.NewProc("SetBkMode") procSetTextColor = gdi32.NewProc("SetTextColor") procCreateFont = gdi32.NewProc("CreateFontW") procGetStockObject = gdi32.NewProc("GetStockObject") overlayClassName, _ = windows.UTF16PtrFromString("FancyWinZoneOverlay") overlayWindowProc = windows.NewCallback(overlayWndProc) overlayViews = struct { sync.RWMutex items map[windows.Handle]*overlayView }{items: make(map[windows.Handle]*overlayView)} ) type windowClassEx struct { Size uint32 Style uint32 WndProc uintptr ClsExtra int32 WndExtra int32 Instance windows.Handle Icon windows.Handle Cursor windows.Handle Background windows.Handle MenuName *uint16 ClassName *uint16 IconSmall windows.Handle } type paintStruct struct { DC windows.Handle Erase int32 Paint rect Restore int32 IncUpdate int32 Reserved [32]byte } type overlayView struct { hwnd windows.Handle work layout.Rect zones []config.Zone gap int style config.Overlay } type overlayManager struct { classReady bool hwnd windows.Handle monitor windows.Handle signature string } func (m *overlayManager) show(mon windows.Handle, work layout.Rect, zones []config.Zone, gap int, style config.Overlay) error { signature := overlaySignature(mon, work, zones, gap, style) if m.hwnd != 0 && m.signature == signature { return nil } m.hide() if err := m.ensureClass(); err != nil { return err } instance, _, instanceErr := procGetModuleHandle.Call(0) if instance == 0 { return fmt.Errorf("GetModuleHandleW: %v", instanceErr) } w, h := work.Right-work.Left, work.Bottom-work.Top raw, _, createErr := procCreateWindowEx.Call( wsExTopmost|wsExTransparent|wsExToolWindow|wsExLayered|wsExNoActivate, uintptr(unsafe.Pointer(overlayClassName)), uintptr(unsafe.Pointer(overlayClassName)), wsPopup, uintptr(work.Left), uintptr(work.Top), uintptr(w), uintptr(h), 0, 0, instance, 0, ) if raw == 0 { return fmt.Errorf("CreateWindowExW: %v", createErr) } hwnd := windows.Handle(raw) view := &overlayView{hwnd: hwnd, work: work, zones: append([]config.Zone(nil), zones...), gap: gap, style: style} overlayViews.Lock() overlayViews.items[hwnd] = view overlayViews.Unlock() alpha := byte((style.Opacity*255 + 50) / 100) if ok, _, err := procSetLayeredWindowAttributes.Call(raw, overlayColorKey, uintptr(alpha), lwaColorKey|lwaAlpha); ok == 0 { m.destroy(hwnd) return fmt.Errorf("SetLayeredWindowAttributes: %v", err) } procInvalidateRect.Call(raw, 0, 1) procUpdateWindow.Call(raw) if ok, _, err := procSetWindowPos.Call(raw, hwndTopmost, uintptr(work.Left), uintptr(work.Top), uintptr(w), uintptr(h), swpNoActivate|swpNoOwnerZOrder|swpShowWindow); ok == 0 { m.destroy(hwnd) return fmt.Errorf("show overlay: %v", err) } m.hwnd = hwnd m.monitor = mon m.signature = signature return nil } func (m *overlayManager) hide() { if m.hwnd != 0 { m.destroy(m.hwnd) } m.hwnd = 0 m.monitor = 0 m.signature = "" } func (m *overlayManager) destroy(hwnd windows.Handle) { overlayViews.Lock() delete(overlayViews.items, hwnd) overlayViews.Unlock() procDestroyWindow.Call(uintptr(hwnd)) } func (m *overlayManager) ensureClass() error { if m.classReady { return nil } instance, _, instanceErr := procGetModuleHandle.Call(0) if instance == 0 { return fmt.Errorf("GetModuleHandleW: %v", instanceErr) } wc := windowClassEx{ Size: uint32(unsafe.Sizeof(windowClassEx{})), WndProc: overlayWindowProc, Instance: windows.Handle(instance), ClassName: overlayClassName, } if atom, _, err := procRegisterClassEx.Call(uintptr(unsafe.Pointer(&wc))); atom == 0 { return fmt.Errorf("RegisterClassExW: %v", err) } m.classReady = true return nil } func overlayWndProc(rawHWND, message, wParam, lParam uintptr) uintptr { hwnd := windows.Handle(rawHWND) switch message { case wmNcHitTest: return ^uintptr(0) // HTTRANSPARENT: always pass pointer input through. case wmEraseBackground: return 1 case wmPaint: overlayViews.RLock() view := overlayViews.items[hwnd] overlayViews.RUnlock() if view != nil { view.paint() return 0 } } r, _, _ := procDefWindowProc.Call(rawHWND, message, wParam, lParam) return r } func (v *overlayView) paint() { var ps paintStruct dc, _, _ := procBeginPaint.Call(uintptr(v.hwnd), uintptr(unsafe.Pointer(&ps))) if dc == 0 { return } defer procEndPaint.Call(uintptr(v.hwnd), uintptr(unsafe.Pointer(&ps))) var client rect procGetClientRect.Call(uintptr(v.hwnd), uintptr(unsafe.Pointer(&client))) background, _, _ := procCreateSolidBrush.Call(overlayColorKey) if background != 0 { procFillRect.Call(dc, uintptr(unsafe.Pointer(&client)), background) procDeleteObject.Call(background) } color := colorRef(v.style.Color) pen, _, _ := procCreatePen.Call(psSolid, uintptr(v.style.BorderWidth), uintptr(color)) nullFill, _, _ := procGetStockObject.Call(nullBrush) oldPen, _, _ := procSelectObject.Call(dc, pen) oldBrush, _, _ := procSelectObject.Call(dc, nullFill) defer func() { procSelectObject.Call(dc, oldPen) procSelectObject.Call(dc, oldBrush) if pen != 0 { procDeleteObject.Call(pen) } }() fontName, _ := windows.UTF16PtrFromString("Segoe UI") font, _, _ := procCreateFont.Call(^uintptr(55), 0, 0, 0, fontWeightBold, 0, 0, 0, 1, 0, 0, nonAntialiased, 0, uintptr(unsafe.Pointer(fontName))) oldFont := uintptr(0) if font != 0 { oldFont, _, _ = procSelectObject.Call(dc, font) defer func() { procSelectObject.Call(dc, oldFont); procDeleteObject.Call(font) }() } procSetBkMode.Call(dc, bkModeTransparent) for i, zone := range v.zones { r := layout.Resolve(v.work, zone, v.gap) r.Left -= v.work.Left r.Right -= v.work.Left r.Top -= v.work.Top r.Bottom -= v.work.Top procRectangle.Call(dc, uintptr(r.Left), uintptr(r.Top), uintptr(r.Right), uintptr(r.Bottom)) name := strings.TrimSpace(zone.Name) if name == "" { name = fmt.Sprintf("Zone %d", i+1) } text, err := windows.UTF16FromString(name) if err != nil || len(text) == 0 { continue } shadow := rect{Left: r.Left + 2, Top: r.Top + 2, Right: r.Right + 2, Bottom: r.Bottom + 2} procSetTextColor.Call(dc, 0x00000000) procDrawText.Call(dc, uintptr(unsafe.Pointer(&text[0])), ^uintptr(0), uintptr(unsafe.Pointer(&shadow)), dtCenter|dtVCenter|dtSingleLine|dtEndEllipsis) label := rect{Left: r.Left, Top: r.Top, Right: r.Right, Bottom: r.Bottom} procSetTextColor.Call(dc, uintptr(color)) procDrawText.Call(dc, uintptr(unsafe.Pointer(&text[0])), ^uintptr(0), uintptr(unsafe.Pointer(&label)), dtCenter|dtVCenter|dtSingleLine|dtEndEllipsis) } } func colorRef(value string) uint32 { rgb, err := strconv.ParseUint(strings.TrimPrefix(value, "#"), 16, 24) if err != nil { return 0x00EFAE00 } r := uint32(rgb >> 16) g := uint32((rgb >> 8) & 0xff) b := uint32(rgb & 0xff) return r | g<<8 | b<<16 } func overlaySignature(mon windows.Handle, work layout.Rect, zones []config.Zone, gap int, style config.Overlay) string { var b strings.Builder fmt.Fprintf(&b, "%x|%v|%d|%v", uintptr(mon), work, gap, style) for _, zone := range zones { fmt.Fprintf(&b, "|%s:%g:%g:%g:%g", zone.Name, zone.X, zone.Y, zone.Width, zone.Height) } return b.String() }