golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ui/iconprovider.go (about) 1 /* SPDX-License-Identifier: MIT 2 * 3 * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. 4 */ 5 6 package ui 7 8 import ( 9 "github.com/lxn/walk" 10 11 "golang.zx2c4.com/wireguard/windows/l18n" 12 "golang.zx2c4.com/wireguard/windows/manager" 13 ) 14 15 type widthAndState struct { 16 width int 17 state manager.TunnelState 18 } 19 20 type widthAndDllIdx struct { 21 width int 22 idx int32 23 dll string 24 } 25 26 var cachedOverlayIconsForWidthAndState = make(map[widthAndState]walk.Image) 27 28 func iconWithOverlayForState(state manager.TunnelState, size int) (icon walk.Image, err error) { 29 icon = cachedOverlayIconsForWidthAndState[widthAndState{size, state}] 30 if icon != nil { 31 return 32 } 33 34 wireguardIcon, err := loadLogoIcon(size) 35 if err != nil { 36 return 37 } 38 39 if state == manager.TunnelStopped { 40 return wireguardIcon, err // TODO: if we find something prettier than the gray dot, then remove this clause 41 } 42 43 iconSize := wireguardIcon.Size() 44 w := int(float64(iconSize.Width) * 0.65) 45 h := int(float64(iconSize.Height) * 0.65) 46 overlayBounds := walk.Rectangle{iconSize.Width - w, iconSize.Height - h, w, h} 47 overlayIcon, err := iconForState(state, overlayBounds.Width) 48 if err != nil { 49 return 50 } 51 52 icon = walk.NewPaintFuncImage(walk.Size{size, size}, func(canvas *walk.Canvas, bounds walk.Rectangle) error { 53 if err := canvas.DrawImageStretched(wireguardIcon, bounds); err != nil { 54 return err 55 } 56 if err := canvas.DrawImageStretched(overlayIcon, overlayBounds); err != nil { 57 return err 58 } 59 return nil 60 }) 61 62 cachedOverlayIconsForWidthAndState[widthAndState{size, state}] = icon 63 64 return 65 } 66 67 var cachedIconsForWidthAndState = make(map[widthAndState]*walk.Icon) 68 69 func iconForState(state manager.TunnelState, size int) (icon *walk.Icon, err error) { 70 icon = cachedIconsForWidthAndState[widthAndState{size, state}] 71 if icon != nil { 72 return 73 } 74 switch state { 75 case manager.TunnelStarted: 76 icon, err = loadSystemIcon("imageres", -106, size) 77 case manager.TunnelStopped: 78 icon, err = walk.NewIconFromResourceIdWithSize(8, walk.Size{size, size}) // TODO: replace with real icon from imageres/shell32 79 default: 80 icon, err = loadSystemIcon("shell32", -16739, size) // TODO: this doesn't look that great overlayed on the app icon 81 } 82 if err == nil { 83 cachedIconsForWidthAndState[widthAndState{size, state}] = icon 84 } 85 return 86 } 87 88 func textForState(state manager.TunnelState, withEllipsis bool) (text string) { 89 switch state { 90 case manager.TunnelStarted: 91 text = l18n.Sprintf("Active") 92 case manager.TunnelStarting: 93 text = l18n.Sprintf("Activating") 94 case manager.TunnelStopped: 95 text = l18n.Sprintf("Inactive") 96 case manager.TunnelStopping: 97 text = l18n.Sprintf("Deactivating") 98 case manager.TunnelUnknown: 99 text = l18n.Sprintf("Unknown state") 100 } 101 if withEllipsis { 102 switch state { 103 case manager.TunnelStarting, manager.TunnelStopping: 104 text += "…" 105 } 106 } 107 return 108 } 109 110 var cachedSystemIconsForWidthAndDllIdx = make(map[widthAndDllIdx]*walk.Icon) 111 112 func loadSystemIcon(dll string, index int32, size int) (icon *walk.Icon, err error) { 113 icon = cachedSystemIconsForWidthAndDllIdx[widthAndDllIdx{size, index, dll}] 114 if icon != nil { 115 return 116 } 117 icon, err = walk.NewIconFromSysDLLWithSize(dll, int(index), size) 118 if err == nil { 119 cachedSystemIconsForWidthAndDllIdx[widthAndDllIdx{size, index, dll}] = icon 120 } 121 return 122 } 123 124 func loadShieldIcon(size int) (icon *walk.Icon, err error) { 125 icon, err = loadSystemIcon("imageres", -1028, size) 126 if err != nil { 127 icon, err = loadSystemIcon("imageres", 1, size) 128 } 129 return 130 } 131 132 var cachedLogoIconsForWidth = make(map[int]*walk.Icon) 133 134 func loadLogoIcon(size int) (icon *walk.Icon, err error) { 135 icon = cachedLogoIconsForWidth[size] 136 if icon != nil { 137 return 138 } 139 icon, err = walk.NewIconFromResourceIdWithSize(7, walk.Size{size, size}) 140 if err == nil { 141 cachedLogoIconsForWidth[size] = icon 142 } 143 return 144 }