Skip to content

Commit e4935e6

Browse files
authored
Merge pull request #1 from Sunbreak/develop
Add LogicConf operations & Impl on Windows
2 parents 832f176 + 81d2ae3 commit e4935e6

13 files changed

+1212
-10
lines changed

bin/k380_conf.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:logic_conf/logic_conf.dart';
4+
5+
const VENDOR_LOGIC = 0x046D;
6+
7+
const PRODUCT_K380 = 0xB342;
8+
9+
const USAGEPAGE_CUSTOM = 0xFF00;
10+
11+
final k380_seq_fkeys_on =
12+
Uint8List.fromList([0x10, 0xff, 0x0b, 0x1e, 0x00, 0x00, 0x00]);
13+
final k380_seq_fkeys_off =
14+
Uint8List.fromList([0x10, 0xff, 0x0b, 0x1e, 0x01, 0x00, 0x00]);
15+
16+
void main(List<String> arguments) {
17+
if (arguments.length != 1 || (arguments[0] != 'on' && arguments[0] != 'off')) {
18+
print('Choose "on" or "off" please');
19+
return;
20+
}
21+
22+
var logicDevices = LogicConf.listDevices()
23+
.where((e) => e['vendorId'] == VENDOR_LOGIC)
24+
.toList();
25+
if (logicDevices.isEmpty) {
26+
print('Logic HID device not found');
27+
return;
28+
}
29+
30+
var k380 = logicDevices.cast<dynamic?>().firstWhere((e) {
31+
return e['productId'] == PRODUCT_K380 &&
32+
e['usagePage'] == USAGEPAGE_CUSTOM &&
33+
e['usage'] == 1;
34+
}, orElse: () => null);
35+
if (k380 == null) {
36+
print('K380 not found');
37+
return;
38+
}
39+
40+
if (!LogicConf.openDevice(k380['path'])) {
41+
print('K380 access denied');
42+
return;
43+
}
44+
45+
var data = arguments[0] == 'on' ? k380_seq_fkeys_on : k380_seq_fkeys_off;
46+
var sendData = LogicConf.sendData(data);
47+
if (sendData != data.length) {
48+
print('K380 config error');
49+
} else {
50+
print('K380 config success');
51+
}
52+
53+
LogicConf.closeDevice();
54+
}

bin/logic_conf.dart

Lines changed: 0 additions & 3 deletions
This file was deleted.

lib/logic_conf.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'dart:io';
2+
import 'dart:typed_data';
3+
4+
import 'src/logic_conf_interface.dart';
5+
import 'src/logic_conf_windows.dart';
6+
7+
bool _manualDartRegistrationNeeded = true;
8+
9+
LogicConfPlatform get _platform {
10+
// This is to manually endorse Dart implementations until automatic
11+
// registration of Dart plugins is implemented. For details see
12+
// https://github.com/flutter/flutter/issues/52267.
13+
if (_manualDartRegistrationNeeded) {
14+
// Only do the initial registration if it hasn't already been overridden
15+
// with a non-default instance.
16+
if (Platform.isWindows) {
17+
LogicConfPlatform.instance = LogicConfWindows();
18+
}
19+
_manualDartRegistrationNeeded = false;
20+
}
21+
22+
return LogicConfPlatform.instance!;
23+
}
24+
25+
class LogicConf {
26+
static List<dynamic> listDevices() => _platform.listDevices();
27+
28+
static bool openDevice(String path) => _platform.openDevice(path);
29+
30+
static void closeDevice() => _platform.closeDevice();
31+
32+
static int sendData(Uint8List data) => _platform.sendData(data);
33+
}

lib/src/logic_conf_interface.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import 'dart:typed_data';
2+
3+
abstract class LogicConfPlatform {
4+
static LogicConfPlatform? _instance;
5+
6+
static LogicConfPlatform? get instance => _instance;
7+
8+
static set instance(LogicConfPlatform? instance) {
9+
_instance = instance;
10+
}
11+
12+
List<dynamic> listDevices();
13+
14+
bool openDevice(String path);
15+
16+
void closeDevice();
17+
18+
int sendData(Uint8List data);
19+
}

