-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgeticon_windows.go
256 lines (225 loc) · 6.22 KB
/
geticon_windows.go
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package geticon
import (
"bytes"
"encoding/binary"
"fmt"
"image"
"image/png"
"unsafe"
"github.com/fcjr/geticon/internal/winapi"
"golang.org/x/image/bmp"
"golang.org/x/sys/windows"
)
const pngHeader = "\x89PNG\r\n\x1a\n"
// https://devblogs.microsoft.com/oldnewthing/20120720-00/?p=7083
type grpIconDirHeader struct {
IDReserved uint16
IDType uint16
IDCount uint16
}
type grpIconResourceDirEntry struct {
BWidth byte
BHeight byte
BColorCount byte
BReserved byte
WPlanes uint16
WBitCount uint16
DWBytesInRes uint32
NID uint16
}
type grpIconDiskDirEntry struct {
BWidth byte
BHeight byte
BColorCount byte
BReserved byte
WPlanes uint16
WBitCount uint16
DWBytesinRes uint32
DWImageOffset uint32
}
// http://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/2003_w/misc/bmp_file_format/bmp_file_format.htm
type bitmapFileHeader struct {
Signature [2]byte
FileSize uint32
Reserved uint32
DataOffset uint32
Size uint32
Width uint32
Height uint32
Planes uint16
BitsPerPixel uint16
Compression uint32
ImageSize uint32
XpixelsPerM uint32
YpixelsPerM uint32
ColorsUsed uint32
ImportantColors uint32
}
// FromPid returns the app icon of the app currently running
// on the given pid.
func FromPid(pid uint32) (image.Image, error) {
// get path from pid
exePath, err := winapi.QueryFullProcessImageNameW(pid, 0)
if err != nil {
return nil, err
}
return FromPath(exePath)
}
// FromPath returns the app icon app at the specified path.
func FromPath(exePath string) (image.Image, error) {
// get handle
exeHandle, err := winapi.LoadLibraryEx(
exePath,
winapi.LoadLibraryAsImageResource|winapi.LoadLibraryAsDatafile,
)
if err != nil {
return nil, err
}
defer windows.CloseHandle(exeHandle)
var img image.Image
var innerErr error
// enum rt_group_icons and grab the first one
err = winapi.EnumResourceNamesA(
exeHandle,
winapi.MakeIntResource(winapi.RtGroupIcon),
func(hModule windows.Handle, lpType, lpName, lParam uintptr) uintptr {
resPtr, _, err := getResource(hModule, lpType, lpName)
if err != nil {
innerErr = err
return uintptr(0)
}
if resPtr == 0 {
innerErr = fmt.Errorf("error no resource found")
return uintptr(0)
}
iconHeader := *(*grpIconDirHeader)(unsafe.Pointer(resPtr))
// find best icon
entriesPtr := resPtr + 6
bestEntry := *(*grpIconResourceDirEntry)(unsafe.Pointer(entriesPtr))
for i := 0; i < int(iconHeader.IDCount); i++ {
entry := *(*grpIconResourceDirEntry)(unsafe.Pointer(entriesPtr + uintptr(i*14)))
eW := entry.BWidth
eH := entry.BHeight
bW := bestEntry.BWidth
bH := bestEntry.BHeight
// find best icon
if (eW == 0 || (eW > bW && bW != 0)) && // 0 is actually 256 b/c its stored in a byte
(eH == 0 || (eH > bH && bH != 0)) {
bestEntry = entry
}
}
// get best image
bestPtr, bestLen, err := getResource(hModule, winapi.RtIcon, uintptr(bestEntry.NID))
if err != nil {
innerErr = err
return uintptr(0)
}
if bestLen == 0 {
innerErr = fmt.Errorf("best image had was zero bytes")
return uintptr(0)
}
tmpData := cToGoSlice(unsafe.Pointer(bestPtr), int(bestLen))
// TODO figure out a better way to copy this
imgData := make([]byte, bestLen)
for i, b := range tmpData {
imgData[i] = b
}
if isPNG(imgData) {
img, innerErr = png.Decode(bytes.NewBuffer(imgData))
return uintptr(0)
}
// if not png then it must be a bitmap
// ico just contains the raw bitmap data so
// to encode it with the standard library we must
// create a file header
// cut the InfoHeader from imgData and build
// our own b/c for some reason its not working
// TODO figure this out
if len(imgData) < 40 {
innerErr = fmt.Errorf("invalid bitmap data")
return uintptr(0)
}
imgData = imgData[40:]
var dataOffset uint32
if bestEntry.BColorCount == 0 && bestEntry.WBitCount <= 8 {
dataOffset = 14 + 40 + 4*(1<<bestEntry.WBitCount)
} else {
dataOffset = 14 + 40 + 4*uint32(bestEntry.BColorCount)
}
bmpHeader := &bitmapFileHeader{
Signature: [2]byte{'B', 'M'},
FileSize: 14 + 40 + uint32(len(imgData)),
DataOffset: dataOffset,
Size: 40,
Width: uint32(bestEntry.BWidth),
Height: uint32(bestEntry.BHeight),
Planes: bestEntry.WPlanes,
BitsPerPixel: bestEntry.WBitCount,
ColorsUsed: uint32(bestEntry.BColorCount),
}
headerBytes, err := encodeToBytes(bmpHeader)
if err != nil {
innerErr = err
return uintptr(0)
}
imgData = append(headerBytes, imgData...)
// TODO the builtin bmp package doesn't support
// 1 = monochrome palette or
// 4 = 4bit palletized
img, innerErr = bmp.Decode(bytes.NewBuffer(imgData))
// if innerErr != nil {
// fmt.Println(innerErr)
// }
return uintptr(0)
},
nil,
)
// error out if non user enum error
if err != nil && err != windows.ERROR_RESOURCE_ENUM_USER_STOP {
return nil, err
}
return img, innerErr
}
// https://devblogs.microsoft.com/oldnewthing/20120720-00/?p=7083 algo at the end of the article
func getResource(hModule windows.Handle, lpType, lpName uintptr) (resPtr uintptr, resLen uint32, err error) {
rInfo, err := winapi.FindResourceA(hModule, lpName, lpType)
if err != nil {
return 0, 0, err
}
if rInfo == 0 {
return 0, 0, fmt.Errorf("no resource found")
}
resSize, err := winapi.SizeofResource(hModule, rInfo)
if err != nil {
return 0, 0, err
}
if resSize == 0 {
return 0, 0, fmt.Errorf("zero size resource")
}
res, err := winapi.LoadResource(hModule, rInfo)
if err != nil {
return 0, 0, err
}
if res == 0 {
return 0, 0, fmt.Errorf("couldn't load resource")
}
lockedRes, err := winapi.LockResource(res)
if err != nil {
return 0, 0, err
}
if res == 0 {
return 0, 0, fmt.Errorf("couldn't lock resource")
}
return uintptr(lockedRes), resSize, nil
}
func isPNG(b []byte) bool {
return len(b) >= 8 && string(b[0:8]) == pngHeader
}
func encodeToBytes(i interface{}) ([]byte, error) {
buf := bytes.Buffer{}
err := binary.Write(&buf, binary.LittleEndian, i)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}