blob: 2f9e46d247f19a425bee50574d9786b9d5ef122f [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"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000020#include "base/message_loop/message_loop.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000021#include "base/posix/eintr_wrapper.h"
brettwf7f870f2015-06-09 11:05:24 -070022#include "base/posix/safe_strerror.h"
fdoray8baaff02016-08-25 08:36:37 -070023#include "base/single_thread_task_runner.h"
agoodef212b2a2015-03-19 12:53:23 -070024#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000025#include "base/strings/stringprintf.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000026#include "base/time/time.h"
Takashi Toyoshima70e44ee2017-07-19 08:38:20 +000027#include "build/build_config.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"
toyoshimf4d61522017-02-10 02:03:32 -080030#include "media/midi/midi_service.h"
toyoshimefcee5e2017-06-14 07:14:39 -070031#include "media/midi/task_service.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000032
toyoshime147c5e2015-05-07 21:58:31 -070033namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000034
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000035namespace {
36
toyoshimec2570a2016-10-21 02:15:27 -070037using mojom::PortState;
38using mojom::Result;
toyoshim2f3a48f2016-10-17 01:54:13 -070039
toyoshimefcee5e2017-06-14 07:14:39 -070040enum {
41 kDefaultRunnerNotUsedOnAlsa = TaskService::kDefaultRunnerId,
42 kEventTaskRunner,
43 kSendTaskRunner
44};
toyoshimf4d61522017-02-10 02:03:32 -080045
agoode@chromium.org25227512014-06-08 05:12:05 +000046// Per-output buffer. This can be smaller, but then large sysex messages
47// will be (harmlessly) split across multiple seq events. This should
48// not have any real practical effect, except perhaps to slightly reorder
49// realtime messages with respect to sysex.
toyoshimefcee5e2017-06-14 07:14:39 -070050constexpr size_t kSendBufferSize = 256;
agoode@chromium.org25227512014-06-08 05:12:05 +000051
agoodeb09423b2015-05-11 11:39:57 -070052// Minimum client id for which we will have ALSA card devices for. When we
53// are searching for card devices (used to get the path, id, and manufacturer),
54// we don't want to get confused by kernel clients that do not have a card.
55// See seq_clientmgr.c in the ALSA code for this.
56// TODO(agoode): Add proper client -> card export from the kernel to avoid
57// hardcoding.
toyoshimefcee5e2017-06-14 07:14:39 -070058constexpr int kMinimumClientIdForCards = 16;
agoodeb09423b2015-05-11 11:39:57 -070059
agoodef212b2a2015-03-19 12:53:23 -070060// ALSA constants.
61const char kAlsaHw[] = "hw";
62
agoode975043d2015-05-11 00:46:17 -070063// udev constants.
64const char kUdev[] = "udev";
65const char kUdevSubsystemSound[] = "sound";
66const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
67const char kUdevActionChange[] = "change";
68const char kUdevActionRemove[] = "remove";
69
agoodeb09423b2015-05-11 11:39:57 -070070const char kUdevIdVendor[] = "ID_VENDOR";
71const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
72const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
73const char kUdevIdVendorId[] = "ID_VENDOR_ID";
74const char kUdevIdModelId[] = "ID_MODEL_ID";
75const char kUdevIdBus[] = "ID_BUS";
76const char kUdevIdPath[] = "ID_PATH";
77const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
78const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
79
80const char kSysattrVendorName[] = "vendor_name";
81const char kSysattrVendor[] = "vendor";
82const char kSysattrModel[] = "model";
83const char kSysattrGuid[] = "guid";
84
85const char kCardSyspath[] = "/card";
86
agoode@chromium.org25227512014-06-08 05:12:05 +000087// Constants for the capabilities we search for in inputs and outputs.
88// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
toyoshimefcee5e2017-06-14 07:14:39 -070089constexpr unsigned int kRequiredInputPortCaps =
agoode@chromium.org25227512014-06-08 05:12:05 +000090 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
toyoshimefcee5e2017-06-14 07:14:39 -070091constexpr unsigned int kRequiredOutputPortCaps =
agoode@chromium.org25227512014-06-08 05:12:05 +000092 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
93
toyoshimefcee5e2017-06-14 07:14:39 -070094constexpr unsigned int kCreateOutputPortCaps =
agoode99d63292015-04-13 08:39:25 -070095 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
toyoshimefcee5e2017-06-14 07:14:39 -070096constexpr unsigned int kCreateInputPortCaps =
agoode99d63292015-04-13 08:39:25 -070097 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
toyoshimefcee5e2017-06-14 07:14:39 -070098constexpr unsigned int kCreatePortType =
agoode99d63292015-04-13 08:39:25 -070099 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +0000100
agoode99d63292015-04-13 08:39:25 -0700101int AddrToInt(int client, int port) {
102 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -0700103}
agoodef212b2a2015-03-19 12:53:23 -0700104
agoodeb09423b2015-05-11 11:39:57 -0700105// Returns true if this client has an ALSA card associated with it.
106bool IsCardClient(snd_seq_client_type_t type, int client_id) {
107 return (type == SND_SEQ_KERNEL_CLIENT) &&
108 (client_id >= kMinimumClientIdForCards);
109}
110
111// TODO(agoode): Move this to device/udev_linux.
112const std::string UdevDeviceGetPropertyOrSysattr(
113 struct udev_device* udev_device,
114 const char* property_key,
115 const char* sysattr_key) {
116 // First try the property.
117 std::string value =
118 device::UdevDeviceGetPropertyValue(udev_device, property_key);
119
120 // If no property, look for sysattrs and walk up the parent devices too.
121 while (value.empty() && udev_device) {
122 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
123 udev_device = device::udev_device_get_parent(udev_device);
124 }
125 return value;
126}
127
128int GetCardNumber(udev_device* dev) {
129 const char* syspath = device::udev_device_get_syspath(dev);
130 if (!syspath)
131 return -1;
132
133 std::string syspath_str(syspath);
134 size_t i = syspath_str.rfind(kCardSyspath);
135 if (i == std::string::npos)
136 return -1;
137
138 int number;
139 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
140 return -1;
141 return number;
142}
143
agooded87fc0f2015-05-21 08:29:31 -0700144std::string GetVendor(udev_device* dev) {
145 // Try to get the vendor string. Sometimes it is encoded.
146 std::string vendor = device::UdevDecodeString(
147 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
148 // Sometimes it is not encoded.
149 if (vendor.empty())
150 vendor =
151 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
152 return vendor;
153}
154
agoode8caab0b2015-03-23 18:48:02 -0700155void SetStringIfNonEmpty(base::DictionaryValue* value,
156 const std::string& path,
157 const std::string& in_value) {
158 if (!in_value.empty())
159 value->SetString(path, in_value);
160}
161
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000162} // namespace
163
toyoshimf4d61522017-02-10 02:03:32 -0800164MidiManagerAlsa::MidiManagerAlsa(MidiService* service) : MidiManager(service) {}
agoodeb2ead822016-03-11 12:14:35 -0800165
166MidiManagerAlsa::~MidiManagerAlsa() {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000167 base::AutoLock lock(lazy_init_member_lock_);
168
toyoshim131beb52016-07-25 00:42:41 -0700169 // Extra CHECK to verify all members are already reset.
toyoshim131beb52016-07-25 00:42:41 -0700170 CHECK(!in_client_);
171 CHECK(!out_client_);
172 CHECK(!decoder_);
173 CHECK(!udev_);
174 CHECK(!udev_monitor_);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000175}
176
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000177void MidiManagerAlsa::StartInitialization() {
toyoshimefcee5e2017-06-14 07:14:39 -0700178 if (!service()->task_service()->BindInstance()) {
179 NOTREACHED();
180 return CompleteInitialization(Result::INITIALIZATION_ERROR);
toyoshimf4d61522017-02-10 02:03:32 -0800181 }
182
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000183 base::AutoLock lock(lazy_init_member_lock_);
184
agoodeb2ead822016-03-11 12:14:35 -0800185 // Create client handles.
186 snd_seq_t* tmp_seq = nullptr;
187 int err =
188 snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000189 if (err != 0) {
190 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700191 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000192 }
agoodeb2ead822016-03-11 12:14:35 -0800193 ScopedSndSeqPtr in_client(tmp_seq);
194 tmp_seq = nullptr;
195 in_client_id_ = snd_seq_client_id(in_client.get());
196
197 err = snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
198 if (err != 0) {
199 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
200 return CompleteInitialization(Result::INITIALIZATION_ERROR);
201 }
202 ScopedSndSeqPtr out_client(tmp_seq);
203 tmp_seq = nullptr;
204 out_client_id_ = snd_seq_client_id(out_client.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000205
206 // Name the clients.
agoodeb2ead822016-03-11 12:14:35 -0800207 err = snd_seq_set_client_name(in_client.get(), "Chrome (input)");
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 }
agoodeb2ead822016-03-11 12:14:35 -0800212 err = snd_seq_set_client_name(out_client.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000213 if (err != 0) {
214 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700215 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000216 }
217
218 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700219 in_port_id_ = snd_seq_create_simple_port(
agoodeb2ead822016-03-11 12:14:35 -0800220 in_client.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700221 if (in_port_id_ < 0) {
222 VLOG(1) << "snd_seq_create_simple_port fails: "
223 << snd_strerror(in_port_id_);
toyoshimf1b88962015-07-09 14:14:51 -0700224 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000225 }
226
227 // Subscribe to the announce port.
228 snd_seq_port_subscribe_t* subs;
229 snd_seq_port_subscribe_alloca(&subs);
230 snd_seq_addr_t announce_sender;
231 snd_seq_addr_t announce_dest;
232 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
233 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700234 announce_dest.client = in_client_id_;
235 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000236 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
237 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoodeb2ead822016-03-11 12:14:35 -0800238 err = snd_seq_subscribe_port(in_client.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000239 if (err != 0) {
240 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
241 << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700242 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000243 }
244
agoodeb2ead822016-03-11 12:14:35 -0800245 // Initialize decoder.
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000246 ScopedSndMidiEventPtr decoder = CreateScopedSndMidiEventPtr(0);
agoodeb2ead822016-03-11 12:14:35 -0800247 snd_midi_event_no_status(decoder.get(), 1);
agoode@chromium.org25227512014-06-08 05:12:05 +0000248
agoodeb2ead822016-03-11 12:14:35 -0800249 // Initialize udev and monitor.
250 device::ScopedUdevPtr udev(device::udev_new());
251 device::ScopedUdevMonitorPtr udev_monitor(
252 device::udev_monitor_new_from_netlink(udev.get(), kUdev));
253 if (!udev_monitor.get()) {
agoode975043d2015-05-11 00:46:17 -0700254 VLOG(1) << "udev_monitor_new_from_netlink fails";
toyoshimf1b88962015-07-09 14:14:51 -0700255 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700256 }
257 err = device::udev_monitor_filter_add_match_subsystem_devtype(
agoodeb2ead822016-03-11 12:14:35 -0800258 udev_monitor.get(), kUdevSubsystemSound, nullptr);
agoode975043d2015-05-11 00:46:17 -0700259 if (err != 0) {
260 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700261 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700262 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700263 }
agoodeb2ead822016-03-11 12:14:35 -0800264 err = device::udev_monitor_enable_receiving(udev_monitor.get());
agoode975043d2015-05-11 00:46:17 -0700265 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700266 VLOG(1) << "udev_monitor_enable_receiving fails: "
267 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700268 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700269 }
270
agoodeb2ead822016-03-11 12:14:35 -0800271 // Success! Now, initialize members from the temporaries. Do not
272 // initialize these earlier, since they need to be destroyed by the
273 // thread that calls Finalize(), not the destructor thread (and we
274 // check this in the destructor).
275 in_client_.reset(in_client.release());
276 out_client_.reset(out_client.release());
277 decoder_.reset(decoder.release());
278 udev_.reset(udev.release());
agoode740f5222016-07-06 23:46:32 -0700279 udev_monitor_.reset(udev_monitor.release());
agoodeb2ead822016-03-11 12:14:35 -0800280
281 // Generate hotplug events for existing ports.
282 // TODO(agoode): Check the return value for failure.
283 EnumerateAlsaPorts();
284
285 // Generate hotplug events for existing udev devices. This must be done
286 // after udev_monitor_enable_receiving() is called. See the algorithm
287 // at http://www.signal11.us/oss/udev/.
agoode975043d2015-05-11 00:46:17 -0700288 EnumerateUdevCards();
289
agoodeb2ead822016-03-11 12:14:35 -0800290 // Start processing events. Don't do this before enumeration of both
291 // ALSA and udev.
toyoshimefcee5e2017-06-14 07:14:39 -0700292 service()->task_service()->PostBoundTask(
293 kEventTaskRunner,
294 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
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() {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000300 {
301 base::AutoLock lock(out_client_lock_);
302 // Close the out client. This will trigger the event thread to stop,
303 // because of SND_SEQ_EVENT_CLIENT_EXIT.
304 out_client_.reset();
305 }
toyoshim8e7d6e02015-10-06 08:47:17 -0700306
toyoshimefcee5e2017-06-14 07:14:39 -0700307 // Ensure that no task is running any more.
308 bool result = service()->task_service()->UnbindInstance();
309 CHECK(result);
agoodeb2ead822016-03-11 12:14:35 -0800310
311 // Destruct the other stuff we initialized in StartInitialization().
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000312 base::AutoLock lock(lazy_init_member_lock_);
agoodeb2ead822016-03-11 12:14:35 -0800313 udev_monitor_.reset();
314 udev_.reset();
315 decoder_.reset();
316 in_client_.reset();
toyoshim8e7d6e02015-10-06 08:47:17 -0700317}
318
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000319void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -0500320 uint32_t port_index,
321 const std::vector<uint8_t>& data,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000322 double timestamp) {
toyoshimefcee5e2017-06-14 07:14:39 -0700323 service()->task_service()->PostBoundDelayedTask(
324 kSendTaskRunner,
325 base::BindOnce(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
326 client, port_index, data),
Takashi Toyoshimaafb27d52017-09-13 11:50:41 +0000327 MidiService::TimestampToTimeDeltaDelay(timestamp));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000328}
329
agooded87fc0f2015-05-21 08:29:31 -0700330MidiManagerAlsa::MidiPort::Id::Id() = default;
331
332MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
333 const std::string& vendor_id,
334 const std::string& model_id,
335 const std::string& usb_interface_num,
336 const std::string& serial)
337 : bus_(bus),
338 vendor_id_(vendor_id),
339 model_id_(model_id),
340 usb_interface_num_(usb_interface_num),
341 serial_(serial) {
342}
343
344MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
345
346MidiManagerAlsa::MidiPort::Id::~Id() = default;
347
348bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
349 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
350 (model_id_ == rhs.model_id_) &&
351 (usb_interface_num_ == rhs.usb_interface_num_) &&
352 (serial_ == rhs.serial_);
353}
354
355bool MidiManagerAlsa::MidiPort::Id::empty() const {
356 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
357 usb_interface_num_.empty() && serial_.empty();
358}
359
agoode99d63292015-04-13 08:39:25 -0700360MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700361 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700362 int client_id,
363 int port_id,
364 int midi_device,
365 const std::string& client_name,
366 const std::string& port_name,
367 const std::string& manufacturer,
368 const std::string& version,
369 Type type)
370 : id_(id),
371 midi_device_(midi_device),
372 type_(type),
373 path_(path),
374 client_id_(client_id),
375 port_id_(port_id),
376 client_name_(client_name),
377 port_name_(port_name),
378 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700379 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000380}
381
agoodeb0582872015-05-20 05:22:24 -0700382MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000383
agoode99d63292015-04-13 08:39:25 -0700384// Note: keep synchronized with the MidiPort::Match* methods.
danakj75afea02016-04-25 20:36:04 -0700385std::unique_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
386 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
agoode99d63292015-04-13 08:39:25 -0700387
388 std::string type;
389 switch (type_) {
390 case Type::kInput:
391 type = "input";
392 break;
agoode99d63292015-04-13 08:39:25 -0700393 case Type::kOutput:
394 type = "output";
395 break;
396 }
397 value->SetString("type", type);
398 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700399 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
400 SetStringIfNonEmpty(value.get(), "portName", port_name_);
401 value->SetInteger("clientId", client_id_);
402 value->SetInteger("portId", port_id_);
403 value->SetInteger("midiDevice", midi_device_);
404
agooded87fc0f2015-05-21 08:29:31 -0700405 // Flatten id fields.
406 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
407 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
408 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
409 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
410 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
411
dchengc2aeece2015-12-27 00:54:00 -0800412 return std::move(value);
agoodebd4be9b2015-03-16 19:17:25 -0700413}
agoode@chromium.org25227512014-06-08 05:12:05 +0000414
agoode99d63292015-04-13 08:39:25 -0700415std::string MidiManagerAlsa::MidiPort::JSONValue() const {
416 std::string json;
417 JSONStringValueSerializer serializer(&json);
418 serializer.Serialize(*Value().get());
419 return json;
agoodef212b2a2015-03-19 12:53:23 -0700420}
421
agoode99d63292015-04-13 08:39:25 -0700422// TODO(agoode): Do not use SHA256 here. Instead store a persistent
423// mapping and just use a UUID or other random string.
424// http://crbug.com/465320
425std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
Avi Drissman3528fd02015-12-18 20:11:31 -0500426 uint8_t hash[crypto::kSHA256Length];
agoode99d63292015-04-13 08:39:25 -0700427 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
428 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700429}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000430
agoode99d63292015-04-13 08:39:25 -0700431bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
432 // Matches on:
433 // connected == true
434 // type
435 // path
436 // id
437 // client_id
438 // port_id
439 // midi_device
440 // client_name
441 // port_name
442 return connected() && (type() == query.type()) && (path() == query.path()) &&
443 (id() == query.id()) && (client_id() == query.client_id()) &&
444 (port_id() == query.port_id()) &&
445 (midi_device() == query.midi_device()) &&
446 (client_name() == query.client_name()) &&
447 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700448}
449
agoode99d63292015-04-13 08:39:25 -0700450bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
451 // Matches on:
452 // connected == false
453 // type
454 // path
455 // id
456 // port_id
457 // midi_device
458 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700459}
460
agoode99d63292015-04-13 08:39:25 -0700461bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
462 // Matches on:
463 // connected == false
464 // type
465 // id
466 // port_id
467 // midi_device
468 return !connected() && (type() == query.type()) && (id() == query.id()) &&
469 (port_id() == query.port_id()) &&
470 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700471}
472
agoode99d63292015-04-13 08:39:25 -0700473bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
474 // Matches on:
475 // connected == false
476 // type
477 // path.empty(), for both this and query
478 // id.empty(), for both this and query
479 // client_id
480 // port_id
481 // client_name
482 // port_name
483 // midi_device == -1, for both this and query
484 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700485}
486
agoode99d63292015-04-13 08:39:25 -0700487bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
488 // Matches on:
489 // connected == false
490 // type
491 // path.empty(), for both this and query
492 // id.empty(), for both this and query
493 // port_id
494 // client_name
495 // port_name
496 // midi_device == -1, for both this and query
497 return !connected() && (type() == query.type()) && path().empty() &&
498 query.path().empty() && id().empty() && query.id().empty() &&
499 (port_id() == query.port_id()) &&
500 (client_name() == query.client_name()) &&
501 (port_name() == query.port_name()) && (midi_device() == -1) &&
502 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000503}
504
agoodeb0582872015-05-20 05:22:24 -0700505MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700506
507MidiManagerAlsa::MidiPortStateBase::iterator
508MidiManagerAlsa::MidiPortStateBase::Find(
509 const MidiManagerAlsa::MidiPort& port) {
510 auto result = FindConnected(port);
511 if (result == end())
512 result = FindDisconnected(port);
513 return result;
514}
515
516MidiManagerAlsa::MidiPortStateBase::iterator
517MidiManagerAlsa::MidiPortStateBase::FindConnected(
518 const MidiManagerAlsa::MidiPort& port) {
519 // Exact match required for connected ports.
danakj75afea02016-04-25 20:36:04 -0700520 auto it = std::find_if(ports_.begin(), ports_.end(),
521 [&port](std::unique_ptr<MidiPort>& p) {
522 return p->MatchConnected(port);
523 });
agoode99d63292015-04-13 08:39:25 -0700524 return it;
525}
526
527MidiManagerAlsa::MidiPortStateBase::iterator
528MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
529 const MidiManagerAlsa::MidiPort& port) {
530 // Always match on:
531 // type
532 // Possible things to match on:
533 // path
534 // id
535 // client_id
536 // port_id
537 // midi_device
538 // client_name
539 // port_name
540
541 if (!port.path().empty()) {
542 // If path is present, then we have a card-based client.
543
544 // Pass 1. Match on path, id, midi_device, port_id.
545 // This is the best possible match for hardware card-based clients.
546 // This will also match the empty id correctly for devices without an id.
danakj75afea02016-04-25 20:36:04 -0700547 auto it = std::find_if(ports_.begin(), ports_.end(),
548 [&port](std::unique_ptr<MidiPort>& p) {
549 return p->MatchCardPass1(port);
550 });
agoode99d63292015-04-13 08:39:25 -0700551 if (it != ports_.end())
552 return it;
553
554 if (!port.id().empty()) {
555 // Pass 2. Match on id, midi_device, port_id.
556 // This will give us a high-confidence match when a user moves a device to
557 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
558 // has a hardware id.
danakj75afea02016-04-25 20:36:04 -0700559 it = std::find_if(ports_.begin(), ports_.end(),
560 [&port](std::unique_ptr<MidiPort>& p) {
561 return p->MatchCardPass2(port);
562 });
agoode99d63292015-04-13 08:39:25 -0700563 if (it != ports_.end())
564 return it;
565 }
566 } else {
567 // Else, we have a non-card-based client.
568 // Pass 1. Match on client_id, port_id, client_name, port_name.
569 // This will give us a reasonably good match.
danakj75afea02016-04-25 20:36:04 -0700570 auto it = std::find_if(ports_.begin(), ports_.end(),
571 [&port](std::unique_ptr<MidiPort>& p) {
572 return p->MatchNoCardPass1(port);
573 });
agoode99d63292015-04-13 08:39:25 -0700574 if (it != ports_.end())
575 return it;
576
577 // Pass 2. Match on port_id, client_name, port_name.
578 // This is weaker but similar to pass 2 in the hardware card-based clients
579 // match.
danakj75afea02016-04-25 20:36:04 -0700580 it = std::find_if(ports_.begin(), ports_.end(),
581 [&port](std::unique_ptr<MidiPort>& p) {
582 return p->MatchNoCardPass2(port);
583 });
agoode99d63292015-04-13 08:39:25 -0700584 if (it != ports_.end())
585 return it;
586 }
587
588 // No match.
589 return ports_.end();
590}
591
agoodeb0582872015-05-20 05:22:24 -0700592MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700593
agoodedf1b9ff2015-06-25 18:14:50 -0700594MidiManagerAlsa::MidiPortState::MidiPortState() = default;
agoode99d63292015-04-13 08:39:25 -0700595
danakj75afea02016-04-25 20:36:04 -0700596uint32_t MidiManagerAlsa::MidiPortState::push_back(
597 std::unique_ptr<MidiPort> port) {
agoode99d63292015-04-13 08:39:25 -0700598 // Add the web midi index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500599 uint32_t web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700600 switch (port->type()) {
601 case MidiPort::Type::kInput:
602 web_port_index = num_input_ports_++;
603 break;
604 case MidiPort::Type::kOutput:
605 web_port_index = num_output_ports_++;
606 break;
607 }
608 port->set_web_port_index(web_port_index);
dchengc2aeece2015-12-27 00:54:00 -0800609 MidiPortStateBase::push_back(std::move(port));
agoode99d63292015-04-13 08:39:25 -0700610 return web_port_index;
611}
612
agoodedf1b9ff2015-06-25 18:14:50 -0700613MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700614
agoodeb0582872015-05-20 05:22:24 -0700615MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700616
617void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
618 const std::string& client_name,
619 snd_seq_client_type_t type) {
620 ClientExit(client_id);
ricea37e45762016-08-25 02:43:39 -0700621 clients_.insert(
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000622 std::make_pair(client_id, std::make_unique<Client>(client_name, type)));
agoodeb09423b2015-05-11 11:39:57 -0700623 if (IsCardClient(type, client_id))
624 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700625}
626
627bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
628 return clients_.find(client_id) != clients_.end();
629}
630
631void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
632 auto it = clients_.find(client_id);
633 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700634 if (IsCardClient(it->second->type(), client_id))
635 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700636 clients_.erase(it);
637 }
638}
639
640void MidiManagerAlsa::AlsaSeqState::PortStart(
641 int client_id,
642 int port_id,
643 const std::string& port_name,
644 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
645 bool midi) {
646 auto it = clients_.find(client_id);
647 if (it != clients_.end())
648 it->second->AddPort(port_id,
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000649 std::make_unique<Port>(port_name, direction, midi));
agoode99d63292015-04-13 08:39:25 -0700650}
651
652void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
653 auto it = clients_.find(client_id);
654 if (it != clients_.end())
655 it->second->RemovePort(port_id);
656}
657
658snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
659 int client_id) const {
660 auto it = clients_.find(client_id);
661 if (it == clients_.end())
662 return SND_SEQ_USER_CLIENT;
663 return it->second->type();
664}
665
danakj75afea02016-04-25 20:36:04 -0700666std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700667MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
danakj75afea02016-04-25 20:36:04 -0700668 std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
agoode99d63292015-04-13 08:39:25 -0700669 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700670 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700671
agoodeb09423b2015-05-11 11:39:57 -0700672 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700673 for (const auto& client_pair : clients_) {
674 int client_id = client_pair.first;
vmpstr0205abb2016-06-28 18:50:56 -0700675 auto* client = client_pair.second.get();
agoode99d63292015-04-13 08:39:25 -0700676
677 // Get client metadata.
678 const std::string client_name = client->name();
679 std::string manufacturer;
680 std::string driver;
681 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700682 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700683 std::string card_name;
684 std::string card_longname;
685 int midi_device = -1;
686
agoodeb09423b2015-05-11 11:39:57 -0700687 if (IsCardClient(client->type(), client_id)) {
688 auto& card = card_it->second;
689 if (card_midi_device == -1)
690 card_midi_device = 0;
691
692 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700693 path = card->path();
694 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
695 card->usb_interface_num(), card->serial());
696 card_name = card->name();
697 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700698 midi_device = card_midi_device;
699
700 ++card_midi_device;
701 if (card_midi_device >= card->midi_device_count()) {
702 card_midi_device = -1;
703 ++card_it;
704 }
705 }
706
agoode99d63292015-04-13 08:39:25 -0700707 for (const auto& port_pair : *client) {
708 int port_id = port_pair.first;
709 const auto& port = port_pair.second;
710
711 if (port->midi()) {
712 std::string version;
713 if (!driver.empty()) {
714 version = driver + " / ";
715 }
716 version +=
717 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
718 SND_LIB_MINOR, SND_LIB_SUBMINOR);
719 PortDirection direction = port->direction();
720 if (direction == PortDirection::kInput ||
721 direction == PortDirection::kDuplex) {
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000722 midi_ports->push_back(std::make_unique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700723 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700724 port->name(), manufacturer, version, MidiPort::Type::kInput));
agoode99d63292015-04-13 08:39:25 -0700725 }
726 if (direction == PortDirection::kOutput ||
727 direction == PortDirection::kDuplex) {
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000728 midi_ports->push_back(std::make_unique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700729 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700730 port->name(), manufacturer, version, MidiPort::Type::kOutput));
agoode99d63292015-04-13 08:39:25 -0700731 }
732 }
733 }
734 }
735
dchengc2aeece2015-12-27 00:54:00 -0800736 return midi_ports;
agoode99d63292015-04-13 08:39:25 -0700737}
738
739MidiManagerAlsa::AlsaSeqState::Port::Port(
740 const std::string& name,
741 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
742 bool midi)
743 : name_(name), direction_(direction), midi_(midi) {
744}
745
agoodeb0582872015-05-20 05:22:24 -0700746MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700747
748MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
749 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700750 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700751}
752
agoodeb0582872015-05-20 05:22:24 -0700753MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700754
danakj75afea02016-04-25 20:36:04 -0700755void MidiManagerAlsa::AlsaSeqState::Client::AddPort(
756 int addr,
757 std::unique_ptr<Port> port) {
limasdfe59d0392015-11-19 20:28:57 -0800758 ports_[addr] = std::move(port);
agoode99d63292015-04-13 08:39:25 -0700759}
760
761void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700762 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700763}
764
765MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
766MidiManagerAlsa::AlsaSeqState::Client::begin() const {
767 return ports_.begin();
768}
769
770MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
771MidiManagerAlsa::AlsaSeqState::Client::end() const {
772 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700773}
774
agoodeb09423b2015-05-11 11:39:57 -0700775MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700776 const std::string& name,
777 const std::string& longname,
778 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700779 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700780 : name_(name),
781 longname_(longname),
782 driver_(driver),
783 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
784 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
785 vendor_id_(
786 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
787 model_id_(
788 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
789 usb_interface_num_(
790 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
791 serial_(UdevDeviceGetPropertyOrSysattr(dev,
792 kUdevIdSerialShort,
793 kSysattrGuid)),
794 midi_device_count_(midi_device_count),
795 manufacturer_(ExtractManufacturerString(
796 GetVendor(dev),
797 vendor_id_,
798 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
799 name,
800 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700801}
802
agoodeb0582872015-05-20 05:22:24 -0700803MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700804
agoode55a8b522015-03-08 12:40:17 -0700805// static
agoodeb09423b2015-05-11 11:39:57 -0700806std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700807 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700808 const std::string& udev_id_vendor_id,
809 const std::string& udev_id_vendor_from_database,
810 const std::string& alsa_name,
811 const std::string& alsa_longname) {
812 // Let's try to determine the manufacturer. Here is the ordered preference
813 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700814 // 1. Vendor name from the hardware device string, from udev properties
815 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700816 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700817 // 3. Heuristic from ALSA.
818
agoodee83758c2015-03-23 22:07:54 -0700819 // Is the vendor string present and not just the vendor hex id?
820 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700821 return udev_id_vendor;
822 }
823
824 // Is there a vendor string in the hardware database?
825 if (!udev_id_vendor_from_database.empty()) {
826 return udev_id_vendor_from_database;
827 }
828
829 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
830 // We assume that card longname is in the format of
831 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
832 // a manufacturer name here.
833 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700834 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700835 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700836 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700837 return alsa_longname.substr(0, name_index - 1);
838 }
839
840 // Failure.
841 return "";
842}
843
toyoshimefcee5e2017-06-14 07:14:39 -0700844void MidiManagerAlsa::SendMidiData(MidiManagerClient* client,
toyoshimf4d61522017-02-10 02:03:32 -0800845 uint32_t port_index,
Avi Drissman3528fd02015-12-18 20:11:31 -0500846 const std::vector<uint8_t>& data) {
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000847 ScopedSndMidiEventPtr encoder = CreateScopedSndMidiEventPtr(kSendBufferSize);
agoode5a1aa112015-06-21 20:51:00 -0700848 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700849 snd_seq_event_t event;
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000850 int result = snd_midi_event_encode_byte(encoder.get(), datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700851 if (result == 1) {
852 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700853 base::AutoLock lock(out_ports_lock_);
854 auto it = out_ports_.find(port_index);
855 if (it != out_ports_.end()) {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000856 base::AutoLock lock(out_client_lock_);
857 if (!out_client_)
858 return;
agoode99d63292015-04-13 08:39:25 -0700859 snd_seq_ev_set_source(&event, it->second);
860 snd_seq_ev_set_subs(&event);
861 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700862 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700863 }
agoodebd4be9b2015-03-16 19:17:25 -0700864 }
865 }
toyoshimf4d61522017-02-10 02:03:32 -0800866
867 // Acknowledge send.
868 AccumulateMidiBytesSent(client, data.size());
agoodebd4be9b2015-03-16 19:17:25 -0700869}
870
toyoshimefcee5e2017-06-14 07:14:39 -0700871void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700872 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700873
agoode975043d2015-05-11 00:46:17 -0700874 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700875 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700876 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
877 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700878
agoode975043d2015-05-11 00:46:17 -0700879 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
880 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700881 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700882 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700883 } else {
agoode975043d2015-05-11 00:46:17 -0700884 if (pfd[0].revents & POLLIN) {
885 // Read available incoming MIDI data.
886 int remaining;
887 double timestamp =
888 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
889 do {
890 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700891 err = snd_seq_event_input(in_client_.get(), &event);
892 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700893
894 if (err == -ENOSPC) {
895 // Handle out of space error.
896 VLOG(1) << "snd_seq_event_input detected buffer overrun";
897 // We've lost events: check another way to see if we need to shut
898 // down.
agoode975043d2015-05-11 00:46:17 -0700899 } else if (err == -EAGAIN) {
900 // We've read all the data.
901 } else if (err < 0) {
902 // Handle other errors.
903 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
904 // TODO(agoode): Use RecordAction() or similar to log this.
905 loop_again = false;
906 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
907 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
908 // Handle announce events.
909 switch (event->type) {
910 case SND_SEQ_EVENT_PORT_START:
911 // Don't use SND_SEQ_EVENT_CLIENT_START because the
912 // client name may not be set by the time we query
913 // it. It should be set by the time ports are made.
914 ProcessClientStartEvent(event->data.addr.client);
915 ProcessPortStartEvent(event->data.addr);
916 break;
917 case SND_SEQ_EVENT_CLIENT_EXIT:
918 // Check for disconnection of our "out" client. This means "shut
919 // down".
920 if (event->data.addr.client == out_client_id_) {
921 loop_again = false;
922 remaining = 0;
923 } else
924 ProcessClientExitEvent(event->data.addr);
925 break;
926 case SND_SEQ_EVENT_PORT_EXIT:
927 ProcessPortExitEvent(event->data.addr);
928 break;
929 }
930 } else {
931 // Normal operation.
932 ProcessSingleEvent(event, timestamp);
933 }
934 } while (remaining > 0);
935 }
936 if (pfd[1].revents & POLLIN) {
937 device::ScopedUdevDevicePtr dev(
938 device::udev_monitor_receive_device(udev_monitor_.get()));
939 if (dev.get())
940 ProcessUdevEvent(dev.get());
941 else
942 VLOG(1) << "udev_monitor_receive_device fails";
943 }
agoodebd4be9b2015-03-16 19:17:25 -0700944 }
945
agoodebd4be9b2015-03-16 19:17:25 -0700946 // Do again.
toyoshimf4d61522017-02-10 02:03:32 -0800947 if (loop_again) {
toyoshimefcee5e2017-06-14 07:14:39 -0700948 service()->task_service()->PostBoundTask(
949 kEventTaskRunner,
950 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
toyoshimf4d61522017-02-10 02:03:32 -0800951 }
agoodebd4be9b2015-03-16 19:17:25 -0700952}
953
954void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
955 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700956 auto source_it =
957 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700958 if (source_it != source_map_.end()) {
Avi Drissman3528fd02015-12-18 20:11:31 -0500959 uint32_t source = source_it->second;
agoodebd4be9b2015-03-16 19:17:25 -0700960 if (event->type == SND_SEQ_EVENT_SYSEX) {
961 // Special! Variable-length sysex.
Avi Drissman3528fd02015-12-18 20:11:31 -0500962 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
agoodebd4be9b2015-03-16 19:17:25 -0700963 event->data.ext.len, timestamp);
964 } else {
965 // Otherwise, decode this and send that on.
966 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700967 long count =
968 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700969 if (count <= 0) {
970 if (count != -ENOENT) {
971 // ENOENT means that it's not a MIDI message, which is not an
972 // error, but other negative values are errors for us.
973 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
974 // TODO(agoode): Record this failure.
975 }
976 } else {
977 ReceiveMidiData(source, buf, count, timestamp);
978 }
979 }
980 }
981}
982
agoode99d63292015-04-13 08:39:25 -0700983void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
984 // Ignore if client is already started.
985 if (alsa_seq_state_.ClientStarted(client_id))
986 return;
987
988 snd_seq_client_info_t* client_info;
989 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -0700990 int err =
991 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -0700992 if (err != 0)
993 return;
994
995 // Skip our own clients.
996 if ((client_id == in_client_id_) || (client_id == out_client_id_))
997 return;
998
999 // Update our view of ALSA seq state.
1000 alsa_seq_state_.ClientStart(client_id,
1001 snd_seq_client_info_get_name(client_info),
1002 snd_seq_client_info_get_type(client_info));
1003
1004 // Generate Web MIDI events.
1005 UpdatePortStateAndGenerateEvents();
1006}
1007
1008void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
1009 snd_seq_port_info_t* port_info;
1010 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -07001011 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
1012 port_info);
agoode99d63292015-04-13 08:39:25 -07001013 if (err != 0)
1014 return;
1015
1016 unsigned int caps = snd_seq_port_info_get_capability(port_info);
1017 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
1018 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
1019 AlsaSeqState::PortDirection direction;
1020 if (input && output)
1021 direction = AlsaSeqState::PortDirection::kDuplex;
1022 else if (input)
1023 direction = AlsaSeqState::PortDirection::kInput;
1024 else if (output)
1025 direction = AlsaSeqState::PortDirection::kOutput;
1026 else
1027 return;
1028
1029 // Update our view of ALSA seq state.
1030 alsa_seq_state_.PortStart(
1031 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1032 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1033 // Generate Web MIDI events.
1034 UpdatePortStateAndGenerateEvents();
1035}
1036
1037void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1038 // Update our view of ALSA seq state.
1039 alsa_seq_state_.ClientExit(addr.client);
1040 // Generate Web MIDI events.
1041 UpdatePortStateAndGenerateEvents();
1042}
1043
1044void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1045 // Update our view of ALSA seq state.
1046 alsa_seq_state_.PortExit(addr.client, addr.port);
1047 // Generate Web MIDI events.
1048 UpdatePortStateAndGenerateEvents();
1049}
1050
agoode975043d2015-05-11 00:46:17 -07001051void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1052 // Only card devices have this property set, and only when they are
1053 // fully initialized.
1054 if (!device::udev_device_get_property_value(dev,
1055 kUdevPropertySoundInitialized))
1056 return;
1057
1058 // Get the action. If no action, then we are doing first time enumeration
1059 // and the device is treated as new.
1060 const char* action = device::udev_device_get_action(dev);
1061 if (!action)
1062 action = kUdevActionChange;
1063
1064 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001065 AddCard(dev);
1066 // Generate Web MIDI events.
1067 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001068 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001069 RemoveCard(GetCardNumber(dev));
1070 // Generate Web MIDI events.
1071 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001072 }
1073}
1074
agoodeb09423b2015-05-11 11:39:57 -07001075void MidiManagerAlsa::AddCard(udev_device* dev) {
1076 int number = GetCardNumber(dev);
1077 if (number == -1)
1078 return;
1079
1080 RemoveCard(number);
1081
1082 snd_ctl_card_info_t* card;
1083 snd_hwdep_info_t* hwdep;
1084 snd_ctl_card_info_alloca(&card);
1085 snd_hwdep_info_alloca(&hwdep);
1086 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1087 snd_ctl_t* handle;
1088 int err = snd_ctl_open(&handle, id.c_str(), 0);
1089 if (err != 0) {
1090 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1091 return;
1092 }
1093 err = snd_ctl_card_info(handle, card);
1094 if (err != 0) {
1095 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1096 snd_ctl_close(handle);
1097 return;
1098 }
1099 std::string name = snd_ctl_card_info_get_name(card);
1100 std::string longname = snd_ctl_card_info_get_longname(card);
1101 std::string driver = snd_ctl_card_info_get_driver(card);
1102
1103 // Count rawmidi devices (not subdevices).
1104 int midi_count = 0;
1105 for (int device = -1;
1106 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1107 ++midi_count;
1108
1109 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1110 //
1111 // Explanation:
1112 // Any kernel driver can create an ALSA client (visible to us).
1113 // With modern hardware, only rawmidi devices do this. Kernel
1114 // drivers create rawmidi devices and the rawmidi subsystem makes
1115 // the seq clients. But the OPL3 driver is special, it does not
1116 // make a rawmidi device but a seq client directly. (This is the
1117 // only one to worry about in the kernel code, as of 2015-03-23.)
1118 //
1119 // OPL3 is very old (but still possible to get in new
1120 // hardware). It is unlikely that new drivers would not use
1121 // rawmidi and defeat our heuristic.
1122 //
1123 // Longer term, support should be added in the kernel to expose a
1124 // direct link from card->client (or client->card) so that all
1125 // these heuristics will be obsolete. Once that is there, we can
1126 // assume our old heuristics will work on old kernels and the new
1127 // robust code will be used on new. Then we will not need to worry
1128 // about changes to kernel internals breaking our code.
1129 // See the TODO above at kMinimumClientIdForCards.
1130 for (int device = -1;
1131 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1132 err = snd_ctl_hwdep_info(handle, hwdep);
1133 if (err != 0) {
1134 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1135 continue;
1136 }
1137 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1138 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1139 iface == SND_HWDEP_IFACE_OPL4)
1140 ++midi_count;
1141 }
1142 snd_ctl_close(handle);
1143
mgiucad9af8452015-06-25 02:11:57 -07001144 if (midi_count > 0) {
danakj75afea02016-04-25 20:36:04 -07001145 std::unique_ptr<AlsaCard> card(
mgiucad9af8452015-06-25 02:11:57 -07001146 new AlsaCard(dev, name, longname, driver, midi_count));
limasdfe59d0392015-11-19 20:28:57 -08001147 alsa_cards_.insert(std::make_pair(number, std::move(card)));
mgiucad9af8452015-06-25 02:11:57 -07001148 alsa_card_midi_count_ += midi_count;
1149 }
agoodeb09423b2015-05-11 11:39:57 -07001150}
1151
1152void MidiManagerAlsa::RemoveCard(int number) {
1153 auto it = alsa_cards_.find(number);
1154 if (it == alsa_cards_.end())
1155 return;
1156
1157 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001158 alsa_cards_.erase(it);
1159}
1160
agoode99d63292015-04-13 08:39:25 -07001161void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001162 // Verify that our information from ALSA and udev are in sync. If
1163 // not, we cannot generate events right now.
1164 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1165 return;
1166
agoode99d63292015-04-13 08:39:25 -07001167 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001168 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001169
1170 // Disconnect any connected old ports that are now missing.
agoode5ebc4932015-12-01 08:25:12 -08001171 for (auto& old_port : port_state_) {
agoode99d63292015-04-13 08:39:25 -07001172 if (old_port->connected() &&
1173 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1174 old_port->set_connected(false);
Avi Drissman3528fd02015-12-18 20:11:31 -05001175 uint32_t web_port_index = old_port->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001176 switch (old_port->type()) {
1177 case MidiPort::Type::kInput:
1178 source_map_.erase(
1179 AddrToInt(old_port->client_id(), old_port->port_id()));
toyoshimec2570a2016-10-21 02:15:27 -07001180 SetInputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001181 break;
1182 case MidiPort::Type::kOutput:
1183 DeleteAlsaOutputPort(web_port_index);
toyoshimec2570a2016-10-21 02:15:27 -07001184 SetOutputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001185 break;
1186 }
1187 }
1188 }
1189
1190 // Reconnect or add new ports.
1191 auto it = new_port_state->begin();
1192 while (it != new_port_state->end()) {
agoode5ebc4932015-12-01 08:25:12 -08001193 auto& new_port = *it;
agoode99d63292015-04-13 08:39:25 -07001194 auto old_port = port_state_.Find(*new_port);
1195 if (old_port == port_state_.end()) {
1196 // Add new port.
agoode5ebc4932015-12-01 08:25:12 -08001197 const auto& opaque_key = new_port->OpaqueKey();
1198 const auto& manufacturer = new_port->manufacturer();
1199 const auto& port_name = new_port->port_name();
1200 const auto& version = new_port->version();
1201 const auto& type = new_port->type();
1202 const auto& client_id = new_port->client_id();
1203 const auto& port_id = new_port->port_id();
1204
dchengc2aeece2015-12-27 00:54:00 -08001205 uint32_t web_port_index = port_state_.push_back(std::move(new_port));
agoode5ebc4932015-12-01 08:25:12 -08001206 it = new_port_state->erase(it);
1207
1208 MidiPortInfo info(opaque_key, manufacturer, port_name, version,
toyoshimec2570a2016-10-21 02:15:27 -07001209 PortState::OPENED);
agoode5ebc4932015-12-01 08:25:12 -08001210 switch (type) {
agoode99d63292015-04-13 08:39:25 -07001211 case MidiPort::Type::kInput:
agoode5ebc4932015-12-01 08:25:12 -08001212 if (Subscribe(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001213 AddInputPort(info);
1214 break;
agoode99d63292015-04-13 08:39:25 -07001215 case MidiPort::Type::kOutput:
agoode5ebc4932015-12-01 08:25:12 -08001216 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001217 AddOutputPort(info);
1218 break;
1219 }
agoode99d63292015-04-13 08:39:25 -07001220 } else if (!(*old_port)->connected()) {
1221 // Reconnect.
Avi Drissman3528fd02015-12-18 20:11:31 -05001222 uint32_t web_port_index = (*old_port)->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001223 (*old_port)->Update(new_port->path(), new_port->client_id(),
1224 new_port->port_id(), new_port->client_name(),
1225 new_port->port_name(), new_port->manufacturer(),
1226 new_port->version());
1227 switch ((*old_port)->type()) {
1228 case MidiPort::Type::kInput:
1229 if (Subscribe(web_port_index, (*old_port)->client_id(),
1230 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001231 SetInputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001232 break;
agoode99d63292015-04-13 08:39:25 -07001233 case MidiPort::Type::kOutput:
1234 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1235 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001236 SetOutputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001237 break;
1238 }
1239 (*old_port)->set_connected(true);
1240 ++it;
1241 } else {
1242 ++it;
1243 }
1244 }
1245}
1246
agoode975043d2015-05-11 00:46:17 -07001247// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001248void MidiManagerAlsa::EnumerateAlsaPorts() {
1249 snd_seq_client_info_t* client_info;
1250 snd_seq_client_info_alloca(&client_info);
1251 snd_seq_port_info_t* port_info;
1252 snd_seq_port_info_alloca(&port_info);
1253
1254 // Enumerate clients.
1255 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001256 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001257 int client_id = snd_seq_client_info_get_client(client_info);
1258 ProcessClientStartEvent(client_id);
1259
1260 // Enumerate ports.
1261 snd_seq_port_info_set_client(port_info, client_id);
1262 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001263 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001264 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1265 ProcessPortStartEvent(*addr);
1266 }
1267 }
1268}
1269
agoode975043d2015-05-11 00:46:17 -07001270bool MidiManagerAlsa::EnumerateUdevCards() {
1271 int err;
1272
1273 device::ScopedUdevEnumeratePtr enumerate(
1274 device::udev_enumerate_new(udev_.get()));
1275 if (!enumerate.get()) {
1276 VLOG(1) << "udev_enumerate_new fails";
1277 return false;
1278 }
1279
1280 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1281 kUdevSubsystemSound);
1282 if (err) {
1283 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001284 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001285 return false;
1286 }
1287
1288 err = device::udev_enumerate_scan_devices(enumerate.get());
1289 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001290 VLOG(1) << "udev_enumerate_scan_devices fails: "
1291 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001292 return false;
1293 }
1294
1295 udev_list_entry* list_entry;
1296 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1297 udev_list_entry_foreach(list_entry, devices) {
1298 const char* path = device::udev_list_entry_get_name(list_entry);
1299 device::ScopedUdevDevicePtr dev(
1300 device::udev_device_new_from_syspath(udev_.get(), path));
1301 if (dev.get())
1302 ProcessUdevEvent(dev.get());
1303 }
1304
1305 return true;
1306}
1307
Avi Drissman3528fd02015-12-18 20:11:31 -05001308bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
agoode99d63292015-04-13 08:39:25 -07001309 int client_id,
1310 int port_id) {
1311 // Create the port.
1312 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001313 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001314 if (out_port < 0) {
1315 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1316 return false;
1317 }
1318 // Activate port subscription.
1319 snd_seq_port_subscribe_t* subs;
1320 snd_seq_port_subscribe_alloca(&subs);
1321 snd_seq_addr_t sender;
1322 sender.client = out_client_id_;
1323 sender.port = out_port;
1324 snd_seq_port_subscribe_set_sender(subs, &sender);
1325 snd_seq_addr_t dest;
1326 dest.client = client_id;
1327 dest.port = port_id;
1328 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001329 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001330 if (err != 0) {
1331 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001332 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001333 return false;
1334 }
1335
1336 // Update our map.
1337 base::AutoLock lock(out_ports_lock_);
1338 out_ports_[port_index] = out_port;
1339 return true;
1340}
1341
Avi Drissman3528fd02015-12-18 20:11:31 -05001342void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
agoode99d63292015-04-13 08:39:25 -07001343 base::AutoLock lock(out_ports_lock_);
1344 auto it = out_ports_.find(port_index);
1345 if (it == out_ports_.end())
1346 return;
1347
1348 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001349 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001350 out_ports_.erase(it);
1351}
1352
Avi Drissman3528fd02015-12-18 20:11:31 -05001353bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1354 int client_id,
1355 int port_id) {
agoode99d63292015-04-13 08:39:25 -07001356 // Activate port subscription.
1357 snd_seq_port_subscribe_t* subs;
1358 snd_seq_port_subscribe_alloca(&subs);
1359 snd_seq_addr_t sender;
1360 sender.client = client_id;
1361 sender.port = port_id;
1362 snd_seq_port_subscribe_set_sender(subs, &sender);
1363 snd_seq_addr_t dest;
1364 dest.client = in_client_id_;
1365 dest.port = in_port_id_;
1366 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001367 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001368 if (err != 0) {
1369 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1370 return false;
1371 }
1372
1373 // Update our map.
1374 source_map_[AddrToInt(client_id, port_id)] = port_index;
1375 return true;
1376}
1377
Takashi Toyoshima0ae49702017-09-01 04:59:51 +00001378MidiManagerAlsa::ScopedSndMidiEventPtr
1379MidiManagerAlsa::CreateScopedSndMidiEventPtr(size_t size) {
1380 snd_midi_event_t* coder;
1381 snd_midi_event_new(size, &coder);
1382 return ScopedSndMidiEventPtr(coder);
1383}
1384
toyoshimf4d61522017-02-10 02:03:32 -08001385MidiManager* MidiManager::Create(MidiService* service) {
1386 return new MidiManagerAlsa(service);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001387}
1388
toyoshime147c5e2015-05-07 21:58:31 -07001389} // namespace midi