blob: 76fc8c40c17f52e7cf33243ba828f344924850f7 [file] [log] [blame]
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +00005#include "media/midi/midi_manager_alsa.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00006
agoode975043d2015-05-11 00:46:17 -07007#include <poll.h>
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +00008#include <stdlib.h>
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +00009#include <algorithm>
10#include <string>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000011
12#include "base/bind.h"
agoodef212b2a2015-03-19 12:53:23 -070013#include "base/json/json_string_value_serializer.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000014#include "base/logging.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000015#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000016#include "base/posix/eintr_wrapper.h"
agoode975043d2015-05-11 00:46:17 -070017#include "base/safe_strerror_posix.h"
agoodef212b2a2015-03-19 12:53:23 -070018#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "base/strings/stringprintf.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000020#include "base/time/time.h"
agoodef212b2a2015-03-19 12:53:23 -070021#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000022#include "media/midi/midi_port_info.h"
23
24namespace media {
toyoshime147c5e2015-05-07 21:58:31 -070025namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000026
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000027namespace {
28
agoode@chromium.org25227512014-06-08 05:12:05 +000029// Per-output buffer. This can be smaller, but then large sysex messages
30// will be (harmlessly) split across multiple seq events. This should
31// not have any real practical effect, except perhaps to slightly reorder
32// realtime messages with respect to sysex.
33const size_t kSendBufferSize = 256;
34
agoodeb09423b2015-05-11 11:39:57 -070035// Minimum client id for which we will have ALSA card devices for. When we
36// are searching for card devices (used to get the path, id, and manufacturer),
37// we don't want to get confused by kernel clients that do not have a card.
38// See seq_clientmgr.c in the ALSA code for this.
39// TODO(agoode): Add proper client -> card export from the kernel to avoid
40// hardcoding.
41const int kMinimumClientIdForCards = 16;
42
agoodef212b2a2015-03-19 12:53:23 -070043// ALSA constants.
44const char kAlsaHw[] = "hw";
45
agoode975043d2015-05-11 00:46:17 -070046// udev constants.
47const char kUdev[] = "udev";
48const char kUdevSubsystemSound[] = "sound";
49const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
50const char kUdevActionChange[] = "change";
51const char kUdevActionRemove[] = "remove";
52
agoodeb09423b2015-05-11 11:39:57 -070053const char kUdevIdVendor[] = "ID_VENDOR";
54const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
55const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
56const char kUdevIdVendorId[] = "ID_VENDOR_ID";
57const char kUdevIdModelId[] = "ID_MODEL_ID";
58const char kUdevIdBus[] = "ID_BUS";
59const char kUdevIdPath[] = "ID_PATH";
60const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
61const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
62
63const char kSysattrVendorName[] = "vendor_name";
64const char kSysattrVendor[] = "vendor";
65const char kSysattrModel[] = "model";
66const char kSysattrGuid[] = "guid";
67
68const char kCardSyspath[] = "/card";
69
agoode@chromium.org25227512014-06-08 05:12:05 +000070// Constants for the capabilities we search for in inputs and outputs.
71// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
72const unsigned int kRequiredInputPortCaps =
73 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
74const unsigned int kRequiredOutputPortCaps =
75 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
76
agoode99d63292015-04-13 08:39:25 -070077const unsigned int kCreateOutputPortCaps =
78 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
79const unsigned int kCreateInputPortCaps =
80 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
81const unsigned int kCreatePortType =
82 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +000083
agoode99d63292015-04-13 08:39:25 -070084int AddrToInt(int client, int port) {
85 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -070086}
agoodef212b2a2015-03-19 12:53:23 -070087
agoodeb09423b2015-05-11 11:39:57 -070088// Returns true if this client has an ALSA card associated with it.
89bool IsCardClient(snd_seq_client_type_t type, int client_id) {
90 return (type == SND_SEQ_KERNEL_CLIENT) &&
91 (client_id >= kMinimumClientIdForCards);
92}
93
94// TODO(agoode): Move this to device/udev_linux.
95const std::string UdevDeviceGetPropertyOrSysattr(
96 struct udev_device* udev_device,
97 const char* property_key,
98 const char* sysattr_key) {
99 // First try the property.
100 std::string value =
101 device::UdevDeviceGetPropertyValue(udev_device, property_key);
102
103 // If no property, look for sysattrs and walk up the parent devices too.
104 while (value.empty() && udev_device) {
105 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
106 udev_device = device::udev_device_get_parent(udev_device);
107 }
108 return value;
109}
110
111int GetCardNumber(udev_device* dev) {
112 const char* syspath = device::udev_device_get_syspath(dev);
113 if (!syspath)
114 return -1;
115
116 std::string syspath_str(syspath);
117 size_t i = syspath_str.rfind(kCardSyspath);
118 if (i == std::string::npos)
119 return -1;
120
121 int number;
122 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
123 return -1;
124 return number;
125}
126
agoode8caab0b2015-03-23 18:48:02 -0700127void SetStringIfNonEmpty(base::DictionaryValue* value,
128 const std::string& path,
129 const std::string& in_value) {
130 if (!in_value.empty())
131 value->SetString(path, in_value);
132}
133
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000134} // namespace
135
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000136MidiManagerAlsa::MidiManagerAlsa()
agoode@chromium.org25227512014-06-08 05:12:05 +0000137 : in_client_(NULL),
138 out_client_(NULL),
139 out_client_id_(-1),
agoode99d63292015-04-13 08:39:25 -0700140 in_port_id_(-1),
agoodeb09423b2015-05-11 11:39:57 -0700141 alsa_cards_deleter_(&alsa_cards_),
142 alsa_card_midi_count_(0),
agoode@chromium.org25227512014-06-08 05:12:05 +0000143 decoder_(NULL),
agoode7de413f2015-04-24 00:13:39 -0700144 udev_(device::udev_new()),
agoode@chromium.org25227512014-06-08 05:12:05 +0000145 send_thread_("MidiSendThread"),
146 event_thread_("MidiEventThread"),
147 event_thread_shutdown_(false) {
148 // Initialize decoder.
149 snd_midi_event_new(0, &decoder_);
150 snd_midi_event_no_status(decoder_, 1);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000151}
152
agoodebd4be9b2015-03-16 19:17:25 -0700153MidiManagerAlsa::~MidiManagerAlsa() {
154 // Tell the event thread it will soon be time to shut down. This gives
155 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
156 // message is lost.
157 {
158 base::AutoLock lock(shutdown_lock_);
159 event_thread_shutdown_ = true;
160 }
161
162 // Stop the send thread.
163 send_thread_.Stop();
164
165 // Close the out client. This will trigger the event thread to stop,
166 // because of SND_SEQ_EVENT_CLIENT_EXIT.
167 if (out_client_)
168 snd_seq_close(out_client_);
169
170 // Wait for the event thread to stop.
171 event_thread_.Stop();
172
173 // Close the in client.
174 if (in_client_)
175 snd_seq_close(in_client_);
176
177 // Free the decoder.
178 snd_midi_event_free(decoder_);
agoodebd4be9b2015-03-16 19:17:25 -0700179}
180
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000181void MidiManagerAlsa::StartInitialization() {
agoode@chromium.org25227512014-06-08 05:12:05 +0000182 // TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
183
184 // Create client handles.
agoode975043d2015-05-11 00:46:17 -0700185 int err =
186 snd_seq_open(&in_client_, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000187 if (err != 0) {
188 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
189 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
190 }
agoode99d63292015-04-13 08:39:25 -0700191 in_client_id_ = snd_seq_client_id(in_client_);
agoodef212b2a2015-03-19 12:53:23 -0700192 err = snd_seq_open(&out_client_, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
agoode@chromium.org25227512014-06-08 05:12:05 +0000193 if (err != 0) {
194 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
195 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
196 }
197 out_client_id_ = snd_seq_client_id(out_client_);
198
199 // Name the clients.
200 err = snd_seq_set_client_name(in_client_, "Chrome (input)");
201 if (err != 0) {
202 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
203 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
204 }
205 err = snd_seq_set_client_name(out_client_, "Chrome (output)");
206 if (err != 0) {
207 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
208 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
209 }
210
211 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700212 in_port_id_ = snd_seq_create_simple_port(
213 in_client_, NULL, kCreateInputPortCaps, kCreatePortType);
214 if (in_port_id_ < 0) {
215 VLOG(1) << "snd_seq_create_simple_port fails: "
216 << snd_strerror(in_port_id_);
agoode@chromium.org25227512014-06-08 05:12:05 +0000217 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
218 }
219
220 // Subscribe to the announce port.
221 snd_seq_port_subscribe_t* subs;
222 snd_seq_port_subscribe_alloca(&subs);
223 snd_seq_addr_t announce_sender;
224 snd_seq_addr_t announce_dest;
225 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
226 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700227 announce_dest.client = in_client_id_;
228 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000229 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
230 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
231 err = snd_seq_subscribe_port(in_client_, subs);
232 if (err != 0) {
233 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
234 << snd_strerror(err);
235 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
236 }
237
agoode99d63292015-04-13 08:39:25 -0700238 // Generate hotplug events for existing ports.
agoode975043d2015-05-11 00:46:17 -0700239 // TODO(agoode): Check the return value for failure.
agoode99d63292015-04-13 08:39:25 -0700240 EnumerateAlsaPorts();
agoode@chromium.org25227512014-06-08 05:12:05 +0000241
agoode975043d2015-05-11 00:46:17 -0700242 // Initialize udev monitor.
243 udev_monitor_.reset(
244 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
245 if (!udev_monitor_.get()) {
246 VLOG(1) << "udev_monitor_new_from_netlink fails";
247 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
248 }
249 err = device::udev_monitor_filter_add_match_subsystem_devtype(
250 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
251 if (err != 0) {
252 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
253 << safe_strerror(-err);
254 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
255 }
256 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
257 if (err != 0) {
258 VLOG(1) << "udev_monitor_enable_receiving fails: " << safe_strerror(-err);
259 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
260 }
261
262 // Generate hotplug events for existing udev devices.
263 EnumerateUdevCards();
264
agoode99d63292015-04-13 08:39:25 -0700265 // Start processing events.
agoode@chromium.org25227512014-06-08 05:12:05 +0000266 event_thread_.Start();
267 event_thread_.message_loop()->PostTask(
268 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700269 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000270
271 CompleteInitialization(MIDI_OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000272}
273
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000274void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000275 uint32 port_index,
276 const std::vector<uint8>& data,
277 double timestamp) {
agoode@chromium.org25227512014-06-08 05:12:05 +0000278 // Not correct right now. http://crbug.com/374341.
279 if (!send_thread_.IsRunning())
280 send_thread_.Start();
281
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000282 base::TimeDelta delay;
283 if (timestamp != 0.0) {
284 base::TimeTicks time_to_send =
285 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700286 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000287 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
288 }
289
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000290 send_thread_.message_loop()->PostDelayedTask(
agoodebd4be9b2015-03-16 19:17:25 -0700291 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
292 base::Unretained(this), port_index, data),
293 delay);
agoode@chromium.org25227512014-06-08 05:12:05 +0000294
295 // Acknowledge send.
296 send_thread_.message_loop()->PostTask(
agoodebd4be9b2015-03-16 19:17:25 -0700297 FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
298 base::Unretained(client), data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000299}
300
agoode99d63292015-04-13 08:39:25 -0700301MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
302 const std::string& id,
303 int client_id,
304 int port_id,
305 int midi_device,
306 const std::string& client_name,
307 const std::string& port_name,
308 const std::string& manufacturer,
309 const std::string& version,
310 Type type)
311 : id_(id),
312 midi_device_(midi_device),
313 type_(type),
314 path_(path),
315 client_id_(client_id),
316 port_id_(port_id),
317 client_name_(client_name),
318 port_name_(port_name),
319 manufacturer_(manufacturer),
320 version_(version),
321 web_port_index_(0),
322 connected_(true) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000323}
324
agoodeb0582872015-05-20 05:22:24 -0700325MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000326
agoode99d63292015-04-13 08:39:25 -0700327// Note: keep synchronized with the MidiPort::Match* methods.
328scoped_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
329 scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
330
331 std::string type;
332 switch (type_) {
333 case Type::kInput:
334 type = "input";
335 break;
agoode99d63292015-04-13 08:39:25 -0700336 case Type::kOutput:
337 type = "output";
338 break;
339 }
340 value->SetString("type", type);
341 SetStringIfNonEmpty(value.get(), "path", path_);
342 SetStringIfNonEmpty(value.get(), "id", id_);
343 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
344 SetStringIfNonEmpty(value.get(), "portName", port_name_);
345 value->SetInteger("clientId", client_id_);
346 value->SetInteger("portId", port_id_);
347 value->SetInteger("midiDevice", midi_device_);
348
349 return value.Pass();
agoodebd4be9b2015-03-16 19:17:25 -0700350}
agoode@chromium.org25227512014-06-08 05:12:05 +0000351
agoode99d63292015-04-13 08:39:25 -0700352std::string MidiManagerAlsa::MidiPort::JSONValue() const {
353 std::string json;
354 JSONStringValueSerializer serializer(&json);
355 serializer.Serialize(*Value().get());
356 return json;
agoodef212b2a2015-03-19 12:53:23 -0700357}
358
agoode99d63292015-04-13 08:39:25 -0700359// TODO(agoode): Do not use SHA256 here. Instead store a persistent
360// mapping and just use a UUID or other random string.
361// http://crbug.com/465320
362std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
363 uint8 hash[crypto::kSHA256Length];
364 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
365 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700366}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000367
agoode99d63292015-04-13 08:39:25 -0700368bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
369 // Matches on:
370 // connected == true
371 // type
372 // path
373 // id
374 // client_id
375 // port_id
376 // midi_device
377 // client_name
378 // port_name
379 return connected() && (type() == query.type()) && (path() == query.path()) &&
380 (id() == query.id()) && (client_id() == query.client_id()) &&
381 (port_id() == query.port_id()) &&
382 (midi_device() == query.midi_device()) &&
383 (client_name() == query.client_name()) &&
384 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700385}
386
agoode99d63292015-04-13 08:39:25 -0700387bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
388 // Matches on:
389 // connected == false
390 // type
391 // path
392 // id
393 // port_id
394 // midi_device
395 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700396}
397
agoode99d63292015-04-13 08:39:25 -0700398bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
399 // Matches on:
400 // connected == false
401 // type
402 // id
403 // port_id
404 // midi_device
405 return !connected() && (type() == query.type()) && (id() == query.id()) &&
406 (port_id() == query.port_id()) &&
407 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700408}
409
agoode99d63292015-04-13 08:39:25 -0700410bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
411 // Matches on:
412 // connected == false
413 // type
414 // path.empty(), for both this and query
415 // id.empty(), for both this and query
416 // client_id
417 // port_id
418 // client_name
419 // port_name
420 // midi_device == -1, for both this and query
421 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700422}
423
agoode99d63292015-04-13 08:39:25 -0700424bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
425 // Matches on:
426 // connected == false
427 // type
428 // path.empty(), for both this and query
429 // id.empty(), for both this and query
430 // port_id
431 // client_name
432 // port_name
433 // midi_device == -1, for both this and query
434 return !connected() && (type() == query.type()) && path().empty() &&
435 query.path().empty() && id().empty() && query.id().empty() &&
436 (port_id() == query.port_id()) &&
437 (client_name() == query.client_name()) &&
438 (port_name() == query.port_name()) && (midi_device() == -1) &&
439 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000440}
441
agoodeb0582872015-05-20 05:22:24 -0700442MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700443
444MidiManagerAlsa::MidiPortStateBase::iterator
445MidiManagerAlsa::MidiPortStateBase::Find(
446 const MidiManagerAlsa::MidiPort& port) {
447 auto result = FindConnected(port);
448 if (result == end())
449 result = FindDisconnected(port);
450 return result;
451}
452
453MidiManagerAlsa::MidiPortStateBase::iterator
454MidiManagerAlsa::MidiPortStateBase::FindConnected(
455 const MidiManagerAlsa::MidiPort& port) {
456 // Exact match required for connected ports.
457 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
458 return p->MatchConnected(port);
459 });
460 return it;
461}
462
463MidiManagerAlsa::MidiPortStateBase::iterator
464MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
465 const MidiManagerAlsa::MidiPort& port) {
466 // Always match on:
467 // type
468 // Possible things to match on:
469 // path
470 // id
471 // client_id
472 // port_id
473 // midi_device
474 // client_name
475 // port_name
476
477 if (!port.path().empty()) {
478 // If path is present, then we have a card-based client.
479
480 // Pass 1. Match on path, id, midi_device, port_id.
481 // This is the best possible match for hardware card-based clients.
482 // This will also match the empty id correctly for devices without an id.
483 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
484 return p->MatchCardPass1(port);
485 });
486 if (it != ports_.end())
487 return it;
488
489 if (!port.id().empty()) {
490 // Pass 2. Match on id, midi_device, port_id.
491 // This will give us a high-confidence match when a user moves a device to
492 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
493 // has a hardware id.
494 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
495 return p->MatchCardPass2(port);
496 });
497 if (it != ports_.end())
498 return it;
499 }
500 } else {
501 // Else, we have a non-card-based client.
502 // Pass 1. Match on client_id, port_id, client_name, port_name.
503 // This will give us a reasonably good match.
504 auto it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
505 return p->MatchNoCardPass1(port);
506 });
507 if (it != ports_.end())
508 return it;
509
510 // Pass 2. Match on port_id, client_name, port_name.
511 // This is weaker but similar to pass 2 in the hardware card-based clients
512 // match.
513 it = std::find_if(ports_.begin(), ports_.end(), [&port](MidiPort* p) {
514 return p->MatchNoCardPass2(port);
515 });
516 if (it != ports_.end())
517 return it;
518 }
519
520 // No match.
521 return ports_.end();
522}
523
agoodeb0582872015-05-20 05:22:24 -0700524MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700525
526void MidiManagerAlsa::TemporaryMidiPortState::Insert(
527 scoped_ptr<MidiPort> port) {
528 ports()->push_back(port.Pass());
529}
530
531MidiManagerAlsa::MidiPortState::MidiPortState()
532 : num_input_ports_(0), num_output_ports_(0) {
533}
534
535uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) {
536 // Add the web midi index.
vivek.vg0365a022015-04-14 23:11:23 -0700537 uint32 web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700538 switch (port->type()) {
539 case MidiPort::Type::kInput:
540 web_port_index = num_input_ports_++;
541 break;
542 case MidiPort::Type::kOutput:
543 web_port_index = num_output_ports_++;
544 break;
545 }
546 port->set_web_port_index(web_port_index);
547 ports()->push_back(port.Pass());
548 return web_port_index;
549}
550
agoodeb09423b2015-05-11 11:39:57 -0700551MidiManagerAlsa::AlsaSeqState::AlsaSeqState()
552 : clients_deleter_(&clients_), card_client_count_(0) {
agoode99d63292015-04-13 08:39:25 -0700553}
554
agoodeb0582872015-05-20 05:22:24 -0700555MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700556
557void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
558 const std::string& client_name,
559 snd_seq_client_type_t type) {
560 ClientExit(client_id);
561 clients_[client_id] = new Client(client_name, type);
agoodeb09423b2015-05-11 11:39:57 -0700562 if (IsCardClient(type, client_id))
563 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700564}
565
566bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
567 return clients_.find(client_id) != clients_.end();
568}
569
570void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
571 auto it = clients_.find(client_id);
572 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700573 if (IsCardClient(it->second->type(), client_id))
574 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700575 delete it->second;
576 clients_.erase(it);
577 }
578}
579
580void MidiManagerAlsa::AlsaSeqState::PortStart(
581 int client_id,
582 int port_id,
583 const std::string& port_name,
584 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
585 bool midi) {
586 auto it = clients_.find(client_id);
587 if (it != clients_.end())
588 it->second->AddPort(port_id,
589 scoped_ptr<Port>(new Port(port_name, direction, midi)));
590}
591
592void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
593 auto it = clients_.find(client_id);
594 if (it != clients_.end())
595 it->second->RemovePort(port_id);
596}
597
598snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
599 int client_id) const {
600 auto it = clients_.find(client_id);
601 if (it == clients_.end())
602 return SND_SEQ_USER_CLIENT;
603 return it->second->type();
604}
605
606scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700607MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
agoode99d63292015-04-13 08:39:25 -0700608 scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
609 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700610 // TODO(agoode): Use more information from udev, to allow hardware matching.
611 // See http://crbug.com/486471.
612 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700613
agoodeb09423b2015-05-11 11:39:57 -0700614 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700615 for (const auto& client_pair : clients_) {
616 int client_id = client_pair.first;
617 const auto& client = client_pair.second;
618
619 // Get client metadata.
620 const std::string client_name = client->name();
621 std::string manufacturer;
622 std::string driver;
623 std::string path;
624 std::string id;
625 std::string serial;
626 std::string card_name;
627 std::string card_longname;
628 int midi_device = -1;
629
agoodeb09423b2015-05-11 11:39:57 -0700630 if (IsCardClient(client->type(), client_id)) {
631 auto& card = card_it->second;
632 if (card_midi_device == -1)
633 card_midi_device = 0;
634
635 manufacturer = card->manufacturer();
636 midi_device = card_midi_device;
637
638 ++card_midi_device;
639 if (card_midi_device >= card->midi_device_count()) {
640 card_midi_device = -1;
641 ++card_it;
642 }
643 }
644
agoode99d63292015-04-13 08:39:25 -0700645 for (const auto& port_pair : *client) {
646 int port_id = port_pair.first;
647 const auto& port = port_pair.second;
648
649 if (port->midi()) {
650 std::string version;
651 if (!driver.empty()) {
652 version = driver + " / ";
653 }
654 version +=
655 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
656 SND_LIB_MINOR, SND_LIB_SUBMINOR);
657 PortDirection direction = port->direction();
658 if (direction == PortDirection::kInput ||
659 direction == PortDirection::kDuplex) {
660 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
661 path, id, client_id, port_id, midi_device, client->name(),
662 port->name(), manufacturer, version, MidiPort::Type::kInput)));
663 }
664 if (direction == PortDirection::kOutput ||
665 direction == PortDirection::kDuplex) {
666 midi_ports->Insert(scoped_ptr<MidiPort>(new MidiPort(
667 path, id, client_id, port_id, midi_device, client->name(),
668 port->name(), manufacturer, version, MidiPort::Type::kOutput)));
669 }
670 }
671 }
672 }
673
674 return midi_ports.Pass();
675}
676
677MidiManagerAlsa::AlsaSeqState::Port::Port(
678 const std::string& name,
679 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
680 bool midi)
681 : name_(name), direction_(direction), midi_(midi) {
682}
683
agoodeb0582872015-05-20 05:22:24 -0700684MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700685
686MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
687 snd_seq_client_type_t type)
688 : name_(name), type_(type), ports_deleter_(&ports_) {
689}
690
agoodeb0582872015-05-20 05:22:24 -0700691MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700692
693void MidiManagerAlsa::AlsaSeqState::Client::AddPort(int addr,
694 scoped_ptr<Port> port) {
695 RemovePort(addr);
696 ports_[addr] = port.release();
697}
698
699void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
700 auto it = ports_.find(addr);
701 if (it != ports_.end()) {
702 delete it->second;
703 ports_.erase(it);
704 }
705}
706
707MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
708MidiManagerAlsa::AlsaSeqState::Client::begin() const {
709 return ports_.begin();
710}
711
712MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
713MidiManagerAlsa::AlsaSeqState::Client::end() const {
714 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700715}
716
agoodeb09423b2015-05-11 11:39:57 -0700717MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
718 const std::string& alsa_name,
719 const std::string& alsa_longname,
720 const std::string& alsa_driver,
721 int midi_device_count)
722 : alsa_name_(alsa_name),
723 alsa_longname_(alsa_longname),
724 alsa_driver_(alsa_driver),
725 midi_device_count_(midi_device_count) {
726 // Try to get the vendor string. Sometimes it is encoded.
727 std::string vendor = device::UdevDecodeString(
728 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
729 // Sometimes it is not encoded.
730 if (vendor.empty())
731 vendor =
732 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
733 // Also get the vendor string from the hardware database.
734 std::string vendor_from_database =
735 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase);
736
737 // Get the device path.
738 path_ = device::UdevDeviceGetPropertyValue(dev, kUdevIdPath);
739 // Get the bus.
740 bus_ = device::UdevDeviceGetPropertyValue(dev, kUdevIdBus);
741
742 // Get the "serial" number. (Often untrustable or missing.)
743 serial_ =
744 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdSerialShort, kSysattrGuid);
745
746 // Get the vendor id, by either property or sysattr.
747 vendor_id_ =
748 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor);
749 // Get the model id, by either property or sysattr.
750 model_id_ =
751 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel);
752 // Get the usb interface number.
753 usb_interface_num_ =
754 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum);
755 manufacturer_ = ExtractManufacturerString(
756 vendor, vendor_id_, vendor_from_database, alsa_name, alsa_longname);
757}
758
agoodeb0582872015-05-20 05:22:24 -0700759MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700760
agoode55a8b522015-03-08 12:40:17 -0700761// static
agoodeb09423b2015-05-11 11:39:57 -0700762std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700763 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700764 const std::string& udev_id_vendor_id,
765 const std::string& udev_id_vendor_from_database,
766 const std::string& alsa_name,
767 const std::string& alsa_longname) {
768 // Let's try to determine the manufacturer. Here is the ordered preference
769 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700770 // 1. Vendor name from the hardware device string, from udev properties
771 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700772 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700773 // 3. Heuristic from ALSA.
774
agoodee83758c2015-03-23 22:07:54 -0700775 // Is the vendor string present and not just the vendor hex id?
776 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700777 return udev_id_vendor;
778 }
779
780 // Is there a vendor string in the hardware database?
781 if (!udev_id_vendor_from_database.empty()) {
782 return udev_id_vendor_from_database;
783 }
784
785 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
786 // We assume that card longname is in the format of
787 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
788 // a manufacturer name here.
789 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700790 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700791 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700792 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700793 return alsa_longname.substr(0, name_index - 1);
794 }
795
796 // Failure.
797 return "";
798}
799
agoodebd4be9b2015-03-16 19:17:25 -0700800void MidiManagerAlsa::SendMidiData(uint32 port_index,
801 const std::vector<uint8>& data) {
802 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
803
agoodef212b2a2015-03-19 12:53:23 -0700804 snd_midi_event_t* encoder;
805 snd_midi_event_new(kSendBufferSize, &encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700806 for (unsigned int i = 0; i < data.size(); i++) {
807 snd_seq_event_t event;
808 int result = snd_midi_event_encode_byte(encoder, data[i], &event);
809 if (result == 1) {
810 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700811 base::AutoLock lock(out_ports_lock_);
812 auto it = out_ports_.find(port_index);
813 if (it != out_ports_.end()) {
814 snd_seq_ev_set_source(&event, it->second);
815 snd_seq_ev_set_subs(&event);
816 snd_seq_ev_set_direct(&event);
817 snd_seq_event_output_direct(out_client_, &event);
818 }
agoodebd4be9b2015-03-16 19:17:25 -0700819 }
820 }
agoodef212b2a2015-03-19 12:53:23 -0700821 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700822}
823
824void MidiManagerAlsa::ScheduleEventLoop() {
825 event_thread_.message_loop()->PostTask(
826 FROM_HERE,
827 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
828}
829
830void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700831 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700832
agoode975043d2015-05-11 00:46:17 -0700833 struct pollfd pfd[2];
834 snd_seq_poll_descriptors(in_client_, &pfd[0], 1, POLLIN);
835 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
836 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700837
agoode975043d2015-05-11 00:46:17 -0700838 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
839 if (err < 0) {
840 VLOG(1) << "poll fails: " << safe_strerror(errno);
841 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700842 } else {
agoode975043d2015-05-11 00:46:17 -0700843 if (pfd[0].revents & POLLIN) {
844 // Read available incoming MIDI data.
845 int remaining;
846 double timestamp =
847 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
848 do {
849 snd_seq_event_t* event;
850 err = snd_seq_event_input(in_client_, &event);
851 remaining = snd_seq_event_input_pending(in_client_, 0);
852
853 if (err == -ENOSPC) {
854 // Handle out of space error.
855 VLOG(1) << "snd_seq_event_input detected buffer overrun";
856 // We've lost events: check another way to see if we need to shut
857 // down.
858 base::AutoLock lock(shutdown_lock_);
859 if (event_thread_shutdown_)
860 loop_again = false;
861 } else if (err == -EAGAIN) {
862 // We've read all the data.
863 } else if (err < 0) {
864 // Handle other errors.
865 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
866 // TODO(agoode): Use RecordAction() or similar to log this.
867 loop_again = false;
868 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
869 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
870 // Handle announce events.
871 switch (event->type) {
872 case SND_SEQ_EVENT_PORT_START:
873 // Don't use SND_SEQ_EVENT_CLIENT_START because the
874 // client name may not be set by the time we query
875 // it. It should be set by the time ports are made.
876 ProcessClientStartEvent(event->data.addr.client);
877 ProcessPortStartEvent(event->data.addr);
878 break;
879 case SND_SEQ_EVENT_CLIENT_EXIT:
880 // Check for disconnection of our "out" client. This means "shut
881 // down".
882 if (event->data.addr.client == out_client_id_) {
883 loop_again = false;
884 remaining = 0;
885 } else
886 ProcessClientExitEvent(event->data.addr);
887 break;
888 case SND_SEQ_EVENT_PORT_EXIT:
889 ProcessPortExitEvent(event->data.addr);
890 break;
891 }
892 } else {
893 // Normal operation.
894 ProcessSingleEvent(event, timestamp);
895 }
896 } while (remaining > 0);
897 }
898 if (pfd[1].revents & POLLIN) {
899 device::ScopedUdevDevicePtr dev(
900 device::udev_monitor_receive_device(udev_monitor_.get()));
901 if (dev.get())
902 ProcessUdevEvent(dev.get());
903 else
904 VLOG(1) << "udev_monitor_receive_device fails";
905 }
agoodebd4be9b2015-03-16 19:17:25 -0700906 }
907
agoodebd4be9b2015-03-16 19:17:25 -0700908 // Do again.
agoode975043d2015-05-11 00:46:17 -0700909 if (loop_again)
910 ScheduleEventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700911}
912
913void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
914 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700915 auto source_it =
916 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700917 if (source_it != source_map_.end()) {
918 uint32 source = source_it->second;
919 if (event->type == SND_SEQ_EVENT_SYSEX) {
920 // Special! Variable-length sysex.
921 ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
922 event->data.ext.len, timestamp);
923 } else {
924 // Otherwise, decode this and send that on.
925 unsigned char buf[12];
926 long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
927 if (count <= 0) {
928 if (count != -ENOENT) {
929 // ENOENT means that it's not a MIDI message, which is not an
930 // error, but other negative values are errors for us.
931 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
932 // TODO(agoode): Record this failure.
933 }
934 } else {
935 ReceiveMidiData(source, buf, count, timestamp);
936 }
937 }
938 }
939}
940
agoode99d63292015-04-13 08:39:25 -0700941void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
942 // Ignore if client is already started.
943 if (alsa_seq_state_.ClientStarted(client_id))
944 return;
945
946 snd_seq_client_info_t* client_info;
947 snd_seq_client_info_alloca(&client_info);
948 int err = snd_seq_get_any_client_info(in_client_, client_id, client_info);
949 if (err != 0)
950 return;
951
952 // Skip our own clients.
953 if ((client_id == in_client_id_) || (client_id == out_client_id_))
954 return;
955
956 // Update our view of ALSA seq state.
957 alsa_seq_state_.ClientStart(client_id,
958 snd_seq_client_info_get_name(client_info),
959 snd_seq_client_info_get_type(client_info));
960
961 // Generate Web MIDI events.
962 UpdatePortStateAndGenerateEvents();
963}
964
965void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
966 snd_seq_port_info_t* port_info;
967 snd_seq_port_info_alloca(&port_info);
968 int err =
969 snd_seq_get_any_port_info(in_client_, addr.client, addr.port, port_info);
970 if (err != 0)
971 return;
972
973 unsigned int caps = snd_seq_port_info_get_capability(port_info);
974 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
975 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
976 AlsaSeqState::PortDirection direction;
977 if (input && output)
978 direction = AlsaSeqState::PortDirection::kDuplex;
979 else if (input)
980 direction = AlsaSeqState::PortDirection::kInput;
981 else if (output)
982 direction = AlsaSeqState::PortDirection::kOutput;
983 else
984 return;
985
986 // Update our view of ALSA seq state.
987 alsa_seq_state_.PortStart(
988 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
989 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
990 // Generate Web MIDI events.
991 UpdatePortStateAndGenerateEvents();
992}
993
994void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
995 // Update our view of ALSA seq state.
996 alsa_seq_state_.ClientExit(addr.client);
997 // Generate Web MIDI events.
998 UpdatePortStateAndGenerateEvents();
999}
1000
1001void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1002 // Update our view of ALSA seq state.
1003 alsa_seq_state_.PortExit(addr.client, addr.port);
1004 // Generate Web MIDI events.
1005 UpdatePortStateAndGenerateEvents();
1006}
1007
agoode975043d2015-05-11 00:46:17 -07001008void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1009 // Only card devices have this property set, and only when they are
1010 // fully initialized.
1011 if (!device::udev_device_get_property_value(dev,
1012 kUdevPropertySoundInitialized))
1013 return;
1014
1015 // Get the action. If no action, then we are doing first time enumeration
1016 // and the device is treated as new.
1017 const char* action = device::udev_device_get_action(dev);
1018 if (!action)
1019 action = kUdevActionChange;
1020
1021 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001022 AddCard(dev);
1023 // Generate Web MIDI events.
1024 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001025 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001026 RemoveCard(GetCardNumber(dev));
1027 // Generate Web MIDI events.
1028 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001029 }
1030}
1031
agoodeb09423b2015-05-11 11:39:57 -07001032void MidiManagerAlsa::AddCard(udev_device* dev) {
1033 int number = GetCardNumber(dev);
1034 if (number == -1)
1035 return;
1036
1037 RemoveCard(number);
1038
1039 snd_ctl_card_info_t* card;
1040 snd_hwdep_info_t* hwdep;
1041 snd_ctl_card_info_alloca(&card);
1042 snd_hwdep_info_alloca(&hwdep);
1043 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1044 snd_ctl_t* handle;
1045 int err = snd_ctl_open(&handle, id.c_str(), 0);
1046 if (err != 0) {
1047 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1048 return;
1049 }
1050 err = snd_ctl_card_info(handle, card);
1051 if (err != 0) {
1052 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1053 snd_ctl_close(handle);
1054 return;
1055 }
1056 std::string name = snd_ctl_card_info_get_name(card);
1057 std::string longname = snd_ctl_card_info_get_longname(card);
1058 std::string driver = snd_ctl_card_info_get_driver(card);
1059
1060 // Count rawmidi devices (not subdevices).
1061 int midi_count = 0;
1062 for (int device = -1;
1063 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1064 ++midi_count;
1065
1066 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1067 //
1068 // Explanation:
1069 // Any kernel driver can create an ALSA client (visible to us).
1070 // With modern hardware, only rawmidi devices do this. Kernel
1071 // drivers create rawmidi devices and the rawmidi subsystem makes
1072 // the seq clients. But the OPL3 driver is special, it does not
1073 // make a rawmidi device but a seq client directly. (This is the
1074 // only one to worry about in the kernel code, as of 2015-03-23.)
1075 //
1076 // OPL3 is very old (but still possible to get in new
1077 // hardware). It is unlikely that new drivers would not use
1078 // rawmidi and defeat our heuristic.
1079 //
1080 // Longer term, support should be added in the kernel to expose a
1081 // direct link from card->client (or client->card) so that all
1082 // these heuristics will be obsolete. Once that is there, we can
1083 // assume our old heuristics will work on old kernels and the new
1084 // robust code will be used on new. Then we will not need to worry
1085 // about changes to kernel internals breaking our code.
1086 // See the TODO above at kMinimumClientIdForCards.
1087 for (int device = -1;
1088 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1089 err = snd_ctl_hwdep_info(handle, hwdep);
1090 if (err != 0) {
1091 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1092 continue;
1093 }
1094 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1095 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1096 iface == SND_HWDEP_IFACE_OPL4)
1097 ++midi_count;
1098 }
1099 snd_ctl_close(handle);
1100
1101 if (midi_count > 0)
1102 alsa_cards_[number] = new AlsaCard(dev, name, longname, driver, midi_count);
1103 alsa_card_midi_count_ += midi_count;
1104}
1105
1106void MidiManagerAlsa::RemoveCard(int number) {
1107 auto it = alsa_cards_.find(number);
1108 if (it == alsa_cards_.end())
1109 return;
1110
1111 alsa_card_midi_count_ -= it->second->midi_device_count();
1112 delete it->second;
1113 alsa_cards_.erase(it);
1114}
1115
agoode99d63292015-04-13 08:39:25 -07001116void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001117 // Verify that our information from ALSA and udev are in sync. If
1118 // not, we cannot generate events right now.
1119 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1120 return;
1121
agoode99d63292015-04-13 08:39:25 -07001122 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001123 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001124
1125 // Disconnect any connected old ports that are now missing.
1126 for (auto* old_port : port_state_) {
1127 if (old_port->connected() &&
1128 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1129 old_port->set_connected(false);
1130 uint32 web_port_index = old_port->web_port_index();
1131 switch (old_port->type()) {
1132 case MidiPort::Type::kInput:
1133 source_map_.erase(
1134 AddrToInt(old_port->client_id(), old_port->port_id()));
1135 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1136 break;
1137 case MidiPort::Type::kOutput:
1138 DeleteAlsaOutputPort(web_port_index);
1139 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1140 break;
1141 }
1142 }
1143 }
1144
1145 // Reconnect or add new ports.
1146 auto it = new_port_state->begin();
1147 while (it != new_port_state->end()) {
1148 auto* new_port = *it;
1149 auto old_port = port_state_.Find(*new_port);
1150 if (old_port == port_state_.end()) {
1151 // Add new port.
1152 uint32 web_port_index =
1153 port_state_.Insert(scoped_ptr<MidiPort>(new_port));
1154 MidiPortInfo info(new_port->OpaqueKey(), new_port->manufacturer(),
1155 new_port->port_name(), new_port->version(),
1156 MIDI_PORT_OPENED);
1157 switch (new_port->type()) {
1158 case MidiPort::Type::kInput:
1159 if (Subscribe(web_port_index, new_port->client_id(),
1160 new_port->port_id()))
1161 AddInputPort(info);
1162 break;
agoode99d63292015-04-13 08:39:25 -07001163 case MidiPort::Type::kOutput:
1164 if (CreateAlsaOutputPort(web_port_index, new_port->client_id(),
1165 new_port->port_id()))
1166 AddOutputPort(info);
1167 break;
1168 }
1169 it = new_port_state->weak_erase(it);
1170 } else if (!(*old_port)->connected()) {
1171 // Reconnect.
1172 uint32 web_port_index = (*old_port)->web_port_index();
1173 (*old_port)->Update(new_port->path(), new_port->client_id(),
1174 new_port->port_id(), new_port->client_name(),
1175 new_port->port_name(), new_port->manufacturer(),
1176 new_port->version());
1177 switch ((*old_port)->type()) {
1178 case MidiPort::Type::kInput:
1179 if (Subscribe(web_port_index, (*old_port)->client_id(),
1180 (*old_port)->port_id()))
1181 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1182 break;
agoode99d63292015-04-13 08:39:25 -07001183 case MidiPort::Type::kOutput:
1184 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1185 (*old_port)->port_id()))
1186 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1187 break;
1188 }
1189 (*old_port)->set_connected(true);
1190 ++it;
1191 } else {
1192 ++it;
1193 }
1194 }
1195}
1196
agoode975043d2015-05-11 00:46:17 -07001197// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001198void MidiManagerAlsa::EnumerateAlsaPorts() {
1199 snd_seq_client_info_t* client_info;
1200 snd_seq_client_info_alloca(&client_info);
1201 snd_seq_port_info_t* port_info;
1202 snd_seq_port_info_alloca(&port_info);
1203
1204 // Enumerate clients.
1205 snd_seq_client_info_set_client(client_info, -1);
1206 while (!snd_seq_query_next_client(in_client_, client_info)) {
1207 int client_id = snd_seq_client_info_get_client(client_info);
1208 ProcessClientStartEvent(client_id);
1209
1210 // Enumerate ports.
1211 snd_seq_port_info_set_client(port_info, client_id);
1212 snd_seq_port_info_set_port(port_info, -1);
1213 while (!snd_seq_query_next_port(in_client_, port_info)) {
1214 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1215 ProcessPortStartEvent(*addr);
1216 }
1217 }
1218}
1219
agoode975043d2015-05-11 00:46:17 -07001220bool MidiManagerAlsa::EnumerateUdevCards() {
1221 int err;
1222
1223 device::ScopedUdevEnumeratePtr enumerate(
1224 device::udev_enumerate_new(udev_.get()));
1225 if (!enumerate.get()) {
1226 VLOG(1) << "udev_enumerate_new fails";
1227 return false;
1228 }
1229
1230 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1231 kUdevSubsystemSound);
1232 if (err) {
1233 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
1234 << safe_strerror(-err);
1235 return false;
1236 }
1237
1238 err = device::udev_enumerate_scan_devices(enumerate.get());
1239 if (err) {
1240 VLOG(1) << "udev_enumerate_scan_devices fails: " << safe_strerror(-err);
1241 return false;
1242 }
1243
1244 udev_list_entry* list_entry;
1245 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1246 udev_list_entry_foreach(list_entry, devices) {
1247 const char* path = device::udev_list_entry_get_name(list_entry);
1248 device::ScopedUdevDevicePtr dev(
1249 device::udev_device_new_from_syspath(udev_.get(), path));
1250 if (dev.get())
1251 ProcessUdevEvent(dev.get());
1252 }
1253
1254 return true;
1255}
1256
agoode99d63292015-04-13 08:39:25 -07001257bool MidiManagerAlsa::CreateAlsaOutputPort(uint32 port_index,
1258 int client_id,
1259 int port_id) {
1260 // Create the port.
1261 int out_port = snd_seq_create_simple_port(
1262 out_client_, NULL, kCreateOutputPortCaps, kCreatePortType);
1263 if (out_port < 0) {
1264 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1265 return false;
1266 }
1267 // Activate port subscription.
1268 snd_seq_port_subscribe_t* subs;
1269 snd_seq_port_subscribe_alloca(&subs);
1270 snd_seq_addr_t sender;
1271 sender.client = out_client_id_;
1272 sender.port = out_port;
1273 snd_seq_port_subscribe_set_sender(subs, &sender);
1274 snd_seq_addr_t dest;
1275 dest.client = client_id;
1276 dest.port = port_id;
1277 snd_seq_port_subscribe_set_dest(subs, &dest);
1278 int err = snd_seq_subscribe_port(out_client_, subs);
1279 if (err != 0) {
1280 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1281 snd_seq_delete_simple_port(out_client_, out_port);
1282 return false;
1283 }
1284
1285 // Update our map.
1286 base::AutoLock lock(out_ports_lock_);
1287 out_ports_[port_index] = out_port;
1288 return true;
1289}
1290
1291void MidiManagerAlsa::DeleteAlsaOutputPort(uint32 port_index) {
1292 base::AutoLock lock(out_ports_lock_);
1293 auto it = out_ports_.find(port_index);
1294 if (it == out_ports_.end())
1295 return;
1296
1297 int alsa_port = it->second;
1298 snd_seq_delete_simple_port(out_client_, alsa_port);
1299 out_ports_.erase(it);
1300}
1301
1302bool MidiManagerAlsa::Subscribe(uint32 port_index, int client_id, int port_id) {
1303 // Activate port subscription.
1304 snd_seq_port_subscribe_t* subs;
1305 snd_seq_port_subscribe_alloca(&subs);
1306 snd_seq_addr_t sender;
1307 sender.client = client_id;
1308 sender.port = port_id;
1309 snd_seq_port_subscribe_set_sender(subs, &sender);
1310 snd_seq_addr_t dest;
1311 dest.client = in_client_id_;
1312 dest.port = in_port_id_;
1313 snd_seq_port_subscribe_set_dest(subs, &dest);
1314 int err = snd_seq_subscribe_port(in_client_, subs);
1315 if (err != 0) {
1316 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1317 return false;
1318 }
1319
1320 // Update our map.
1321 source_map_[AddrToInt(client_id, port_id)] = port_index;
1322 return true;
1323}
1324
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +00001325MidiManager* MidiManager::Create() {
1326 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001327}
1328
toyoshime147c5e2015-05-07 21:58:31 -07001329} // namespace midi
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001330} // namespace media