blob: 8a8fb9cdbcc97d1b21bd3751ed53d400b75098c5 [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
mostynbbd693172015-11-17 19:06:54 -08007#include <errno.h>
agoode975043d2015-05-11 00:46:17 -07008#include <poll.h>
avi793390d2015-12-22 22:22:36 -08009#include <stddef.h>
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000010#include <stdlib.h>
danakj75afea02016-04-25 20:36:04 -070011
yhirano@chromium.orgcfa642c2014-05-01 08:54:41 +000012#include <algorithm>
13#include <string>
limasdfe59d0392015-11-19 20:28:57 -080014#include <utility>
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000015
16#include "base/bind.h"
agoodef212b2a2015-03-19 12:53:23 -070017#include "base/json/json_string_value_serializer.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000018#include "base/logging.h"
avi793390d2015-12-22 22:22:36 -080019#include "base/macros.h"
danakj75afea02016-04-25 20:36:04 -070020#include "base/memory/ptr_util.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000021#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000022#include "base/posix/eintr_wrapper.h"
brettwf7f870f2015-06-09 11:05:24 -070023#include "base/posix/safe_strerror.h"
fdoray8baaff02016-08-25 08:36:37 -070024#include "base/single_thread_task_runner.h"
agoodef212b2a2015-03-19 12:53:23 -070025#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000026#include "base/strings/stringprintf.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000027#include "base/time/time.h"
agoodef212b2a2015-03-19 12:53:23 -070028#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000029#include "media/midi/midi_port_info.h"
30
toyoshime147c5e2015-05-07 21:58:31 -070031namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000032
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000033namespace {
34
toyoshim2f3a48f2016-10-17 01:54:13 -070035using midi::mojom::Result;
36
agoode@chromium.org25227512014-06-08 05:12:05 +000037// Per-output buffer. This can be smaller, but then large sysex messages
38// will be (harmlessly) split across multiple seq events. This should
39// not have any real practical effect, except perhaps to slightly reorder
40// realtime messages with respect to sysex.
41const size_t kSendBufferSize = 256;
42
agoodeb09423b2015-05-11 11:39:57 -070043// Minimum client id for which we will have ALSA card devices for. When we
44// are searching for card devices (used to get the path, id, and manufacturer),
45// we don't want to get confused by kernel clients that do not have a card.
46// See seq_clientmgr.c in the ALSA code for this.
47// TODO(agoode): Add proper client -> card export from the kernel to avoid
48// hardcoding.
49const int kMinimumClientIdForCards = 16;
50
agoodef212b2a2015-03-19 12:53:23 -070051// ALSA constants.
52const char kAlsaHw[] = "hw";
53
agoode975043d2015-05-11 00:46:17 -070054// udev constants.
55const char kUdev[] = "udev";
56const char kUdevSubsystemSound[] = "sound";
57const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
58const char kUdevActionChange[] = "change";
59const char kUdevActionRemove[] = "remove";
60
agoodeb09423b2015-05-11 11:39:57 -070061const char kUdevIdVendor[] = "ID_VENDOR";
62const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
63const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
64const char kUdevIdVendorId[] = "ID_VENDOR_ID";
65const char kUdevIdModelId[] = "ID_MODEL_ID";
66const char kUdevIdBus[] = "ID_BUS";
67const char kUdevIdPath[] = "ID_PATH";
68const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
69const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
70
71const char kSysattrVendorName[] = "vendor_name";
72const char kSysattrVendor[] = "vendor";
73const char kSysattrModel[] = "model";
74const char kSysattrGuid[] = "guid";
75
76const char kCardSyspath[] = "/card";
77
agoode@chromium.org25227512014-06-08 05:12:05 +000078// Constants for the capabilities we search for in inputs and outputs.
79// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
80const unsigned int kRequiredInputPortCaps =
81 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
82const unsigned int kRequiredOutputPortCaps =
83 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
84
agoode99d63292015-04-13 08:39:25 -070085const unsigned int kCreateOutputPortCaps =
86 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
87const unsigned int kCreateInputPortCaps =
88 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
89const unsigned int kCreatePortType =
90 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +000091
agoode99d63292015-04-13 08:39:25 -070092int AddrToInt(int client, int port) {
93 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -070094}
agoodef212b2a2015-03-19 12:53:23 -070095
agoodeb09423b2015-05-11 11:39:57 -070096// Returns true if this client has an ALSA card associated with it.
97bool IsCardClient(snd_seq_client_type_t type, int client_id) {
98 return (type == SND_SEQ_KERNEL_CLIENT) &&
99 (client_id >= kMinimumClientIdForCards);
100}
101
102// TODO(agoode): Move this to device/udev_linux.
103const std::string UdevDeviceGetPropertyOrSysattr(
104 struct udev_device* udev_device,
105 const char* property_key,
106 const char* sysattr_key) {
107 // First try the property.
108 std::string value =
109 device::UdevDeviceGetPropertyValue(udev_device, property_key);
110
111 // If no property, look for sysattrs and walk up the parent devices too.
112 while (value.empty() && udev_device) {
113 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
114 udev_device = device::udev_device_get_parent(udev_device);
115 }
116 return value;
117}
118
119int GetCardNumber(udev_device* dev) {
120 const char* syspath = device::udev_device_get_syspath(dev);
121 if (!syspath)
122 return -1;
123
124 std::string syspath_str(syspath);
125 size_t i = syspath_str.rfind(kCardSyspath);
126 if (i == std::string::npos)
127 return -1;
128
129 int number;
130 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
131 return -1;
132 return number;
133}
134
agooded87fc0f2015-05-21 08:29:31 -0700135std::string GetVendor(udev_device* dev) {
136 // Try to get the vendor string. Sometimes it is encoded.
137 std::string vendor = device::UdevDecodeString(
138 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
139 // Sometimes it is not encoded.
140 if (vendor.empty())
141 vendor =
142 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
143 return vendor;
144}
145
agoode8caab0b2015-03-23 18:48:02 -0700146void SetStringIfNonEmpty(base::DictionaryValue* value,
147 const std::string& path,
148 const std::string& in_value) {
149 if (!in_value.empty())
150 value->SetString(path, in_value);
151}
152
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000153} // namespace
154
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000155MidiManagerAlsa::MidiManagerAlsa()
agoodeb2ead822016-03-11 12:14:35 -0800156 : event_thread_("MidiEventThread"), send_thread_("MidiSendThread") {}
157
158MidiManagerAlsa::~MidiManagerAlsa() {
159 // Take lock to ensure that the members initialized on the IO thread
160 // are not destructed here.
161 base::AutoLock lock(lazy_init_member_lock_);
162
toyoshim131beb52016-07-25 00:42:41 -0700163 // Extra CHECK to verify all members are already reset.
164 CHECK(!initialization_thread_checker_);
165 CHECK(!in_client_);
166 CHECK(!out_client_);
167 CHECK(!decoder_);
168 CHECK(!udev_);
169 CHECK(!udev_monitor_);
170
171 CHECK(!send_thread_.IsRunning());
172 CHECK(!event_thread_.IsRunning());
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000173}
174
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000175void MidiManagerAlsa::StartInitialization() {
agoodeb2ead822016-03-11 12:14:35 -0800176 base::AutoLock lock(lazy_init_member_lock_);
agoode5a1aa112015-06-21 20:51:00 -0700177
agoodeb2ead822016-03-11 12:14:35 -0800178 initialization_thread_checker_.reset(new base::ThreadChecker());
179
180 // Create client handles.
181 snd_seq_t* tmp_seq = nullptr;
182 int err =
183 snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000184 if (err != 0) {
185 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700186 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000187 }
agoodeb2ead822016-03-11 12:14:35 -0800188 ScopedSndSeqPtr in_client(tmp_seq);
189 tmp_seq = nullptr;
190 in_client_id_ = snd_seq_client_id(in_client.get());
191
192 err = snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
193 if (err != 0) {
194 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
195 return CompleteInitialization(Result::INITIALIZATION_ERROR);
196 }
197 ScopedSndSeqPtr out_client(tmp_seq);
198 tmp_seq = nullptr;
199 out_client_id_ = snd_seq_client_id(out_client.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000200
201 // Name the clients.
agoodeb2ead822016-03-11 12:14:35 -0800202 err = snd_seq_set_client_name(in_client.get(), "Chrome (input)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000203 if (err != 0) {
204 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700205 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000206 }
agoodeb2ead822016-03-11 12:14:35 -0800207 err = snd_seq_set_client_name(out_client.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000208 if (err != 0) {
209 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700210 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000211 }
212
213 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700214 in_port_id_ = snd_seq_create_simple_port(
agoodeb2ead822016-03-11 12:14:35 -0800215 in_client.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700216 if (in_port_id_ < 0) {
217 VLOG(1) << "snd_seq_create_simple_port fails: "
218 << snd_strerror(in_port_id_);
toyoshimf1b88962015-07-09 14:14:51 -0700219 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000220 }
221
222 // Subscribe to the announce port.
223 snd_seq_port_subscribe_t* subs;
224 snd_seq_port_subscribe_alloca(&subs);
225 snd_seq_addr_t announce_sender;
226 snd_seq_addr_t announce_dest;
227 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
228 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700229 announce_dest.client = in_client_id_;
230 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000231 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
232 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoodeb2ead822016-03-11 12:14:35 -0800233 err = snd_seq_subscribe_port(in_client.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000234 if (err != 0) {
235 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
236 << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700237 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000238 }
239
agoodeb2ead822016-03-11 12:14:35 -0800240 // Initialize decoder.
241 snd_midi_event_t* tmp_decoder = nullptr;
242 snd_midi_event_new(0, &tmp_decoder);
243 ScopedSndMidiEventPtr decoder(tmp_decoder);
244 tmp_decoder = nullptr;
245 snd_midi_event_no_status(decoder.get(), 1);
agoode@chromium.org25227512014-06-08 05:12:05 +0000246
agoodeb2ead822016-03-11 12:14:35 -0800247 // Initialize udev and monitor.
248 device::ScopedUdevPtr udev(device::udev_new());
249 device::ScopedUdevMonitorPtr udev_monitor(
250 device::udev_monitor_new_from_netlink(udev.get(), kUdev));
251 if (!udev_monitor.get()) {
agoode975043d2015-05-11 00:46:17 -0700252 VLOG(1) << "udev_monitor_new_from_netlink fails";
toyoshimf1b88962015-07-09 14:14:51 -0700253 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700254 }
255 err = device::udev_monitor_filter_add_match_subsystem_devtype(
agoodeb2ead822016-03-11 12:14:35 -0800256 udev_monitor.get(), kUdevSubsystemSound, nullptr);
agoode975043d2015-05-11 00:46:17 -0700257 if (err != 0) {
258 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700259 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700260 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700261 }
agoodeb2ead822016-03-11 12:14:35 -0800262 err = device::udev_monitor_enable_receiving(udev_monitor.get());
agoode975043d2015-05-11 00:46:17 -0700263 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700264 VLOG(1) << "udev_monitor_enable_receiving fails: "
265 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700266 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700267 }
268
agoodeb2ead822016-03-11 12:14:35 -0800269 // Success! Now, initialize members from the temporaries. Do not
270 // initialize these earlier, since they need to be destroyed by the
271 // thread that calls Finalize(), not the destructor thread (and we
272 // check this in the destructor).
273 in_client_.reset(in_client.release());
274 out_client_.reset(out_client.release());
275 decoder_.reset(decoder.release());
276 udev_.reset(udev.release());
agoode740f5222016-07-06 23:46:32 -0700277 udev_monitor_.reset(udev_monitor.release());
agoodeb2ead822016-03-11 12:14:35 -0800278
279 // Generate hotplug events for existing ports.
280 // TODO(agoode): Check the return value for failure.
281 EnumerateAlsaPorts();
282
283 // Generate hotplug events for existing udev devices. This must be done
284 // after udev_monitor_enable_receiving() is called. See the algorithm
285 // at http://www.signal11.us/oss/udev/.
agoode975043d2015-05-11 00:46:17 -0700286 EnumerateUdevCards();
287
agoodeb2ead822016-03-11 12:14:35 -0800288 // Start processing events. Don't do this before enumeration of both
289 // ALSA and udev.
agoode@chromium.org25227512014-06-08 05:12:05 +0000290 event_thread_.Start();
fdoray8baaff02016-08-25 08:36:37 -0700291 event_thread_.task_runner()->PostTask(
agoode@chromium.org25227512014-06-08 05:12:05 +0000292 FROM_HERE,
agoodebd4be9b2015-03-16 19:17:25 -0700293 base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
agoodeb2ead822016-03-11 12:14:35 -0800294 send_thread_.Start();
agoode@chromium.org25227512014-06-08 05:12:05 +0000295
toyoshimf1b88962015-07-09 14:14:51 -0700296 CompleteInitialization(Result::OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000297}
298
toyoshim8e7d6e02015-10-06 08:47:17 -0700299void MidiManagerAlsa::Finalize() {
agoodeb2ead822016-03-11 12:14:35 -0800300 base::AutoLock lock(lazy_init_member_lock_);
301 DCHECK(initialization_thread_checker_->CalledOnValidThread());
302
toyoshim8e7d6e02015-10-06 08:47:17 -0700303 // Tell the event thread it will soon be time to shut down. This gives
304 // us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
305 // message is lost.
306 {
307 base::AutoLock lock(shutdown_lock_);
308 event_thread_shutdown_ = true;
309 }
310
311 // Stop the send thread.
312 send_thread_.Stop();
313
314 // Close the out client. This will trigger the event thread to stop,
315 // because of SND_SEQ_EVENT_CLIENT_EXIT.
agoodeb2ead822016-03-11 12:14:35 -0800316 out_client_.reset();
toyoshim8e7d6e02015-10-06 08:47:17 -0700317
318 // Wait for the event thread to stop.
319 event_thread_.Stop();
agoodeb2ead822016-03-11 12:14:35 -0800320
321 // Destruct the other stuff we initialized in StartInitialization().
322 udev_monitor_.reset();
323 udev_.reset();
324 decoder_.reset();
325 in_client_.reset();
326 initialization_thread_checker_.reset();
toyoshim8e7d6e02015-10-06 08:47:17 -0700327}
328
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000329void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -0500330 uint32_t port_index,
331 const std::vector<uint8_t>& data,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000332 double timestamp) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000333 base::TimeDelta delay;
334 if (timestamp != 0.0) {
335 base::TimeTicks time_to_send =
336 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700337 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000338 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
339 }
340
fdoray8baaff02016-08-25 08:36:37 -0700341 send_thread_.task_runner()->PostDelayedTask(
agoodebd4be9b2015-03-16 19:17:25 -0700342 FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
343 base::Unretained(this), port_index, data),
344 delay);
agoode@chromium.org25227512014-06-08 05:12:05 +0000345
346 // Acknowledge send.
fdoray8baaff02016-08-25 08:36:37 -0700347 send_thread_.task_runner()->PostTask(
agoodead116b22015-12-07 00:00:35 -0800348 FROM_HERE, base::Bind(&MidiManagerAlsa::AccumulateMidiBytesSent,
349 base::Unretained(this), client, data.size()));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000350}
351
agooded87fc0f2015-05-21 08:29:31 -0700352MidiManagerAlsa::MidiPort::Id::Id() = default;
353
354MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
355 const std::string& vendor_id,
356 const std::string& model_id,
357 const std::string& usb_interface_num,
358 const std::string& serial)
359 : bus_(bus),
360 vendor_id_(vendor_id),
361 model_id_(model_id),
362 usb_interface_num_(usb_interface_num),
363 serial_(serial) {
364}
365
366MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
367
368MidiManagerAlsa::MidiPort::Id::~Id() = default;
369
370bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
371 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
372 (model_id_ == rhs.model_id_) &&
373 (usb_interface_num_ == rhs.usb_interface_num_) &&
374 (serial_ == rhs.serial_);
375}
376
377bool MidiManagerAlsa::MidiPort::Id::empty() const {
378 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
379 usb_interface_num_.empty() && serial_.empty();
380}
381
agoode99d63292015-04-13 08:39:25 -0700382MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700383 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700384 int client_id,
385 int port_id,
386 int midi_device,
387 const std::string& client_name,
388 const std::string& port_name,
389 const std::string& manufacturer,
390 const std::string& version,
391 Type type)
392 : id_(id),
393 midi_device_(midi_device),
394 type_(type),
395 path_(path),
396 client_id_(client_id),
397 port_id_(port_id),
398 client_name_(client_name),
399 port_name_(port_name),
400 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700401 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000402}
403
agoodeb0582872015-05-20 05:22:24 -0700404MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000405
agoode99d63292015-04-13 08:39:25 -0700406// Note: keep synchronized with the MidiPort::Match* methods.
danakj75afea02016-04-25 20:36:04 -0700407std::unique_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
408 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
agoode99d63292015-04-13 08:39:25 -0700409
410 std::string type;
411 switch (type_) {
412 case Type::kInput:
413 type = "input";
414 break;
agoode99d63292015-04-13 08:39:25 -0700415 case Type::kOutput:
416 type = "output";
417 break;
418 }
419 value->SetString("type", type);
420 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700421 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
422 SetStringIfNonEmpty(value.get(), "portName", port_name_);
423 value->SetInteger("clientId", client_id_);
424 value->SetInteger("portId", port_id_);
425 value->SetInteger("midiDevice", midi_device_);
426
agooded87fc0f2015-05-21 08:29:31 -0700427 // Flatten id fields.
428 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
429 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
430 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
431 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
432 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
433
dchengc2aeece2015-12-27 00:54:00 -0800434 return std::move(value);
agoodebd4be9b2015-03-16 19:17:25 -0700435}
agoode@chromium.org25227512014-06-08 05:12:05 +0000436
agoode99d63292015-04-13 08:39:25 -0700437std::string MidiManagerAlsa::MidiPort::JSONValue() const {
438 std::string json;
439 JSONStringValueSerializer serializer(&json);
440 serializer.Serialize(*Value().get());
441 return json;
agoodef212b2a2015-03-19 12:53:23 -0700442}
443
agoode99d63292015-04-13 08:39:25 -0700444// TODO(agoode): Do not use SHA256 here. Instead store a persistent
445// mapping and just use a UUID or other random string.
446// http://crbug.com/465320
447std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
Avi Drissman3528fd02015-12-18 20:11:31 -0500448 uint8_t hash[crypto::kSHA256Length];
agoode99d63292015-04-13 08:39:25 -0700449 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
450 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700451}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000452
agoode99d63292015-04-13 08:39:25 -0700453bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
454 // Matches on:
455 // connected == true
456 // type
457 // path
458 // id
459 // client_id
460 // port_id
461 // midi_device
462 // client_name
463 // port_name
464 return connected() && (type() == query.type()) && (path() == query.path()) &&
465 (id() == query.id()) && (client_id() == query.client_id()) &&
466 (port_id() == query.port_id()) &&
467 (midi_device() == query.midi_device()) &&
468 (client_name() == query.client_name()) &&
469 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700470}
471
agoode99d63292015-04-13 08:39:25 -0700472bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
473 // Matches on:
474 // connected == false
475 // type
476 // path
477 // id
478 // port_id
479 // midi_device
480 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700481}
482
agoode99d63292015-04-13 08:39:25 -0700483bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
484 // Matches on:
485 // connected == false
486 // type
487 // id
488 // port_id
489 // midi_device
490 return !connected() && (type() == query.type()) && (id() == query.id()) &&
491 (port_id() == query.port_id()) &&
492 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700493}
494
agoode99d63292015-04-13 08:39:25 -0700495bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
496 // Matches on:
497 // connected == false
498 // type
499 // path.empty(), for both this and query
500 // id.empty(), for both this and query
501 // client_id
502 // port_id
503 // client_name
504 // port_name
505 // midi_device == -1, for both this and query
506 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700507}
508
agoode99d63292015-04-13 08:39:25 -0700509bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
510 // Matches on:
511 // connected == false
512 // type
513 // path.empty(), for both this and query
514 // id.empty(), for both this and query
515 // port_id
516 // client_name
517 // port_name
518 // midi_device == -1, for both this and query
519 return !connected() && (type() == query.type()) && path().empty() &&
520 query.path().empty() && id().empty() && query.id().empty() &&
521 (port_id() == query.port_id()) &&
522 (client_name() == query.client_name()) &&
523 (port_name() == query.port_name()) && (midi_device() == -1) &&
524 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000525}
526
agoodeb0582872015-05-20 05:22:24 -0700527MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700528
529MidiManagerAlsa::MidiPortStateBase::iterator
530MidiManagerAlsa::MidiPortStateBase::Find(
531 const MidiManagerAlsa::MidiPort& port) {
532 auto result = FindConnected(port);
533 if (result == end())
534 result = FindDisconnected(port);
535 return result;
536}
537
538MidiManagerAlsa::MidiPortStateBase::iterator
539MidiManagerAlsa::MidiPortStateBase::FindConnected(
540 const MidiManagerAlsa::MidiPort& port) {
541 // Exact match required for connected ports.
danakj75afea02016-04-25 20:36:04 -0700542 auto it = std::find_if(ports_.begin(), ports_.end(),
543 [&port](std::unique_ptr<MidiPort>& p) {
544 return p->MatchConnected(port);
545 });
agoode99d63292015-04-13 08:39:25 -0700546 return it;
547}
548
549MidiManagerAlsa::MidiPortStateBase::iterator
550MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
551 const MidiManagerAlsa::MidiPort& port) {
552 // Always match on:
553 // type
554 // Possible things to match on:
555 // path
556 // id
557 // client_id
558 // port_id
559 // midi_device
560 // client_name
561 // port_name
562
563 if (!port.path().empty()) {
564 // If path is present, then we have a card-based client.
565
566 // Pass 1. Match on path, id, midi_device, port_id.
567 // This is the best possible match for hardware card-based clients.
568 // This will also match the empty id correctly for devices without an id.
danakj75afea02016-04-25 20:36:04 -0700569 auto it = std::find_if(ports_.begin(), ports_.end(),
570 [&port](std::unique_ptr<MidiPort>& p) {
571 return p->MatchCardPass1(port);
572 });
agoode99d63292015-04-13 08:39:25 -0700573 if (it != ports_.end())
574 return it;
575
576 if (!port.id().empty()) {
577 // Pass 2. Match on id, midi_device, port_id.
578 // This will give us a high-confidence match when a user moves a device to
579 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
580 // has a hardware id.
danakj75afea02016-04-25 20:36:04 -0700581 it = std::find_if(ports_.begin(), ports_.end(),
582 [&port](std::unique_ptr<MidiPort>& p) {
583 return p->MatchCardPass2(port);
584 });
agoode99d63292015-04-13 08:39:25 -0700585 if (it != ports_.end())
586 return it;
587 }
588 } else {
589 // Else, we have a non-card-based client.
590 // Pass 1. Match on client_id, port_id, client_name, port_name.
591 // This will give us a reasonably good match.
danakj75afea02016-04-25 20:36:04 -0700592 auto it = std::find_if(ports_.begin(), ports_.end(),
593 [&port](std::unique_ptr<MidiPort>& p) {
594 return p->MatchNoCardPass1(port);
595 });
agoode99d63292015-04-13 08:39:25 -0700596 if (it != ports_.end())
597 return it;
598
599 // Pass 2. Match on port_id, client_name, port_name.
600 // This is weaker but similar to pass 2 in the hardware card-based clients
601 // match.
danakj75afea02016-04-25 20:36:04 -0700602 it = std::find_if(ports_.begin(), ports_.end(),
603 [&port](std::unique_ptr<MidiPort>& p) {
604 return p->MatchNoCardPass2(port);
605 });
agoode99d63292015-04-13 08:39:25 -0700606 if (it != ports_.end())
607 return it;
608 }
609
610 // No match.
611 return ports_.end();
612}
613
agoodeb0582872015-05-20 05:22:24 -0700614MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700615
agoodedf1b9ff2015-06-25 18:14:50 -0700616MidiManagerAlsa::MidiPortState::MidiPortState() = default;
agoode99d63292015-04-13 08:39:25 -0700617
danakj75afea02016-04-25 20:36:04 -0700618uint32_t MidiManagerAlsa::MidiPortState::push_back(
619 std::unique_ptr<MidiPort> port) {
agoode99d63292015-04-13 08:39:25 -0700620 // Add the web midi index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500621 uint32_t web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700622 switch (port->type()) {
623 case MidiPort::Type::kInput:
624 web_port_index = num_input_ports_++;
625 break;
626 case MidiPort::Type::kOutput:
627 web_port_index = num_output_ports_++;
628 break;
629 }
630 port->set_web_port_index(web_port_index);
dchengc2aeece2015-12-27 00:54:00 -0800631 MidiPortStateBase::push_back(std::move(port));
agoode99d63292015-04-13 08:39:25 -0700632 return web_port_index;
633}
634
agoodedf1b9ff2015-06-25 18:14:50 -0700635MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700636
agoodeb0582872015-05-20 05:22:24 -0700637MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700638
639void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
640 const std::string& client_name,
641 snd_seq_client_type_t type) {
642 ClientExit(client_id);
ricea37e45762016-08-25 02:43:39 -0700643 clients_.insert(
644 std::make_pair(client_id, base::MakeUnique<Client>(client_name, type)));
agoodeb09423b2015-05-11 11:39:57 -0700645 if (IsCardClient(type, client_id))
646 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700647}
648
649bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
650 return clients_.find(client_id) != clients_.end();
651}
652
653void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
654 auto it = clients_.find(client_id);
655 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700656 if (IsCardClient(it->second->type(), client_id))
657 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700658 clients_.erase(it);
659 }
660}
661
662void MidiManagerAlsa::AlsaSeqState::PortStart(
663 int client_id,
664 int port_id,
665 const std::string& port_name,
666 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
667 bool midi) {
668 auto it = clients_.find(client_id);
669 if (it != clients_.end())
670 it->second->AddPort(port_id,
ricea37e45762016-08-25 02:43:39 -0700671 base::MakeUnique<Port>(port_name, direction, midi));
agoode99d63292015-04-13 08:39:25 -0700672}
673
674void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
675 auto it = clients_.find(client_id);
676 if (it != clients_.end())
677 it->second->RemovePort(port_id);
678}
679
680snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
681 int client_id) const {
682 auto it = clients_.find(client_id);
683 if (it == clients_.end())
684 return SND_SEQ_USER_CLIENT;
685 return it->second->type();
686}
687
danakj75afea02016-04-25 20:36:04 -0700688std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700689MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
danakj75afea02016-04-25 20:36:04 -0700690 std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
agoode99d63292015-04-13 08:39:25 -0700691 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700692 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700693
agoodeb09423b2015-05-11 11:39:57 -0700694 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700695 for (const auto& client_pair : clients_) {
696 int client_id = client_pair.first;
vmpstr0205abb2016-06-28 18:50:56 -0700697 auto* client = client_pair.second.get();
agoode99d63292015-04-13 08:39:25 -0700698
699 // Get client metadata.
700 const std::string client_name = client->name();
701 std::string manufacturer;
702 std::string driver;
703 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700704 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700705 std::string card_name;
706 std::string card_longname;
707 int midi_device = -1;
708
agoodeb09423b2015-05-11 11:39:57 -0700709 if (IsCardClient(client->type(), client_id)) {
710 auto& card = card_it->second;
711 if (card_midi_device == -1)
712 card_midi_device = 0;
713
714 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700715 path = card->path();
716 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
717 card->usb_interface_num(), card->serial());
718 card_name = card->name();
719 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700720 midi_device = card_midi_device;
721
722 ++card_midi_device;
723 if (card_midi_device >= card->midi_device_count()) {
724 card_midi_device = -1;
725 ++card_it;
726 }
727 }
728
agoode99d63292015-04-13 08:39:25 -0700729 for (const auto& port_pair : *client) {
730 int port_id = port_pair.first;
731 const auto& port = port_pair.second;
732
733 if (port->midi()) {
734 std::string version;
735 if (!driver.empty()) {
736 version = driver + " / ";
737 }
738 version +=
739 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
740 SND_LIB_MINOR, SND_LIB_SUBMINOR);
741 PortDirection direction = port->direction();
742 if (direction == PortDirection::kInput ||
743 direction == PortDirection::kDuplex) {
ricea37e45762016-08-25 02:43:39 -0700744 midi_ports->push_back(base::MakeUnique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700745 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700746 port->name(), manufacturer, version, MidiPort::Type::kInput));
agoode99d63292015-04-13 08:39:25 -0700747 }
748 if (direction == PortDirection::kOutput ||
749 direction == PortDirection::kDuplex) {
ricea37e45762016-08-25 02:43:39 -0700750 midi_ports->push_back(base::MakeUnique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700751 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700752 port->name(), manufacturer, version, MidiPort::Type::kOutput));
agoode99d63292015-04-13 08:39:25 -0700753 }
754 }
755 }
756 }
757
dchengc2aeece2015-12-27 00:54:00 -0800758 return midi_ports;
agoode99d63292015-04-13 08:39:25 -0700759}
760
761MidiManagerAlsa::AlsaSeqState::Port::Port(
762 const std::string& name,
763 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
764 bool midi)
765 : name_(name), direction_(direction), midi_(midi) {
766}
767
agoodeb0582872015-05-20 05:22:24 -0700768MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700769
770MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
771 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700772 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700773}
774
agoodeb0582872015-05-20 05:22:24 -0700775MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700776
danakj75afea02016-04-25 20:36:04 -0700777void MidiManagerAlsa::AlsaSeqState::Client::AddPort(
778 int addr,
779 std::unique_ptr<Port> port) {
limasdfe59d0392015-11-19 20:28:57 -0800780 ports_[addr] = std::move(port);
agoode99d63292015-04-13 08:39:25 -0700781}
782
783void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700784 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700785}
786
787MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
788MidiManagerAlsa::AlsaSeqState::Client::begin() const {
789 return ports_.begin();
790}
791
792MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
793MidiManagerAlsa::AlsaSeqState::Client::end() const {
794 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700795}
796
agoodeb09423b2015-05-11 11:39:57 -0700797MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700798 const std::string& name,
799 const std::string& longname,
800 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700801 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700802 : name_(name),
803 longname_(longname),
804 driver_(driver),
805 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
806 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
807 vendor_id_(
808 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
809 model_id_(
810 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
811 usb_interface_num_(
812 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
813 serial_(UdevDeviceGetPropertyOrSysattr(dev,
814 kUdevIdSerialShort,
815 kSysattrGuid)),
816 midi_device_count_(midi_device_count),
817 manufacturer_(ExtractManufacturerString(
818 GetVendor(dev),
819 vendor_id_,
820 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
821 name,
822 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700823}
824
agoodeb0582872015-05-20 05:22:24 -0700825MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700826
agoode55a8b522015-03-08 12:40:17 -0700827// static
agoodeb09423b2015-05-11 11:39:57 -0700828std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700829 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700830 const std::string& udev_id_vendor_id,
831 const std::string& udev_id_vendor_from_database,
832 const std::string& alsa_name,
833 const std::string& alsa_longname) {
834 // Let's try to determine the manufacturer. Here is the ordered preference
835 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700836 // 1. Vendor name from the hardware device string, from udev properties
837 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700838 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700839 // 3. Heuristic from ALSA.
840
agoodee83758c2015-03-23 22:07:54 -0700841 // Is the vendor string present and not just the vendor hex id?
842 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700843 return udev_id_vendor;
844 }
845
846 // Is there a vendor string in the hardware database?
847 if (!udev_id_vendor_from_database.empty()) {
848 return udev_id_vendor_from_database;
849 }
850
851 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
852 // We assume that card longname is in the format of
853 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
854 // a manufacturer name here.
855 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700856 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700857 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700858 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700859 return alsa_longname.substr(0, name_index - 1);
860 }
861
862 // Failure.
863 return "";
864}
865
Avi Drissman3528fd02015-12-18 20:11:31 -0500866void MidiManagerAlsa::SendMidiData(uint32_t port_index,
867 const std::vector<uint8_t>& data) {
skyostil93e2ec22015-06-17 08:49:09 -0700868 DCHECK(send_thread_.task_runner()->BelongsToCurrentThread());
agoodebd4be9b2015-03-16 19:17:25 -0700869
agoodef212b2a2015-03-19 12:53:23 -0700870 snd_midi_event_t* encoder;
871 snd_midi_event_new(kSendBufferSize, &encoder);
agoode5a1aa112015-06-21 20:51:00 -0700872 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700873 snd_seq_event_t event;
agoode5a1aa112015-06-21 20:51:00 -0700874 int result = snd_midi_event_encode_byte(encoder, datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700875 if (result == 1) {
876 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700877 base::AutoLock lock(out_ports_lock_);
878 auto it = out_ports_.find(port_index);
879 if (it != out_ports_.end()) {
880 snd_seq_ev_set_source(&event, it->second);
881 snd_seq_ev_set_subs(&event);
882 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700883 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700884 }
agoodebd4be9b2015-03-16 19:17:25 -0700885 }
886 }
agoodef212b2a2015-03-19 12:53:23 -0700887 snd_midi_event_free(encoder);
agoodebd4be9b2015-03-16 19:17:25 -0700888}
889
890void MidiManagerAlsa::ScheduleEventLoop() {
fdoray8baaff02016-08-25 08:36:37 -0700891 event_thread_.task_runner()->PostTask(
agoodebd4be9b2015-03-16 19:17:25 -0700892 FROM_HERE,
893 base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
894}
895
896void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700897 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700898
agoode975043d2015-05-11 00:46:17 -0700899 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700900 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700901 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
902 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700903
agoode975043d2015-05-11 00:46:17 -0700904 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
905 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700906 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700907 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700908 } else {
agoode975043d2015-05-11 00:46:17 -0700909 if (pfd[0].revents & POLLIN) {
910 // Read available incoming MIDI data.
911 int remaining;
912 double timestamp =
913 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
914 do {
915 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700916 err = snd_seq_event_input(in_client_.get(), &event);
917 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700918
919 if (err == -ENOSPC) {
920 // Handle out of space error.
921 VLOG(1) << "snd_seq_event_input detected buffer overrun";
922 // We've lost events: check another way to see if we need to shut
923 // down.
924 base::AutoLock lock(shutdown_lock_);
925 if (event_thread_shutdown_)
926 loop_again = false;
927 } else if (err == -EAGAIN) {
928 // We've read all the data.
929 } else if (err < 0) {
930 // Handle other errors.
931 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
932 // TODO(agoode): Use RecordAction() or similar to log this.
933 loop_again = false;
934 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
935 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
936 // Handle announce events.
937 switch (event->type) {
938 case SND_SEQ_EVENT_PORT_START:
939 // Don't use SND_SEQ_EVENT_CLIENT_START because the
940 // client name may not be set by the time we query
941 // it. It should be set by the time ports are made.
942 ProcessClientStartEvent(event->data.addr.client);
943 ProcessPortStartEvent(event->data.addr);
944 break;
945 case SND_SEQ_EVENT_CLIENT_EXIT:
946 // Check for disconnection of our "out" client. This means "shut
947 // down".
948 if (event->data.addr.client == out_client_id_) {
949 loop_again = false;
950 remaining = 0;
951 } else
952 ProcessClientExitEvent(event->data.addr);
953 break;
954 case SND_SEQ_EVENT_PORT_EXIT:
955 ProcessPortExitEvent(event->data.addr);
956 break;
957 }
958 } else {
959 // Normal operation.
960 ProcessSingleEvent(event, timestamp);
961 }
962 } while (remaining > 0);
963 }
964 if (pfd[1].revents & POLLIN) {
965 device::ScopedUdevDevicePtr dev(
966 device::udev_monitor_receive_device(udev_monitor_.get()));
967 if (dev.get())
968 ProcessUdevEvent(dev.get());
969 else
970 VLOG(1) << "udev_monitor_receive_device fails";
971 }
agoodebd4be9b2015-03-16 19:17:25 -0700972 }
973
agoodebd4be9b2015-03-16 19:17:25 -0700974 // Do again.
agoode975043d2015-05-11 00:46:17 -0700975 if (loop_again)
976 ScheduleEventLoop();
agoodebd4be9b2015-03-16 19:17:25 -0700977}
978
979void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
980 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700981 auto source_it =
982 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700983 if (source_it != source_map_.end()) {
Avi Drissman3528fd02015-12-18 20:11:31 -0500984 uint32_t source = source_it->second;
agoodebd4be9b2015-03-16 19:17:25 -0700985 if (event->type == SND_SEQ_EVENT_SYSEX) {
986 // Special! Variable-length sysex.
Avi Drissman3528fd02015-12-18 20:11:31 -0500987 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
agoodebd4be9b2015-03-16 19:17:25 -0700988 event->data.ext.len, timestamp);
989 } else {
990 // Otherwise, decode this and send that on.
991 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700992 long count =
993 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700994 if (count <= 0) {
995 if (count != -ENOENT) {
996 // ENOENT means that it's not a MIDI message, which is not an
997 // error, but other negative values are errors for us.
998 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
999 // TODO(agoode): Record this failure.
1000 }
1001 } else {
1002 ReceiveMidiData(source, buf, count, timestamp);
1003 }
1004 }
1005 }
1006}
1007
agoode99d63292015-04-13 08:39:25 -07001008void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
1009 // Ignore if client is already started.
1010 if (alsa_seq_state_.ClientStarted(client_id))
1011 return;
1012
1013 snd_seq_client_info_t* client_info;
1014 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -07001015 int err =
1016 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -07001017 if (err != 0)
1018 return;
1019
1020 // Skip our own clients.
1021 if ((client_id == in_client_id_) || (client_id == out_client_id_))
1022 return;
1023
1024 // Update our view of ALSA seq state.
1025 alsa_seq_state_.ClientStart(client_id,
1026 snd_seq_client_info_get_name(client_info),
1027 snd_seq_client_info_get_type(client_info));
1028
1029 // Generate Web MIDI events.
1030 UpdatePortStateAndGenerateEvents();
1031}
1032
1033void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
1034 snd_seq_port_info_t* port_info;
1035 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -07001036 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
1037 port_info);
agoode99d63292015-04-13 08:39:25 -07001038 if (err != 0)
1039 return;
1040
1041 unsigned int caps = snd_seq_port_info_get_capability(port_info);
1042 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
1043 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
1044 AlsaSeqState::PortDirection direction;
1045 if (input && output)
1046 direction = AlsaSeqState::PortDirection::kDuplex;
1047 else if (input)
1048 direction = AlsaSeqState::PortDirection::kInput;
1049 else if (output)
1050 direction = AlsaSeqState::PortDirection::kOutput;
1051 else
1052 return;
1053
1054 // Update our view of ALSA seq state.
1055 alsa_seq_state_.PortStart(
1056 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1057 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1058 // Generate Web MIDI events.
1059 UpdatePortStateAndGenerateEvents();
1060}
1061
1062void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1063 // Update our view of ALSA seq state.
1064 alsa_seq_state_.ClientExit(addr.client);
1065 // Generate Web MIDI events.
1066 UpdatePortStateAndGenerateEvents();
1067}
1068
1069void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1070 // Update our view of ALSA seq state.
1071 alsa_seq_state_.PortExit(addr.client, addr.port);
1072 // Generate Web MIDI events.
1073 UpdatePortStateAndGenerateEvents();
1074}
1075
agoode975043d2015-05-11 00:46:17 -07001076void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1077 // Only card devices have this property set, and only when they are
1078 // fully initialized.
1079 if (!device::udev_device_get_property_value(dev,
1080 kUdevPropertySoundInitialized))
1081 return;
1082
1083 // Get the action. If no action, then we are doing first time enumeration
1084 // and the device is treated as new.
1085 const char* action = device::udev_device_get_action(dev);
1086 if (!action)
1087 action = kUdevActionChange;
1088
1089 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001090 AddCard(dev);
1091 // Generate Web MIDI events.
1092 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001093 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001094 RemoveCard(GetCardNumber(dev));
1095 // Generate Web MIDI events.
1096 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001097 }
1098}
1099
agoodeb09423b2015-05-11 11:39:57 -07001100void MidiManagerAlsa::AddCard(udev_device* dev) {
1101 int number = GetCardNumber(dev);
1102 if (number == -1)
1103 return;
1104
1105 RemoveCard(number);
1106
1107 snd_ctl_card_info_t* card;
1108 snd_hwdep_info_t* hwdep;
1109 snd_ctl_card_info_alloca(&card);
1110 snd_hwdep_info_alloca(&hwdep);
1111 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1112 snd_ctl_t* handle;
1113 int err = snd_ctl_open(&handle, id.c_str(), 0);
1114 if (err != 0) {
1115 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1116 return;
1117 }
1118 err = snd_ctl_card_info(handle, card);
1119 if (err != 0) {
1120 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1121 snd_ctl_close(handle);
1122 return;
1123 }
1124 std::string name = snd_ctl_card_info_get_name(card);
1125 std::string longname = snd_ctl_card_info_get_longname(card);
1126 std::string driver = snd_ctl_card_info_get_driver(card);
1127
1128 // Count rawmidi devices (not subdevices).
1129 int midi_count = 0;
1130 for (int device = -1;
1131 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1132 ++midi_count;
1133
1134 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1135 //
1136 // Explanation:
1137 // Any kernel driver can create an ALSA client (visible to us).
1138 // With modern hardware, only rawmidi devices do this. Kernel
1139 // drivers create rawmidi devices and the rawmidi subsystem makes
1140 // the seq clients. But the OPL3 driver is special, it does not
1141 // make a rawmidi device but a seq client directly. (This is the
1142 // only one to worry about in the kernel code, as of 2015-03-23.)
1143 //
1144 // OPL3 is very old (but still possible to get in new
1145 // hardware). It is unlikely that new drivers would not use
1146 // rawmidi and defeat our heuristic.
1147 //
1148 // Longer term, support should be added in the kernel to expose a
1149 // direct link from card->client (or client->card) so that all
1150 // these heuristics will be obsolete. Once that is there, we can
1151 // assume our old heuristics will work on old kernels and the new
1152 // robust code will be used on new. Then we will not need to worry
1153 // about changes to kernel internals breaking our code.
1154 // See the TODO above at kMinimumClientIdForCards.
1155 for (int device = -1;
1156 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1157 err = snd_ctl_hwdep_info(handle, hwdep);
1158 if (err != 0) {
1159 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1160 continue;
1161 }
1162 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1163 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1164 iface == SND_HWDEP_IFACE_OPL4)
1165 ++midi_count;
1166 }
1167 snd_ctl_close(handle);
1168
mgiucad9af8452015-06-25 02:11:57 -07001169 if (midi_count > 0) {
danakj75afea02016-04-25 20:36:04 -07001170 std::unique_ptr<AlsaCard> card(
mgiucad9af8452015-06-25 02:11:57 -07001171 new AlsaCard(dev, name, longname, driver, midi_count));
limasdfe59d0392015-11-19 20:28:57 -08001172 alsa_cards_.insert(std::make_pair(number, std::move(card)));
mgiucad9af8452015-06-25 02:11:57 -07001173 alsa_card_midi_count_ += midi_count;
1174 }
agoodeb09423b2015-05-11 11:39:57 -07001175}
1176
1177void MidiManagerAlsa::RemoveCard(int number) {
1178 auto it = alsa_cards_.find(number);
1179 if (it == alsa_cards_.end())
1180 return;
1181
1182 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001183 alsa_cards_.erase(it);
1184}
1185
agoode99d63292015-04-13 08:39:25 -07001186void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001187 // Verify that our information from ALSA and udev are in sync. If
1188 // not, we cannot generate events right now.
1189 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1190 return;
1191
agoode99d63292015-04-13 08:39:25 -07001192 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001193 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001194
1195 // Disconnect any connected old ports that are now missing.
agoode5ebc4932015-12-01 08:25:12 -08001196 for (auto& old_port : port_state_) {
agoode99d63292015-04-13 08:39:25 -07001197 if (old_port->connected() &&
1198 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1199 old_port->set_connected(false);
Avi Drissman3528fd02015-12-18 20:11:31 -05001200 uint32_t web_port_index = old_port->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001201 switch (old_port->type()) {
1202 case MidiPort::Type::kInput:
1203 source_map_.erase(
1204 AddrToInt(old_port->client_id(), old_port->port_id()));
1205 SetInputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1206 break;
1207 case MidiPort::Type::kOutput:
1208 DeleteAlsaOutputPort(web_port_index);
1209 SetOutputPortState(web_port_index, MIDI_PORT_DISCONNECTED);
1210 break;
1211 }
1212 }
1213 }
1214
1215 // Reconnect or add new ports.
1216 auto it = new_port_state->begin();
1217 while (it != new_port_state->end()) {
agoode5ebc4932015-12-01 08:25:12 -08001218 auto& new_port = *it;
agoode99d63292015-04-13 08:39:25 -07001219 auto old_port = port_state_.Find(*new_port);
1220 if (old_port == port_state_.end()) {
1221 // Add new port.
agoode5ebc4932015-12-01 08:25:12 -08001222 const auto& opaque_key = new_port->OpaqueKey();
1223 const auto& manufacturer = new_port->manufacturer();
1224 const auto& port_name = new_port->port_name();
1225 const auto& version = new_port->version();
1226 const auto& type = new_port->type();
1227 const auto& client_id = new_port->client_id();
1228 const auto& port_id = new_port->port_id();
1229
dchengc2aeece2015-12-27 00:54:00 -08001230 uint32_t web_port_index = port_state_.push_back(std::move(new_port));
agoode5ebc4932015-12-01 08:25:12 -08001231 it = new_port_state->erase(it);
1232
1233 MidiPortInfo info(opaque_key, manufacturer, port_name, version,
agoode99d63292015-04-13 08:39:25 -07001234 MIDI_PORT_OPENED);
agoode5ebc4932015-12-01 08:25:12 -08001235 switch (type) {
agoode99d63292015-04-13 08:39:25 -07001236 case MidiPort::Type::kInput:
agoode5ebc4932015-12-01 08:25:12 -08001237 if (Subscribe(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001238 AddInputPort(info);
1239 break;
agoode99d63292015-04-13 08:39:25 -07001240 case MidiPort::Type::kOutput:
agoode5ebc4932015-12-01 08:25:12 -08001241 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001242 AddOutputPort(info);
1243 break;
1244 }
agoode99d63292015-04-13 08:39:25 -07001245 } else if (!(*old_port)->connected()) {
1246 // Reconnect.
Avi Drissman3528fd02015-12-18 20:11:31 -05001247 uint32_t web_port_index = (*old_port)->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001248 (*old_port)->Update(new_port->path(), new_port->client_id(),
1249 new_port->port_id(), new_port->client_name(),
1250 new_port->port_name(), new_port->manufacturer(),
1251 new_port->version());
1252 switch ((*old_port)->type()) {
1253 case MidiPort::Type::kInput:
1254 if (Subscribe(web_port_index, (*old_port)->client_id(),
1255 (*old_port)->port_id()))
1256 SetInputPortState(web_port_index, MIDI_PORT_OPENED);
1257 break;
agoode99d63292015-04-13 08:39:25 -07001258 case MidiPort::Type::kOutput:
1259 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1260 (*old_port)->port_id()))
1261 SetOutputPortState(web_port_index, MIDI_PORT_OPENED);
1262 break;
1263 }
1264 (*old_port)->set_connected(true);
1265 ++it;
1266 } else {
1267 ++it;
1268 }
1269 }
1270}
1271
agoode975043d2015-05-11 00:46:17 -07001272// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001273void MidiManagerAlsa::EnumerateAlsaPorts() {
1274 snd_seq_client_info_t* client_info;
1275 snd_seq_client_info_alloca(&client_info);
1276 snd_seq_port_info_t* port_info;
1277 snd_seq_port_info_alloca(&port_info);
1278
1279 // Enumerate clients.
1280 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001281 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001282 int client_id = snd_seq_client_info_get_client(client_info);
1283 ProcessClientStartEvent(client_id);
1284
1285 // Enumerate ports.
1286 snd_seq_port_info_set_client(port_info, client_id);
1287 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001288 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001289 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1290 ProcessPortStartEvent(*addr);
1291 }
1292 }
1293}
1294
agoode975043d2015-05-11 00:46:17 -07001295bool MidiManagerAlsa::EnumerateUdevCards() {
1296 int err;
1297
1298 device::ScopedUdevEnumeratePtr enumerate(
1299 device::udev_enumerate_new(udev_.get()));
1300 if (!enumerate.get()) {
1301 VLOG(1) << "udev_enumerate_new fails";
1302 return false;
1303 }
1304
1305 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1306 kUdevSubsystemSound);
1307 if (err) {
1308 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001309 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001310 return false;
1311 }
1312
1313 err = device::udev_enumerate_scan_devices(enumerate.get());
1314 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001315 VLOG(1) << "udev_enumerate_scan_devices fails: "
1316 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001317 return false;
1318 }
1319
1320 udev_list_entry* list_entry;
1321 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1322 udev_list_entry_foreach(list_entry, devices) {
1323 const char* path = device::udev_list_entry_get_name(list_entry);
1324 device::ScopedUdevDevicePtr dev(
1325 device::udev_device_new_from_syspath(udev_.get(), path));
1326 if (dev.get())
1327 ProcessUdevEvent(dev.get());
1328 }
1329
1330 return true;
1331}
1332
Avi Drissman3528fd02015-12-18 20:11:31 -05001333bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
agoode99d63292015-04-13 08:39:25 -07001334 int client_id,
1335 int port_id) {
1336 // Create the port.
1337 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001338 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001339 if (out_port < 0) {
1340 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1341 return false;
1342 }
1343 // Activate port subscription.
1344 snd_seq_port_subscribe_t* subs;
1345 snd_seq_port_subscribe_alloca(&subs);
1346 snd_seq_addr_t sender;
1347 sender.client = out_client_id_;
1348 sender.port = out_port;
1349 snd_seq_port_subscribe_set_sender(subs, &sender);
1350 snd_seq_addr_t dest;
1351 dest.client = client_id;
1352 dest.port = port_id;
1353 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001354 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001355 if (err != 0) {
1356 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001357 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001358 return false;
1359 }
1360
1361 // Update our map.
1362 base::AutoLock lock(out_ports_lock_);
1363 out_ports_[port_index] = out_port;
1364 return true;
1365}
1366
Avi Drissman3528fd02015-12-18 20:11:31 -05001367void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
agoode99d63292015-04-13 08:39:25 -07001368 base::AutoLock lock(out_ports_lock_);
1369 auto it = out_ports_.find(port_index);
1370 if (it == out_ports_.end())
1371 return;
1372
1373 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001374 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001375 out_ports_.erase(it);
1376}
1377
Avi Drissman3528fd02015-12-18 20:11:31 -05001378bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1379 int client_id,
1380 int port_id) {
agoode99d63292015-04-13 08:39:25 -07001381 // Activate port subscription.
1382 snd_seq_port_subscribe_t* subs;
1383 snd_seq_port_subscribe_alloca(&subs);
1384 snd_seq_addr_t sender;
1385 sender.client = client_id;
1386 sender.port = port_id;
1387 snd_seq_port_subscribe_set_sender(subs, &sender);
1388 snd_seq_addr_t dest;
1389 dest.client = in_client_id_;
1390 dest.port = in_port_id_;
1391 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001392 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001393 if (err != 0) {
1394 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1395 return false;
1396 }
1397
1398 // Update our map.
1399 source_map_[AddrToInt(client_id, port_id)] = port_index;
1400 return true;
1401}
1402
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +00001403MidiManager* MidiManager::Create() {
1404 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001405}
1406
toyoshime147c5e2015-05-07 21:58:31 -07001407} // namespace midi