lib/src/logic_conf_windows.dart

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import 'dart:convert';
2+
import 'dart:ffi';
3+
import 'dart:typed_data';
4+
5+
import 'package:ffi/ffi.dart';
6+
import 'package:win32/win32.dart';
7+
8+
import 'logic_conf_interface.dart';
9+
import 'windows/hidsdi.dart' as hid;
10+
import 'windows/setupapi.dart' as sp;
11+
12+
class LogicConfWindows extends LogicConfPlatform {
13+
var _devHandle = INVALID_HANDLE_VALUE;
14+
15+
final _setupapi = sp.SetupAPI(DynamicLibrary.open('setupapi.dll'));
16+
17+
final _hidSdi = hid.HidSdi(DynamicLibrary.open('hid.dll'));
18+
19+
@override
20+
List<dynamic> listDevices() {
21+
var hidInterfaceClassGuid = calloc<GUID>()
22+
..ref.setGUID('{4D1E55B2-F16F-11CF-88CB-001111000030}');
23+
24+
var deviceInfoSetPtr = _setupapi.SetupDiGetClassDevsW(
25+
hidInterfaceClassGuid.cast(),
26+
nullptr,
27+
nullptr,
28+
sp.DIGCF_PRESENT | sp.DIGCF_DEVICEINTERFACE,
29+
);
30+
31+
var deviceList = _iterateDevice(deviceInfoSetPtr, hidInterfaceClassGuid).toList();
32+
33+
_setupapi.SetupDiDestroyDeviceInfoList(deviceInfoSetPtr);
34+
calloc.free(hidInterfaceClassGuid);
35+
return deviceList;
36+
}
37+
38+
Iterable<Map<String, dynamic>> _iterateDevice(Pointer<Void> deviceInfoSetPtr, Pointer<GUID> hidInterfaceClassGuid) sync* {
39+
var requiredSizePtr = calloc<Uint32>();
40+
var devicInterfaceDataPtr = calloc<sp.SP_DEVICE_INTERFACE_DATA>();
41+
devicInterfaceDataPtr.ref.cbSize = sizeOf<sp.SP_DEVICE_INTERFACE_DATA>();
42+
43+
for (var index = 0; _setupapi.SetupDiEnumDeviceInterfaces(deviceInfoSetPtr, nullptr, hidInterfaceClassGuid.cast(), index, devicInterfaceDataPtr) == TRUE; index++) {
44+
// Get requiredSize
45+
_setupapi.SetupDiGetDeviceInterfaceDetailW(deviceInfoSetPtr, devicInterfaceDataPtr, nullptr, 0, requiredSizePtr, nullptr);
46+
47+
var detailDataMemoryPtr = calloc<Uint16>(requiredSizePtr.value);
48+
var devHandle = INVALID_HANDLE_VALUE;
49+
50+
try {
51+
var deviceInterfaceDetailDataPtr = Pointer<sp.SP_DEVICE_INTERFACE_DETAIL_DATA_W>.fromAddress(detailDataMemoryPtr.address);
52+
deviceInterfaceDetailDataPtr.ref.cbSize = sizeOf<sp.SP_DEVICE_INTERFACE_DETAIL_DATA_W>();
53+
54+
var getDeviceInterfaceDetail = _setupapi.SetupDiGetDeviceInterfaceDetailW(deviceInfoSetPtr, devicInterfaceDataPtr, deviceInterfaceDetailDataPtr, requiredSizePtr.value, nullptr, nullptr);
55+
if (getDeviceInterfaceDetail != TRUE) {
56+
print('SetupDiGetDeviceInterfaceDetailW error ${GetLastError()}');
57+
continue;
58+
}
59+
// FIXME Utf16.decode
60+
var devicePath = utf8.decode(deviceInterfaceDetailDataPtr.getDevicePathData(requiredSizePtr.value));
61+
62+
devHandle = CreateFile(devicePath.toNativeUtf16(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, NULL);
63+
if (devHandle == INVALID_HANDLE_VALUE) {
64+
print('CreateFile error ${GetLastError()}');
65+
continue;
66+
}
67+
var devHandlePtr = Pointer<Void>.fromAddress(devHandle);
68+
69+
yield {
70+
'path': devicePath,
71+
..._getAttributes(devHandlePtr),
72+
..._getPreparsedData(devHandlePtr),
73+
};
74+
} finally {
75+
calloc.free(detailDataMemoryPtr);
76+
if (devHandle != INVALID_HANDLE_VALUE) {
77+
CloseHandle(devHandle);
78+
}
79+
}
80+
}
81+
82+
calloc.free(requiredSizePtr);
83+
calloc.free(devicInterfaceDataPtr);
84+
}
85+
86+
Map<String, dynamic> _getAttributes(Pointer<Void> devHandlePtr) {
87+
var res = <String, dynamic>{};
88+
89+
var attributesPtr = calloc<hid.HIDD_ATTRIBUTES>();
90+
if (_hidSdi.HidD_GetAttributes(devHandlePtr, attributesPtr) == TRUE) {
91+
res = {
92+
'vendorId': attributesPtr.ref.VendorID,
93+
'productId': attributesPtr.ref.ProductID,
94+
};
95+
} else {
96+
print('HidD_GetAttributes error ${GetLastError()}');
97+
}
98+
calloc.free(attributesPtr);
99+
100+
return res;
101+
}
102+
103+
Map<String, dynamic> _getPreparsedData(Pointer<Void> devHandlePtr) {
104+
var res = <String, dynamic>{};
105+
106+
var preparsedDataRefPtr = calloc<Pointer<hid.HIDP_PREPARSED_DATA>>();
107+
if (_hidSdi.HidD_GetPreparsedData(devHandlePtr, preparsedDataRefPtr) == TRUE) {
108+
var capsPtr = calloc<hid.HIDP_CAPS>();
109+
var getCaps = _hidSdi.HidP_GetCaps(preparsedDataRefPtr.value, capsPtr);
110+
if (getCaps == hid.HIDP_STATUS_SUCCESS) {
111+
res = {
112+
'usagePage': capsPtr.ref.UsagePage,
113+
'usage': capsPtr.ref.Usage,
114+
};
115+
} else {
116+
print('HidP_GetCaps error $getCaps');
117+
}
118+
calloc.free(capsPtr);
119+
_hidSdi.HidD_FreePreparsedData(preparsedDataRefPtr.value);
120+
}
121+
calloc.free(preparsedDataRefPtr);
122+
123+
return res;
124+
}
125+
126+
@override
127+
bool openDevice(String path) {
128+
_devHandle = CreateFile(
129+
path.toNativeUtf16(),
130+
GENERIC_READ | GENERIC_WRITE,
131+
FILE_SHARE_READ | FILE_SHARE_WRITE,
132+
nullptr,
133+
OPEN_EXISTING,
134+
0,
135+
NULL,
136+
);
137+
return _devHandle != INVALID_HANDLE_VALUE;
138+
}
139+
140+
@override
141+
void closeDevice() {
142+
if (_devHandle != INVALID_HANDLE_VALUE) {
143+
CloseHandle(_devHandle);
144+
}
145+
}
146+
147+
@override
148+
int sendData(Uint8List data) {
149+
var dataPtr = calloc<Uint8>(data.length);
150+
dataPtr.asTypedList(data.length).setAll(0, data);
151+
var writtenLengthPtr = calloc<Uint32>();
152+
try {
153+
var writeFile = WriteFile(_devHandle, dataPtr, data.length, writtenLengthPtr, nullptr);
154+
if (writeFile != TRUE) {
155+
return -1;
156+
}
157+
return writtenLengthPtr.value;
158+
} finally {
159+
calloc.free(dataPtr);
160+
calloc.free(writtenLengthPtr);
161+
}
162+
}
163+
}
164+
165+
extension Pointer_SP_DEVICE_INTERFACE_DETAIL_DATA_W on Pointer<sp.SP_DEVICE_INTERFACE_DETAIL_DATA_W> {
166+
Uint16List getDevicePathData(int requiredSize) => Pointer<Uint16>.fromAddress(address).asTypedList(requiredSize).sublist(2);
167+
}

0 commit comments

Comments
 (0)