/
winuser_windows.go
112 lines (103 loc) · 3.22 KB
/
winuser_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
package winuser
import (
"bytes"
"encoding/binary"
"fmt"
"reflect"
"strings"
"unsafe"
"github.com/christowolf/usb-event/internal/message"
"github.com/christowolf/usb-event/internal/types"
"github.com/lxn/win"
"golang.org/x/sys/windows"
)
const (
Arrival = EventType(message.DBT_DEVICEARRIVAL)
)
type deviceInfo struct {
size types.DWORD
deviceType types.DWORD
reserved types.DWORD
classGuid windows.GUID
}
type EventType int
type EventInfo struct {
DeviceType types.DWORD
Guid windows.GUID
DeviceName string
EventType EventType
}
type Notifier struct {
Channel chan EventInfo
}
// WndProc realizes the WNDPROC callback function,
// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc.
// For now, this only supports DBT_DEVICEARRIVAL.
func (n *Notifier) WndProc(hwnd windows.HWND, msg types.DWORD, wParam, lParam uintptr) uintptr {
switch msg {
case message.WM_DEVICECHANGE:
switch wParam {
case uintptr(message.DBT_DEVICEARRIVAL):
dType, guid, name, err := readDeviceInfo(lParam)
if err != nil {
name = fmt.Sprintf("error: failed to read device information: %s", err)
}
n.Channel <- EventInfo{dType, guid, name, Arrival}
}
// TODO: https://gist.github.com/nathan-osman/18c2e227ad00a223b61c0b3c16d452c3
}
return win.DefWindowProc(win.HWND(hwnd), uint32(msg), wParam, lParam)
}
// readDeviceInfo parses binary data into a readable form.
// Based on https://github.com/unreality/nCryptAgent/blob/eecebcab1e366420f6479090b5cfa803f3979f57/deviceevents/events.go#L63.
func readDeviceInfo(pDevInfo uintptr) (dtype types.DWORD, guid windows.GUID, name string, err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
var devInfo deviceInfo
var devInfoBytes []byte
// Do some pointer arithmetic to align the struct.
b := (*reflect.SliceHeader)(unsafe.Pointer(&devInfoBytes))
b.Data = pDevInfo
b.Len = int(uint32(unsafe.Sizeof(devInfo)))
b.Cap = b.Len
// Read the binary data.
r := bytes.NewReader(devInfoBytes)
// Windows is little endian.
err = binary.Read(r, binary.LittleEndian, &devInfo.size)
if err != nil {
return types.DWORD(0), windows.GUID{}, "", err
}
err = binary.Read(r, binary.LittleEndian, &devInfo.deviceType)
if err != nil {
return types.DWORD(0), windows.GUID{}, "", err
}
err = binary.Read(r, binary.LittleEndian, &devInfo.reserved)
if err != nil {
return types.DWORD(0), windows.GUID{}, "", err
}
err = binary.Read(r, binary.LittleEndian, &devInfo.classGuid)
if err != nil {
return types.DWORD(0), windows.GUID{}, "", err
}
// Read the device name.
// Based on (6) in https://pkg.go.dev/unsafe#Pointer.
var devName string
s := (*reflect.StringHeader)(unsafe.Pointer(&devName))
s.Data = pDevInfo + unsafe.Sizeof(devInfo)
read := uint32(devInfo.size)
size := uint32(unsafe.Sizeof(devInfo))
if read < size {
err = fmt.Errorf("read %d bytes, expected at least %d", read, size)
return types.DWORD(0), windows.GUID{}, "", err
}
s.Len = int(read - size)
name = strings.ReplaceAll(devName, "\x00", "")
name = strings.Replace(name, `\\?\`, "", 1)
name = strings.Replace(name, "#", `\`, 2)
name = strings.Split(name, "#")[0]
// Return all relevant data.
return devInfo.deviceType, devInfo.classGuid, name, nil
}