1 /*******************************************************
2 Demo Program for HIDAPI
3
4 Alan Ott
5 Signal 11 Software
6
7 2010-07-20
8
9 Copyright 2010, All Rights Reserved
10
11 This contents of this file may be used by anyone
12 for any reason without any conditions and may be
13 used as a starting point for your own applications
14 which use HIDAPI.
15 ********************************************************/
16
17
18 #include <fx.h>
19
20 #include "hidapi.h"
21 #include "mac_support.h"
22 #include <string.h>
23 #include <stdlib.h>
24 #include <limits.h>
25
26 #ifdef _WIN32
27 // Thanks Microsoft, but I know how to use strncpy().
28 #pragma warning(disable:4996)
29 #endif
30
31 class MainWindow : public FXMainWindow {
32 FXDECLARE(MainWindow)
33
34 public:
35 enum {
36 ID_FIRST = FXMainWindow::ID_LAST,
37 ID_CONNECT,
38 ID_DISCONNECT,
39 ID_RESCAN,
40 ID_SEND_OUTPUT_REPORT,
41 ID_SEND_FEATURE_REPORT,
42 ID_GET_FEATURE_REPORT,
43 ID_CLEAR,
44 ID_TIMER,
45 ID_MAC_TIMER,
46 ID_LAST,
47 };
48
49 private:
50 FXList *device_list;
51 FXButton *connect_button;
52 FXButton *disconnect_button;
53 FXButton *rescan_button;
54 FXButton *output_button;
55 FXLabel *connected_label;
56 FXTextField *output_text;
57 FXTextField *output_len;
58 FXButton *feature_button;
59 FXButton *get_feature_button;
60 FXTextField *feature_text;
61 FXTextField *feature_len;
62 FXTextField *get_feature_text;
63 FXText *input_text;
64 FXFont *title_font;
65
66 struct hid_device_info *devices;
67 hid_device *connected_device;
68 size_t getDataFromTextField(FXTextField *tf, char *buf, size_t len);
69 int getLengthFromTextField(FXTextField *tf);
70
71
72 protected:
MainWindow()73 MainWindow() {};
74 public:
75 MainWindow(FXApp *a);
76 ~MainWindow();
77 virtual void create();
78
79 long onConnect(FXObject *sender, FXSelector sel, void *ptr);
80 long onDisconnect(FXObject *sender, FXSelector sel, void *ptr);
81 long onRescan(FXObject *sender, FXSelector sel, void *ptr);
82 long onSendOutputReport(FXObject *sender, FXSelector sel, void *ptr);
83 long onSendFeatureReport(FXObject *sender, FXSelector sel, void *ptr);
84 long onGetFeatureReport(FXObject *sender, FXSelector sel, void *ptr);
85 long onClear(FXObject *sender, FXSelector sel, void *ptr);
86 long onTimeout(FXObject *sender, FXSelector sel, void *ptr);
87 long onMacTimeout(FXObject *sender, FXSelector sel, void *ptr);
88 };
89
90 // FOX 1.7 changes the timeouts to all be nanoseconds.
91 // Fox 1.6 had all timeouts as milliseconds.
92 #if (FOX_MINOR >= 7)
93 const int timeout_scalar = 1000*1000;
94 #else
95 const int timeout_scalar = 1;
96 #endif
97
98 FXMainWindow *g_main_window;
99
100
101 FXDEFMAP(MainWindow) MainWindowMap [] = {
102 FXMAPFUNC(SEL_COMMAND, MainWindow::ID_CONNECT, MainWindow::onConnect ),
103 FXMAPFUNC(SEL_COMMAND, MainWindow::ID_DISCONNECT, MainWindow::onDisconnect ),
104 FXMAPFUNC(SEL_COMMAND, MainWindow::ID_RESCAN, MainWindow::onRescan ),
105 FXMAPFUNC(SEL_COMMAND, MainWindow::ID_SEND_OUTPUT_REPORT, MainWindow::onSendOutputReport ),
106 FXMAPFUNC(SEL_COMMAND, MainWindow::ID_SEND_FEATURE_REPORT, MainWindow::onSendFeatureReport ),
107 FXMAPFUNC(SEL_COMMAND, MainWindow::ID_GET_FEATURE_REPORT, MainWindow::onGetFeatureReport ),
108 FXMAPFUNC(SEL_COMMAND, MainWindow::ID_CLEAR, MainWindow::onClear ),
109 FXMAPFUNC(SEL_TIMEOUT, MainWindow::ID_TIMER, MainWindow::onTimeout ),
110 FXMAPFUNC(SEL_TIMEOUT, MainWindow::ID_MAC_TIMER, MainWindow::onMacTimeout ),
111 };
112
113 FXIMPLEMENT(MainWindow, FXMainWindow, MainWindowMap, ARRAYNUMBER(MainWindowMap));
114
MainWindow(FXApp * app)115 MainWindow::MainWindow(FXApp *app)
116 : FXMainWindow(app, "HIDAPI Test Application", NULL, NULL, DECOR_ALL, 200,100, 425,700)
117 {
118 devices = NULL;
119 connected_device = NULL;
120
121 FXVerticalFrame *vf = new FXVerticalFrame(this, LAYOUT_FILL_Y|LAYOUT_FILL_X);
122
123 FXLabel *label = new FXLabel(vf, "HIDAPI Test Tool");
124 title_font = new FXFont(getApp(), "Arial", 14, FXFont::Bold);
125 label->setFont(title_font);
126
127 new FXLabel(vf,
128 "Select a device and press Connect.", NULL, JUSTIFY_LEFT);
129 new FXLabel(vf,
130 "Output data bytes can be entered in the Output section, \n"
131 "separated by space, comma or brackets. Data starting with 0x\n"
132 "is treated as hex. Data beginning with a 0 is treated as \n"
133 "octal. All other data is treated as decimal.", NULL, JUSTIFY_LEFT);
134 new FXLabel(vf,
135 "Data received from the device appears in the Input section.",
136 NULL, JUSTIFY_LEFT);
137 new FXLabel(vf,
138 "Optionally, a report length may be specified. Extra bytes are\n"
139 "padded with zeros. If no length is specified, the length is \n"
140 "inferred from the data.",
141 NULL, JUSTIFY_LEFT);
142 new FXLabel(vf, "");
143
144 // Device List and Connect/Disconnect buttons
145 FXHorizontalFrame *hf = new FXHorizontalFrame(vf, LAYOUT_FILL_X);
146 //device_list = new FXList(new FXHorizontalFrame(hf,FRAME_SUNKEN|FRAME_THICK, 0,0,0,0, 0,0,0,0), NULL, 0, LISTBOX_NORMAL|LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT, 0,0,300,200);
147 device_list = new FXList(new FXHorizontalFrame(hf,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y, 0,0,0,0, 0,0,0,0), NULL, 0, LISTBOX_NORMAL|LAYOUT_FILL_X|LAYOUT_FILL_Y, 0,0,300,200);
148 FXVerticalFrame *buttonVF = new FXVerticalFrame(hf);
149 connect_button = new FXButton(buttonVF, "Connect", NULL, this, ID_CONNECT, BUTTON_NORMAL|LAYOUT_FILL_X);
150 disconnect_button = new FXButton(buttonVF, "Disconnect", NULL, this, ID_DISCONNECT, BUTTON_NORMAL|LAYOUT_FILL_X);
151 disconnect_button->disable();
152 rescan_button = new FXButton(buttonVF, "Re-Scan devices", NULL, this, ID_RESCAN, BUTTON_NORMAL|LAYOUT_FILL_X);
153 new FXHorizontalFrame(buttonVF, 0, 0,0,0,0, 0,0,50,0);
154
155 connected_label = new FXLabel(vf, "Disconnected");
156
157 new FXHorizontalFrame(vf);
158
159 // Output Group Box
160 FXGroupBox *gb = new FXGroupBox(vf, "Output", FRAME_GROOVE|LAYOUT_FILL_X);
161 FXMatrix *matrix = new FXMatrix(gb, 3, MATRIX_BY_COLUMNS|LAYOUT_FILL_X);
162 new FXLabel(matrix, "Data");
163 new FXLabel(matrix, "Length");
164 new FXLabel(matrix, "");
165
166 //hf = new FXHorizontalFrame(gb, LAYOUT_FILL_X);
167 output_text = new FXTextField(matrix, 30, NULL, 0, TEXTFIELD_NORMAL|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN);
168 output_text->setText("1 0x81 0");
169 output_len = new FXTextField(matrix, 5, NULL, 0, TEXTFIELD_NORMAL|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN);
170 output_button = new FXButton(matrix, "Send Output Report", NULL, this, ID_SEND_OUTPUT_REPORT, BUTTON_NORMAL|LAYOUT_FILL_X);
171 output_button->disable();
172 //new FXHorizontalFrame(matrix, LAYOUT_FILL_X);
173
174 //hf = new FXHorizontalFrame(gb, LAYOUT_FILL_X);
175 feature_text = new FXTextField(matrix, 30, NULL, 0, TEXTFIELD_NORMAL|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN);
176 feature_len = new FXTextField(matrix, 5, NULL, 0, TEXTFIELD_NORMAL|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN);
177 feature_button = new FXButton(matrix, "Send Feature Report", NULL, this, ID_SEND_FEATURE_REPORT, BUTTON_NORMAL|LAYOUT_FILL_X);
178 feature_button->disable();
179
180 get_feature_text = new FXTextField(matrix, 30, NULL, 0, TEXTFIELD_NORMAL|LAYOUT_FILL_X|LAYOUT_FILL_COLUMN);
181 new FXWindow(matrix);
182 get_feature_button = new FXButton(matrix, "Get Feature Report", NULL, this, ID_GET_FEATURE_REPORT, BUTTON_NORMAL|LAYOUT_FILL_X);
183 get_feature_button->disable();
184
185
186 // Input Group Box
187 gb = new FXGroupBox(vf, "Input", FRAME_GROOVE|LAYOUT_FILL_X|LAYOUT_FILL_Y);
188 FXVerticalFrame *innerVF = new FXVerticalFrame(gb, LAYOUT_FILL_X|LAYOUT_FILL_Y);
189 input_text = new FXText(new FXHorizontalFrame(innerVF,LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK, 0,0,0,0, 0,0,0,0), NULL, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y);
190 input_text->setEditable(false);
191 new FXButton(innerVF, "Clear", NULL, this, ID_CLEAR, BUTTON_NORMAL|LAYOUT_RIGHT);
192
193
194 }
195
~MainWindow()196 MainWindow::~MainWindow()
197 {
198 if (connected_device)
199 hid_close(connected_device);
200 hid_exit();
201 delete title_font;
202 }
203
204 void
create()205 MainWindow::create()
206 {
207 FXMainWindow::create();
208 show();
209
210 onRescan(NULL, 0, NULL);
211
212
213 #ifdef __APPLE__
214 init_apple_message_system();
215 #endif
216
217 getApp()->addTimeout(this, ID_MAC_TIMER,
218 50 * timeout_scalar /*50ms*/);
219 }
220
221 long
onConnect(FXObject * sender,FXSelector sel,void * ptr)222 MainWindow::onConnect(FXObject *sender, FXSelector sel, void *ptr)
223 {
224 if (connected_device != NULL)
225 return 1;
226
227 FXint cur_item = device_list->getCurrentItem();
228 if (cur_item < 0)
229 return -1;
230 FXListItem *item = device_list->getItem(cur_item);
231 if (!item)
232 return -1;
233 struct hid_device_info *device_info = (struct hid_device_info*) item->getData();
234 if (!device_info)
235 return -1;
236
237 connected_device = hid_open_path(device_info->path);
238
239 if (!connected_device) {
240 FXMessageBox::error(this, MBOX_OK, "Device Error", "Unable To Connect to Device");
241 return -1;
242 }
243
244 hid_set_nonblocking(connected_device, 1);
245
246 getApp()->addTimeout(this, ID_TIMER,
247 5 * timeout_scalar /*5ms*/);
248
249 FXString s;
250 s.format("Connected to: %04hx:%04hx -", device_info->vendor_id, device_info->product_id);
251 s += FXString(" ") + device_info->manufacturer_string;
252 s += FXString(" ") + device_info->product_string;
253 connected_label->setText(s);
254 output_button->enable();
255 feature_button->enable();
256 get_feature_button->enable();
257 connect_button->disable();
258 disconnect_button->enable();
259 input_text->setText("");
260
261
262 return 1;
263 }
264
265 long
onDisconnect(FXObject * sender,FXSelector sel,void * ptr)266 MainWindow::onDisconnect(FXObject *sender, FXSelector sel, void *ptr)
267 {
268 hid_close(connected_device);
269 connected_device = NULL;
270 connected_label->setText("Disconnected");
271 output_button->disable();
272 feature_button->disable();
273 get_feature_button->disable();
274 connect_button->enable();
275 disconnect_button->disable();
276
277 getApp()->removeTimeout(this, ID_TIMER);
278
279 return 1;
280 }
281
282 long
onRescan(FXObject * sender,FXSelector sel,void * ptr)283 MainWindow::onRescan(FXObject *sender, FXSelector sel, void *ptr)
284 {
285 struct hid_device_info *cur_dev;
286
287 device_list->clearItems();
288
289 // List the Devices
290 hid_free_enumeration(devices);
291 devices = hid_enumerate(0x0, 0x0);
292 cur_dev = devices;
293 while (cur_dev) {
294 // Add it to the List Box.
295 FXString s;
296 FXString usage_str;
297 s.format("%04hx:%04hx -", cur_dev->vendor_id, cur_dev->product_id);
298 s += FXString(" ") + cur_dev->manufacturer_string;
299 s += FXString(" ") + cur_dev->product_string;
300 usage_str.format(" (usage: %04hx:%04hx) ", cur_dev->usage_page, cur_dev->usage);
301 s += usage_str;
302 FXListItem *li = new FXListItem(s, NULL, cur_dev);
303 device_list->appendItem(li);
304
305 cur_dev = cur_dev->next;
306 }
307
308 if (device_list->getNumItems() == 0)
309 device_list->appendItem("*** No Devices Connected ***");
310 else {
311 device_list->selectItem(0);
312 }
313
314 return 1;
315 }
316
317 size_t
getDataFromTextField(FXTextField * tf,char * buf,size_t len)318 MainWindow::getDataFromTextField(FXTextField *tf, char *buf, size_t len)
319 {
320 const char *delim = " ,{}\t\r\n";
321 FXString data = tf->getText();
322 const FXchar *d = data.text();
323 size_t i = 0;
324
325 // Copy the string from the GUI.
326 size_t sz = strlen(d);
327 char *str = (char*) malloc(sz+1);
328 strcpy(str, d);
329
330 // For each token in the string, parse and store in buf[].
331 char *token = strtok(str, delim);
332 while (token) {
333 char *endptr;
334 long int val = strtol(token, &endptr, 0);
335 buf[i++] = val;
336 token = strtok(NULL, delim);
337 }
338
339 free(str);
340 return i;
341 }
342
343 /* getLengthFromTextField()
344 Returns length:
345 0: empty text field
346 >0: valid length
347 -1: invalid length */
348 int
getLengthFromTextField(FXTextField * tf)349 MainWindow::getLengthFromTextField(FXTextField *tf)
350 {
351 long int len;
352 FXString str = tf->getText();
353 size_t sz = str.length();
354
355 if (sz > 0) {
356 char *endptr;
357 len = strtol(str.text(), &endptr, 0);
358 if (endptr != str.text() && *endptr == '\0') {
359 if (len <= 0) {
360 FXMessageBox::error(this, MBOX_OK, "Invalid length", "Enter a length greater than zero.");
361 return -1;
362 }
363 return len;
364 }
365 else
366 return -1;
367 }
368
369 return 0;
370 }
371
372 long
onSendOutputReport(FXObject * sender,FXSelector sel,void * ptr)373 MainWindow::onSendOutputReport(FXObject *sender, FXSelector sel, void *ptr)
374 {
375 char buf[256];
376 size_t data_len, len;
377 int textfield_len;
378
379 memset(buf, 0x0, sizeof(buf));
380 textfield_len = getLengthFromTextField(output_len);
381 data_len = getDataFromTextField(output_text, buf, sizeof(buf));
382
383 if (textfield_len < 0) {
384 FXMessageBox::error(this, MBOX_OK, "Invalid length", "Length field is invalid. Please enter a number in hex, octal, or decimal.");
385 return 1;
386 }
387
388 if (textfield_len > sizeof(buf)) {
389 FXMessageBox::error(this, MBOX_OK, "Invalid length", "Length field is too long.");
390 return 1;
391 }
392
393 len = (textfield_len)? textfield_len: data_len;
394
395 int res = hid_write(connected_device, (const unsigned char*)buf, len);
396 if (res < 0) {
397 FXMessageBox::error(this, MBOX_OK, "Error Writing", "Could not write to device. Error reported was: %ls", hid_error(connected_device));
398 }
399
400 return 1;
401 }
402
403 long
onSendFeatureReport(FXObject * sender,FXSelector sel,void * ptr)404 MainWindow::onSendFeatureReport(FXObject *sender, FXSelector sel, void *ptr)
405 {
406 char buf[256];
407 size_t data_len, len;
408 int textfield_len;
409
410 memset(buf, 0x0, sizeof(buf));
411 textfield_len = getLengthFromTextField(feature_len);
412 data_len = getDataFromTextField(feature_text, buf, sizeof(buf));
413
414 if (textfield_len < 0) {
415 FXMessageBox::error(this, MBOX_OK, "Invalid length", "Length field is invalid. Please enter a number in hex, octal, or decimal.");
416 return 1;
417 }
418
419 if (textfield_len > sizeof(buf)) {
420 FXMessageBox::error(this, MBOX_OK, "Invalid length", "Length field is too long.");
421 return 1;
422 }
423
424 len = (textfield_len)? textfield_len: data_len;
425
426 int res = hid_send_feature_report(connected_device, (const unsigned char*)buf, len);
427 if (res < 0) {
428 FXMessageBox::error(this, MBOX_OK, "Error Writing", "Could not send feature report to device. Error reported was: %ls", hid_error(connected_device));
429 }
430
431 return 1;
432 }
433
434 long
onGetFeatureReport(FXObject * sender,FXSelector sel,void * ptr)435 MainWindow::onGetFeatureReport(FXObject *sender, FXSelector sel, void *ptr)
436 {
437 char buf[256];
438 size_t len;
439
440 memset(buf, 0x0, sizeof(buf));
441 len = getDataFromTextField(get_feature_text, buf, sizeof(buf));
442
443 if (len != 1) {
444 FXMessageBox::error(this, MBOX_OK, "Too many numbers", "Enter only a single report number in the text field");
445 }
446
447 int res = hid_get_feature_report(connected_device, (unsigned char*)buf, sizeof(buf));
448 if (res < 0) {
449 FXMessageBox::error(this, MBOX_OK, "Error Getting Report", "Could not get feature report from device. Error reported was: %ls", hid_error(connected_device));
450 }
451
452 if (res > 0) {
453 FXString s;
454 s.format("Returned Feature Report. %d bytes:\n", res);
455 for (int i = 0; i < res; i++) {
456 FXString t;
457 t.format("%02hhx ", buf[i]);
458 s += t;
459 if ((i+1) % 4 == 0)
460 s += " ";
461 if ((i+1) % 16 == 0)
462 s += "\n";
463 }
464 s += "\n";
465 input_text->appendText(s);
466 input_text->setBottomLine(INT_MAX);
467 }
468
469 return 1;
470 }
471
472 long
onClear(FXObject * sender,FXSelector sel,void * ptr)473 MainWindow::onClear(FXObject *sender, FXSelector sel, void *ptr)
474 {
475 input_text->setText("");
476 return 1;
477 }
478
479 long
onTimeout(FXObject * sender,FXSelector sel,void * ptr)480 MainWindow::onTimeout(FXObject *sender, FXSelector sel, void *ptr)
481 {
482 unsigned char buf[256];
483 int res = hid_read(connected_device, buf, sizeof(buf));
484
485 if (res > 0) {
486 FXString s;
487 s.format("Received %d bytes:\n", res);
488 for (int i = 0; i < res; i++) {
489 FXString t;
490 t.format("%02hhx ", buf[i]);
491 s += t;
492 if ((i+1) % 4 == 0)
493 s += " ";
494 if ((i+1) % 16 == 0)
495 s += "\n";
496 }
497 s += "\n";
498 input_text->appendText(s);
499 input_text->setBottomLine(INT_MAX);
500 }
501 if (res < 0) {
502 input_text->appendText("hid_read() returned error\n");
503 input_text->setBottomLine(INT_MAX);
504 }
505
506 getApp()->addTimeout(this, ID_TIMER,
507 5 * timeout_scalar /*5ms*/);
508 return 1;
509 }
510
511 long
onMacTimeout(FXObject * sender,FXSelector sel,void * ptr)512 MainWindow::onMacTimeout(FXObject *sender, FXSelector sel, void *ptr)
513 {
514 #ifdef __APPLE__
515 check_apple_events();
516
517 getApp()->addTimeout(this, ID_MAC_TIMER,
518 50 * timeout_scalar /*50ms*/);
519 #endif
520
521 return 1;
522 }
523
main(int argc,char ** argv)524 int main(int argc, char **argv)
525 {
526 FXApp app("HIDAPI Test Application", "Signal 11 Software");
527 app.init(argc, argv);
528 g_main_window = new MainWindow(&app);
529 app.create();
530 app.run();
531 return 0;
532 }
533