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