blob: d57784415fcca5583a64cabf9095b4414e01b072 [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"
Takashi Toyoshima70e44ee2017-07-19 08:38:20 +000028#include "build/build_config.h"
agoodef212b2a2015-03-19 12:53:23 -070029#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000030#include "media/midi/midi_port_info.h"
toyoshimf4d61522017-02-10 02:03:32 -080031#include "media/midi/midi_service.h"
toyoshimefcee5e2017-06-14 07:14:39 -070032#include "media/midi/task_service.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000033
toyoshime147c5e2015-05-07 21:58:31 -070034namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000035
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000036namespace {
37
toyoshimec2570a2016-10-21 02:15:27 -070038using mojom::PortState;
39using mojom::Result;
toyoshim2f3a48f2016-10-17 01:54:13 -070040
toyoshimefcee5e2017-06-14 07:14:39 -070041enum {
42 kDefaultRunnerNotUsedOnAlsa = TaskService::kDefaultRunnerId,
43 kEventTaskRunner,
44 kSendTaskRunner
45};
toyoshimf4d61522017-02-10 02:03:32 -080046
agoode@chromium.org25227512014-06-08 05:12:05 +000047// Per-output buffer. This can be smaller, but then large sysex messages
48// will be (harmlessly) split across multiple seq events. This should
49// not have any real practical effect, except perhaps to slightly reorder
50// realtime messages with respect to sysex.
toyoshimefcee5e2017-06-14 07:14:39 -070051constexpr size_t kSendBufferSize = 256;
agoode@chromium.org25227512014-06-08 05:12:05 +000052
agoodeb09423b2015-05-11 11:39:57 -070053// Minimum client id for which we will have ALSA card devices for. When we
54// are searching for card devices (used to get the path, id, and manufacturer),
55// we don't want to get confused by kernel clients that do not have a card.
56// See seq_clientmgr.c in the ALSA code for this.
57// TODO(agoode): Add proper client -> card export from the kernel to avoid
58// hardcoding.
toyoshimefcee5e2017-06-14 07:14:39 -070059constexpr int kMinimumClientIdForCards = 16;
agoodeb09423b2015-05-11 11:39:57 -070060
agoodef212b2a2015-03-19 12:53:23 -070061// ALSA constants.
62const char kAlsaHw[] = "hw";
63
agoode975043d2015-05-11 00:46:17 -070064// udev constants.
65const char kUdev[] = "udev";
66const char kUdevSubsystemSound[] = "sound";
67const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
68const char kUdevActionChange[] = "change";
69const char kUdevActionRemove[] = "remove";
70
agoodeb09423b2015-05-11 11:39:57 -070071const char kUdevIdVendor[] = "ID_VENDOR";
72const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
73const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
74const char kUdevIdVendorId[] = "ID_VENDOR_ID";
75const char kUdevIdModelId[] = "ID_MODEL_ID";
76const char kUdevIdBus[] = "ID_BUS";
77const char kUdevIdPath[] = "ID_PATH";
78const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
79const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
80
81const char kSysattrVendorName[] = "vendor_name";
82const char kSysattrVendor[] = "vendor";
83const char kSysattrModel[] = "model";
84const char kSysattrGuid[] = "guid";
85
86const char kCardSyspath[] = "/card";
87
agoode@chromium.org25227512014-06-08 05:12:05 +000088// Constants for the capabilities we search for in inputs and outputs.
89// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
toyoshimefcee5e2017-06-14 07:14:39 -070090constexpr unsigned int kRequiredInputPortCaps =
agoode@chromium.org25227512014-06-08 05:12:05 +000091 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
toyoshimefcee5e2017-06-14 07:14:39 -070092constexpr unsigned int kRequiredOutputPortCaps =
agoode@chromium.org25227512014-06-08 05:12:05 +000093 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
94
toyoshimefcee5e2017-06-14 07:14:39 -070095constexpr unsigned int kCreateOutputPortCaps =
agoode99d63292015-04-13 08:39:25 -070096 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
toyoshimefcee5e2017-06-14 07:14:39 -070097constexpr unsigned int kCreateInputPortCaps =
agoode99d63292015-04-13 08:39:25 -070098 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
toyoshimefcee5e2017-06-14 07:14:39 -070099constexpr unsigned int kCreatePortType =
agoode99d63292015-04-13 08:39:25 -0700100 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +0000101
agoode99d63292015-04-13 08:39:25 -0700102int AddrToInt(int client, int port) {
103 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -0700104}
agoodef212b2a2015-03-19 12:53:23 -0700105
agoodeb09423b2015-05-11 11:39:57 -0700106// Returns true if this client has an ALSA card associated with it.
107bool IsCardClient(snd_seq_client_type_t type, int client_id) {
108 return (type == SND_SEQ_KERNEL_CLIENT) &&
109 (client_id >= kMinimumClientIdForCards);
110}
111
112// TODO(agoode): Move this to device/udev_linux.
113const std::string UdevDeviceGetPropertyOrSysattr(
114 struct udev_device* udev_device,
115 const char* property_key,
116 const char* sysattr_key) {
117 // First try the property.
118 std::string value =
119 device::UdevDeviceGetPropertyValue(udev_device, property_key);
120
121 // If no property, look for sysattrs and walk up the parent devices too.
122 while (value.empty() && udev_device) {
123 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
124 udev_device = device::udev_device_get_parent(udev_device);
125 }
126 return value;
127}
128
129int GetCardNumber(udev_device* dev) {
130 const char* syspath = device::udev_device_get_syspath(dev);
131 if (!syspath)
132 return -1;
133
134 std::string syspath_str(syspath);
135 size_t i = syspath_str.rfind(kCardSyspath);
136 if (i == std::string::npos)
137 return -1;
138
139 int number;
140 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
141 return -1;
142 return number;
143}
144
agooded87fc0f2015-05-21 08:29:31 -0700145std::string GetVendor(udev_device* dev) {
146 // Try to get the vendor string. Sometimes it is encoded.
147 std::string vendor = device::UdevDecodeString(
148 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
149 // Sometimes it is not encoded.
150 if (vendor.empty())
151 vendor =
152 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
153 return vendor;
154}
155
agoode8caab0b2015-03-23 18:48:02 -0700156void SetStringIfNonEmpty(base::DictionaryValue* value,
157 const std::string& path,
158 const std::string& in_value) {
159 if (!in_value.empty())
160 value->SetString(path, in_value);
161}
162
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000163} // namespace
164
toyoshimf4d61522017-02-10 02:03:32 -0800165MidiManagerAlsa::MidiManagerAlsa(MidiService* service) : MidiManager(service) {}
agoodeb2ead822016-03-11 12:14:35 -0800166
167MidiManagerAlsa::~MidiManagerAlsa() {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000168 base::AutoLock lock(lazy_init_member_lock_);
169
toyoshim131beb52016-07-25 00:42:41 -0700170 // Extra CHECK to verify all members are already reset.
toyoshim131beb52016-07-25 00:42:41 -0700171 CHECK(!in_client_);
172 CHECK(!out_client_);
173 CHECK(!decoder_);
174 CHECK(!udev_);
175 CHECK(!udev_monitor_);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000176}
177
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000178void MidiManagerAlsa::StartInitialization() {
toyoshimefcee5e2017-06-14 07:14:39 -0700179 if (!service()->task_service()->BindInstance()) {
180 NOTREACHED();
181 return CompleteInitialization(Result::INITIALIZATION_ERROR);
toyoshimf4d61522017-02-10 02:03:32 -0800182 }
183
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000184 base::AutoLock lock(lazy_init_member_lock_);
185
agoodeb2ead822016-03-11 12:14:35 -0800186 // Create client handles.
187 snd_seq_t* tmp_seq = nullptr;
188 int err =
189 snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000190 if (err != 0) {
191 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700192 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000193 }
agoodeb2ead822016-03-11 12:14:35 -0800194 ScopedSndSeqPtr in_client(tmp_seq);
195 tmp_seq = nullptr;
196 in_client_id_ = snd_seq_client_id(in_client.get());
197
198 err = snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
199 if (err != 0) {
200 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
201 return CompleteInitialization(Result::INITIALIZATION_ERROR);
202 }
203 ScopedSndSeqPtr out_client(tmp_seq);
204 tmp_seq = nullptr;
205 out_client_id_ = snd_seq_client_id(out_client.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000206
207 // Name the clients.
agoodeb2ead822016-03-11 12:14:35 -0800208 err = snd_seq_set_client_name(in_client.get(), "Chrome (input)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000209 if (err != 0) {
210 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700211 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000212 }
agoodeb2ead822016-03-11 12:14:35 -0800213 err = snd_seq_set_client_name(out_client.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000214 if (err != 0) {
215 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700216 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000217 }
218
219 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700220 in_port_id_ = snd_seq_create_simple_port(
agoodeb2ead822016-03-11 12:14:35 -0800221 in_client.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700222 if (in_port_id_ < 0) {
223 VLOG(1) << "snd_seq_create_simple_port fails: "
224 << snd_strerror(in_port_id_);
toyoshimf1b88962015-07-09 14:14:51 -0700225 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000226 }
227
228 // Subscribe to the announce port.
229 snd_seq_port_subscribe_t* subs;
230 snd_seq_port_subscribe_alloca(&subs);
231 snd_seq_addr_t announce_sender;
232 snd_seq_addr_t announce_dest;
233 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
234 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700235 announce_dest.client = in_client_id_;
236 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000237 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
238 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoodeb2ead822016-03-11 12:14:35 -0800239 err = snd_seq_subscribe_port(in_client.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000240 if (err != 0) {
241 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
242 << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700243 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000244 }
245
agoodeb2ead822016-03-11 12:14:35 -0800246 // Initialize decoder.
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000247 ScopedSndMidiEventPtr decoder = CreateScopedSndMidiEventPtr(0);
agoodeb2ead822016-03-11 12:14:35 -0800248 snd_midi_event_no_status(decoder.get(), 1);
agoode@chromium.org25227512014-06-08 05:12:05 +0000249
agoodeb2ead822016-03-11 12:14:35 -0800250 // Initialize udev and monitor.
251 device::ScopedUdevPtr udev(device::udev_new());
252 device::ScopedUdevMonitorPtr udev_monitor(
253 device::udev_monitor_new_from_netlink(udev.get(), kUdev));
254 if (!udev_monitor.get()) {
agoode975043d2015-05-11 00:46:17 -0700255 VLOG(1) << "udev_monitor_new_from_netlink fails";
toyoshimf1b88962015-07-09 14:14:51 -0700256 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700257 }
258 err = device::udev_monitor_filter_add_match_subsystem_devtype(
agoodeb2ead822016-03-11 12:14:35 -0800259 udev_monitor.get(), kUdevSubsystemSound, nullptr);
agoode975043d2015-05-11 00:46:17 -0700260 if (err != 0) {
261 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700262 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700263 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700264 }
agoodeb2ead822016-03-11 12:14:35 -0800265 err = device::udev_monitor_enable_receiving(udev_monitor.get());
agoode975043d2015-05-11 00:46:17 -0700266 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700267 VLOG(1) << "udev_monitor_enable_receiving fails: "
268 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700269 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700270 }
271
agoodeb2ead822016-03-11 12:14:35 -0800272 // Success! Now, initialize members from the temporaries. Do not
273 // initialize these earlier, since they need to be destroyed by the
274 // thread that calls Finalize(), not the destructor thread (and we
275 // check this in the destructor).
276 in_client_.reset(in_client.release());
277 out_client_.reset(out_client.release());
278 decoder_.reset(decoder.release());
279 udev_.reset(udev.release());
agoode740f5222016-07-06 23:46:32 -0700280 udev_monitor_.reset(udev_monitor.release());
agoodeb2ead822016-03-11 12:14:35 -0800281
282 // Generate hotplug events for existing ports.
283 // TODO(agoode): Check the return value for failure.
284 EnumerateAlsaPorts();
285
286 // Generate hotplug events for existing udev devices. This must be done
287 // after udev_monitor_enable_receiving() is called. See the algorithm
288 // at http://www.signal11.us/oss/udev/.
agoode975043d2015-05-11 00:46:17 -0700289 EnumerateUdevCards();
290
agoodeb2ead822016-03-11 12:14:35 -0800291 // Start processing events. Don't do this before enumeration of both
292 // ALSA and udev.
toyoshimefcee5e2017-06-14 07:14:39 -0700293 service()->task_service()->PostBoundTask(
294 kEventTaskRunner,
295 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000296
toyoshimf1b88962015-07-09 14:14:51 -0700297 CompleteInitialization(Result::OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000298}
299
toyoshim8e7d6e02015-10-06 08:47:17 -0700300void MidiManagerAlsa::Finalize() {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000301 {
302 base::AutoLock lock(out_client_lock_);
303 // Close the out client. This will trigger the event thread to stop,
304 // because of SND_SEQ_EVENT_CLIENT_EXIT.
305 out_client_.reset();
306 }
toyoshim8e7d6e02015-10-06 08:47:17 -0700307
toyoshimefcee5e2017-06-14 07:14:39 -0700308 // Ensure that no task is running any more.
309 bool result = service()->task_service()->UnbindInstance();
310 CHECK(result);
agoodeb2ead822016-03-11 12:14:35 -0800311
312 // Destruct the other stuff we initialized in StartInitialization().
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000313 base::AutoLock lock(lazy_init_member_lock_);
agoodeb2ead822016-03-11 12:14:35 -0800314 udev_monitor_.reset();
315 udev_.reset();
316 decoder_.reset();
317 in_client_.reset();
toyoshim8e7d6e02015-10-06 08:47:17 -0700318}
319
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000320void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -0500321 uint32_t port_index,
322 const std::vector<uint8_t>& data,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000323 double timestamp) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000324 base::TimeDelta delay;
325 if (timestamp != 0.0) {
326 base::TimeTicks time_to_send =
327 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
agoodebd4be9b2015-03-16 19:17:25 -0700328 timestamp * base::Time::kMicrosecondsPerSecond);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000329 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
330 }
331
toyoshimefcee5e2017-06-14 07:14:39 -0700332 service()->task_service()->PostBoundDelayedTask(
333 kSendTaskRunner,
334 base::BindOnce(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
335 client, port_index, data),
336 delay);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000337}
338
agooded87fc0f2015-05-21 08:29:31 -0700339MidiManagerAlsa::MidiPort::Id::Id() = default;
340
341MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
342 const std::string& vendor_id,
343 const std::string& model_id,
344 const std::string& usb_interface_num,
345 const std::string& serial)
346 : bus_(bus),
347 vendor_id_(vendor_id),
348 model_id_(model_id),
349 usb_interface_num_(usb_interface_num),
350 serial_(serial) {
351}
352
353MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
354
355MidiManagerAlsa::MidiPort::Id::~Id() = default;
356
357bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
358 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
359 (model_id_ == rhs.model_id_) &&
360 (usb_interface_num_ == rhs.usb_interface_num_) &&
361 (serial_ == rhs.serial_);
362}
363
364bool MidiManagerAlsa::MidiPort::Id::empty() const {
365 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
366 usb_interface_num_.empty() && serial_.empty();
367}
368
agoode99d63292015-04-13 08:39:25 -0700369MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700370 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700371 int client_id,
372 int port_id,
373 int midi_device,
374 const std::string& client_name,
375 const std::string& port_name,
376 const std::string& manufacturer,
377 const std::string& version,
378 Type type)
379 : id_(id),
380 midi_device_(midi_device),
381 type_(type),
382 path_(path),
383 client_id_(client_id),
384 port_id_(port_id),
385 client_name_(client_name),
386 port_name_(port_name),
387 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700388 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000389}
390
agoodeb0582872015-05-20 05:22:24 -0700391MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000392
agoode99d63292015-04-13 08:39:25 -0700393// Note: keep synchronized with the MidiPort::Match* methods.
danakj75afea02016-04-25 20:36:04 -0700394std::unique_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
395 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
agoode99d63292015-04-13 08:39:25 -0700396
397 std::string type;
398 switch (type_) {
399 case Type::kInput:
400 type = "input";
401 break;
agoode99d63292015-04-13 08:39:25 -0700402 case Type::kOutput:
403 type = "output";
404 break;
405 }
406 value->SetString("type", type);
407 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700408 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
409 SetStringIfNonEmpty(value.get(), "portName", port_name_);
410 value->SetInteger("clientId", client_id_);
411 value->SetInteger("portId", port_id_);
412 value->SetInteger("midiDevice", midi_device_);
413
agooded87fc0f2015-05-21 08:29:31 -0700414 // Flatten id fields.
415 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
416 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
417 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
418 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
419 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
420
dchengc2aeece2015-12-27 00:54:00 -0800421 return std::move(value);
agoodebd4be9b2015-03-16 19:17:25 -0700422}
agoode@chromium.org25227512014-06-08 05:12:05 +0000423
agoode99d63292015-04-13 08:39:25 -0700424std::string MidiManagerAlsa::MidiPort::JSONValue() const {
425 std::string json;
426 JSONStringValueSerializer serializer(&json);
427 serializer.Serialize(*Value().get());
428 return json;
agoodef212b2a2015-03-19 12:53:23 -0700429}
430
agoode99d63292015-04-13 08:39:25 -0700431// TODO(agoode): Do not use SHA256 here. Instead store a persistent
432// mapping and just use a UUID or other random string.
433// http://crbug.com/465320
434std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
Avi Drissman3528fd02015-12-18 20:11:31 -0500435 uint8_t hash[crypto::kSHA256Length];
agoode99d63292015-04-13 08:39:25 -0700436 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
437 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700438}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000439
agoode99d63292015-04-13 08:39:25 -0700440bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
441 // Matches on:
442 // connected == true
443 // type
444 // path
445 // id
446 // client_id
447 // port_id
448 // midi_device
449 // client_name
450 // port_name
451 return connected() && (type() == query.type()) && (path() == query.path()) &&
452 (id() == query.id()) && (client_id() == query.client_id()) &&
453 (port_id() == query.port_id()) &&
454 (midi_device() == query.midi_device()) &&
455 (client_name() == query.client_name()) &&
456 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700457}
458
agoode99d63292015-04-13 08:39:25 -0700459bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
460 // Matches on:
461 // connected == false
462 // type
463 // path
464 // id
465 // port_id
466 // midi_device
467 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700468}
469
agoode99d63292015-04-13 08:39:25 -0700470bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
471 // Matches on:
472 // connected == false
473 // type
474 // id
475 // port_id
476 // midi_device
477 return !connected() && (type() == query.type()) && (id() == query.id()) &&
478 (port_id() == query.port_id()) &&
479 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700480}
481
agoode99d63292015-04-13 08:39:25 -0700482bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
483 // Matches on:
484 // connected == false
485 // type
486 // path.empty(), for both this and query
487 // id.empty(), for both this and query
488 // client_id
489 // port_id
490 // client_name
491 // port_name
492 // midi_device == -1, for both this and query
493 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700494}
495
agoode99d63292015-04-13 08:39:25 -0700496bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
497 // Matches on:
498 // connected == false
499 // type
500 // path.empty(), for both this and query
501 // id.empty(), for both this and query
502 // port_id
503 // client_name
504 // port_name
505 // midi_device == -1, for both this and query
506 return !connected() && (type() == query.type()) && path().empty() &&
507 query.path().empty() && id().empty() && query.id().empty() &&
508 (port_id() == query.port_id()) &&
509 (client_name() == query.client_name()) &&
510 (port_name() == query.port_name()) && (midi_device() == -1) &&
511 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000512}
513
agoodeb0582872015-05-20 05:22:24 -0700514MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700515
516MidiManagerAlsa::MidiPortStateBase::iterator
517MidiManagerAlsa::MidiPortStateBase::Find(
518 const MidiManagerAlsa::MidiPort& port) {
519 auto result = FindConnected(port);
520 if (result == end())
521 result = FindDisconnected(port);
522 return result;
523}
524
525MidiManagerAlsa::MidiPortStateBase::iterator
526MidiManagerAlsa::MidiPortStateBase::FindConnected(
527 const MidiManagerAlsa::MidiPort& port) {
528 // Exact match required for connected ports.
danakj75afea02016-04-25 20:36:04 -0700529 auto it = std::find_if(ports_.begin(), ports_.end(),
530 [&port](std::unique_ptr<MidiPort>& p) {
531 return p->MatchConnected(port);
532 });
agoode99d63292015-04-13 08:39:25 -0700533 return it;
534}
535
536MidiManagerAlsa::MidiPortStateBase::iterator
537MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
538 const MidiManagerAlsa::MidiPort& port) {
539 // Always match on:
540 // type
541 // Possible things to match on:
542 // path
543 // id
544 // client_id
545 // port_id
546 // midi_device
547 // client_name
548 // port_name
549
550 if (!port.path().empty()) {
551 // If path is present, then we have a card-based client.
552
553 // Pass 1. Match on path, id, midi_device, port_id.
554 // This is the best possible match for hardware card-based clients.
555 // This will also match the empty id correctly for devices without an id.
danakj75afea02016-04-25 20:36:04 -0700556 auto it = std::find_if(ports_.begin(), ports_.end(),
557 [&port](std::unique_ptr<MidiPort>& p) {
558 return p->MatchCardPass1(port);
559 });
agoode99d63292015-04-13 08:39:25 -0700560 if (it != ports_.end())
561 return it;
562
563 if (!port.id().empty()) {
564 // Pass 2. Match on id, midi_device, port_id.
565 // This will give us a high-confidence match when a user moves a device to
566 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
567 // has a hardware id.
danakj75afea02016-04-25 20:36:04 -0700568 it = std::find_if(ports_.begin(), ports_.end(),
569 [&port](std::unique_ptr<MidiPort>& p) {
570 return p->MatchCardPass2(port);
571 });
agoode99d63292015-04-13 08:39:25 -0700572 if (it != ports_.end())
573 return it;
574 }
575 } else {
576 // Else, we have a non-card-based client.
577 // Pass 1. Match on client_id, port_id, client_name, port_name.
578 // This will give us a reasonably good match.
danakj75afea02016-04-25 20:36:04 -0700579 auto it = std::find_if(ports_.begin(), ports_.end(),
580 [&port](std::unique_ptr<MidiPort>& p) {
581 return p->MatchNoCardPass1(port);
582 });
agoode99d63292015-04-13 08:39:25 -0700583 if (it != ports_.end())
584 return it;
585
586 // Pass 2. Match on port_id, client_name, port_name.
587 // This is weaker but similar to pass 2 in the hardware card-based clients
588 // match.
danakj75afea02016-04-25 20:36:04 -0700589 it = std::find_if(ports_.begin(), ports_.end(),
590 [&port](std::unique_ptr<MidiPort>& p) {
591 return p->MatchNoCardPass2(port);
592 });
agoode99d63292015-04-13 08:39:25 -0700593 if (it != ports_.end())
594 return it;
595 }
596
597 // No match.
598 return ports_.end();
599}
600
agoodeb0582872015-05-20 05:22:24 -0700601MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700602
agoodedf1b9ff2015-06-25 18:14:50 -0700603MidiManagerAlsa::MidiPortState::MidiPortState() = default;
agoode99d63292015-04-13 08:39:25 -0700604
danakj75afea02016-04-25 20:36:04 -0700605uint32_t MidiManagerAlsa::MidiPortState::push_back(
606 std::unique_ptr<MidiPort> port) {
agoode99d63292015-04-13 08:39:25 -0700607 // Add the web midi index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500608 uint32_t web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700609 switch (port->type()) {
610 case MidiPort::Type::kInput:
611 web_port_index = num_input_ports_++;
612 break;
613 case MidiPort::Type::kOutput:
614 web_port_index = num_output_ports_++;
615 break;
616 }
617 port->set_web_port_index(web_port_index);
dchengc2aeece2015-12-27 00:54:00 -0800618 MidiPortStateBase::push_back(std::move(port));
agoode99d63292015-04-13 08:39:25 -0700619 return web_port_index;
620}
621
agoodedf1b9ff2015-06-25 18:14:50 -0700622MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700623
agoodeb0582872015-05-20 05:22:24 -0700624MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700625
626void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
627 const std::string& client_name,
628 snd_seq_client_type_t type) {
629 ClientExit(client_id);
ricea37e45762016-08-25 02:43:39 -0700630 clients_.insert(
631 std::make_pair(client_id, base::MakeUnique<Client>(client_name, type)));
agoodeb09423b2015-05-11 11:39:57 -0700632 if (IsCardClient(type, client_id))
633 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700634}
635
636bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
637 return clients_.find(client_id) != clients_.end();
638}
639
640void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
641 auto it = clients_.find(client_id);
642 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700643 if (IsCardClient(it->second->type(), client_id))
644 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700645 clients_.erase(it);
646 }
647}
648
649void MidiManagerAlsa::AlsaSeqState::PortStart(
650 int client_id,
651 int port_id,
652 const std::string& port_name,
653 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
654 bool midi) {
655 auto it = clients_.find(client_id);
656 if (it != clients_.end())
657 it->second->AddPort(port_id,
ricea37e45762016-08-25 02:43:39 -0700658 base::MakeUnique<Port>(port_name, direction, midi));
agoode99d63292015-04-13 08:39:25 -0700659}
660
661void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
662 auto it = clients_.find(client_id);
663 if (it != clients_.end())
664 it->second->RemovePort(port_id);
665}
666
667snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
668 int client_id) const {
669 auto it = clients_.find(client_id);
670 if (it == clients_.end())
671 return SND_SEQ_USER_CLIENT;
672 return it->second->type();
673}
674
danakj75afea02016-04-25 20:36:04 -0700675std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700676MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
danakj75afea02016-04-25 20:36:04 -0700677 std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
agoode99d63292015-04-13 08:39:25 -0700678 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700679 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700680
agoodeb09423b2015-05-11 11:39:57 -0700681 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700682 for (const auto& client_pair : clients_) {
683 int client_id = client_pair.first;
vmpstr0205abb2016-06-28 18:50:56 -0700684 auto* client = client_pair.second.get();
agoode99d63292015-04-13 08:39:25 -0700685
686 // Get client metadata.
687 const std::string client_name = client->name();
688 std::string manufacturer;
689 std::string driver;
690 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700691 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700692 std::string card_name;
693 std::string card_longname;
694 int midi_device = -1;
695
agoodeb09423b2015-05-11 11:39:57 -0700696 if (IsCardClient(client->type(), client_id)) {
697 auto& card = card_it->second;
698 if (card_midi_device == -1)
699 card_midi_device = 0;
700
701 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700702 path = card->path();
703 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
704 card->usb_interface_num(), card->serial());
705 card_name = card->name();
706 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700707 midi_device = card_midi_device;
708
709 ++card_midi_device;
710 if (card_midi_device >= card->midi_device_count()) {
711 card_midi_device = -1;
712 ++card_it;
713 }
714 }
715
agoode99d63292015-04-13 08:39:25 -0700716 for (const auto& port_pair : *client) {
717 int port_id = port_pair.first;
718 const auto& port = port_pair.second;
719
720 if (port->midi()) {
721 std::string version;
722 if (!driver.empty()) {
723 version = driver + " / ";
724 }
725 version +=
726 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
727 SND_LIB_MINOR, SND_LIB_SUBMINOR);
728 PortDirection direction = port->direction();
729 if (direction == PortDirection::kInput ||
730 direction == PortDirection::kDuplex) {
ricea37e45762016-08-25 02:43:39 -0700731 midi_ports->push_back(base::MakeUnique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700732 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700733 port->name(), manufacturer, version, MidiPort::Type::kInput));
agoode99d63292015-04-13 08:39:25 -0700734 }
735 if (direction == PortDirection::kOutput ||
736 direction == PortDirection::kDuplex) {
ricea37e45762016-08-25 02:43:39 -0700737 midi_ports->push_back(base::MakeUnique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700738 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700739 port->name(), manufacturer, version, MidiPort::Type::kOutput));
agoode99d63292015-04-13 08:39:25 -0700740 }
741 }
742 }
743 }
744
dchengc2aeece2015-12-27 00:54:00 -0800745 return midi_ports;
agoode99d63292015-04-13 08:39:25 -0700746}
747
748MidiManagerAlsa::AlsaSeqState::Port::Port(
749 const std::string& name,
750 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
751 bool midi)
752 : name_(name), direction_(direction), midi_(midi) {
753}
754
agoodeb0582872015-05-20 05:22:24 -0700755MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700756
757MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
758 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700759 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700760}
761
agoodeb0582872015-05-20 05:22:24 -0700762MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700763
danakj75afea02016-04-25 20:36:04 -0700764void MidiManagerAlsa::AlsaSeqState::Client::AddPort(
765 int addr,
766 std::unique_ptr<Port> port) {
limasdfe59d0392015-11-19 20:28:57 -0800767 ports_[addr] = std::move(port);
agoode99d63292015-04-13 08:39:25 -0700768}
769
770void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700771 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700772}
773
774MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
775MidiManagerAlsa::AlsaSeqState::Client::begin() const {
776 return ports_.begin();
777}
778
779MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
780MidiManagerAlsa::AlsaSeqState::Client::end() const {
781 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700782}
783
agoodeb09423b2015-05-11 11:39:57 -0700784MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700785 const std::string& name,
786 const std::string& longname,
787 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700788 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700789 : name_(name),
790 longname_(longname),
791 driver_(driver),
792 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
793 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
794 vendor_id_(
795 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
796 model_id_(
797 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
798 usb_interface_num_(
799 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
800 serial_(UdevDeviceGetPropertyOrSysattr(dev,
801 kUdevIdSerialShort,
802 kSysattrGuid)),
803 midi_device_count_(midi_device_count),
804 manufacturer_(ExtractManufacturerString(
805 GetVendor(dev),
806 vendor_id_,
807 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
808 name,
809 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700810}
811
agoodeb0582872015-05-20 05:22:24 -0700812MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700813
agoode55a8b522015-03-08 12:40:17 -0700814// static
agoodeb09423b2015-05-11 11:39:57 -0700815std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700816 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700817 const std::string& udev_id_vendor_id,
818 const std::string& udev_id_vendor_from_database,
819 const std::string& alsa_name,
820 const std::string& alsa_longname) {
821 // Let's try to determine the manufacturer. Here is the ordered preference
822 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700823 // 1. Vendor name from the hardware device string, from udev properties
824 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700825 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700826 // 3. Heuristic from ALSA.
827
agoodee83758c2015-03-23 22:07:54 -0700828 // Is the vendor string present and not just the vendor hex id?
829 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700830 return udev_id_vendor;
831 }
832
833 // Is there a vendor string in the hardware database?
834 if (!udev_id_vendor_from_database.empty()) {
835 return udev_id_vendor_from_database;
836 }
837
838 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
839 // We assume that card longname is in the format of
840 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
841 // a manufacturer name here.
842 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700843 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700844 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700845 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700846 return alsa_longname.substr(0, name_index - 1);
847 }
848
849 // Failure.
850 return "";
851}
852
toyoshimefcee5e2017-06-14 07:14:39 -0700853void MidiManagerAlsa::SendMidiData(MidiManagerClient* client,
toyoshimf4d61522017-02-10 02:03:32 -0800854 uint32_t port_index,
Avi Drissman3528fd02015-12-18 20:11:31 -0500855 const std::vector<uint8_t>& data) {
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000856 ScopedSndMidiEventPtr encoder = CreateScopedSndMidiEventPtr(kSendBufferSize);
agoode5a1aa112015-06-21 20:51:00 -0700857 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700858 snd_seq_event_t event;
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000859 int result = snd_midi_event_encode_byte(encoder.get(), datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700860 if (result == 1) {
861 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700862 base::AutoLock lock(out_ports_lock_);
863 auto it = out_ports_.find(port_index);
864 if (it != out_ports_.end()) {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000865 base::AutoLock lock(out_client_lock_);
866 if (!out_client_)
867 return;
agoode99d63292015-04-13 08:39:25 -0700868 snd_seq_ev_set_source(&event, it->second);
869 snd_seq_ev_set_subs(&event);
870 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700871 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700872 }
agoodebd4be9b2015-03-16 19:17:25 -0700873 }
874 }
toyoshimf4d61522017-02-10 02:03:32 -0800875
876 // Acknowledge send.
877 AccumulateMidiBytesSent(client, data.size());
agoodebd4be9b2015-03-16 19:17:25 -0700878}
879
toyoshimefcee5e2017-06-14 07:14:39 -0700880void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700881 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700882
agoode975043d2015-05-11 00:46:17 -0700883 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700884 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700885 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
886 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700887
agoode975043d2015-05-11 00:46:17 -0700888 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
889 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700890 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700891 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700892 } else {
agoode975043d2015-05-11 00:46:17 -0700893 if (pfd[0].revents & POLLIN) {
894 // Read available incoming MIDI data.
895 int remaining;
896 double timestamp =
897 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
898 do {
899 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700900 err = snd_seq_event_input(in_client_.get(), &event);
901 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700902
903 if (err == -ENOSPC) {
904 // Handle out of space error.
905 VLOG(1) << "snd_seq_event_input detected buffer overrun";
906 // We've lost events: check another way to see if we need to shut
907 // down.
agoode975043d2015-05-11 00:46:17 -0700908 } else if (err == -EAGAIN) {
909 // We've read all the data.
910 } else if (err < 0) {
911 // Handle other errors.
912 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
913 // TODO(agoode): Use RecordAction() or similar to log this.
914 loop_again = false;
915 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
916 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
917 // Handle announce events.
918 switch (event->type) {
919 case SND_SEQ_EVENT_PORT_START:
920 // Don't use SND_SEQ_EVENT_CLIENT_START because the
921 // client name may not be set by the time we query
922 // it. It should be set by the time ports are made.
923 ProcessClientStartEvent(event->data.addr.client);
924 ProcessPortStartEvent(event->data.addr);
925 break;
926 case SND_SEQ_EVENT_CLIENT_EXIT:
927 // Check for disconnection of our "out" client. This means "shut
928 // down".
929 if (event->data.addr.client == out_client_id_) {
930 loop_again = false;
931 remaining = 0;
932 } else
933 ProcessClientExitEvent(event->data.addr);
934 break;
935 case SND_SEQ_EVENT_PORT_EXIT:
936 ProcessPortExitEvent(event->data.addr);
937 break;
938 }
939 } else {
940 // Normal operation.
941 ProcessSingleEvent(event, timestamp);
942 }
943 } while (remaining > 0);
944 }
945 if (pfd[1].revents & POLLIN) {
946 device::ScopedUdevDevicePtr dev(
947 device::udev_monitor_receive_device(udev_monitor_.get()));
948 if (dev.get())
949 ProcessUdevEvent(dev.get());
950 else
951 VLOG(1) << "udev_monitor_receive_device fails";
952 }
agoodebd4be9b2015-03-16 19:17:25 -0700953 }
954
agoodebd4be9b2015-03-16 19:17:25 -0700955 // Do again.
toyoshimf4d61522017-02-10 02:03:32 -0800956 if (loop_again) {
toyoshimefcee5e2017-06-14 07:14:39 -0700957 service()->task_service()->PostBoundTask(
958 kEventTaskRunner,
959 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
toyoshimf4d61522017-02-10 02:03:32 -0800960 }
agoodebd4be9b2015-03-16 19:17:25 -0700961}
962
963void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
964 double timestamp) {
agoode99d63292015-04-13 08:39:25 -0700965 auto source_it =
966 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700967 if (source_it != source_map_.end()) {
Avi Drissman3528fd02015-12-18 20:11:31 -0500968 uint32_t source = source_it->second;
agoodebd4be9b2015-03-16 19:17:25 -0700969 if (event->type == SND_SEQ_EVENT_SYSEX) {
970 // Special! Variable-length sysex.
Avi Drissman3528fd02015-12-18 20:11:31 -0500971 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
agoodebd4be9b2015-03-16 19:17:25 -0700972 event->data.ext.len, timestamp);
973 } else {
974 // Otherwise, decode this and send that on.
975 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700976 long count =
977 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700978 if (count <= 0) {
979 if (count != -ENOENT) {
980 // ENOENT means that it's not a MIDI message, which is not an
981 // error, but other negative values are errors for us.
982 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
983 // TODO(agoode): Record this failure.
984 }
985 } else {
986 ReceiveMidiData(source, buf, count, timestamp);
987 }
988 }
989 }
990}
991
agoode99d63292015-04-13 08:39:25 -0700992void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
993 // Ignore if client is already started.
994 if (alsa_seq_state_.ClientStarted(client_id))
995 return;
996
997 snd_seq_client_info_t* client_info;
998 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -0700999 int err =
1000 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -07001001 if (err != 0)
1002 return;
1003
1004 // Skip our own clients.
1005 if ((client_id == in_client_id_) || (client_id == out_client_id_))
1006 return;
1007
1008 // Update our view of ALSA seq state.
1009 alsa_seq_state_.ClientStart(client_id,
1010 snd_seq_client_info_get_name(client_info),
1011 snd_seq_client_info_get_type(client_info));
1012
1013 // Generate Web MIDI events.
1014 UpdatePortStateAndGenerateEvents();
1015}
1016
1017void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
1018 snd_seq_port_info_t* port_info;
1019 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -07001020 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
1021 port_info);
agoode99d63292015-04-13 08:39:25 -07001022 if (err != 0)
1023 return;
1024
1025 unsigned int caps = snd_seq_port_info_get_capability(port_info);
1026 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
1027 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
1028 AlsaSeqState::PortDirection direction;
1029 if (input && output)
1030 direction = AlsaSeqState::PortDirection::kDuplex;
1031 else if (input)
1032 direction = AlsaSeqState::PortDirection::kInput;
1033 else if (output)
1034 direction = AlsaSeqState::PortDirection::kOutput;
1035 else
1036 return;
1037
1038 // Update our view of ALSA seq state.
1039 alsa_seq_state_.PortStart(
1040 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1041 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1042 // Generate Web MIDI events.
1043 UpdatePortStateAndGenerateEvents();
1044}
1045
1046void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1047 // Update our view of ALSA seq state.
1048 alsa_seq_state_.ClientExit(addr.client);
1049 // Generate Web MIDI events.
1050 UpdatePortStateAndGenerateEvents();
1051}
1052
1053void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1054 // Update our view of ALSA seq state.
1055 alsa_seq_state_.PortExit(addr.client, addr.port);
1056 // Generate Web MIDI events.
1057 UpdatePortStateAndGenerateEvents();
1058}
1059
agoode975043d2015-05-11 00:46:17 -07001060void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1061 // Only card devices have this property set, and only when they are
1062 // fully initialized.
1063 if (!device::udev_device_get_property_value(dev,
1064 kUdevPropertySoundInitialized))
1065 return;
1066
1067 // Get the action. If no action, then we are doing first time enumeration
1068 // and the device is treated as new.
1069 const char* action = device::udev_device_get_action(dev);
1070 if (!action)
1071 action = kUdevActionChange;
1072
1073 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001074 AddCard(dev);
1075 // Generate Web MIDI events.
1076 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001077 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001078 RemoveCard(GetCardNumber(dev));
1079 // Generate Web MIDI events.
1080 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001081 }
1082}
1083
agoodeb09423b2015-05-11 11:39:57 -07001084void MidiManagerAlsa::AddCard(udev_device* dev) {
1085 int number = GetCardNumber(dev);
1086 if (number == -1)
1087 return;
1088
1089 RemoveCard(number);
1090
1091 snd_ctl_card_info_t* card;
1092 snd_hwdep_info_t* hwdep;
1093 snd_ctl_card_info_alloca(&card);
1094 snd_hwdep_info_alloca(&hwdep);
1095 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1096 snd_ctl_t* handle;
1097 int err = snd_ctl_open(&handle, id.c_str(), 0);
1098 if (err != 0) {
1099 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1100 return;
1101 }
1102 err = snd_ctl_card_info(handle, card);
1103 if (err != 0) {
1104 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1105 snd_ctl_close(handle);
1106 return;
1107 }
1108 std::string name = snd_ctl_card_info_get_name(card);
1109 std::string longname = snd_ctl_card_info_get_longname(card);
1110 std::string driver = snd_ctl_card_info_get_driver(card);
1111
1112 // Count rawmidi devices (not subdevices).
1113 int midi_count = 0;
1114 for (int device = -1;
1115 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1116 ++midi_count;
1117
1118 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1119 //
1120 // Explanation:
1121 // Any kernel driver can create an ALSA client (visible to us).
1122 // With modern hardware, only rawmidi devices do this. Kernel
1123 // drivers create rawmidi devices and the rawmidi subsystem makes
1124 // the seq clients. But the OPL3 driver is special, it does not
1125 // make a rawmidi device but a seq client directly. (This is the
1126 // only one to worry about in the kernel code, as of 2015-03-23.)
1127 //
1128 // OPL3 is very old (but still possible to get in new
1129 // hardware). It is unlikely that new drivers would not use
1130 // rawmidi and defeat our heuristic.
1131 //
1132 // Longer term, support should be added in the kernel to expose a
1133 // direct link from card->client (or client->card) so that all
1134 // these heuristics will be obsolete. Once that is there, we can
1135 // assume our old heuristics will work on old kernels and the new
1136 // robust code will be used on new. Then we will not need to worry
1137 // about changes to kernel internals breaking our code.
1138 // See the TODO above at kMinimumClientIdForCards.
1139 for (int device = -1;
1140 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1141 err = snd_ctl_hwdep_info(handle, hwdep);
1142 if (err != 0) {
1143 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1144 continue;
1145 }
1146 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1147 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1148 iface == SND_HWDEP_IFACE_OPL4)
1149 ++midi_count;
1150 }
1151 snd_ctl_close(handle);
1152
mgiucad9af8452015-06-25 02:11:57 -07001153 if (midi_count > 0) {
danakj75afea02016-04-25 20:36:04 -07001154 std::unique_ptr<AlsaCard> card(
mgiucad9af8452015-06-25 02:11:57 -07001155 new AlsaCard(dev, name, longname, driver, midi_count));
limasdfe59d0392015-11-19 20:28:57 -08001156 alsa_cards_.insert(std::make_pair(number, std::move(card)));
mgiucad9af8452015-06-25 02:11:57 -07001157 alsa_card_midi_count_ += midi_count;
1158 }
agoodeb09423b2015-05-11 11:39:57 -07001159}
1160
1161void MidiManagerAlsa::RemoveCard(int number) {
1162 auto it = alsa_cards_.find(number);
1163 if (it == alsa_cards_.end())
1164 return;
1165
1166 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001167 alsa_cards_.erase(it);
1168}
1169
agoode99d63292015-04-13 08:39:25 -07001170void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001171 // Verify that our information from ALSA and udev are in sync. If
1172 // not, we cannot generate events right now.
1173 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1174 return;
1175
agoode99d63292015-04-13 08:39:25 -07001176 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001177 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001178
1179 // Disconnect any connected old ports that are now missing.
agoode5ebc4932015-12-01 08:25:12 -08001180 for (auto& old_port : port_state_) {
agoode99d63292015-04-13 08:39:25 -07001181 if (old_port->connected() &&
1182 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1183 old_port->set_connected(false);
Avi Drissman3528fd02015-12-18 20:11:31 -05001184 uint32_t web_port_index = old_port->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001185 switch (old_port->type()) {
1186 case MidiPort::Type::kInput:
1187 source_map_.erase(
1188 AddrToInt(old_port->client_id(), old_port->port_id()));
toyoshimec2570a2016-10-21 02:15:27 -07001189 SetInputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001190 break;
1191 case MidiPort::Type::kOutput:
1192 DeleteAlsaOutputPort(web_port_index);
toyoshimec2570a2016-10-21 02:15:27 -07001193 SetOutputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001194 break;
1195 }
1196 }
1197 }
1198
1199 // Reconnect or add new ports.
1200 auto it = new_port_state->begin();
1201 while (it != new_port_state->end()) {
agoode5ebc4932015-12-01 08:25:12 -08001202 auto& new_port = *it;
agoode99d63292015-04-13 08:39:25 -07001203 auto old_port = port_state_.Find(*new_port);
1204 if (old_port == port_state_.end()) {
1205 // Add new port.
agoode5ebc4932015-12-01 08:25:12 -08001206 const auto& opaque_key = new_port->OpaqueKey();
1207 const auto& manufacturer = new_port->manufacturer();
1208 const auto& port_name = new_port->port_name();
1209 const auto& version = new_port->version();
1210 const auto& type = new_port->type();
1211 const auto& client_id = new_port->client_id();
1212 const auto& port_id = new_port->port_id();
1213
dchengc2aeece2015-12-27 00:54:00 -08001214 uint32_t web_port_index = port_state_.push_back(std::move(new_port));
agoode5ebc4932015-12-01 08:25:12 -08001215 it = new_port_state->erase(it);
1216
1217 MidiPortInfo info(opaque_key, manufacturer, port_name, version,
toyoshimec2570a2016-10-21 02:15:27 -07001218 PortState::OPENED);
agoode5ebc4932015-12-01 08:25:12 -08001219 switch (type) {
agoode99d63292015-04-13 08:39:25 -07001220 case MidiPort::Type::kInput:
agoode5ebc4932015-12-01 08:25:12 -08001221 if (Subscribe(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001222 AddInputPort(info);
1223 break;
agoode99d63292015-04-13 08:39:25 -07001224 case MidiPort::Type::kOutput:
agoode5ebc4932015-12-01 08:25:12 -08001225 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001226 AddOutputPort(info);
1227 break;
1228 }
agoode99d63292015-04-13 08:39:25 -07001229 } else if (!(*old_port)->connected()) {
1230 // Reconnect.
Avi Drissman3528fd02015-12-18 20:11:31 -05001231 uint32_t web_port_index = (*old_port)->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001232 (*old_port)->Update(new_port->path(), new_port->client_id(),
1233 new_port->port_id(), new_port->client_name(),
1234 new_port->port_name(), new_port->manufacturer(),
1235 new_port->version());
1236 switch ((*old_port)->type()) {
1237 case MidiPort::Type::kInput:
1238 if (Subscribe(web_port_index, (*old_port)->client_id(),
1239 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001240 SetInputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001241 break;
agoode99d63292015-04-13 08:39:25 -07001242 case MidiPort::Type::kOutput:
1243 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1244 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001245 SetOutputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001246 break;
1247 }
1248 (*old_port)->set_connected(true);
1249 ++it;
1250 } else {
1251 ++it;
1252 }
1253 }
1254}
1255
agoode975043d2015-05-11 00:46:17 -07001256// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001257void MidiManagerAlsa::EnumerateAlsaPorts() {
1258 snd_seq_client_info_t* client_info;
1259 snd_seq_client_info_alloca(&client_info);
1260 snd_seq_port_info_t* port_info;
1261 snd_seq_port_info_alloca(&port_info);
1262
1263 // Enumerate clients.
1264 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001265 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001266 int client_id = snd_seq_client_info_get_client(client_info);
1267 ProcessClientStartEvent(client_id);
1268
1269 // Enumerate ports.
1270 snd_seq_port_info_set_client(port_info, client_id);
1271 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001272 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001273 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1274 ProcessPortStartEvent(*addr);
1275 }
1276 }
1277}
1278
agoode975043d2015-05-11 00:46:17 -07001279bool MidiManagerAlsa::EnumerateUdevCards() {
1280 int err;
1281
1282 device::ScopedUdevEnumeratePtr enumerate(
1283 device::udev_enumerate_new(udev_.get()));
1284 if (!enumerate.get()) {
1285 VLOG(1) << "udev_enumerate_new fails";
1286 return false;
1287 }
1288
1289 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1290 kUdevSubsystemSound);
1291 if (err) {
1292 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001293 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001294 return false;
1295 }
1296
1297 err = device::udev_enumerate_scan_devices(enumerate.get());
1298 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001299 VLOG(1) << "udev_enumerate_scan_devices fails: "
1300 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001301 return false;
1302 }
1303
1304 udev_list_entry* list_entry;
1305 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1306 udev_list_entry_foreach(list_entry, devices) {
1307 const char* path = device::udev_list_entry_get_name(list_entry);
1308 device::ScopedUdevDevicePtr dev(
1309 device::udev_device_new_from_syspath(udev_.get(), path));
1310 if (dev.get())
1311 ProcessUdevEvent(dev.get());
1312 }
1313
1314 return true;
1315}
1316
Avi Drissman3528fd02015-12-18 20:11:31 -05001317bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
agoode99d63292015-04-13 08:39:25 -07001318 int client_id,
1319 int port_id) {
1320 // Create the port.
1321 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001322 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001323 if (out_port < 0) {
1324 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1325 return false;
1326 }
1327 // Activate port subscription.
1328 snd_seq_port_subscribe_t* subs;
1329 snd_seq_port_subscribe_alloca(&subs);
1330 snd_seq_addr_t sender;
1331 sender.client = out_client_id_;
1332 sender.port = out_port;
1333 snd_seq_port_subscribe_set_sender(subs, &sender);
1334 snd_seq_addr_t dest;
1335 dest.client = client_id;
1336 dest.port = port_id;
1337 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001338 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001339 if (err != 0) {
1340 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001341 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001342 return false;
1343 }
1344
1345 // Update our map.
1346 base::AutoLock lock(out_ports_lock_);
1347 out_ports_[port_index] = out_port;
1348 return true;
1349}
1350
Avi Drissman3528fd02015-12-18 20:11:31 -05001351void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
agoode99d63292015-04-13 08:39:25 -07001352 base::AutoLock lock(out_ports_lock_);
1353 auto it = out_ports_.find(port_index);
1354 if (it == out_ports_.end())
1355 return;
1356
1357 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001358 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001359 out_ports_.erase(it);
1360}
1361
Avi Drissman3528fd02015-12-18 20:11:31 -05001362bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1363 int client_id,
1364 int port_id) {
agoode99d63292015-04-13 08:39:25 -07001365 // Activate port subscription.
1366 snd_seq_port_subscribe_t* subs;
1367 snd_seq_port_subscribe_alloca(&subs);
1368 snd_seq_addr_t sender;
1369 sender.client = client_id;
1370 sender.port = port_id;
1371 snd_seq_port_subscribe_set_sender(subs, &sender);
1372 snd_seq_addr_t dest;
1373 dest.client = in_client_id_;
1374 dest.port = in_port_id_;
1375 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001376 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001377 if (err != 0) {
1378 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1379 return false;
1380 }
1381
1382 // Update our map.
1383 source_map_[AddrToInt(client_id, port_id)] = port_index;
1384 return true;
1385}
1386
Takashi Toyoshima0ae49702017-09-01 04:59:51 +00001387MidiManagerAlsa::ScopedSndMidiEventPtr
1388MidiManagerAlsa::CreateScopedSndMidiEventPtr(size_t size) {
1389 snd_midi_event_t* coder;
1390 snd_midi_event_new(size, &coder);
1391 return ScopedSndMidiEventPtr(coder);
1392}
1393
Takashi Toyoshima70e44ee2017-07-19 08:38:20 +00001394#if !defined(OS_CHROMEOS)
toyoshimf4d61522017-02-10 02:03:32 -08001395MidiManager* MidiManager::Create(MidiService* service) {
1396 return new MidiManagerAlsa(service);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001397}
Takashi Toyoshima70e44ee2017-07-19 08:38:20 +00001398#endif
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001399
toyoshime147c5e2015-05-07 21:58:31 -07001400} // namespace midi