hidapi 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|