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.
@@ -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,10 @@
1
+
2
+ Fixnum.class_eval do
3
+
4
+ ##
5
+ # Converts an integer into a hex string with the specified length.
6
+ def to_hex(length = 8)
7
+ to_s(16).rjust(length, '0')
8
+ end
9
+
10
+ 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