1 // 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
5 #include "media/midi/midi_manager_alsa.h"
6
7 #include <errno.h>
8 #include <poll.h>
9 #include <stddef.h>
10 #include <stdlib.h>
11
12 #include <algorithm>
13 #include <string>
14 #include <utility>
15
16 #include "base/bind.h"
17 #include "base/json/json_string_value_serializer.h"
18 #include "base/logging.h"
19 #include "base/posix/eintr_wrapper.h"
20 #include "base/posix/safe_strerror.h"
21 #include "base/single_thread_task_runner.h"
22 #include "base/stl_util.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/time/time.h"
26 #include "build/build_config.h"
27 #include "crypto/sha2.h"
28 #include "media/midi/midi_service.h"
29 #include "media/midi/midi_service.mojom.h"
30 #include "media/midi/task_service.h"
31
32 namespace midi {
33
34 namespace {
35
36 using mojom::PortState;
37 using mojom::Result;
38
39 enum {
40 kDefaultRunnerNotUsedOnAlsa = TaskService::kDefaultRunnerId,
41 kEventTaskRunner,
42 kSendTaskRunner
43 };
44
45 // Per-output buffer. This can be smaller, but then large sysex messages
46 // will be (harmlessly) split across multiple seq events. This should
47 // not have any real practical effect, except perhaps to slightly reorder
48 // realtime messages with respect to sysex.
49 constexpr size_t kSendBufferSize = 256;
50
51 // Minimum client id for which we will have ALSA card devices for. When we
52 // are searching for card devices (used to get the path, id, and manufacturer),
53 // we don't want to get confused by kernel clients that do not have a card.
54 // See seq_clientmgr.c in the ALSA code for this.
55 // TODO(agoode): Add proper client -> card export from the kernel to avoid
56 // hardcoding.
57 constexpr int kMinimumClientIdForCards = 16;
58
59 // ALSA constants.
60 const char kAlsaHw[] = "hw";
61
62 // udev constants.
63 const char kUdev[] = "udev";
64 const char kUdevSubsystemSound[] = "sound";
65 const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
66 const char kUdevActionChange[] = "change";
67 const char kUdevActionRemove[] = "remove";
68
69 const char kUdevIdVendor[] = "ID_VENDOR";
70 const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
71 const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
72 const char kUdevIdVendorId[] = "ID_VENDOR_ID";
73 const char kUdevIdModelId[] = "ID_MODEL_ID";
74 const char kUdevIdBus[] = "ID_BUS";
75 const char kUdevIdPath[] = "ID_PATH";
76 const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
77 const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
78
79 const char kSysattrVendorName[] = "vendor_name";
80 const char kSysattrVendor[] = "vendor";
81 const char kSysattrModel[] = "model";
82 const char kSysattrGuid[] = "guid";
83
84 const char kCardSyspath[] = "/card";
85
86 // Constants for the capabilities we search for in inputs and outputs.
87 // See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
88 constexpr unsigned int kRequiredInputPortCaps =
89 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
90 constexpr unsigned int kRequiredOutputPortCaps =
91 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
92
93 constexpr unsigned int kCreateOutputPortCaps =
94 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
95 constexpr unsigned int kCreateInputPortCaps =
96 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
97 constexpr unsigned int kCreatePortType =
98 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
99
AddrToInt(int client,int port)100 int AddrToInt(int client, int port) {
101 return (client << 8) | port;
102 }
103
104 // Returns true if this client has an ALSA card associated with it.
IsCardClient(snd_seq_client_type_t type,int client_id)105 bool IsCardClient(snd_seq_client_type_t type, int client_id) {
106 return (type == SND_SEQ_KERNEL_CLIENT) &&
107 (client_id >= kMinimumClientIdForCards);
108 }
109
110 // TODO(agoode): Move this to device/udev_linux.
UdevDeviceGetPropertyOrSysattr(struct udev_device * udev_device,const char * property_key,const char * sysattr_key)111 const std::string UdevDeviceGetPropertyOrSysattr(
112 struct udev_device* udev_device,
113 const char* property_key,
114 const char* sysattr_key) {
115 // First try the property.
116 std::string value =
117 device::UdevDeviceGetPropertyValue(udev_device, property_key);
118
119 // If no property, look for sysattrs and walk up the parent devices too.
120 while (value.empty() && udev_device) {
121 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
122 udev_device = device::udev_device_get_parent(udev_device);
123 }
124 return value;
125 }
126
GetCardNumber(udev_device * dev)127 int GetCardNumber(udev_device* dev) {
128 const char* syspath = device::udev_device_get_syspath(dev);
129 if (!syspath)
130 return -1;
131
132 std::string syspath_str(syspath);
133 size_t i = syspath_str.rfind(kCardSyspath);
134 if (i == std::string::npos)
135 return -1;
136
137 int number;
138 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
139 return -1;
140 return number;
141 }
142
GetVendor(udev_device * dev)143 std::string GetVendor(udev_device* dev) {
144 // Try to get the vendor string. Sometimes it is encoded.
145 std::string vendor = device::UdevDecodeString(
146 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
147 // Sometimes it is not encoded.
148 if (vendor.empty())
149 vendor =
150 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
151 return vendor;
152 }
153
SetStringIfNonEmpty(base::DictionaryValue * value,const std::string & path,const std::string & in_value)154 void SetStringIfNonEmpty(base::DictionaryValue* value,
155 const std::string& path,
156 const std::string& in_value) {
157 if (!in_value.empty())
158 value->SetString(path, in_value);
159 }
160
161 } // namespace
162
MidiManagerAlsa(MidiService * service)163 MidiManagerAlsa::MidiManagerAlsa(MidiService* service) : MidiManager(service) {}
164
~MidiManagerAlsa()165 MidiManagerAlsa::~MidiManagerAlsa() {
166 {
167 base::AutoLock lock(out_client_lock_);
168 // Close the out client. This will trigger the event thread to stop,
169 // because of SND_SEQ_EVENT_CLIENT_EXIT.
170 out_client_.reset();
171 }
172 // Ensure that no task is running any more.
173 if (!service()->task_service()->UnbindInstance())
174 return;
175
176 // |out_client_| should be reset before UnbindInstance() call to avoid
177 // a deadlock, but other finalization steps should be implemented after the
178 // UnbindInstance() call above, if we need.
179 }
180
StartInitialization()181 void MidiManagerAlsa::StartInitialization() {
182 if (!service()->task_service()->BindInstance())
183 return CompleteInitialization(Result::INITIALIZATION_ERROR);
184
185 // Create client handles and name the clients.
186 int err;
187 {
188 snd_seq_t* in_seq = nullptr;
189 err = snd_seq_open(&in_seq, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
190 if (err != 0) {
191 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
192 return CompleteInitialization(Result::INITIALIZATION_ERROR);
193 }
194 in_client_ = ScopedSndSeqPtr(in_seq);
195 in_client_id_ = snd_seq_client_id(in_client_.get());
196 err = snd_seq_set_client_name(in_client_.get(), "Chrome (input)");
197 if (err != 0) {
198 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
199 return CompleteInitialization(Result::INITIALIZATION_ERROR);
200 }
201 }
202
203 {
204 snd_seq_t* out_seq = nullptr;
205 err = snd_seq_open(&out_seq, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
206 if (err != 0) {
207 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
208 return CompleteInitialization(Result::INITIALIZATION_ERROR);
209 }
210 base::AutoLock lock(out_client_lock_);
211 out_client_ = ScopedSndSeqPtr(out_seq);
212 out_client_id_ = snd_seq_client_id(out_client_.get());
213 err = snd_seq_set_client_name(out_client_.get(), "Chrome (output)");
214 if (err != 0) {
215 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
216 return CompleteInitialization(Result::INITIALIZATION_ERROR);
217 }
218 }
219
220 // Create input port.
221 in_port_id_ = snd_seq_create_simple_port(
222 in_client_.get(), NULL, kCreateInputPortCaps, kCreatePortType);
223 if (in_port_id_ < 0) {
224 VLOG(1) << "snd_seq_create_simple_port fails: "
225 << snd_strerror(in_port_id_);
226 return CompleteInitialization(Result::INITIALIZATION_ERROR);
227 }
228
229 // Subscribe to the announce port.
230 snd_seq_port_subscribe_t* subs;
231 snd_seq_port_subscribe_alloca(&subs);
232 snd_seq_addr_t announce_sender;
233 snd_seq_addr_t announce_dest;
234 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
235 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
236 announce_dest.client = in_client_id_;
237 announce_dest.port = in_port_id_;
238 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
239 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
240 err = snd_seq_subscribe_port(in_client_.get(), subs);
241 if (err != 0) {
242 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
243 << snd_strerror(err);
244 return CompleteInitialization(Result::INITIALIZATION_ERROR);
245 }
246
247 // Initialize decoder.
248 decoder_ = CreateScopedSndMidiEventPtr(0);
249 snd_midi_event_no_status(decoder_.get(), 1);
250
251 // Initialize udev and monitor.
252 udev_ = device::ScopedUdevPtr(device::udev_new());
253 udev_monitor_ = device::ScopedUdevMonitorPtr(
254 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
255 if (!udev_monitor_.get()) {
256 VLOG(1) << "udev_monitor_new_from_netlink fails";
257 return CompleteInitialization(Result::INITIALIZATION_ERROR);
258 }
259 err = device::udev_monitor_filter_add_match_subsystem_devtype(
260 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
261 if (err != 0) {
262 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
263 << base::safe_strerror(-err);
264 return CompleteInitialization(Result::INITIALIZATION_ERROR);
265 }
266 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
267 if (err != 0) {
268 VLOG(1) << "udev_monitor_enable_receiving fails: "
269 << base::safe_strerror(-err);
270 return CompleteInitialization(Result::INITIALIZATION_ERROR);
271 }
272
273 // Generate hotplug events for existing ports.
274 // TODO(agoode): Check the return value for failure.
275 EnumerateAlsaPorts();
276
277 // Generate hotplug events for existing udev devices. This must be done
278 // after udev_monitor_enable_receiving() is called. See the algorithm
279 // at http://www.signal11.us/oss/udev/.
280 EnumerateUdevCards();
281
282 // Start processing events. Don't do this before enumeration of both
283 // ALSA and udev.
284 service()->task_service()->PostBoundTask(
285 kEventTaskRunner,
286 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
287
288 CompleteInitialization(Result::OK);
289 }
290
DispatchSendMidiData(MidiManagerClient * client,uint32_t port_index,const std::vector<uint8_t> & data,base::TimeTicks timestamp)291 void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
292 uint32_t port_index,
293 const std::vector<uint8_t>& data,
294 base::TimeTicks timestamp) {
295 service()->task_service()->PostBoundDelayedTask(
296 kSendTaskRunner,
297 base::BindOnce(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
298 client, port_index, data),
299 MidiService::TimestampToTimeDeltaDelay(timestamp));
300 }
301
302 MidiManagerAlsa::MidiPort::Id::Id() = default;
303
Id(const std::string & bus,const std::string & vendor_id,const std::string & model_id,const std::string & usb_interface_num,const std::string & serial)304 MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
305 const std::string& vendor_id,
306 const std::string& model_id,
307 const std::string& usb_interface_num,
308 const std::string& serial)
309 : bus_(bus),
310 vendor_id_(vendor_id),
311 model_id_(model_id),
312 usb_interface_num_(usb_interface_num),
313 serial_(serial) {
314 }
315
316 MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
317
318 MidiManagerAlsa::MidiPort::Id::~Id() = default;
319
operator ==(const Id & rhs) const320 bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
321 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
322 (model_id_ == rhs.model_id_) &&
323 (usb_interface_num_ == rhs.usb_interface_num_) &&
324 (serial_ == rhs.serial_);
325 }
326
empty() const327 bool MidiManagerAlsa::MidiPort::Id::empty() const {
328 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
329 usb_interface_num_.empty() && serial_.empty();
330 }
331
MidiPort(const std::string & path,const Id & id,int client_id,int port_id,int midi_device,const std::string & client_name,const std::string & port_name,const std::string & manufacturer,const std::string & version,Type type)332 MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
333 const Id& id,
334 int client_id,
335 int port_id,
336 int midi_device,
337 const std::string& client_name,
338 const std::string& port_name,
339 const std::string& manufacturer,
340 const std::string& version,
341 Type type)
342 : id_(id),
343 midi_device_(midi_device),
344 type_(type),
345 path_(path),
346 client_id_(client_id),
347 port_id_(port_id),
348 client_name_(client_name),
349 port_name_(port_name),
350 manufacturer_(manufacturer),
351 version_(version) {
352 }
353
354 MidiManagerAlsa::MidiPort::~MidiPort() = default;
355
356 // Note: keep synchronized with the MidiPort::Match* methods.
Value() const357 std::unique_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
358 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
359
360 std::string type;
361 switch (type_) {
362 case Type::kInput:
363 type = "input";
364 break;
365 case Type::kOutput:
366 type = "output";
367 break;
368 }
369 value->SetString("type", type);
370 SetStringIfNonEmpty(value.get(), "path", path_);
371 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
372 SetStringIfNonEmpty(value.get(), "portName", port_name_);
373 value->SetInteger("clientId", client_id_);
374 value->SetInteger("portId", port_id_);
375 value->SetInteger("midiDevice", midi_device_);
376
377 // Flatten id fields.
378 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
379 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
380 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
381 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
382 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
383
384 return std::move(value);
385 }
386
JSONValue() const387 std::string MidiManagerAlsa::MidiPort::JSONValue() const {
388 std::string json;
389 JSONStringValueSerializer serializer(&json);
390 serializer.Serialize(*Value().get());
391 return json;
392 }
393
394 // TODO(agoode): Do not use SHA256 here. Instead store a persistent
395 // mapping and just use a UUID or other random string.
396 // http://crbug.com/465320
OpaqueKey() const397 std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
398 uint8_t hash[crypto::kSHA256Length];
399 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
400 return base::HexEncode(&hash, sizeof(hash));
401 }
402
MatchConnected(const MidiPort & query) const403 bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
404 // Matches on:
405 // connected == true
406 // type
407 // path
408 // id
409 // client_id
410 // port_id
411 // midi_device
412 // client_name
413 // port_name
414 return connected() && (type() == query.type()) && (path() == query.path()) &&
415 (id() == query.id()) && (client_id() == query.client_id()) &&
416 (port_id() == query.port_id()) &&
417 (midi_device() == query.midi_device()) &&
418 (client_name() == query.client_name()) &&
419 (port_name() == query.port_name());
420 }
421
MatchCardPass1(const MidiPort & query) const422 bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
423 // Matches on:
424 // connected == false
425 // type
426 // path
427 // id
428 // port_id
429 // midi_device
430 return MatchCardPass2(query) && (path() == query.path());
431 }
432
MatchCardPass2(const MidiPort & query) const433 bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
434 // Matches on:
435 // connected == false
436 // type
437 // id
438 // port_id
439 // midi_device
440 return !connected() && (type() == query.type()) && (id() == query.id()) &&
441 (port_id() == query.port_id()) &&
442 (midi_device() == query.midi_device());
443 }
444
MatchNoCardPass1(const MidiPort & query) const445 bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
446 // Matches on:
447 // connected == false
448 // type
449 // path.empty(), for both this and query
450 // id.empty(), for both this and query
451 // client_id
452 // port_id
453 // client_name
454 // port_name
455 // midi_device == -1, for both this and query
456 return MatchNoCardPass2(query) && (client_id() == query.client_id());
457 }
458
MatchNoCardPass2(const MidiPort & query) const459 bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
460 // Matches on:
461 // connected == false
462 // type
463 // path.empty(), for both this and query
464 // id.empty(), for both this and query
465 // port_id
466 // client_name
467 // port_name
468 // midi_device == -1, for both this and query
469 return !connected() && (type() == query.type()) && path().empty() &&
470 query.path().empty() && id().empty() && query.id().empty() &&
471 (port_id() == query.port_id()) &&
472 (client_name() == query.client_name()) &&
473 (port_name() == query.port_name()) && (midi_device() == -1) &&
474 (query.midi_device() == -1);
475 }
476
477 MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
478
479 MidiManagerAlsa::MidiPortStateBase::iterator
Find(const MidiManagerAlsa::MidiPort & port)480 MidiManagerAlsa::MidiPortStateBase::Find(
481 const MidiManagerAlsa::MidiPort& port) {
482 auto result = FindConnected(port);
483 if (result == end())
484 result = FindDisconnected(port);
485 return result;
486 }
487
488 MidiManagerAlsa::MidiPortStateBase::iterator
FindConnected(const MidiManagerAlsa::MidiPort & port)489 MidiManagerAlsa::MidiPortStateBase::FindConnected(
490 const MidiManagerAlsa::MidiPort& port) {
491 // Exact match required for connected ports.
492 auto it = std::find_if(ports_.begin(), ports_.end(),
493 [&port](std::unique_ptr<MidiPort>& p) {
494 return p->MatchConnected(port);
495 });
496 return it;
497 }
498
499 MidiManagerAlsa::MidiPortStateBase::iterator
FindDisconnected(const MidiManagerAlsa::MidiPort & port)500 MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
501 const MidiManagerAlsa::MidiPort& port) {
502 // Always match on:
503 // type
504 // Possible things to match on:
505 // path
506 // id
507 // client_id
508 // port_id
509 // midi_device
510 // client_name
511 // port_name
512
513 if (!port.path().empty()) {
514 // If path is present, then we have a card-based client.
515
516 // Pass 1. Match on path, id, midi_device, port_id.
517 // This is the best possible match for hardware card-based clients.
518 // This will also match the empty id correctly for devices without an id.
519 auto it = std::find_if(ports_.begin(), ports_.end(),
520 [&port](std::unique_ptr<MidiPort>& p) {
521 return p->MatchCardPass1(port);
522 });
523 if (it != ports_.end())
524 return it;
525
526 if (!port.id().empty()) {
527 // Pass 2. Match on id, midi_device, port_id.
528 // This will give us a high-confidence match when a user moves a device to
529 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
530 // has a hardware id.
531 it = std::find_if(ports_.begin(), ports_.end(),
532 [&port](std::unique_ptr<MidiPort>& p) {
533 return p->MatchCardPass2(port);
534 });
535 if (it != ports_.end())
536 return it;
537 }
538 } else {
539 // Else, we have a non-card-based client.
540 // Pass 1. Match on client_id, port_id, client_name, port_name.
541 // This will give us a reasonably good match.
542 auto it = std::find_if(ports_.begin(), ports_.end(),
543 [&port](std::unique_ptr<MidiPort>& p) {
544 return p->MatchNoCardPass1(port);
545 });
546 if (it != ports_.end())
547 return it;
548
549 // Pass 2. Match on port_id, client_name, port_name.
550 // This is weaker but similar to pass 2 in the hardware card-based clients
551 // match.
552 it = std::find_if(ports_.begin(), ports_.end(),
553 [&port](std::unique_ptr<MidiPort>& p) {
554 return p->MatchNoCardPass2(port);
555 });
556 if (it != ports_.end())
557 return it;
558 }
559
560 // No match.
561 return ports_.end();
562 }
563
564 MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
565
566 MidiManagerAlsa::MidiPortState::MidiPortState() = default;
567
push_back(std::unique_ptr<MidiPort> port)568 uint32_t MidiManagerAlsa::MidiPortState::push_back(
569 std::unique_ptr<MidiPort> port) {
570 // Add the web midi index.
571 uint32_t web_port_index = 0;
572 switch (port->type()) {
573 case MidiPort::Type::kInput:
574 web_port_index = num_input_ports_++;
575 break;
576 case MidiPort::Type::kOutput:
577 web_port_index = num_output_ports_++;
578 break;
579 }
580 port->set_web_port_index(web_port_index);
581 MidiPortStateBase::push_back(std::move(port));
582 return web_port_index;
583 }
584
585 MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
586
587 MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
588
ClientStart(int client_id,const std::string & client_name,snd_seq_client_type_t type)589 void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
590 const std::string& client_name,
591 snd_seq_client_type_t type) {
592 ClientExit(client_id);
593 clients_.insert(
594 std::make_pair(client_id, std::make_unique<Client>(client_name, type)));
595 if (IsCardClient(type, client_id))
596 ++card_client_count_;
597 }
598
ClientStarted(int client_id)599 bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
600 return clients_.find(client_id) != clients_.end();
601 }
602
ClientExit(int client_id)603 void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
604 auto it = clients_.find(client_id);
605 if (it != clients_.end()) {
606 if (IsCardClient(it->second->type(), client_id))
607 --card_client_count_;
608 clients_.erase(it);
609 }
610 }
611
PortStart(int client_id,int port_id,const std::string & port_name,MidiManagerAlsa::AlsaSeqState::PortDirection direction,bool midi)612 void MidiManagerAlsa::AlsaSeqState::PortStart(
613 int client_id,
614 int port_id,
615 const std::string& port_name,
616 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
617 bool midi) {
618 auto it = clients_.find(client_id);
619 if (it != clients_.end())
620 it->second->AddPort(port_id,
621 std::make_unique<Port>(port_name, direction, midi));
622 }
623
PortExit(int client_id,int port_id)624 void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
625 auto it = clients_.find(client_id);
626 if (it != clients_.end())
627 it->second->RemovePort(port_id);
628 }
629
ClientType(int client_id) const630 snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
631 int client_id) const {
632 auto it = clients_.find(client_id);
633 if (it == clients_.end())
634 return SND_SEQ_USER_CLIENT;
635 return it->second->type();
636 }
637
638 std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState>
ToMidiPortState(const AlsaCardMap & alsa_cards)639 MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
640 std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
641 new TemporaryMidiPortState);
642 auto card_it = alsa_cards.begin();
643
644 int card_midi_device = -1;
645 for (const auto& client_pair : clients_) {
646 int client_id = client_pair.first;
647 auto* client = client_pair.second.get();
648
649 // Get client metadata.
650 const std::string client_name = client->name();
651 std::string manufacturer;
652 std::string driver;
653 std::string path;
654 MidiPort::Id id;
655 std::string card_name;
656 std::string card_longname;
657 int midi_device = -1;
658
659 if (IsCardClient(client->type(), client_id)) {
660 auto& card = card_it->second;
661 if (card_midi_device == -1)
662 card_midi_device = 0;
663
664 manufacturer = card->manufacturer();
665 path = card->path();
666 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
667 card->usb_interface_num(), card->serial());
668 card_name = card->name();
669 card_longname = card->longname();
670 midi_device = card_midi_device;
671
672 ++card_midi_device;
673 if (card_midi_device >= card->midi_device_count()) {
674 card_midi_device = -1;
675 ++card_it;
676 }
677 }
678
679 for (const auto& port_pair : *client) {
680 int port_id = port_pair.first;
681 const auto& port = port_pair.second;
682
683 if (port->midi()) {
684 std::string version;
685 if (!driver.empty()) {
686 version = driver + " / ";
687 }
688 version +=
689 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
690 SND_LIB_MINOR, SND_LIB_SUBMINOR);
691 PortDirection direction = port->direction();
692 if (direction == PortDirection::kInput ||
693 direction == PortDirection::kDuplex) {
694 midi_ports->push_back(std::make_unique<MidiPort>(
695 path, id, client_id, port_id, midi_device, client->name(),
696 port->name(), manufacturer, version, MidiPort::Type::kInput));
697 }
698 if (direction == PortDirection::kOutput ||
699 direction == PortDirection::kDuplex) {
700 midi_ports->push_back(std::make_unique<MidiPort>(
701 path, id, client_id, port_id, midi_device, client->name(),
702 port->name(), manufacturer, version, MidiPort::Type::kOutput));
703 }
704 }
705 }
706 }
707
708 return midi_ports;
709 }
710
Port(const std::string & name,MidiManagerAlsa::AlsaSeqState::PortDirection direction,bool midi)711 MidiManagerAlsa::AlsaSeqState::Port::Port(
712 const std::string& name,
713 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
714 bool midi)
715 : name_(name), direction_(direction), midi_(midi) {
716 }
717
718 MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
719
Client(const std::string & name,snd_seq_client_type_t type)720 MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
721 snd_seq_client_type_t type)
722 : name_(name), type_(type) {
723 }
724
725 MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
726
AddPort(int addr,std::unique_ptr<Port> port)727 void MidiManagerAlsa::AlsaSeqState::Client::AddPort(
728 int addr,
729 std::unique_ptr<Port> port) {
730 ports_[addr] = std::move(port);
731 }
732
RemovePort(int addr)733 void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
734 ports_.erase(addr);
735 }
736
737 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
begin() const738 MidiManagerAlsa::AlsaSeqState::Client::begin() const {
739 return ports_.begin();
740 }
741
742 MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
end() const743 MidiManagerAlsa::AlsaSeqState::Client::end() const {
744 return ports_.end();
745 }
746
AlsaCard(udev_device * dev,const std::string & name,const std::string & longname,const std::string & driver,int midi_device_count)747 MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
748 const std::string& name,
749 const std::string& longname,
750 const std::string& driver,
751 int midi_device_count)
752 : name_(name),
753 longname_(longname),
754 driver_(driver),
755 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
756 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
757 vendor_id_(
758 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
759 model_id_(
760 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
761 usb_interface_num_(
762 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
763 serial_(UdevDeviceGetPropertyOrSysattr(dev,
764 kUdevIdSerialShort,
765 kSysattrGuid)),
766 midi_device_count_(midi_device_count),
767 manufacturer_(ExtractManufacturerString(
768 GetVendor(dev),
769 vendor_id_,
770 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
771 name,
772 longname)) {
773 }
774
775 MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
776
777 // static
ExtractManufacturerString(const std::string & udev_id_vendor,const std::string & udev_id_vendor_id,const std::string & udev_id_vendor_from_database,const std::string & alsa_name,const std::string & alsa_longname)778 std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
779 const std::string& udev_id_vendor,
780 const std::string& udev_id_vendor_id,
781 const std::string& udev_id_vendor_from_database,
782 const std::string& alsa_name,
783 const std::string& alsa_longname) {
784 // Let's try to determine the manufacturer. Here is the ordered preference
785 // in extraction:
786 // 1. Vendor name from the hardware device string, from udev properties
787 // or sysattrs.
788 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
789 // 3. Heuristic from ALSA.
790
791 // Is the vendor string present and not just the vendor hex id?
792 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
793 return udev_id_vendor;
794 }
795
796 // Is there a vendor string in the hardware database?
797 if (!udev_id_vendor_from_database.empty()) {
798 return udev_id_vendor_from_database;
799 }
800
801 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
802 // We assume that card longname is in the format of
803 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
804 // a manufacturer name here.
805 size_t at_index = alsa_longname.rfind(" at ");
806 if (at_index && at_index != std::string::npos) {
807 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
808 if (name_index && name_index != std::string::npos)
809 return alsa_longname.substr(0, name_index - 1);
810 }
811
812 // Failure.
813 return "";
814 }
815
SendMidiData(MidiManagerClient * client,uint32_t port_index,const std::vector<uint8_t> & data)816 void MidiManagerAlsa::SendMidiData(MidiManagerClient* client,
817 uint32_t port_index,
818 const std::vector<uint8_t>& data) {
819 ScopedSndMidiEventPtr encoder = CreateScopedSndMidiEventPtr(kSendBufferSize);
820 for (const auto datum : data) {
821 snd_seq_event_t event;
822 int result = snd_midi_event_encode_byte(encoder.get(), datum, &event);
823 if (result == 1) {
824 // Full event, send it.
825 base::AutoLock lock(out_ports_lock_);
826 auto it = out_ports_.find(port_index);
827 if (it != out_ports_.end()) {
828 base::AutoLock lock(out_client_lock_);
829 if (!out_client_)
830 return;
831 snd_seq_ev_set_source(&event, it->second);
832 snd_seq_ev_set_subs(&event);
833 snd_seq_ev_set_direct(&event);
834 snd_seq_event_output_direct(out_client_.get(), &event);
835 }
836 }
837 }
838
839 // Acknowledge send.
840 AccumulateMidiBytesSent(client, data.size());
841 }
842
EventLoop()843 void MidiManagerAlsa::EventLoop() {
844 bool loop_again = true;
845
846 struct pollfd pfd[2];
847 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
848 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
849 pfd[1].events = POLLIN;
850
851 int err = HANDLE_EINTR(poll(pfd, base::size(pfd), -1));
852 if (err < 0) {
853 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
854 loop_again = false;
855 } else {
856 if (pfd[0].revents & POLLIN) {
857 // Read available incoming MIDI data.
858 int remaining;
859 base::TimeTicks timestamp = base::TimeTicks::Now();
860 do {
861 snd_seq_event_t* event;
862 err = snd_seq_event_input(in_client_.get(), &event);
863 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
864
865 if (err == -ENOSPC) {
866 // Handle out of space error.
867 VLOG(1) << "snd_seq_event_input detected buffer overrun";
868 // We've lost events: check another way to see if we need to shut
869 // down.
870 } else if (err == -EAGAIN) {
871 // We've read all the data.
872 } else if (err < 0) {
873 // Handle other errors.
874 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
875 // TODO(agoode): Use RecordAction() or similar to log this.
876 loop_again = false;
877 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
878 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
879 // Handle announce events.
880 switch (event->type) {
881 case SND_SEQ_EVENT_PORT_START:
882 // Don't use SND_SEQ_EVENT_CLIENT_START because the
883 // client name may not be set by the time we query
884 // it. It should be set by the time ports are made.
885 ProcessClientStartEvent(event->data.addr.client);
886 ProcessPortStartEvent(event->data.addr);
887 break;
888 case SND_SEQ_EVENT_CLIENT_EXIT:
889 // Check for disconnection of our "out" client. This means "shut
890 // down".
891 if (event->data.addr.client == out_client_id_) {
892 loop_again = false;
893 remaining = 0;
894 } else
895 ProcessClientExitEvent(event->data.addr);
896 break;
897 case SND_SEQ_EVENT_PORT_EXIT:
898 ProcessPortExitEvent(event->data.addr);
899 break;
900 }
901 } else {
902 // Normal operation.
903 ProcessSingleEvent(event, timestamp);
904 }
905 } while (remaining > 0);
906 }
907 if (pfd[1].revents & POLLIN) {
908 device::ScopedUdevDevicePtr dev(
909 device::udev_monitor_receive_device(udev_monitor_.get()));
910 if (dev.get())
911 ProcessUdevEvent(dev.get());
912 else
913 VLOG(1) << "udev_monitor_receive_device fails";
914 }
915 }
916
917 // Do again.
918 if (loop_again) {
919 service()->task_service()->PostBoundTask(
920 kEventTaskRunner,
921 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
922 }
923 }
924
ProcessSingleEvent(snd_seq_event_t * event,base::TimeTicks timestamp)925 void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
926 base::TimeTicks timestamp) {
927 auto source_it =
928 source_map_.find(AddrToInt(event->source.client, event->source.port));
929 if (source_it != source_map_.end()) {
930 uint32_t source = source_it->second;
931 if (event->type == SND_SEQ_EVENT_SYSEX) {
932 // Special! Variable-length sysex.
933 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
934 event->data.ext.len, timestamp);
935 } else {
936 // Otherwise, decode this and send that on.
937 unsigned char buf[12];
938 long count =
939 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
940 if (count <= 0) {
941 if (count != -ENOENT) {
942 // ENOENT means that it's not a MIDI message, which is not an
943 // error, but other negative values are errors for us.
944 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
945 // TODO(agoode): Record this failure.
946 }
947 } else {
948 ReceiveMidiData(source, buf, count, timestamp);
949 }
950 }
951 }
952 }
953
ProcessClientStartEvent(int client_id)954 void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
955 // Ignore if client is already started.
956 if (alsa_seq_state_.ClientStarted(client_id))
957 return;
958
959 snd_seq_client_info_t* client_info;
960 snd_seq_client_info_alloca(&client_info);
961 int err =
962 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
963 if (err != 0)
964 return;
965
966 // Skip our own clients.
967 if ((client_id == in_client_id_) || (client_id == out_client_id_))
968 return;
969
970 // Update our view of ALSA seq state.
971 alsa_seq_state_.ClientStart(client_id,
972 snd_seq_client_info_get_name(client_info),
973 snd_seq_client_info_get_type(client_info));
974
975 // Generate Web MIDI events.
976 UpdatePortStateAndGenerateEvents();
977 }
978
ProcessPortStartEvent(const snd_seq_addr_t & addr)979 void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
980 snd_seq_port_info_t* port_info;
981 snd_seq_port_info_alloca(&port_info);
982 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
983 port_info);
984 if (err != 0)
985 return;
986
987 unsigned int caps = snd_seq_port_info_get_capability(port_info);
988 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
989 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
990 AlsaSeqState::PortDirection direction;
991 if (input && output)
992 direction = AlsaSeqState::PortDirection::kDuplex;
993 else if (input)
994 direction = AlsaSeqState::PortDirection::kInput;
995 else if (output)
996 direction = AlsaSeqState::PortDirection::kOutput;
997 else
998 return;
999
1000 // Update our view of ALSA seq state.
1001 alsa_seq_state_.PortStart(
1002 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1003 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1004 // Generate Web MIDI events.
1005 UpdatePortStateAndGenerateEvents();
1006 }
1007
ProcessClientExitEvent(const snd_seq_addr_t & addr)1008 void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1009 // Update our view of ALSA seq state.
1010 alsa_seq_state_.ClientExit(addr.client);
1011 // Generate Web MIDI events.
1012 UpdatePortStateAndGenerateEvents();
1013 }
1014
ProcessPortExitEvent(const snd_seq_addr_t & addr)1015 void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1016 // Update our view of ALSA seq state.
1017 alsa_seq_state_.PortExit(addr.client, addr.port);
1018 // Generate Web MIDI events.
1019 UpdatePortStateAndGenerateEvents();
1020 }
1021
ProcessUdevEvent(udev_device * dev)1022 void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1023 // Only card devices have this property set, and only when they are
1024 // fully initialized.
1025 if (!device::udev_device_get_property_value(dev,
1026 kUdevPropertySoundInitialized))
1027 return;
1028
1029 // Get the action. If no action, then we are doing first time enumeration
1030 // and the device is treated as new.
1031 const char* action = device::udev_device_get_action(dev);
1032 if (!action)
1033 action = kUdevActionChange;
1034
1035 if (strcmp(action, kUdevActionChange) == 0) {
1036 AddCard(dev);
1037 // Generate Web MIDI events.
1038 UpdatePortStateAndGenerateEvents();
1039 } else if (strcmp(action, kUdevActionRemove) == 0) {
1040 RemoveCard(GetCardNumber(dev));
1041 // Generate Web MIDI events.
1042 UpdatePortStateAndGenerateEvents();
1043 }
1044 }
1045
AddCard(udev_device * dev)1046 void MidiManagerAlsa::AddCard(udev_device* dev) {
1047 int number = GetCardNumber(dev);
1048 if (number == -1)
1049 return;
1050
1051 RemoveCard(number);
1052
1053 snd_ctl_card_info_t* card;
1054 snd_hwdep_info_t* hwdep;
1055 snd_ctl_card_info_alloca(&card);
1056 snd_hwdep_info_alloca(&hwdep);
1057 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1058 snd_ctl_t* handle;
1059 int err = snd_ctl_open(&handle, id.c_str(), 0);
1060 if (err != 0) {
1061 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1062 return;
1063 }
1064 err = snd_ctl_card_info(handle, card);
1065 if (err != 0) {
1066 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1067 snd_ctl_close(handle);
1068 return;
1069 }
1070 std::string name = snd_ctl_card_info_get_name(card);
1071 std::string longname = snd_ctl_card_info_get_longname(card);
1072 std::string driver = snd_ctl_card_info_get_driver(card);
1073
1074 // Count rawmidi devices (not subdevices).
1075 int midi_count = 0;
1076 for (int device = -1;
1077 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1078 ++midi_count;
1079
1080 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1081 //
1082 // Explanation:
1083 // Any kernel driver can create an ALSA client (visible to us).
1084 // With modern hardware, only rawmidi devices do this. Kernel
1085 // drivers create rawmidi devices and the rawmidi subsystem makes
1086 // the seq clients. But the OPL3 driver is special, it does not
1087 // make a rawmidi device but a seq client directly. (This is the
1088 // only one to worry about in the kernel code, as of 2015-03-23.)
1089 //
1090 // OPL3 is very old (but still possible to get in new
1091 // hardware). It is unlikely that new drivers would not use
1092 // rawmidi and defeat our heuristic.
1093 //
1094 // Longer term, support should be added in the kernel to expose a
1095 // direct link from card->client (or client->card) so that all
1096 // these heuristics will be obsolete. Once that is there, we can
1097 // assume our old heuristics will work on old kernels and the new
1098 // robust code will be used on new. Then we will not need to worry
1099 // about changes to kernel internals breaking our code.
1100 // See the TODO above at kMinimumClientIdForCards.
1101 for (int device = -1;
1102 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1103 err = snd_ctl_hwdep_info(handle, hwdep);
1104 if (err != 0) {
1105 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1106 continue;
1107 }
1108 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1109 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1110 iface == SND_HWDEP_IFACE_OPL4)
1111 ++midi_count;
1112 }
1113 snd_ctl_close(handle);
1114
1115 if (midi_count > 0) {
1116 std::unique_ptr<AlsaCard> card(
1117 new AlsaCard(dev, name, longname, driver, midi_count));
1118 alsa_cards_.insert(std::make_pair(number, std::move(card)));
1119 alsa_card_midi_count_ += midi_count;
1120 }
1121 }
1122
RemoveCard(int number)1123 void MidiManagerAlsa::RemoveCard(int number) {
1124 auto it = alsa_cards_.find(number);
1125 if (it == alsa_cards_.end())
1126 return;
1127
1128 alsa_card_midi_count_ -= it->second->midi_device_count();
1129 alsa_cards_.erase(it);
1130 }
1131
UpdatePortStateAndGenerateEvents()1132 void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
1133 // Verify that our information from ALSA and udev are in sync. If
1134 // not, we cannot generate events right now.
1135 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1136 return;
1137
1138 // Generate new port state.
1139 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
1140
1141 // Disconnect any connected old ports that are now missing.
1142 for (auto& old_port : port_state_) {
1143 if (old_port->connected() &&
1144 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1145 old_port->set_connected(false);
1146 uint32_t web_port_index = old_port->web_port_index();
1147 switch (old_port->type()) {
1148 case MidiPort::Type::kInput:
1149 source_map_.erase(
1150 AddrToInt(old_port->client_id(), old_port->port_id()));
1151 SetInputPortState(web_port_index, PortState::DISCONNECTED);
1152 break;
1153 case MidiPort::Type::kOutput:
1154 DeleteAlsaOutputPort(web_port_index);
1155 SetOutputPortState(web_port_index, PortState::DISCONNECTED);
1156 break;
1157 }
1158 }
1159 }
1160
1161 // Reconnect or add new ports.
1162 auto it = new_port_state->begin();
1163 while (it != new_port_state->end()) {
1164 auto& new_port = *it;
1165 auto old_port = port_state_.Find(*new_port);
1166 if (old_port == port_state_.end()) {
1167 // Add new port.
1168 const auto& opaque_key = new_port->OpaqueKey();
1169 const auto& manufacturer = new_port->manufacturer();
1170 const auto& port_name = new_port->port_name();
1171 const auto& version = new_port->version();
1172 const auto& type = new_port->type();
1173 const auto& client_id = new_port->client_id();
1174 const auto& port_id = new_port->port_id();
1175
1176 uint32_t web_port_index = port_state_.push_back(std::move(new_port));
1177 it = new_port_state->erase(it);
1178
1179 mojom::PortInfo info(opaque_key, manufacturer, port_name, version,
1180 PortState::OPENED);
1181 switch (type) {
1182 case MidiPort::Type::kInput:
1183 if (Subscribe(web_port_index, client_id, port_id))
1184 AddInputPort(info);
1185 break;
1186 case MidiPort::Type::kOutput:
1187 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
1188 AddOutputPort(info);
1189 break;
1190 }
1191 } else if (!(*old_port)->connected()) {
1192 // Reconnect.
1193 uint32_t web_port_index = (*old_port)->web_port_index();
1194 (*old_port)->Update(new_port->path(), new_port->client_id(),
1195 new_port->port_id(), new_port->client_name(),
1196 new_port->port_name(), new_port->manufacturer(),
1197 new_port->version());
1198 switch ((*old_port)->type()) {
1199 case MidiPort::Type::kInput:
1200 if (Subscribe(web_port_index, (*old_port)->client_id(),
1201 (*old_port)->port_id()))
1202 SetInputPortState(web_port_index, PortState::OPENED);
1203 break;
1204 case MidiPort::Type::kOutput:
1205 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1206 (*old_port)->port_id()))
1207 SetOutputPortState(web_port_index, PortState::OPENED);
1208 break;
1209 }
1210 (*old_port)->set_connected(true);
1211 ++it;
1212 } else {
1213 ++it;
1214 }
1215 }
1216 }
1217
1218 // TODO(agoode): return false on failure.
EnumerateAlsaPorts()1219 void MidiManagerAlsa::EnumerateAlsaPorts() {
1220 snd_seq_client_info_t* client_info;
1221 snd_seq_client_info_alloca(&client_info);
1222 snd_seq_port_info_t* port_info;
1223 snd_seq_port_info_alloca(&port_info);
1224
1225 // Enumerate clients.
1226 snd_seq_client_info_set_client(client_info, -1);
1227 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
1228 int client_id = snd_seq_client_info_get_client(client_info);
1229 ProcessClientStartEvent(client_id);
1230
1231 // Enumerate ports.
1232 snd_seq_port_info_set_client(port_info, client_id);
1233 snd_seq_port_info_set_port(port_info, -1);
1234 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
1235 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1236 ProcessPortStartEvent(*addr);
1237 }
1238 }
1239 }
1240
EnumerateUdevCards()1241 bool MidiManagerAlsa::EnumerateUdevCards() {
1242 int err;
1243
1244 device::ScopedUdevEnumeratePtr enumerate(
1245 device::udev_enumerate_new(udev_.get()));
1246 if (!enumerate.get()) {
1247 VLOG(1) << "udev_enumerate_new fails";
1248 return false;
1249 }
1250
1251 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1252 kUdevSubsystemSound);
1253 if (err) {
1254 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
1255 << base::safe_strerror(-err);
1256 return false;
1257 }
1258
1259 err = device::udev_enumerate_scan_devices(enumerate.get());
1260 if (err) {
1261 VLOG(1) << "udev_enumerate_scan_devices fails: "
1262 << base::safe_strerror(-err);
1263 return false;
1264 }
1265
1266 udev_list_entry* list_entry;
1267 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1268 udev_list_entry_foreach(list_entry, devices) {
1269 const char* path = device::udev_list_entry_get_name(list_entry);
1270 device::ScopedUdevDevicePtr dev(
1271 device::udev_device_new_from_syspath(udev_.get(), path));
1272 if (dev.get())
1273 ProcessUdevEvent(dev.get());
1274 }
1275
1276 return true;
1277 }
1278
CreateAlsaOutputPort(uint32_t port_index,int client_id,int port_id)1279 bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
1280 int client_id,
1281 int port_id) {
1282 // Create the port.
1283 int out_port;
1284 {
1285 base::AutoLock lock(out_client_lock_);
1286 out_port = snd_seq_create_simple_port(
1287 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
1288
1289 if (out_port < 0) {
1290 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1291 return false;
1292 }
1293
1294 // Activate port subscription.
1295 snd_seq_port_subscribe_t* subs;
1296 snd_seq_port_subscribe_alloca(&subs);
1297 snd_seq_addr_t sender;
1298 sender.client = out_client_id_;
1299 sender.port = out_port;
1300 snd_seq_port_subscribe_set_sender(subs, &sender);
1301 snd_seq_addr_t dest;
1302 dest.client = client_id;
1303 dest.port = port_id;
1304 snd_seq_port_subscribe_set_dest(subs, &dest);
1305 int err = snd_seq_subscribe_port(out_client_.get(), subs);
1306 if (err != 0) {
1307 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1308 snd_seq_delete_simple_port(out_client_.get(), out_port);
1309 return false;
1310 }
1311 }
1312
1313 // Update our map.
1314 base::AutoLock lock(out_ports_lock_);
1315 out_ports_[port_index] = out_port;
1316 return true;
1317 }
1318
DeleteAlsaOutputPort(uint32_t port_index)1319 void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
1320 int alsa_port;
1321 {
1322 base::AutoLock lock(out_ports_lock_);
1323 auto it = out_ports_.find(port_index);
1324 if (it == out_ports_.end())
1325 return;
1326 alsa_port = it->second;
1327 out_ports_.erase(it);
1328 }
1329 {
1330 base::AutoLock lock(out_client_lock_);
1331 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
1332 }
1333 }
1334
Subscribe(uint32_t port_index,int client_id,int port_id)1335 bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1336 int client_id,
1337 int port_id) {
1338 // Activate port subscription.
1339 snd_seq_port_subscribe_t* subs;
1340 snd_seq_port_subscribe_alloca(&subs);
1341 snd_seq_addr_t sender;
1342 sender.client = client_id;
1343 sender.port = port_id;
1344 snd_seq_port_subscribe_set_sender(subs, &sender);
1345 snd_seq_addr_t dest;
1346 dest.client = in_client_id_;
1347 dest.port = in_port_id_;
1348 snd_seq_port_subscribe_set_dest(subs, &dest);
1349 int err = snd_seq_subscribe_port(in_client_.get(), subs);
1350 if (err != 0) {
1351 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1352 return false;
1353 }
1354
1355 // Update our map.
1356 source_map_[AddrToInt(client_id, port_id)] = port_index;
1357 return true;
1358 }
1359
1360 MidiManagerAlsa::ScopedSndMidiEventPtr
CreateScopedSndMidiEventPtr(size_t size)1361 MidiManagerAlsa::CreateScopedSndMidiEventPtr(size_t size) {
1362 snd_midi_event_t* coder;
1363 snd_midi_event_new(size, &coder);
1364 return ScopedSndMidiEventPtr(coder);
1365 }
1366
Create(MidiService * service)1367 MidiManager* MidiManager::Create(MidiService* service) {
1368 return new MidiManagerAlsa(service);
1369 }
1370
1371 } // namespace midi
1372