plugins/internal/discovery/device_discovery.go (321 lines of code) (raw):

//go:build windows package discovery import ( "errors" "fmt" "unsafe" "golang.org/x/sys/windows" ) var ( discoverydll = windows.NewLazyDLL("directx-device-discovery.dll") procGetDiscoveryLibraryVersion = discoverydll.NewProc("GetDiscoveryLibraryVersion") procDisableDiscoveryLogging = discoverydll.NewProc("DisableDiscoveryLogging") procEnableDiscoveryLogging = discoverydll.NewProc("EnableDiscoveryLogging") procCreateDeviceDiscoveryInstance = discoverydll.NewProc("CreateDeviceDiscoveryInstance") procDestroyDeviceDiscoveryInstance = discoverydll.NewProc("DestroyDeviceDiscoveryInstance") procGetLastErrorMessage = discoverydll.NewProc("DeviceDiscovery_GetLastErrorMessage") procIsRefreshRequired = discoverydll.NewProc("DeviceDiscovery_IsRefreshRequired") procDiscoverDevices = discoverydll.NewProc("DeviceDiscovery_DiscoverDevices") procGetNumDevices = discoverydll.NewProc("DeviceDiscovery_GetNumDevices") procGetDeviceAdapterLUID = discoverydll.NewProc("DeviceDiscovery_GetDeviceAdapterLUID") procGetDeviceID = discoverydll.NewProc("DeviceDiscovery_GetDeviceID") procGetDeviceDescription = discoverydll.NewProc("DeviceDiscovery_GetDeviceDescription") procGetDeviceDriverRegistryKey = discoverydll.NewProc("DeviceDiscovery_GetDeviceDriverRegistryKey") procGetDeviceDriverStorePath = discoverydll.NewProc("DeviceDiscovery_GetDeviceDriverStorePath") procGetDeviceLocationPath = discoverydll.NewProc("DeviceDiscovery_GetDeviceLocationPath") procGetDeviceVendor = discoverydll.NewProc("DeviceDiscovery_GetDeviceVendor") procGetNumRuntimeFiles = discoverydll.NewProc("DeviceDiscovery_GetNumRuntimeFiles") procGetRuntimeFileSource = discoverydll.NewProc("DeviceDiscovery_GetRuntimeFileSource") procGetRuntimeFileDestination = discoverydll.NewProc("DeviceDiscovery_GetRuntimeFileDestination") procGetNumRuntimeFilesWow64 = discoverydll.NewProc("DeviceDiscovery_GetNumRuntimeFilesWow64") procGetRuntimeFileSourceWow64 = discoverydll.NewProc("DeviceDiscovery_GetRuntimeFileSourceWow64") procGetRuntimeFileDestinationWow64 = discoverydll.NewProc("DeviceDiscovery_GetRuntimeFileDestinationWow64") procIsDeviceIntegrated = discoverydll.NewProc("DeviceDiscovery_IsDeviceIntegrated") procIsDeviceDetachable = discoverydll.NewProc("DeviceDiscovery_IsDeviceDetachable") procDoesDeviceSupportDisplay = discoverydll.NewProc("DeviceDiscovery_DoesDeviceSupportDisplay") procDoesDeviceSupportCompute = discoverydll.NewProc("DeviceDiscovery_DoesDeviceSupportCompute") ) type DeviceDiscovery struct { // The handle to the underlying DeviceDiscovery object handle uintptr // The list of discovered devices Devices []*Device } // Attempts to load the DirectX device discovery library and returns an error if loading fails // (This is useful for catching failures gracefully rather than triggering a panic upon lazy-load) func LoadDiscoveryLibrary() error { if err := discoverydll.Load(); err != nil { return fmt.Errorf("failed to load %s: %s", discoverydll.Name, err.Error()) } return nil } // Wrapper function for GetDiscoveryLibraryVersion func GetDiscoveryLibraryVersion() string { result, _, _ := procGetDiscoveryLibraryVersion.Call() return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(result))) } // Wrapper function for DisableDiscoveryLogging func DisableDiscoveryLogging() { procDisableDiscoveryLogging.Call() } // Wrapper function for EnableDiscoveryLogging func EnableDiscoveryLogging() { procEnableDiscoveryLogging.Call() } func NewDeviceDiscovery() (*DeviceDiscovery, error) { // Attempt to create a DeviceDiscovery instance result, _, _ := procCreateDeviceDiscoveryInstance.Call() if result == 0 { return nil, errors.New("failed to create the DeviceDiscovery instance") } return &DeviceDiscovery{ handle: result, Devices: []*Device{}, }, nil } func (d *DeviceDiscovery) Destroy() { procDestroyDeviceDiscoveryInstance.Call(d.handle) } // Formats a boolean argument for passing to a library function func (d *DeviceDiscovery) booleanArgument(arg bool) uintptr { if arg { return 1 } else { return 0 } } // Handles the result of a library function that returns a boolean func (d *DeviceDiscovery) handleBooleanResult(result uintptr, r2 uintptr, lastError error) (bool, error) { if int32(result) == -1 { return false, d.getLastErrorMessage() } return result == 1, nil } // Handles the result of a library function that returns an unsigned 32-bit integer func (d *DeviceDiscovery) handleUint32Result(result uintptr, r2 uintptr, lastError error) (uint32, error) { if int32(result) == -1 { return 0, d.getLastErrorMessage() } return uint32(result), nil } // Handles the result of a library function that returns a signed 64-bit integer func (d *DeviceDiscovery) handleInt64Result(result uintptr, r2 uintptr, lastError error) (int64, error) { if int64(result) == -1 { return 0, d.getLastErrorMessage() } return int64(result), nil } // Handles the result of a library function that returns a UTF-16 string func (d *DeviceDiscovery) handleStringResult(result uintptr, r2 uintptr, lastError error) (string, error) { if result == 0 { return "", d.getLastErrorMessage() } return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(result))), nil } // Retrieves the error message for the last library function call func (d *DeviceDiscovery) getLastErrorMessage() error { // Retrieve the last error message from the library and convert it to a Go error result, _, _ := procGetLastErrorMessage.Call(d.handle) errorMessage := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(result))) if errorMessage != "" { return errors.New(errorMessage) } return nil } // Performs device discovery and populates our list of devices func (d *DeviceDiscovery) DiscoverDevices(filter DeviceFilter, includeIntegrated bool, includeDetachable bool) error { // Attempt to perform device discovery result, _, _ := procDiscoverDevices.Call(d.handle, uintptr(filter), d.booleanArgument(includeIntegrated), d.booleanArgument(includeDetachable)) if int32(result) == -1 { return d.getLastErrorMessage() } // Determine the number of discovered devices numDevices, err := d.getNumDevices() if err != nil { return err } // Clear the existing list of devices d.Devices = []*Device{} // Retrieve the details of each device in turn for index := 0; index < int(numDevices); index += 1 { // Attempt to retrieve the device details device, err := d.getDevice(index) if err != nil { return err } // Add the device to our list d.Devices = append(d.Devices, device) } return nil } // Retrieves the details for an individual device func (d *DeviceDiscovery) getDevice(device int) (*Device, error) { // Attempt to retrieve the device ID id, err := d.getDeviceID(device) if err != nil { return nil, err } // Attempt to retrieve the device description description, err := d.getDeviceDescription(device) if err != nil { return nil, err } // Attempt to retrieve the device driver registry key registry, err := d.getDeviceDriverRegistryKey(device) if err != nil { return nil, err } // Attempt to retrieve the device driver store path driverStore, err := d.getDeviceDriverStorePath(device) if err != nil { return nil, err } // Attempt to retrieve the device location path location, err := d.getDeviceLocationPath(device) if err != nil { return nil, err } // Attempt to retrieve the device vendor vendor, err := d.getDeviceVendor(device) if err != nil { return nil, err } // Attempt to retrieve the device adapter LUID luid, err := d.getDeviceAdapterLUID(device) if err != nil { return nil, err } // Attempt to retrieve the integrated device hardware flag integrated, err := d.isDeviceIntegrated(device) if err != nil { return nil, err } // Attempt to retrieve the detachable device hardware flag detachable, err := d.isDeviceDetachable(device) if err != nil { return nil, err } // Attempt to retrieve the display support flag display, err := d.doesDeviceSupportDisplay(device) if err != nil { return nil, err } // Attempt to retrieve the compute support flag compute, err := d.doesDeviceSupportCompute(device) if err != nil { return nil, err } // Attempt to retrieve the number of additional runtime files for System32 numRuntimeFiles, err := d.getNumRuntimeFiles(device) if err != nil { return nil, err } // Attempt to retrieve the list of additional runtime files for System32 runtimeFiles := []*RuntimeFile{} for file := 0; file < int(numRuntimeFiles); file += 1 { // Attempt to retrieve the source path for the file sourcePath, err := d.getRuntimeFileSource(device, file) if err != nil { return nil, err } // Attempt to retrieve the destination filename for the file destinationFilename, err := d.getRuntimeFileDestination(device, file) if err != nil { return nil, err } // Add the file details to the list runtimeFiles = append(runtimeFiles, &RuntimeFile{ SourcePath: sourcePath, DestinationFilename: destinationFilename, }) } // Attempt to retrieve the number of additional runtime files for SysWOW64 numRuntimeFilesWow64, err := d.getNumRuntimeFilesWow64(device) if err != nil { return nil, err } // Attempt to retrieve the list of additional runtime files for System32 runtimeFilesWow64 := []*RuntimeFile{} for file := 0; file < int(numRuntimeFilesWow64); file += 1 { // Attempt to retrieve the source path for the file sourcePath, err := d.getRuntimeFileSourceWow64(device, file) if err != nil { return nil, err } // Attempt to retrieve the destination filename for the file destinationFilename, err := d.getRuntimeFileDestinationWow64(device, file) if err != nil { return nil, err } // Add the file details to the list runtimeFilesWow64 = append(runtimeFilesWow64, &RuntimeFile{ SourcePath: sourcePath, DestinationFilename: destinationFilename, }) } // Construct a Device object from the retrieved data return &Device{ ID: id, Description: description, DriverRegistryKey: registry, DriverStorePath: driverStore, LocationPath: location, RuntimeFiles: runtimeFiles, RuntimeFilesWow64: runtimeFilesWow64, Vendor: vendor, AdapterLUID: luid, IsIntegrated: integrated, IsDetachable: detachable, SupportsDisplay: display, SupportsCompute: compute, }, nil } // Wrapper function for DeviceDiscovery_IsRefreshRequired func (d *DeviceDiscovery) IsRefreshRequired() (bool, error) { return d.handleBooleanResult( procIsRefreshRequired.Call(d.handle), ) } // Wrapper function for DeviceDiscovery_GetNumDevices func (d *DeviceDiscovery) getNumDevices() (uint32, error) { return d.handleUint32Result( procGetNumDevices.Call(d.handle), ) } // Wrapper function for DeviceDiscovery_GetDeviceAdapterLUID func (d *DeviceDiscovery) getDeviceAdapterLUID(device int) (int64, error) { return d.handleInt64Result( procGetDeviceAdapterLUID.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetDeviceID func (d *DeviceDiscovery) getDeviceID(device int) (string, error) { return d.handleStringResult( procGetDeviceID.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetDeviceDescription func (d *DeviceDiscovery) getDeviceDescription(device int) (string, error) { return d.handleStringResult( procGetDeviceDescription.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetDeviceDriverRegistryKey func (d *DeviceDiscovery) getDeviceDriverRegistryKey(device int) (string, error) { return d.handleStringResult( procGetDeviceDriverRegistryKey.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetDeviceDriverStorePath func (d *DeviceDiscovery) getDeviceDriverStorePath(device int) (string, error) { return d.handleStringResult( procGetDeviceDriverStorePath.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetDeviceLocationPath func (d *DeviceDiscovery) getDeviceLocationPath(device int) (string, error) { return d.handleStringResult( procGetDeviceLocationPath.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetDeviceVendor func (d *DeviceDiscovery) getDeviceVendor(device int) (string, error) { return d.handleStringResult( procGetDeviceVendor.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetNumRuntimeFiles func (d *DeviceDiscovery) getNumRuntimeFiles(device int) (uint32, error) { return d.handleUint32Result( procGetNumRuntimeFiles.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetRuntimeFileSource func (d *DeviceDiscovery) getRuntimeFileSource(device int, file int) (string, error) { return d.handleStringResult( procGetRuntimeFileSource.Call(d.handle, uintptr(device), uintptr(file)), ) } // Wrapper function for DeviceDiscovery_GetRuntimeFileDestination func (d *DeviceDiscovery) getRuntimeFileDestination(device int, file int) (string, error) { return d.handleStringResult( procGetRuntimeFileDestination.Call(d.handle, uintptr(device), uintptr(file)), ) } // Wrapper function for DeviceDiscovery_GetNumRuntimeFilesWow64 func (d *DeviceDiscovery) getNumRuntimeFilesWow64(device int) (uint32, error) { return d.handleUint32Result( procGetNumRuntimeFilesWow64.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_GetRuntimeFileSourceWow64 func (d *DeviceDiscovery) getRuntimeFileSourceWow64(device int, file int) (string, error) { return d.handleStringResult( procGetRuntimeFileSourceWow64.Call(d.handle, uintptr(device), uintptr(file)), ) } // Wrapper function for DeviceDiscovery_GetRuntimeFileDestinationWow64 func (d *DeviceDiscovery) getRuntimeFileDestinationWow64(device int, file int) (string, error) { return d.handleStringResult( procGetRuntimeFileDestinationWow64.Call(d.handle, uintptr(device), uintptr(file)), ) } // Wrapper function for DeviceDiscovery_IsDeviceIntegrated func (d *DeviceDiscovery) isDeviceIntegrated(device int) (bool, error) { return d.handleBooleanResult( procIsDeviceIntegrated.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_IsDeviceDetachable func (d *DeviceDiscovery) isDeviceDetachable(device int) (bool, error) { return d.handleBooleanResult( procIsDeviceDetachable.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_DoesDeviceSupportDisplay func (d *DeviceDiscovery) doesDeviceSupportDisplay(device int) (bool, error) { return d.handleBooleanResult( procDoesDeviceSupportDisplay.Call(d.handle, uintptr(device)), ) } // Wrapper function for DeviceDiscovery_DoesDeviceSupportCompute func (d *DeviceDiscovery) doesDeviceSupportCompute(device int) (bool, error) { return d.handleBooleanResult( procDoesDeviceSupportCompute.Call(d.handle, uintptr(device)), ) }