hidapi 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +103 -0
- data/Rakefile +11 -0
- data/bin/console +17 -0
- data/bin/setup +8 -0
- data/hidapi.gemspec +27 -0
- data/lib/hidapi.rb +92 -0
- data/lib/hidapi/device.rb +634 -0
- data/lib/hidapi/engine.rb +151 -0
- data/lib/hidapi/errors.rb +18 -0
- data/lib/hidapi/language.rb +221 -0
- data/lib/hidapi/numeric_extensions.rb +10 -0
- data/lib/hidapi/setup_task_helper.rb +240 -0
- data/lib/hidapi/version.rb +3 -0
- data/tmp/.keep +0 -0
- metadata +117 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
module HIDAPI
|
3
|
+
|
4
|
+
##
|
5
|
+
# A wrapper around the USB context that makes it easy to locate HID devices.
|
6
|
+
class Engine
|
7
|
+
|
8
|
+
##
|
9
|
+
# Contains the class code for HID devices from LIBUSB.
|
10
|
+
HID_CLASS = LIBUSB::CLASS_HID
|
11
|
+
|
12
|
+
##
|
13
|
+
# Creates a new engine.
|
14
|
+
def initialize
|
15
|
+
@context = LIBUSB::Context.new
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
##
|
20
|
+
# Enumerates the HID devices matching the vendor and product IDs.
|
21
|
+
#
|
22
|
+
# Both vendor_id and product_id are optional. They will act as a wild card if set to 0 (the default).
|
23
|
+
def enumerate(vendor_id = 0, product_id = 0, options = {})
|
24
|
+
raise HIDAPI::HidApiError, 'not initialized' unless @context
|
25
|
+
|
26
|
+
if options.is_a?(String) || options.is_a?(Symbol)
|
27
|
+
options = { as: options }
|
28
|
+
end
|
29
|
+
|
30
|
+
unless options.nil? || options.is_a?(Hash)
|
31
|
+
raise ArgumentError, 'options hash is invalid'
|
32
|
+
end
|
33
|
+
|
34
|
+
klass = (options || {}).delete(:as) || 'HIDAPI::Device'
|
35
|
+
klass = Object.const_get(klass) unless klass == :no_mapping
|
36
|
+
|
37
|
+
filters = { bClass: HID_CLASS }
|
38
|
+
|
39
|
+
unless vendor_id.nil? || vendor_id.to_i == 0
|
40
|
+
filters[:idVendor] = vendor_id.to_i
|
41
|
+
end
|
42
|
+
unless product_id.nil? || product_id.to_i == 0
|
43
|
+
filters[:idProduct] = product_id.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
list = @context.devices(filters)
|
47
|
+
|
48
|
+
if klass != :no_mapping
|
49
|
+
list.to_a.map{ |dev| klass.new(dev) }
|
50
|
+
else
|
51
|
+
list.to_a
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Gets the first device with the specified vendor_id, product_id, and optionally serial_number.
|
57
|
+
def get_device(vendor_id, product_id, serial_number = nil, options = {})
|
58
|
+
raise ArgumentError, 'vendor_id must be provided' if vendor_id.to_i == 0
|
59
|
+
raise ArgumentError, 'product_id must be provided' if product_id.to_i == 0
|
60
|
+
|
61
|
+
klass = (options || {}).delete(:as) || 'HIDAPI::Device'
|
62
|
+
klass = Object.const_get(klass) unless klass == :no_mapping
|
63
|
+
|
64
|
+
list = enumerate(vendor_id, product_id, as: :no_mapping)
|
65
|
+
return nil unless list && list.count > 0
|
66
|
+
if serial_number.to_s == ''
|
67
|
+
if klass != :no_mapping
|
68
|
+
return klass.new(list.first)
|
69
|
+
else
|
70
|
+
return list.first
|
71
|
+
end
|
72
|
+
end
|
73
|
+
list.each do |dev|
|
74
|
+
if dev.serial_number == serial_number
|
75
|
+
if klass != :no_mapping
|
76
|
+
return klass.new(dev)
|
77
|
+
else
|
78
|
+
return dev
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Opens the first device with the specified vendor_id, product_id, and optionally serial_number.
|
87
|
+
def open(vendor_id, product_id, serial_number = nil)
|
88
|
+
dev = get_device(vendor_id, product_id, serial_number)
|
89
|
+
dev.open if dev
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Gets the device with the specified path.
|
94
|
+
def get_device_by_path(path, options = {})
|
95
|
+
klass = (options || {}).delete(:as) || 'HIDAPI::Device'
|
96
|
+
klass = Object.const_get(klass) unless klass == :no_mapping
|
97
|
+
|
98
|
+
enumerate(as: :no_mapping).each do |usb_dev|
|
99
|
+
usb_dev.settings.each do |intf_desc|
|
100
|
+
if intf_desc.bInterfaceClass == HID_CLASS
|
101
|
+
dev_path = HIDAPI::make_path(usb_dev, intf_desc.bInterfaceNumber)
|
102
|
+
if dev_path == path
|
103
|
+
if klass != :no_mapping
|
104
|
+
return klass.new(usb_dev, intf_desc.bInterfaceNumber)
|
105
|
+
else
|
106
|
+
return usb_dev
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Opens the device with the specified path.
|
116
|
+
def open_path(path)
|
117
|
+
dev = get_device_by_path(path)
|
118
|
+
dev.open if dev
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Gets the USB code for the current locale.
|
123
|
+
def usb_code_for_current_locale
|
124
|
+
@usb_code_for_current_locale ||=
|
125
|
+
begin
|
126
|
+
locale = I18n.locale
|
127
|
+
if locale
|
128
|
+
locale = locale.to_s.partition('.')[0] # remove encoding
|
129
|
+
result = HIDAPI::Language.get_by_code(locale)
|
130
|
+
unless result
|
131
|
+
locale = locale.partition('_')[0] # chop off extra specification
|
132
|
+
result = HIDAPI::Language.get_by_code(locale)
|
133
|
+
end
|
134
|
+
result ? result[:usb_code] : 0
|
135
|
+
else
|
136
|
+
0
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def inspect # :nodoc:
|
143
|
+
"#<#{self.class.name}:#{self.object_id.to_hex(16)} context=0x#{@context.object_id.to_hex(16)}>"
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_s # :nodoc:
|
147
|
+
inspect
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module HIDAPI
|
2
|
+
|
3
|
+
##
|
4
|
+
# A general error from the HIDAPI library.
|
5
|
+
HidApiError = Class.new(StandardError)
|
6
|
+
|
7
|
+
##
|
8
|
+
# The device supplied was invalid for the HIDAPI::Device class.
|
9
|
+
InvalidDevice = Class.new(HidApiError)
|
10
|
+
|
11
|
+
##
|
12
|
+
# Failed to open a device.
|
13
|
+
DeviceOpenFailed = Class.new(HidApiError)
|
14
|
+
|
15
|
+
##
|
16
|
+
# An open device is required for the method called.
|
17
|
+
DeviceNotOpen = Class.new(HidApiError)
|
18
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module HIDAPI
|
2
|
+
##
|
3
|
+
# List of all known USB languages.
|
4
|
+
class Language
|
5
|
+
|
6
|
+
KNOWN = {
|
7
|
+
AFRIKAANS: {name: "Afrikaans", code: "af", usb_code: 0x0436},
|
8
|
+
ALBANIAN: {name: "Albanian", code: "sq", usb_code: 0x041c},
|
9
|
+
ARABIC___UNITED_ARAB_EMIRATES: {name: "Arabic - United Arab Emirates", code: "ar_ae", usb_code: 0x3801},
|
10
|
+
ARABIC___BAHRAIN: {name: "Arabic - Bahrain", code: "ar_bh", usb_code: 0x3c01},
|
11
|
+
ARABIC___ALGERIA: {name: "Arabic - Algeria", code: "ar_dz", usb_code: 0x1401},
|
12
|
+
ARABIC___EGYPT: {name: "Arabic - Egypt", code: "ar_eg", usb_code: 0x0c01},
|
13
|
+
ARABIC___IRAQ: {name: "Arabic - Iraq", code: "ar_iq", usb_code: 0x0801},
|
14
|
+
ARABIC___JORDAN: {name: "Arabic - Jordan", code: "ar_jo", usb_code: 0x2c01},
|
15
|
+
ARABIC___KUWAIT: {name: "Arabic - Kuwait", code: "ar_kw", usb_code: 0x3401},
|
16
|
+
ARABIC___LEBANON: {name: "Arabic - Lebanon", code: "ar_lb", usb_code: 0x3001},
|
17
|
+
ARABIC___LIBYA: {name: "Arabic - Libya", code: "ar_ly", usb_code: 0x1001},
|
18
|
+
ARABIC___MOROCCO: {name: "Arabic - Morocco", code: "ar_ma", usb_code: 0x1801},
|
19
|
+
ARABIC___OMAN: {name: "Arabic - Oman", code: "ar_om", usb_code: 0x2001},
|
20
|
+
ARABIC___QATAR: {name: "Arabic - Qatar", code: "ar_qa", usb_code: 0x4001},
|
21
|
+
ARABIC___SAUDI_ARABIA: {name: "Arabic - Saudi Arabia", code: "ar_sa", usb_code: 0x0401},
|
22
|
+
ARABIC___SYRIA: {name: "Arabic - Syria", code: "ar_sy", usb_code: 0x2801},
|
23
|
+
ARABIC___TUNISIA: {name: "Arabic - Tunisia", code: "ar_tn", usb_code: 0x1c01},
|
24
|
+
ARABIC___YEMEN: {name: "Arabic - Yemen", code: "ar_ye", usb_code: 0x2401},
|
25
|
+
ARMENIAN: {name: "Armenian", code: "hy", usb_code: 0x042b},
|
26
|
+
AZERI___LATIN: {name: "Azeri - Latin", code: "az_az", usb_code: 0x042c},
|
27
|
+
AZERI___CYRILLIC: {name: "Azeri - Cyrillic", code: "az_az", usb_code: 0x082c},
|
28
|
+
BASQUE: {name: "Basque", code: "eu", usb_code: 0x042d},
|
29
|
+
BELARUSIAN: {name: "Belarusian", code: "be", usb_code: 0x0423},
|
30
|
+
BULGARIAN: {name: "Bulgarian", code: "bg", usb_code: 0x0402},
|
31
|
+
CATALAN: {name: "Catalan", code: "ca", usb_code: 0x0403},
|
32
|
+
CHINESE___CHINA: {name: "Chinese - China", code: "zh_cn", usb_code: 0x0804},
|
33
|
+
CHINESE___HONG_KONG_SAR: {name: "Chinese - Hong Kong SAR", code: "zh_hk", usb_code: 0x0c04},
|
34
|
+
CHINESE___MACAU_SAR: {name: "Chinese - Macau SAR", code: "zh_mo", usb_code: 0x1404},
|
35
|
+
CHINESE___SINGAPORE: {name: "Chinese - Singapore", code: "zh_sg", usb_code: 0x1004},
|
36
|
+
CHINESE___TAIWAN: {name: "Chinese - Taiwan", code: "zh_tw", usb_code: 0x0404},
|
37
|
+
CROATIAN: {name: "Croatian", code: "hr", usb_code: 0x041a},
|
38
|
+
CZECH: {name: "Czech", code: "cs", usb_code: 0x0405},
|
39
|
+
DANISH: {name: "Danish", code: "da", usb_code: 0x0406},
|
40
|
+
DUTCH___NETHERLANDS: {name: "Dutch - Netherlands", code: "nl_nl", usb_code: 0x0413},
|
41
|
+
DUTCH___BELGIUM: {name: "Dutch - Belgium", code: "nl_be", usb_code: 0x0813},
|
42
|
+
ENGLISH___AUSTRALIA: {name: "English - Australia", code: "en_au", usb_code: 0x0c09},
|
43
|
+
ENGLISH___BELIZE: {name: "English - Belize", code: "en_bz", usb_code: 0x2809},
|
44
|
+
ENGLISH___CANADA: {name: "English - Canada", code: "en_ca", usb_code: 0x1009},
|
45
|
+
ENGLISH___CARIBBEAN: {name: "English - Caribbean", code: "en_cb", usb_code: 0x2409},
|
46
|
+
ENGLISH___IRELAND: {name: "English - Ireland", code: "en_ie", usb_code: 0x1809},
|
47
|
+
ENGLISH___JAMAICA: {name: "English - Jamaica", code: "en_jm", usb_code: 0x2009},
|
48
|
+
ENGLISH___NEW_ZEALAND: {name: "English - New Zealand", code: "en_nz", usb_code: 0x1409},
|
49
|
+
ENGLISH___PHILLIPPINES: {name: "English - Phillippines", code: "en_ph", usb_code: 0x3409},
|
50
|
+
ENGLISH___SOUTHERN_AFRICA: {name: "English - Southern Africa", code: "en_za", usb_code: 0x1c09},
|
51
|
+
ENGLISH___TRINIDAD: {name: "English - Trinidad", code: "en_tt", usb_code: 0x2c09},
|
52
|
+
ENGLISH___GREAT_BRITAIN: {name: "English - Great Britain", code: "en_gb", usb_code: 0x0809},
|
53
|
+
ENGLISH___UNITED_STATES: {name: "English - United States", code: "en_us", usb_code: 0x0409},
|
54
|
+
ESTONIAN: {name: "Estonian", code: "et", usb_code: 0x0425},
|
55
|
+
FARSI: {name: "Farsi", code: "fa", usb_code: 0x0429},
|
56
|
+
FINNISH: {name: "Finnish", code: "fi", usb_code: 0x040b},
|
57
|
+
FAROESE: {name: "Faroese", code: "fo", usb_code: 0x0438},
|
58
|
+
FRENCH___FRANCE: {name: "French - France", code: "fr_fr", usb_code: 0x040c},
|
59
|
+
FRENCH___BELGIUM: {name: "French - Belgium", code: "fr_be", usb_code: 0x080c},
|
60
|
+
FRENCH___CANADA: {name: "French - Canada", code: "fr_ca", usb_code: 0x0c0c},
|
61
|
+
FRENCH___LUXEMBOURG: {name: "French - Luxembourg", code: "fr_lu", usb_code: 0x140c},
|
62
|
+
FRENCH___SWITZERLAND: {name: "French - Switzerland", code: "fr_ch", usb_code: 0x100c},
|
63
|
+
GAELIC___IRELAND: {name: "Gaelic - Ireland", code: "gd_ie", usb_code: 0x083c},
|
64
|
+
GAELIC___SCOTLAND: {name: "Gaelic - Scotland", code: "gd", usb_code: 0x043c},
|
65
|
+
GERMAN___GERMANY: {name: "German - Germany", code: "de_de", usb_code: 0x0407},
|
66
|
+
GERMAN___AUSTRIA: {name: "German - Austria", code: "de_at", usb_code: 0x0c07},
|
67
|
+
GERMAN___LIECHTENSTEIN: {name: "German - Liechtenstein", code: "de_li", usb_code: 0x1407},
|
68
|
+
GERMAN___LUXEMBOURG: {name: "German - Luxembourg", code: "de_lu", usb_code: 0x1007},
|
69
|
+
GERMAN___SWITZERLAND: {name: "German - Switzerland", code: "de_ch", usb_code: 0x0807},
|
70
|
+
GREEK: {name: "Greek", code: "el", usb_code: 0x0408},
|
71
|
+
HEBREW: {name: "Hebrew", code: "he", usb_code: 0x040d},
|
72
|
+
HINDI: {name: "Hindi", code: "hi", usb_code: 0x0439},
|
73
|
+
HUNGARIAN: {name: "Hungarian", code: "hu", usb_code: 0x040e},
|
74
|
+
ICELANDIC: {name: "Icelandic", code: "is", usb_code: 0x040f},
|
75
|
+
INDONESIAN: {name: "Indonesian", code: "id", usb_code: 0x0421},
|
76
|
+
ITALIAN___ITALY: {name: "Italian - Italy", code: "it_it", usb_code: 0x0410},
|
77
|
+
ITALIAN___SWITZERLAND: {name: "Italian - Switzerland", code: "it_ch", usb_code: 0x0810},
|
78
|
+
JAPANESE: {name: "Japanese", code: "ja", usb_code: 0x0411},
|
79
|
+
KOREAN: {name: "Korean", code: "ko", usb_code: 0x0412},
|
80
|
+
LATVIAN: {name: "Latvian", code: "lv", usb_code: 0x0426},
|
81
|
+
LITHUANIAN: {name: "Lithuanian", code: "lt", usb_code: 0x0427},
|
82
|
+
F_Y_R_O__MACEDONIA: {name: "F.Y.R.O. Macedonia", code: "mk", usb_code: 0x042f},
|
83
|
+
MALAY___MALAYSIA: {name: "Malay - Malaysia", code: "ms_my", usb_code: 0x043e},
|
84
|
+
MALAY___BRUNEI: {name: "Malay – Brunei", code: "ms_bn", usb_code: 0x083e},
|
85
|
+
MALTESE: {name: "Maltese", code: "mt", usb_code: 0x043a},
|
86
|
+
MARATHI: {name: "Marathi", code: "mr", usb_code: 0x044e},
|
87
|
+
NORWEGIAN___BOKML: {name: "Norwegian - Bokml", code: "no_no", usb_code: 0x0414},
|
88
|
+
NORWEGIAN___NYNORSK: {name: "Norwegian - Nynorsk", code: "no_no", usb_code: 0x0814},
|
89
|
+
POLISH: {name: "Polish", code: "pl", usb_code: 0x0415},
|
90
|
+
PORTUGUESE___PORTUGAL: {name: "Portuguese - Portugal", code: "pt_pt", usb_code: 0x0816},
|
91
|
+
PORTUGUESE___BRAZIL: {name: "Portuguese - Brazil", code: "pt_br", usb_code: 0x0416},
|
92
|
+
RAETO_ROMANCE: {name: "Raeto-Romance", code: "rm", usb_code: 0x0417},
|
93
|
+
ROMANIAN___ROMANIA: {name: "Romanian - Romania", code: "ro", usb_code: 0x0418},
|
94
|
+
ROMANIAN___REPUBLIC_OF_MOLDOVA: {name: "Romanian - Republic of Moldova", code: "ro_mo", usb_code: 0x0818},
|
95
|
+
RUSSIAN: {name: "Russian", code: "ru", usb_code: 0x0419},
|
96
|
+
RUSSIAN___REPUBLIC_OF_MOLDOVA: {name: "Russian - Republic of Moldova", code: "ru_mo", usb_code: 0x0819},
|
97
|
+
SANSKRIT: {name: "Sanskrit", code: "sa", usb_code: 0x044f},
|
98
|
+
SERBIAN___CYRILLIC: {name: "Serbian - Cyrillic", code: "sr_sp", usb_code: 0x0c1a},
|
99
|
+
SERBIAN___LATIN: {name: "Serbian - Latin", code: "sr_sp", usb_code: 0x081a},
|
100
|
+
SETSUANA: {name: "Setsuana", code: "tn", usb_code: 0x0432},
|
101
|
+
SLOVENIAN: {name: "Slovenian", code: "sl", usb_code: 0x0424},
|
102
|
+
SLOVAK: {name: "Slovak", code: "sk", usb_code: 0x041b},
|
103
|
+
SORBIAN: {name: "Sorbian", code: "sb", usb_code: 0x042e},
|
104
|
+
SPANISH___SPAIN__TRADITIONAL_: {name: "Spanish - Spain (Traditional)", code: "es_es", usb_code: 0x040a},
|
105
|
+
SPANISH___ARGENTINA: {name: "Spanish - Argentina", code: "es_ar", usb_code: 0x2c0a},
|
106
|
+
SPANISH___BOLIVIA: {name: "Spanish - Bolivia", code: "es_bo", usb_code: 0x400a},
|
107
|
+
SPANISH___CHILE: {name: "Spanish - Chile", code: "es_cl", usb_code: 0x340a},
|
108
|
+
SPANISH___COLOMBIA: {name: "Spanish - Colombia", code: "es_co", usb_code: 0x240a},
|
109
|
+
SPANISH___COSTA_RICA: {name: "Spanish - Costa Rica", code: "es_cr", usb_code: 0x140a},
|
110
|
+
SPANISH___DOMINICAN_REPUBLIC: {name: "Spanish - Dominican Republic", code: "es_do", usb_code: 0x1c0a},
|
111
|
+
SPANISH___ECUADOR: {name: "Spanish - Ecuador", code: "es_ec", usb_code: 0x300a},
|
112
|
+
SPANISH___GUATEMALA: {name: "Spanish - Guatemala", code: "es_gt", usb_code: 0x100a},
|
113
|
+
SPANISH___HONDURAS: {name: "Spanish - Honduras", code: "es_hn", usb_code: 0x480a},
|
114
|
+
SPANISH___MEXICO: {name: "Spanish - Mexico", code: "es_mx", usb_code: 0x080a},
|
115
|
+
SPANISH___NICARAGUA: {name: "Spanish - Nicaragua", code: "es_ni", usb_code: 0x4c0a},
|
116
|
+
SPANISH___PANAMA: {name: "Spanish - Panama", code: "es_pa", usb_code: 0x180a},
|
117
|
+
SPANISH___PERU: {name: "Spanish - Peru", code: "es_pe", usb_code: 0x280a},
|
118
|
+
SPANISH___PUERTO_RICO: {name: "Spanish - Puerto Rico", code: "es_pr", usb_code: 0x500a},
|
119
|
+
SPANISH___PARAGUAY: {name: "Spanish - Paraguay", code: "es_py", usb_code: 0x3c0a},
|
120
|
+
SPANISH___EL_SALVADOR: {name: "Spanish - El Salvador", code: "es_sv", usb_code: 0x440a},
|
121
|
+
SPANISH___URUGUAY: {name: "Spanish - Uruguay", code: "es_uy", usb_code: 0x380a},
|
122
|
+
SPANISH___VENEZUELA: {name: "Spanish - Venezuela", code: "es_ve", usb_code: 0x200a},
|
123
|
+
SOUTHERN_SOTHO: {name: "Southern Sotho", code: "st", usb_code: 0x0430},
|
124
|
+
SWAHILI: {name: "Swahili", code: "sw", usb_code: 0x0441},
|
125
|
+
SWEDISH___SWEDEN: {name: "Swedish - Sweden", code: "sv_se", usb_code: 0x041d},
|
126
|
+
SWEDISH___FINLAND: {name: "Swedish - Finland", code: "sv_fi", usb_code: 0x081d},
|
127
|
+
TAMIL: {name: "Tamil", code: "ta", usb_code: 0x0449},
|
128
|
+
TATAR: {name: "Tatar", code: "tt", usb_code: 0x0444},
|
129
|
+
THAI: {name: "Thai", code: "th", usb_code: 0x041e},
|
130
|
+
TURKISH: {name: "Turkish", code: "tr", usb_code: 0x041f},
|
131
|
+
TSONGA: {name: "Tsonga", code: "ts", usb_code: 0x0431},
|
132
|
+
UKRAINIAN: {name: "Ukrainian", code: "uk", usb_code: 0x0422},
|
133
|
+
URDU: {name: "Urdu", code: "ur", usb_code: 0x0420},
|
134
|
+
UZBEK___CYRILLIC: {name: "Uzbek - Cyrillic", code: "uz_uz", usb_code: 0x0843},
|
135
|
+
UZBEK___LATIN: {name: "Uzbek – Latin", code: "uz_uz", usb_code: 0x0443},
|
136
|
+
VIETNAMESE: {name: "Vietnamese", code: "vi", usb_code: 0x042a},
|
137
|
+
XHOSA: {name: "Xhosa", code: "xh", usb_code: 0x0434},
|
138
|
+
YIDDISH: {name: "Yiddish", code: "yi", usb_code: 0x043d},
|
139
|
+
ZULU: {name: "Zulu", code: "zu", usb_code: 0x0435},
|
140
|
+
}.freeze.each{|_,data| data.each{|_,val| val.freeze}; data.freeze} # make the constant contents constant
|
141
|
+
|
142
|
+
private_constant :KNOWN
|
143
|
+
|
144
|
+
##
|
145
|
+
# Gets a language by key or name.
|
146
|
+
def self.[](name)
|
147
|
+
get_by_name(name)
|
148
|
+
end
|
149
|
+
|
150
|
+
# :nodoc:
|
151
|
+
def self.[]=(*args)
|
152
|
+
raise 'can\'t modify constants'
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
##
|
157
|
+
# Gets a language by name.
|
158
|
+
def self.get_by_name(name)
|
159
|
+
name_sym = name.to_s.upcase.to_sym
|
160
|
+
name = name.to_s.downcase
|
161
|
+
if KNOWN.keys.include?(name_sym)
|
162
|
+
return KNOWN[name_sym]
|
163
|
+
else
|
164
|
+
res = KNOWN.find{|k,v| v[:name].downcase == name}
|
165
|
+
return res[1] if res && res.length == 2
|
166
|
+
end
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Gets a language by code.
|
172
|
+
def self.get_by_code(code)
|
173
|
+
code = code.to_s.downcase
|
174
|
+
res = KNOWN.find{|k,v| v[:code] == code}
|
175
|
+
return res[1] if res && res.length == 2
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Gets a language by USB code.
|
180
|
+
def self.get_by_usb_code(code)
|
181
|
+
code = code.to_i
|
182
|
+
res = KNOWN.find{|k,v| v[:usb_code] == code}
|
183
|
+
return res[1] if res && res.length == 2
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Gets a language.
|
188
|
+
#
|
189
|
+
# The input value can be the name, code, or USB code.
|
190
|
+
def self.get(language)
|
191
|
+
if language.is_a?(Numeric)
|
192
|
+
get_by_usb_code(language)
|
193
|
+
elsif language.is_a?(Symbol) || language.is_a?(String)
|
194
|
+
get_by_name(language) || get_by_code(language)
|
195
|
+
elsif language.is_a?(Hash)
|
196
|
+
if language[:usb_code]
|
197
|
+
get_by_usb_code(language[:usb_code])
|
198
|
+
elsif language[:code]
|
199
|
+
get_by_code(language[:code])
|
200
|
+
elsif language[:name]
|
201
|
+
get_by_name(language[:name])
|
202
|
+
else
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
else
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# :nodoc:
|
211
|
+
def self.method_missing(m,*a,&b)
|
212
|
+
if KNOWN.respond_to?(m)
|
213
|
+
KNOWN.send m, *a, &b
|
214
|
+
else
|
215
|
+
super m, *a, &b
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module HIDAPI
|
6
|
+
class SetupTaskHelper
|
7
|
+
|
8
|
+
attr_reader :vendor_id, :product_id, :simple_name, :interface
|
9
|
+
|
10
|
+
def initialize(vendor_id, product_id, simple_name, interface)
|
11
|
+
@vendor_id = vendor_id.to_s.strip.to_i(16)
|
12
|
+
@product_id = product_id.to_s.strip.to_i(16)
|
13
|
+
@simple_name = simple_name.to_s.strip.gsub(' ', '_').gsub(/[^A-Za-z0-9_\-]/, '')
|
14
|
+
@interface = interface.to_s.strip.to_i
|
15
|
+
@temp_dir = Dir.mktmpdir('hidapi_setup')
|
16
|
+
ObjectSpace.define_finalizer(self, ->{ FileUtils.rm_rf(@temp_dir) })
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
if valid_options?
|
21
|
+
if operating_system == :osx
|
22
|
+
run_osx
|
23
|
+
elsif operating_system == :linux
|
24
|
+
run_linux
|
25
|
+
elsif operating_system == :windows
|
26
|
+
run_windows
|
27
|
+
else
|
28
|
+
puts "Your operating system was detected as '#{operating_system}', but I don't have a setup routine for that."
|
29
|
+
false
|
30
|
+
end
|
31
|
+
else
|
32
|
+
usage
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_options?
|
37
|
+
vendor_id != 0 && product_id != 0 && simple_name != ''
|
38
|
+
end
|
39
|
+
|
40
|
+
def usage()
|
41
|
+
puts <<-USAGE
|
42
|
+
Usage: bundle exec rake setup_hid_device[vendor_id,product_id,simple_name,interface]
|
43
|
+
-OR- #{File.basename(__FILE__)} vendor_id product_id simple_name interface
|
44
|
+
vendor_id should be the 16-bit identifier assigned to the device vendor in hex format.
|
45
|
+
product_id should be the 16-bit identifier assigned by the manufacturer in hex format.
|
46
|
+
simple_name should be a fairly short name for the device.
|
47
|
+
interface is optional and defaults to 0.
|
48
|
+
|
49
|
+
ie - bundle exec rake setup_hid_device[04d8,c002,pico-lcd-graphic]
|
50
|
+
ie - #{File.basename(__FILE__)} 04d8 c002 pico-lcd-graphic
|
51
|
+
USAGE
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def operating_system
|
58
|
+
@operating_system ||=
|
59
|
+
begin
|
60
|
+
|
61
|
+
# unix uses uname, windows uses ver. failing both, use the OS environment variable (which can be specified by the user).
|
62
|
+
kernel_name = `uname -s`.strip.downcase rescue nil
|
63
|
+
kernel_name = `ver`.strip.downcase rescue nil unless kernel_name
|
64
|
+
kernel_name = ENV['OS'].to_s.strip.downcase rescue '' unless kernel_name
|
65
|
+
|
66
|
+
if /darwin/ =~ kernel_name
|
67
|
+
:osx
|
68
|
+
elsif /win/ =~ kernel_name
|
69
|
+
:windows
|
70
|
+
elsif /linux/ =~ kernel_name
|
71
|
+
:linux
|
72
|
+
else
|
73
|
+
:unix
|
74
|
+
end
|
75
|
+
rescue
|
76
|
+
:unknown
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_windows
|
81
|
+
puts "Please use the Zadig tool from http://zadig.akeo.ie/ to install a driver for your device.\nThis script unfortunately does not do this for you automatically at this point in time."
|
82
|
+
end
|
83
|
+
|
84
|
+
def run_osx
|
85
|
+
# OS X needs a codeless kext setup in the /System/Library/Extensions directory.
|
86
|
+
|
87
|
+
path = @temp_dir
|
88
|
+
path += '/' unless path[-1] == '/'
|
89
|
+
path += simple_name
|
90
|
+
|
91
|
+
target = "/System/Library/Extensions/#{simple_name}.kext"
|
92
|
+
|
93
|
+
if Dir.exist?(target)
|
94
|
+
puts 'A kext with the specified name already exists.'
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
|
98
|
+
puts 'Building kext...'
|
99
|
+
Dir.mkdir path
|
100
|
+
Dir.mkdir path + '/Contents'
|
101
|
+
Dir.mkdir path + '/Contents/Resources'
|
102
|
+
Dir.mkdir path + '/Contents/Resources/English.lproj'
|
103
|
+
File.write path + '/Contents/Resources/English.lproj/InfoPlist.strings', "\nCFBundleName = \"VendorSpecificDriver\";\n"
|
104
|
+
File.write path + '/Contents/Info.plist', <<-FILE
|
105
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
106
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
107
|
+
<plist version="1.0">
|
108
|
+
<dict>
|
109
|
+
<key>BuildMachineOSBuild</key>
|
110
|
+
<string>11C74</string>
|
111
|
+
<key>CFBundleDevelopmentRegion</key>
|
112
|
+
<string>English</string>
|
113
|
+
<key>CFBundleIdentifier</key>
|
114
|
+
<string>idv.barkerest.#{simple_name}</string>
|
115
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
116
|
+
<string>6.0</string>
|
117
|
+
<key>CFBundlePackageType</key>
|
118
|
+
<string>KEXT</string>
|
119
|
+
<key>CFBundleSignature</key>
|
120
|
+
<string>????</string>
|
121
|
+
<key>CFBundleVersion</key>
|
122
|
+
<string>1.0</string>
|
123
|
+
<key>DTCompiler</key>
|
124
|
+
<string></string>
|
125
|
+
<key>DTPlatformBuild</key>
|
126
|
+
<string>4D502</string>
|
127
|
+
<key>DTPlatformVersion</key>
|
128
|
+
<string>GM</string>
|
129
|
+
<key>DTSDKBuild</key>
|
130
|
+
<string>11C63</string>
|
131
|
+
<key>DTSDKName</key>
|
132
|
+
<string>macosx10.7</string>
|
133
|
+
<key>DTXcode</key>
|
134
|
+
<string>0421</string>
|
135
|
+
<key>DTXcodeBuild</key>
|
136
|
+
<string>4D502</string>
|
137
|
+
<key>OSBundleRequired</key>
|
138
|
+
<string>Console</string>
|
139
|
+
<key>IOKitPersonalities</key>
|
140
|
+
<dict>
|
141
|
+
<key>#{simple_name}</key>
|
142
|
+
<dict>
|
143
|
+
<key>CFBundleIdentifier</key>
|
144
|
+
<string>com.apple.kpi.iokit</string>
|
145
|
+
<key>IOClass</key>
|
146
|
+
<string>IOService</string>
|
147
|
+
<key>IOProbeScore</key>
|
148
|
+
<integer>11000</integer>
|
149
|
+
<key>IOProviderClass</key>
|
150
|
+
<string>IOUSBInterface</string>
|
151
|
+
<key>bConfigurationValue</key>
|
152
|
+
<integer>1</integer>
|
153
|
+
<key>bInterfaceNumber</key>
|
154
|
+
<integer>#{interface}</integer>
|
155
|
+
<key>idProduct</key>
|
156
|
+
<integer>#{product_id}</integer>
|
157
|
+
<key>idVendor</key>
|
158
|
+
<integer>#{vendor_id}</integer>
|
159
|
+
</dict>
|
160
|
+
</dict>
|
161
|
+
<key>OSBundleLibraries</key>
|
162
|
+
<dict>
|
163
|
+
<key>com.apple.iokit.IOUSBFamily</key>
|
164
|
+
<string>1.8</string>
|
165
|
+
<key>com.apple.kernel.libkern</key>
|
166
|
+
<string>6.0</string>
|
167
|
+
</dict>
|
168
|
+
</dict>
|
169
|
+
</plist>
|
170
|
+
FILE
|
171
|
+
|
172
|
+
puts 'Installing kext...'
|
173
|
+
`sudo cp -r #{path} #{target}`
|
174
|
+
`sudo touch /System/Library/Extensions`
|
175
|
+
|
176
|
+
FileUtils.rm_rf path
|
177
|
+
|
178
|
+
puts "The kext has been installed.\nYou may have to unplug/plug the device or restart your computer."
|
179
|
+
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
def run_linux
|
184
|
+
# Linux needs a udev rule file added under /etc/udev/rules.d
|
185
|
+
# We'll use '60-hidapi.rules' for our purposes.
|
186
|
+
|
187
|
+
target = '/etc/udev/rules.d/60-hidapi.rules'
|
188
|
+
|
189
|
+
contents = File.exist?(target) ? File.read(target) : nil
|
190
|
+
contents ||= <<-DEF
|
191
|
+
# udev rules for libusb access created by Ruby 'hidapi' gem.
|
192
|
+
SUBSYSTEM!="usb", GOTO="hidapi_rules_end"
|
193
|
+
ACTION!="add", GOTO="hidapi_rules_end"
|
194
|
+
|
195
|
+
LABEL="hidapi_rules_end"
|
196
|
+
DEF
|
197
|
+
|
198
|
+
contents = contents.split("\n").map(&:strip)
|
199
|
+
|
200
|
+
# get the position we will be inserting at.
|
201
|
+
last_line = contents.index { |item| item == 'LABEL="hidapi_rules_end"' }
|
202
|
+
|
203
|
+
contents.each do |line|
|
204
|
+
if /^#\s+device:\s+#{simple_name}$/i =~ line
|
205
|
+
puts 'A udev rule for the specified device already exists.'
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
line = "# device: #{simple_name}\nATTR{idVendor}==\"#{vendor_id.to_s(16).rjust(4,'0')}\", ATTR{idProduct}==\"#{product_id.to_s(16).rjust(4,'0')}\", MODE=\"0666\", SYMLINK+=\"hidapi/#{simple_name}@%k\"\n"
|
211
|
+
|
212
|
+
if last_line
|
213
|
+
contents.insert last_line, line
|
214
|
+
else
|
215
|
+
contents << line
|
216
|
+
end
|
217
|
+
|
218
|
+
path = @temp_dir
|
219
|
+
path += '/' unless path[-1] == '/'
|
220
|
+
path += 'rules'
|
221
|
+
|
222
|
+
File.write path, contents.join("\n")
|
223
|
+
|
224
|
+
`sudo cp -f #{path} #{target}`
|
225
|
+
|
226
|
+
File.delete path
|
227
|
+
|
228
|
+
puts "A udev rule for the device has been created.\nYou may have to unplug/plug the device or restart your computer."
|
229
|
+
true
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
if $0 == __FILE__
|
237
|
+
|
238
|
+
HIDAPI::SetupTaskHelper(ARGV[0], ARGV[1], ARGV[2], ARGV[3]).run
|
239
|
+
|
240
|
+
end
|