hidapi 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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