aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/iconprovider.go
blob: ac373d90a55800835656376707ed7ce3e0c83be6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
 */

package ui

import (
	"fmt"
	"github.com/lxn/walk"
	"github.com/lxn/win"
	"golang.org/x/sys/windows"
	"golang.zx2c4.com/wireguard/windows/service"
	"path"
)

type IconProvider struct {
	wireguardIcon       *walk.Icon
	overlayIconsByState map[service.TunnelState]*walk.Icon
}

func NewIconProvider() (*IconProvider, error) {
	tsip := &IconProvider{overlayIconsByState: make(map[service.TunnelState]*walk.Icon)}
	var err error
	if tsip.wireguardIcon, err = walk.NewIconFromResource("$wireguard.ico"); err != nil {
		return nil, err
	}
	return tsip, nil
}

func (tsip *IconProvider) Dispose() {
	if tsip.overlayIconsByState != nil {
		for _, icon := range tsip.overlayIconsByState {
			icon.Dispose()
		}
		tsip.overlayIconsByState = nil
	}
	if tsip.wireguardIcon != nil {
		tsip.wireguardIcon.Dispose()
		tsip.wireguardIcon = nil
	}
}

func (tsip *IconProvider) IconWithOverlayForState(state service.TunnelState) (*walk.Icon, error) {
	if icon, ok := tsip.overlayIconsByState[state]; ok {
		return icon, nil
	}

	size := tsip.wireguardIcon.Size()

	bmp, err := walk.NewBitmapWithTransparentPixels(size)
	if err != nil {
		return nil, err
	}
	defer bmp.Dispose()

	canvas, err := walk.NewCanvasFromImage(bmp)
	if err != nil {
		return nil, err
	}
	defer canvas.Dispose()

	if err := canvas.DrawImage(tsip.wireguardIcon, walk.Point{}); err != nil {
		return nil, err
	}

	overlayIcon, err := tsip.IconForState(state)
	if err != nil {
		return nil, err
	}
	defer overlayIcon.Dispose()

	w := int(float64(size.Width) * 0.65)
	h := int(float64(size.Height) * 0.65)
	bounds := walk.Rectangle{size.Width - w, size.Height - h, w, h}

	if err := canvas.DrawImageStretched(overlayIcon, bounds); err != nil {
		return nil, err
	}
	canvas.Dispose()

	icon, err := walk.NewIconFromBitmap(bmp)
	if err != nil {
		return nil, err
	}
	tsip.overlayIconsByState[state] = icon
	return icon, nil
}

func (tsip *IconProvider) IconForState(state service.TunnelState) (icon *walk.Icon, err error) {
	switch state {
	case service.TunnelStarted:
		icon, err = loadSystemIcon("imageres", 101)

	case service.TunnelStopped:
		icon, err = walk.NewIconFromResource("dot-gray.ico") //TODO: replace with real icon

	default:
		icon, err = loadSystemIcon("shell32", 238) //TODO: this doesn't look that great overlayed on the app icon
	}
	return
}

func loadSystemIcon(dll string, index uint) (*walk.Icon, error) {
	system32, err := windows.GetSystemDirectory()
	if err != nil {
		return nil, err
	}
	hicon := win.ExtractIcon(win.GetModuleHandle(nil), windows.StringToUTF16Ptr(path.Join(system32, dll+".dll")), int32(index))
	if hicon <= 1 {
		return nil, fmt.Errorf("Unable to find icon %d of %s", index, dll)
	}
	return walk.NewIconFromHICON(hicon)
}