blob: 2d9e42aa9e2c2d1526f6bcf69dc45e85f6ec202c [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.org4a8657c2014-02-06 11:23:09 +000020#include "base/posix/eintr_wrapper.h"
brettwf7f870f2015-06-09 11:05:24 -070021#include "base/posix/safe_strerror.h"
fdoray8baaff02016-08-25 08:36:37 -070022#include "base/single_thread_task_runner.h"
agoodef212b2a2015-03-19 12:53:23 -070023#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000024#include "base/strings/stringprintf.h"
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000025#include "base/time/time.h"
Takashi Toyoshima70e44ee2017-07-19 08:38:20 +000026#include "build/build_config.h"
agoodef212b2a2015-03-19 12:53:23 -070027#include "crypto/sha2.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000028#include "media/midi/midi_port_info.h"
toyoshimf4d61522017-02-10 02:03:32 -080029#include "media/midi/midi_service.h"
toyoshimefcee5e2017-06-14 07:14:39 -070030#include "media/midi/task_service.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000031
toyoshime147c5e2015-05-07 21:58:31 -070032namespace midi {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000033
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +000034namespace {
35
toyoshimec2570a2016-10-21 02:15:27 -070036using mojom::PortState;
37using mojom::Result;
toyoshim2f3a48f2016-10-17 01:54:13 -070038
toyoshimefcee5e2017-06-14 07:14:39 -070039enum {
40 kDefaultRunnerNotUsedOnAlsa = TaskService::kDefaultRunnerId,
41 kEventTaskRunner,
42 kSendTaskRunner
43};
toyoshimf4d61522017-02-10 02:03:32 -080044
agoode@chromium.org25227512014-06-08 05:12:05 +000045// Per-output buffer. This can be smaller, but then large sysex messages
46// will be (harmlessly) split across multiple seq events. This should
47// not have any real practical effect, except perhaps to slightly reorder
48// realtime messages with respect to sysex.
toyoshimefcee5e2017-06-14 07:14:39 -070049constexpr size_t kSendBufferSize = 256;
agoode@chromium.org25227512014-06-08 05:12:05 +000050
agoodeb09423b2015-05-11 11:39:57 -070051// Minimum client id for which we will have ALSA card devices for. When we
52// are searching for card devices (used to get the path, id, and manufacturer),
53// we don't want to get confused by kernel clients that do not have a card.
54// See seq_clientmgr.c in the ALSA code for this.
55// TODO(agoode): Add proper client -> card export from the kernel to avoid
56// hardcoding.
toyoshimefcee5e2017-06-14 07:14:39 -070057constexpr int kMinimumClientIdForCards = 16;
agoodeb09423b2015-05-11 11:39:57 -070058
agoodef212b2a2015-03-19 12:53:23 -070059// ALSA constants.
60const char kAlsaHw[] = "hw";
61
agoode975043d2015-05-11 00:46:17 -070062// udev constants.
63const char kUdev[] = "udev";
64const char kUdevSubsystemSound[] = "sound";
65const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED";
66const char kUdevActionChange[] = "change";
67const char kUdevActionRemove[] = "remove";
68
agoodeb09423b2015-05-11 11:39:57 -070069const char kUdevIdVendor[] = "ID_VENDOR";
70const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC";
71const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
72const char kUdevIdVendorId[] = "ID_VENDOR_ID";
73const char kUdevIdModelId[] = "ID_MODEL_ID";
74const char kUdevIdBus[] = "ID_BUS";
75const char kUdevIdPath[] = "ID_PATH";
76const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
77const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT";
78
79const char kSysattrVendorName[] = "vendor_name";
80const char kSysattrVendor[] = "vendor";
81const char kSysattrModel[] = "model";
82const char kSysattrGuid[] = "guid";
83
84const char kCardSyspath[] = "/card";
85
agoode@chromium.org25227512014-06-08 05:12:05 +000086// Constants for the capabilities we search for in inputs and outputs.
87// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html.
toyoshimefcee5e2017-06-14 07:14:39 -070088constexpr unsigned int kRequiredInputPortCaps =
agoode@chromium.org25227512014-06-08 05:12:05 +000089 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
toyoshimefcee5e2017-06-14 07:14:39 -070090constexpr unsigned int kRequiredOutputPortCaps =
agoode@chromium.org25227512014-06-08 05:12:05 +000091 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
92
toyoshimefcee5e2017-06-14 07:14:39 -070093constexpr unsigned int kCreateOutputPortCaps =
agoode99d63292015-04-13 08:39:25 -070094 SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
toyoshimefcee5e2017-06-14 07:14:39 -070095constexpr unsigned int kCreateInputPortCaps =
agoode99d63292015-04-13 08:39:25 -070096 SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
toyoshimefcee5e2017-06-14 07:14:39 -070097constexpr unsigned int kCreatePortType =
agoode99d63292015-04-13 08:39:25 -070098 SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
agoode@chromium.org25227512014-06-08 05:12:05 +000099
agoode99d63292015-04-13 08:39:25 -0700100int AddrToInt(int client, int port) {
101 return (client << 8) | port;
agoodef212b2a2015-03-19 12:53:23 -0700102}
agoodef212b2a2015-03-19 12:53:23 -0700103
agoodeb09423b2015-05-11 11:39:57 -0700104// Returns true if this client has an ALSA card associated with it.
105bool IsCardClient(snd_seq_client_type_t type, int client_id) {
106 return (type == SND_SEQ_KERNEL_CLIENT) &&
107 (client_id >= kMinimumClientIdForCards);
108}
109
110// TODO(agoode): Move this to device/udev_linux.
111const std::string UdevDeviceGetPropertyOrSysattr(
112 struct udev_device* udev_device,
113 const char* property_key,
114 const char* sysattr_key) {
115 // First try the property.
116 std::string value =
117 device::UdevDeviceGetPropertyValue(udev_device, property_key);
118
119 // If no property, look for sysattrs and walk up the parent devices too.
120 while (value.empty() && udev_device) {
121 value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key);
122 udev_device = device::udev_device_get_parent(udev_device);
123 }
124 return value;
125}
126
127int GetCardNumber(udev_device* dev) {
128 const char* syspath = device::udev_device_get_syspath(dev);
129 if (!syspath)
130 return -1;
131
132 std::string syspath_str(syspath);
133 size_t i = syspath_str.rfind(kCardSyspath);
134 if (i == std::string::npos)
135 return -1;
136
137 int number;
138 if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number))
139 return -1;
140 return number;
141}
142
agooded87fc0f2015-05-21 08:29:31 -0700143std::string GetVendor(udev_device* dev) {
144 // Try to get the vendor string. Sometimes it is encoded.
145 std::string vendor = device::UdevDecodeString(
146 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc));
147 // Sometimes it is not encoded.
148 if (vendor.empty())
149 vendor =
150 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName);
151 return vendor;
152}
153
agoode8caab0b2015-03-23 18:48:02 -0700154void SetStringIfNonEmpty(base::DictionaryValue* value,
155 const std::string& path,
156 const std::string& in_value) {
157 if (!in_value.empty())
158 value->SetString(path, in_value);
159}
160
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000161} // namespace
162
toyoshimf4d61522017-02-10 02:03:32 -0800163MidiManagerAlsa::MidiManagerAlsa(MidiService* service) : MidiManager(service) {}
agoodeb2ead822016-03-11 12:14:35 -0800164
165MidiManagerAlsa::~MidiManagerAlsa() {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000166 base::AutoLock lock(lazy_init_member_lock_);
167
toyoshim131beb52016-07-25 00:42:41 -0700168 // Extra CHECK to verify all members are already reset.
toyoshim131beb52016-07-25 00:42:41 -0700169 CHECK(!in_client_);
170 CHECK(!out_client_);
171 CHECK(!decoder_);
172 CHECK(!udev_);
173 CHECK(!udev_monitor_);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000174}
175
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000176void MidiManagerAlsa::StartInitialization() {
toyoshimefcee5e2017-06-14 07:14:39 -0700177 if (!service()->task_service()->BindInstance()) {
178 NOTREACHED();
179 return CompleteInitialization(Result::INITIALIZATION_ERROR);
toyoshimf4d61522017-02-10 02:03:32 -0800180 }
181
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000182 base::AutoLock lock(lazy_init_member_lock_);
183
agoodeb2ead822016-03-11 12:14:35 -0800184 // Create client handles.
185 snd_seq_t* tmp_seq = nullptr;
186 int err =
187 snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
agoode@chromium.org25227512014-06-08 05:12:05 +0000188 if (err != 0) {
189 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700190 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000191 }
agoodeb2ead822016-03-11 12:14:35 -0800192 ScopedSndSeqPtr in_client(tmp_seq);
193 tmp_seq = nullptr;
194 in_client_id_ = snd_seq_client_id(in_client.get());
195
196 err = snd_seq_open(&tmp_seq, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
197 if (err != 0) {
198 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
199 return CompleteInitialization(Result::INITIALIZATION_ERROR);
200 }
201 ScopedSndSeqPtr out_client(tmp_seq);
202 tmp_seq = nullptr;
203 out_client_id_ = snd_seq_client_id(out_client.get());
agoode@chromium.org25227512014-06-08 05:12:05 +0000204
205 // Name the clients.
agoodeb2ead822016-03-11 12:14:35 -0800206 err = snd_seq_set_client_name(in_client.get(), "Chrome (input)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000207 if (err != 0) {
208 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700209 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000210 }
agoodeb2ead822016-03-11 12:14:35 -0800211 err = snd_seq_set_client_name(out_client.get(), "Chrome (output)");
agoode@chromium.org25227512014-06-08 05:12:05 +0000212 if (err != 0) {
213 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700214 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000215 }
216
217 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700218 in_port_id_ = snd_seq_create_simple_port(
agoodeb2ead822016-03-11 12:14:35 -0800219 in_client.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700220 if (in_port_id_ < 0) {
221 VLOG(1) << "snd_seq_create_simple_port fails: "
222 << snd_strerror(in_port_id_);
toyoshimf1b88962015-07-09 14:14:51 -0700223 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000224 }
225
226 // Subscribe to the announce port.
227 snd_seq_port_subscribe_t* subs;
228 snd_seq_port_subscribe_alloca(&subs);
229 snd_seq_addr_t announce_sender;
230 snd_seq_addr_t announce_dest;
231 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
232 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700233 announce_dest.client = in_client_id_;
234 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000235 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
236 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
agoodeb2ead822016-03-11 12:14:35 -0800237 err = snd_seq_subscribe_port(in_client.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000238 if (err != 0) {
239 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
240 << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700241 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000242 }
243
agoodeb2ead822016-03-11 12:14:35 -0800244 // Initialize decoder.
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000245 ScopedSndMidiEventPtr decoder = CreateScopedSndMidiEventPtr(0);
agoodeb2ead822016-03-11 12:14:35 -0800246 snd_midi_event_no_status(decoder.get(), 1);
agoode@chromium.org25227512014-06-08 05:12:05 +0000247
agoodeb2ead822016-03-11 12:14:35 -0800248 // Initialize udev and monitor.
249 device::ScopedUdevPtr udev(device::udev_new());
250 device::ScopedUdevMonitorPtr udev_monitor(
251 device::udev_monitor_new_from_netlink(udev.get(), kUdev));
252 if (!udev_monitor.get()) {
agoode975043d2015-05-11 00:46:17 -0700253 VLOG(1) << "udev_monitor_new_from_netlink fails";
toyoshimf1b88962015-07-09 14:14:51 -0700254 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700255 }
256 err = device::udev_monitor_filter_add_match_subsystem_devtype(
agoodeb2ead822016-03-11 12:14:35 -0800257 udev_monitor.get(), kUdevSubsystemSound, nullptr);
agoode975043d2015-05-11 00:46:17 -0700258 if (err != 0) {
259 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700260 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700261 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700262 }
agoodeb2ead822016-03-11 12:14:35 -0800263 err = device::udev_monitor_enable_receiving(udev_monitor.get());
agoode975043d2015-05-11 00:46:17 -0700264 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700265 VLOG(1) << "udev_monitor_enable_receiving fails: "
266 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700267 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700268 }
269
agoodeb2ead822016-03-11 12:14:35 -0800270 // Success! Now, initialize members from the temporaries. Do not
271 // initialize these earlier, since they need to be destroyed by the
272 // thread that calls Finalize(), not the destructor thread (and we
273 // check this in the destructor).
kokihoon20bdf462018-07-10 18:04:24 +0000274 in_client_ = std::move(in_client);
275 out_client_ = std::move(out_client);
276 decoder_ = std::move(decoder);
277 udev_ = std::move(udev);
278 udev_monitor_ = std::move(udev_monitor);
agoodeb2ead822016-03-11 12:14:35 -0800279
280 // Generate hotplug events for existing ports.
281 // TODO(agoode): Check the return value for failure.
282 EnumerateAlsaPorts();
283
284 // Generate hotplug events for existing udev devices. This must be done
285 // after udev_monitor_enable_receiving() is called. See the algorithm
286 // at http://www.signal11.us/oss/udev/.
agoode975043d2015-05-11 00:46:17 -0700287 EnumerateUdevCards();
288
agoodeb2ead822016-03-11 12:14:35 -0800289 // Start processing events. Don't do this before enumeration of both
290 // ALSA and udev.
toyoshimefcee5e2017-06-14 07:14:39 -0700291 service()->task_service()->PostBoundTask(
292 kEventTaskRunner,
293 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000294
toyoshimf1b88962015-07-09 14:14:51 -0700295 CompleteInitialization(Result::OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000296}
297
toyoshim8e7d6e02015-10-06 08:47:17 -0700298void MidiManagerAlsa::Finalize() {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000299 {
300 base::AutoLock lock(out_client_lock_);
301 // Close the out client. This will trigger the event thread to stop,
302 // because of SND_SEQ_EVENT_CLIENT_EXIT.
303 out_client_.reset();
304 }
toyoshim8e7d6e02015-10-06 08:47:17 -0700305
toyoshimefcee5e2017-06-14 07:14:39 -0700306 // Ensure that no task is running any more.
307 bool result = service()->task_service()->UnbindInstance();
308 CHECK(result);
agoodeb2ead822016-03-11 12:14:35 -0800309
310 // Destruct the other stuff we initialized in StartInitialization().
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000311 base::AutoLock lock(lazy_init_member_lock_);
agoodeb2ead822016-03-11 12:14:35 -0800312 udev_monitor_.reset();
313 udev_.reset();
314 decoder_.reset();
315 in_client_.reset();
toyoshim8e7d6e02015-10-06 08:47:17 -0700316}
317
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000318void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -0500319 uint32_t port_index,
320 const std::vector<uint8_t>& data,
tzik925e2c62018-02-02 07:39:45 +0000321 base::TimeTicks timestamp) {
toyoshimefcee5e2017-06-14 07:14:39 -0700322 service()->task_service()->PostBoundDelayedTask(
323 kSendTaskRunner,
324 base::BindOnce(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
325 client, port_index, data),
Takashi Toyoshimaafb27d52017-09-13 11:50:41 +0000326 MidiService::TimestampToTimeDeltaDelay(timestamp));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000327}
328
agooded87fc0f2015-05-21 08:29:31 -0700329MidiManagerAlsa::MidiPort::Id::Id() = default;
330
331MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
332 const std::string& vendor_id,
333 const std::string& model_id,
334 const std::string& usb_interface_num,
335 const std::string& serial)
336 : bus_(bus),
337 vendor_id_(vendor_id),
338 model_id_(model_id),
339 usb_interface_num_(usb_interface_num),
340 serial_(serial) {
341}
342
343MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
344
345MidiManagerAlsa::MidiPort::Id::~Id() = default;
346
347bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
348 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
349 (model_id_ == rhs.model_id_) &&
350 (usb_interface_num_ == rhs.usb_interface_num_) &&
351 (serial_ == rhs.serial_);
352}
353
354bool MidiManagerAlsa::MidiPort::Id::empty() const {
355 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
356 usb_interface_num_.empty() && serial_.empty();
357}
358
agoode99d63292015-04-13 08:39:25 -0700359MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700360 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700361 int client_id,
362 int port_id,
363 int midi_device,
364 const std::string& client_name,
365 const std::string& port_name,
366 const std::string& manufacturer,
367 const std::string& version,
368 Type type)
369 : id_(id),
370 midi_device_(midi_device),
371 type_(type),
372 path_(path),
373 client_id_(client_id),
374 port_id_(port_id),
375 client_name_(client_name),
376 port_name_(port_name),
377 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700378 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000379}
380
agoodeb0582872015-05-20 05:22:24 -0700381MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000382
agoode99d63292015-04-13 08:39:25 -0700383// Note: keep synchronized with the MidiPort::Match* methods.
danakj75afea02016-04-25 20:36:04 -0700384std::unique_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
385 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
agoode99d63292015-04-13 08:39:25 -0700386
387 std::string type;
388 switch (type_) {
389 case Type::kInput:
390 type = "input";
391 break;
agoode99d63292015-04-13 08:39:25 -0700392 case Type::kOutput:
393 type = "output";
394 break;
395 }
396 value->SetString("type", type);
397 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700398 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
399 SetStringIfNonEmpty(value.get(), "portName", port_name_);
400 value->SetInteger("clientId", client_id_);
401 value->SetInteger("portId", port_id_);
402 value->SetInteger("midiDevice", midi_device_);
403
agooded87fc0f2015-05-21 08:29:31 -0700404 // Flatten id fields.
405 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
406 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
407 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
408 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
409 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
410
dchengc2aeece2015-12-27 00:54:00 -0800411 return std::move(value);
agoodebd4be9b2015-03-16 19:17:25 -0700412}
agoode@chromium.org25227512014-06-08 05:12:05 +0000413
agoode99d63292015-04-13 08:39:25 -0700414std::string MidiManagerAlsa::MidiPort::JSONValue() const {
415 std::string json;
416 JSONStringValueSerializer serializer(&json);
417 serializer.Serialize(*Value().get());
418 return json;
agoodef212b2a2015-03-19 12:53:23 -0700419}
420
agoode99d63292015-04-13 08:39:25 -0700421// TODO(agoode): Do not use SHA256 here. Instead store a persistent
422// mapping and just use a UUID or other random string.
423// http://crbug.com/465320
424std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
Avi Drissman3528fd02015-12-18 20:11:31 -0500425 uint8_t hash[crypto::kSHA256Length];
agoode99d63292015-04-13 08:39:25 -0700426 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
427 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700428}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000429
agoode99d63292015-04-13 08:39:25 -0700430bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
431 // Matches on:
432 // connected == true
433 // type
434 // path
435 // id
436 // client_id
437 // port_id
438 // midi_device
439 // client_name
440 // port_name
441 return connected() && (type() == query.type()) && (path() == query.path()) &&
442 (id() == query.id()) && (client_id() == query.client_id()) &&
443 (port_id() == query.port_id()) &&
444 (midi_device() == query.midi_device()) &&
445 (client_name() == query.client_name()) &&
446 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700447}
448
agoode99d63292015-04-13 08:39:25 -0700449bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
450 // Matches on:
451 // connected == false
452 // type
453 // path
454 // id
455 // port_id
456 // midi_device
457 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700458}
459
agoode99d63292015-04-13 08:39:25 -0700460bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
461 // Matches on:
462 // connected == false
463 // type
464 // id
465 // port_id
466 // midi_device
467 return !connected() && (type() == query.type()) && (id() == query.id()) &&
468 (port_id() == query.port_id()) &&
469 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700470}
471
agoode99d63292015-04-13 08:39:25 -0700472bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
473 // Matches on:
474 // connected == false
475 // type
476 // path.empty(), for both this and query
477 // id.empty(), for both this and query
478 // client_id
479 // port_id
480 // client_name
481 // port_name
482 // midi_device == -1, for both this and query
483 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700484}
485
agoode99d63292015-04-13 08:39:25 -0700486bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
487 // Matches on:
488 // connected == false
489 // type
490 // path.empty(), for both this and query
491 // id.empty(), for both this and query
492 // port_id
493 // client_name
494 // port_name
495 // midi_device == -1, for both this and query
496 return !connected() && (type() == query.type()) && path().empty() &&
497 query.path().empty() && id().empty() && query.id().empty() &&
498 (port_id() == query.port_id()) &&
499 (client_name() == query.client_name()) &&
500 (port_name() == query.port_name()) && (midi_device() == -1) &&
501 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000502}
503
agoodeb0582872015-05-20 05:22:24 -0700504MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700505
506MidiManagerAlsa::MidiPortStateBase::iterator
507MidiManagerAlsa::MidiPortStateBase::Find(
508 const MidiManagerAlsa::MidiPort& port) {
509 auto result = FindConnected(port);
510 if (result == end())
511 result = FindDisconnected(port);
512 return result;
513}
514
515MidiManagerAlsa::MidiPortStateBase::iterator
516MidiManagerAlsa::MidiPortStateBase::FindConnected(
517 const MidiManagerAlsa::MidiPort& port) {
518 // Exact match required for connected ports.
danakj75afea02016-04-25 20:36:04 -0700519 auto it = std::find_if(ports_.begin(), ports_.end(),
520 [&port](std::unique_ptr<MidiPort>& p) {
521 return p->MatchConnected(port);
522 });
agoode99d63292015-04-13 08:39:25 -0700523 return it;
524}
525
526MidiManagerAlsa::MidiPortStateBase::iterator
527MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
528 const MidiManagerAlsa::MidiPort& port) {
529 // Always match on:
530 // type
531 // Possible things to match on:
532 // path
533 // id
534 // client_id
535 // port_id
536 // midi_device
537 // client_name
538 // port_name
539
540 if (!port.path().empty()) {
541 // If path is present, then we have a card-based client.
542
543 // Pass 1. Match on path, id, midi_device, port_id.
544 // This is the best possible match for hardware card-based clients.
545 // This will also match the empty id correctly for devices without an id.
danakj75afea02016-04-25 20:36:04 -0700546 auto it = std::find_if(ports_.begin(), ports_.end(),
547 [&port](std::unique_ptr<MidiPort>& p) {
548 return p->MatchCardPass1(port);
549 });
agoode99d63292015-04-13 08:39:25 -0700550 if (it != ports_.end())
551 return it;
552
553 if (!port.id().empty()) {
554 // Pass 2. Match on id, midi_device, port_id.
555 // This will give us a high-confidence match when a user moves a device to
556 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
557 // has a hardware id.
danakj75afea02016-04-25 20:36:04 -0700558 it = std::find_if(ports_.begin(), ports_.end(),
559 [&port](std::unique_ptr<MidiPort>& p) {
560 return p->MatchCardPass2(port);
561 });
agoode99d63292015-04-13 08:39:25 -0700562 if (it != ports_.end())
563 return it;
564 }
565 } else {
566 // Else, we have a non-card-based client.
567 // Pass 1. Match on client_id, port_id, client_name, port_name.
568 // This will give us a reasonably good match.
danakj75afea02016-04-25 20:36:04 -0700569 auto it = std::find_if(ports_.begin(), ports_.end(),
570 [&port](std::unique_ptr<MidiPort>& p) {
571 return p->MatchNoCardPass1(port);
572 });
agoode99d63292015-04-13 08:39:25 -0700573 if (it != ports_.end())
574 return it;
575
576 // Pass 2. Match on port_id, client_name, port_name.
577 // This is weaker but similar to pass 2 in the hardware card-based clients
578 // match.
danakj75afea02016-04-25 20:36:04 -0700579 it = std::find_if(ports_.begin(), ports_.end(),
580 [&port](std::unique_ptr<MidiPort>& p) {
581 return p->MatchNoCardPass2(port);
582 });
agoode99d63292015-04-13 08:39:25 -0700583 if (it != ports_.end())
584 return it;
585 }
586
587 // No match.
588 return ports_.end();
589}
590
agoodeb0582872015-05-20 05:22:24 -0700591MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700592
agoodedf1b9ff2015-06-25 18:14:50 -0700593MidiManagerAlsa::MidiPortState::MidiPortState() = default;
agoode99d63292015-04-13 08:39:25 -0700594
danakj75afea02016-04-25 20:36:04 -0700595uint32_t MidiManagerAlsa::MidiPortState::push_back(
596 std::unique_ptr<MidiPort> port) {
agoode99d63292015-04-13 08:39:25 -0700597 // Add the web midi index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500598 uint32_t web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700599 switch (port->type()) {
600 case MidiPort::Type::kInput:
601 web_port_index = num_input_ports_++;
602 break;
603 case MidiPort::Type::kOutput:
604 web_port_index = num_output_ports_++;
605 break;
606 }
607 port->set_web_port_index(web_port_index);
dchengc2aeece2015-12-27 00:54:00 -0800608 MidiPortStateBase::push_back(std::move(port));
agoode99d63292015-04-13 08:39:25 -0700609 return web_port_index;
610}
611
agoodedf1b9ff2015-06-25 18:14:50 -0700612MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700613
agoodeb0582872015-05-20 05:22:24 -0700614MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700615
616void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
617 const std::string& client_name,
618 snd_seq_client_type_t type) {
619 ClientExit(client_id);
ricea37e45762016-08-25 02:43:39 -0700620 clients_.insert(
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000621 std::make_pair(client_id, std::make_unique<Client>(client_name, type)));
agoodeb09423b2015-05-11 11:39:57 -0700622 if (IsCardClient(type, client_id))
623 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700624}
625
626bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
627 return clients_.find(client_id) != clients_.end();
628}
629
630void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
631 auto it = clients_.find(client_id);
632 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700633 if (IsCardClient(it->second->type(), client_id))
634 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700635 clients_.erase(it);
636 }
637}
638
639void MidiManagerAlsa::AlsaSeqState::PortStart(
640 int client_id,
641 int port_id,
642 const std::string& port_name,
643 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
644 bool midi) {
645 auto it = clients_.find(client_id);
646 if (it != clients_.end())
647 it->second->AddPort(port_id,
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000648 std::make_unique<Port>(port_name, direction, midi));
agoode99d63292015-04-13 08:39:25 -0700649}
650
651void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
652 auto it = clients_.find(client_id);
653 if (it != clients_.end())
654 it->second->RemovePort(port_id);
655}
656
657snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
658 int client_id) const {
659 auto it = clients_.find(client_id);
660 if (it == clients_.end())
661 return SND_SEQ_USER_CLIENT;
662 return it->second->type();
663}
664
danakj75afea02016-04-25 20:36:04 -0700665std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700666MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
danakj75afea02016-04-25 20:36:04 -0700667 std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
agoode99d63292015-04-13 08:39:25 -0700668 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700669 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700670
agoodeb09423b2015-05-11 11:39:57 -0700671 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700672 for (const auto& client_pair : clients_) {
673 int client_id = client_pair.first;
vmpstr0205abb2016-06-28 18:50:56 -0700674 auto* client = client_pair.second.get();
agoode99d63292015-04-13 08:39:25 -0700675
676 // Get client metadata.
677 const std::string client_name = client->name();
678 std::string manufacturer;
679 std::string driver;
680 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700681 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700682 std::string card_name;
683 std::string card_longname;
684 int midi_device = -1;
685
agoodeb09423b2015-05-11 11:39:57 -0700686 if (IsCardClient(client->type(), client_id)) {
687 auto& card = card_it->second;
688 if (card_midi_device == -1)
689 card_midi_device = 0;
690
691 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700692 path = card->path();
693 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
694 card->usb_interface_num(), card->serial());
695 card_name = card->name();
696 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700697 midi_device = card_midi_device;
698
699 ++card_midi_device;
700 if (card_midi_device >= card->midi_device_count()) {
701 card_midi_device = -1;
702 ++card_it;
703 }
704 }
705
agoode99d63292015-04-13 08:39:25 -0700706 for (const auto& port_pair : *client) {
707 int port_id = port_pair.first;
708 const auto& port = port_pair.second;
709
710 if (port->midi()) {
711 std::string version;
712 if (!driver.empty()) {
713 version = driver + " / ";
714 }
715 version +=
716 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
717 SND_LIB_MINOR, SND_LIB_SUBMINOR);
718 PortDirection direction = port->direction();
719 if (direction == PortDirection::kInput ||
720 direction == PortDirection::kDuplex) {
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000721 midi_ports->push_back(std::make_unique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700722 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700723 port->name(), manufacturer, version, MidiPort::Type::kInput));
agoode99d63292015-04-13 08:39:25 -0700724 }
725 if (direction == PortDirection::kOutput ||
726 direction == PortDirection::kDuplex) {
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000727 midi_ports->push_back(std::make_unique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700728 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700729 port->name(), manufacturer, version, MidiPort::Type::kOutput));
agoode99d63292015-04-13 08:39:25 -0700730 }
731 }
732 }
733 }
734
dchengc2aeece2015-12-27 00:54:00 -0800735 return midi_ports;
agoode99d63292015-04-13 08:39:25 -0700736}
737
738MidiManagerAlsa::AlsaSeqState::Port::Port(
739 const std::string& name,
740 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
741 bool midi)
742 : name_(name), direction_(direction), midi_(midi) {
743}
744
agoodeb0582872015-05-20 05:22:24 -0700745MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700746
747MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
748 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700749 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700750}
751
agoodeb0582872015-05-20 05:22:24 -0700752MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700753
danakj75afea02016-04-25 20:36:04 -0700754void MidiManagerAlsa::AlsaSeqState::Client::AddPort(
755 int addr,
756 std::unique_ptr<Port> port) {
limasdfe59d0392015-11-19 20:28:57 -0800757 ports_[addr] = std::move(port);
agoode99d63292015-04-13 08:39:25 -0700758}
759
760void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700761 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700762}
763
764MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
765MidiManagerAlsa::AlsaSeqState::Client::begin() const {
766 return ports_.begin();
767}
768
769MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
770MidiManagerAlsa::AlsaSeqState::Client::end() const {
771 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700772}
773
agoodeb09423b2015-05-11 11:39:57 -0700774MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700775 const std::string& name,
776 const std::string& longname,
777 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700778 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700779 : name_(name),
780 longname_(longname),
781 driver_(driver),
782 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
783 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
784 vendor_id_(
785 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
786 model_id_(
787 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
788 usb_interface_num_(
789 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
790 serial_(UdevDeviceGetPropertyOrSysattr(dev,
791 kUdevIdSerialShort,
792 kSysattrGuid)),
793 midi_device_count_(midi_device_count),
794 manufacturer_(ExtractManufacturerString(
795 GetVendor(dev),
796 vendor_id_,
797 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
798 name,
799 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700800}
801
agoodeb0582872015-05-20 05:22:24 -0700802MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700803
agoode55a8b522015-03-08 12:40:17 -0700804// static
agoodeb09423b2015-05-11 11:39:57 -0700805std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700806 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700807 const std::string& udev_id_vendor_id,
808 const std::string& udev_id_vendor_from_database,
809 const std::string& alsa_name,
810 const std::string& alsa_longname) {
811 // Let's try to determine the manufacturer. Here is the ordered preference
812 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700813 // 1. Vendor name from the hardware device string, from udev properties
814 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700815 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700816 // 3. Heuristic from ALSA.
817
agoodee83758c2015-03-23 22:07:54 -0700818 // Is the vendor string present and not just the vendor hex id?
819 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700820 return udev_id_vendor;
821 }
822
823 // Is there a vendor string in the hardware database?
824 if (!udev_id_vendor_from_database.empty()) {
825 return udev_id_vendor_from_database;
826 }
827
828 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
829 // We assume that card longname is in the format of
830 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
831 // a manufacturer name here.
832 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700833 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700834 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700835 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700836 return alsa_longname.substr(0, name_index - 1);
837 }
838
839 // Failure.
840 return "";
841}
842
toyoshimefcee5e2017-06-14 07:14:39 -0700843void MidiManagerAlsa::SendMidiData(MidiManagerClient* client,
toyoshimf4d61522017-02-10 02:03:32 -0800844 uint32_t port_index,
Avi Drissman3528fd02015-12-18 20:11:31 -0500845 const std::vector<uint8_t>& data) {
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000846 ScopedSndMidiEventPtr encoder = CreateScopedSndMidiEventPtr(kSendBufferSize);
agoode5a1aa112015-06-21 20:51:00 -0700847 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700848 snd_seq_event_t event;
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000849 int result = snd_midi_event_encode_byte(encoder.get(), datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700850 if (result == 1) {
851 // Full event, send it.
agoode99d63292015-04-13 08:39:25 -0700852 base::AutoLock lock(out_ports_lock_);
853 auto it = out_ports_.find(port_index);
854 if (it != out_ports_.end()) {
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000855 base::AutoLock lock(out_client_lock_);
856 if (!out_client_)
857 return;
agoode99d63292015-04-13 08:39:25 -0700858 snd_seq_ev_set_source(&event, it->second);
859 snd_seq_ev_set_subs(&event);
860 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700861 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700862 }
agoodebd4be9b2015-03-16 19:17:25 -0700863 }
864 }
toyoshimf4d61522017-02-10 02:03:32 -0800865
866 // Acknowledge send.
867 AccumulateMidiBytesSent(client, data.size());
agoodebd4be9b2015-03-16 19:17:25 -0700868}
869
toyoshimefcee5e2017-06-14 07:14:39 -0700870void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700871 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700872
agoode975043d2015-05-11 00:46:17 -0700873 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700874 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700875 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
876 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700877
agoode975043d2015-05-11 00:46:17 -0700878 int err = HANDLE_EINTR(poll(pfd, arraysize(pfd), -1));
879 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700880 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700881 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700882 } else {
agoode975043d2015-05-11 00:46:17 -0700883 if (pfd[0].revents & POLLIN) {
884 // Read available incoming MIDI data.
885 int remaining;
tzik925e2c62018-02-02 07:39:45 +0000886 base::TimeTicks timestamp = base::TimeTicks::Now();
agoode975043d2015-05-11 00:46:17 -0700887 do {
888 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700889 err = snd_seq_event_input(in_client_.get(), &event);
890 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700891
892 if (err == -ENOSPC) {
893 // Handle out of space error.
894 VLOG(1) << "snd_seq_event_input detected buffer overrun";
895 // We've lost events: check another way to see if we need to shut
896 // down.
agoode975043d2015-05-11 00:46:17 -0700897 } else if (err == -EAGAIN) {
898 // We've read all the data.
899 } else if (err < 0) {
900 // Handle other errors.
901 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
902 // TODO(agoode): Use RecordAction() or similar to log this.
903 loop_again = false;
904 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
905 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
906 // Handle announce events.
907 switch (event->type) {
908 case SND_SEQ_EVENT_PORT_START:
909 // Don't use SND_SEQ_EVENT_CLIENT_START because the
910 // client name may not be set by the time we query
911 // it. It should be set by the time ports are made.
912 ProcessClientStartEvent(event->data.addr.client);
913 ProcessPortStartEvent(event->data.addr);
914 break;
915 case SND_SEQ_EVENT_CLIENT_EXIT:
916 // Check for disconnection of our "out" client. This means "shut
917 // down".
918 if (event->data.addr.client == out_client_id_) {
919 loop_again = false;
920 remaining = 0;
921 } else
922 ProcessClientExitEvent(event->data.addr);
923 break;
924 case SND_SEQ_EVENT_PORT_EXIT:
925 ProcessPortExitEvent(event->data.addr);
926 break;
927 }
928 } else {
929 // Normal operation.
930 ProcessSingleEvent(event, timestamp);
931 }
932 } while (remaining > 0);
933 }
934 if (pfd[1].revents & POLLIN) {
935 device::ScopedUdevDevicePtr dev(
936 device::udev_monitor_receive_device(udev_monitor_.get()));
937 if (dev.get())
938 ProcessUdevEvent(dev.get());
939 else
940 VLOG(1) << "udev_monitor_receive_device fails";
941 }
agoodebd4be9b2015-03-16 19:17:25 -0700942 }
943
agoodebd4be9b2015-03-16 19:17:25 -0700944 // Do again.
toyoshimf4d61522017-02-10 02:03:32 -0800945 if (loop_again) {
toyoshimefcee5e2017-06-14 07:14:39 -0700946 service()->task_service()->PostBoundTask(
947 kEventTaskRunner,
948 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
toyoshimf4d61522017-02-10 02:03:32 -0800949 }
agoodebd4be9b2015-03-16 19:17:25 -0700950}
951
952void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
tzik925e2c62018-02-02 07:39:45 +0000953 base::TimeTicks timestamp) {
agoode99d63292015-04-13 08:39:25 -0700954 auto source_it =
955 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700956 if (source_it != source_map_.end()) {
Avi Drissman3528fd02015-12-18 20:11:31 -0500957 uint32_t source = source_it->second;
agoodebd4be9b2015-03-16 19:17:25 -0700958 if (event->type == SND_SEQ_EVENT_SYSEX) {
959 // Special! Variable-length sysex.
Avi Drissman3528fd02015-12-18 20:11:31 -0500960 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
agoodebd4be9b2015-03-16 19:17:25 -0700961 event->data.ext.len, timestamp);
962 } else {
963 // Otherwise, decode this and send that on.
964 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700965 long count =
966 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700967 if (count <= 0) {
968 if (count != -ENOENT) {
969 // ENOENT means that it's not a MIDI message, which is not an
970 // error, but other negative values are errors for us.
971 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
972 // TODO(agoode): Record this failure.
973 }
974 } else {
975 ReceiveMidiData(source, buf, count, timestamp);
976 }
977 }
978 }
979}
980
agoode99d63292015-04-13 08:39:25 -0700981void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
982 // Ignore if client is already started.
983 if (alsa_seq_state_.ClientStarted(client_id))
984 return;
985
986 snd_seq_client_info_t* client_info;
987 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -0700988 int err =
989 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -0700990 if (err != 0)
991 return;
992
993 // Skip our own clients.
994 if ((client_id == in_client_id_) || (client_id == out_client_id_))
995 return;
996
997 // Update our view of ALSA seq state.
998 alsa_seq_state_.ClientStart(client_id,
999 snd_seq_client_info_get_name(client_info),
1000 snd_seq_client_info_get_type(client_info));
1001
1002 // Generate Web MIDI events.
1003 UpdatePortStateAndGenerateEvents();
1004}
1005
1006void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
1007 snd_seq_port_info_t* port_info;
1008 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -07001009 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
1010 port_info);
agoode99d63292015-04-13 08:39:25 -07001011 if (err != 0)
1012 return;
1013
1014 unsigned int caps = snd_seq_port_info_get_capability(port_info);
1015 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
1016 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
1017 AlsaSeqState::PortDirection direction;
1018 if (input && output)
1019 direction = AlsaSeqState::PortDirection::kDuplex;
1020 else if (input)
1021 direction = AlsaSeqState::PortDirection::kInput;
1022 else if (output)
1023 direction = AlsaSeqState::PortDirection::kOutput;
1024 else
1025 return;
1026
1027 // Update our view of ALSA seq state.
1028 alsa_seq_state_.PortStart(
1029 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1030 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1031 // Generate Web MIDI events.
1032 UpdatePortStateAndGenerateEvents();
1033}
1034
1035void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1036 // Update our view of ALSA seq state.
1037 alsa_seq_state_.ClientExit(addr.client);
1038 // Generate Web MIDI events.
1039 UpdatePortStateAndGenerateEvents();
1040}
1041
1042void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1043 // Update our view of ALSA seq state.
1044 alsa_seq_state_.PortExit(addr.client, addr.port);
1045 // Generate Web MIDI events.
1046 UpdatePortStateAndGenerateEvents();
1047}
1048
agoode975043d2015-05-11 00:46:17 -07001049void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1050 // Only card devices have this property set, and only when they are
1051 // fully initialized.
1052 if (!device::udev_device_get_property_value(dev,
1053 kUdevPropertySoundInitialized))
1054 return;
1055
1056 // Get the action. If no action, then we are doing first time enumeration
1057 // and the device is treated as new.
1058 const char* action = device::udev_device_get_action(dev);
1059 if (!action)
1060 action = kUdevActionChange;
1061
1062 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001063 AddCard(dev);
1064 // Generate Web MIDI events.
1065 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001066 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001067 RemoveCard(GetCardNumber(dev));
1068 // Generate Web MIDI events.
1069 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001070 }
1071}
1072
agoodeb09423b2015-05-11 11:39:57 -07001073void MidiManagerAlsa::AddCard(udev_device* dev) {
1074 int number = GetCardNumber(dev);
1075 if (number == -1)
1076 return;
1077
1078 RemoveCard(number);
1079
1080 snd_ctl_card_info_t* card;
1081 snd_hwdep_info_t* hwdep;
1082 snd_ctl_card_info_alloca(&card);
1083 snd_hwdep_info_alloca(&hwdep);
1084 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1085 snd_ctl_t* handle;
1086 int err = snd_ctl_open(&handle, id.c_str(), 0);
1087 if (err != 0) {
1088 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1089 return;
1090 }
1091 err = snd_ctl_card_info(handle, card);
1092 if (err != 0) {
1093 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1094 snd_ctl_close(handle);
1095 return;
1096 }
1097 std::string name = snd_ctl_card_info_get_name(card);
1098 std::string longname = snd_ctl_card_info_get_longname(card);
1099 std::string driver = snd_ctl_card_info_get_driver(card);
1100
1101 // Count rawmidi devices (not subdevices).
1102 int midi_count = 0;
1103 for (int device = -1;
1104 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1105 ++midi_count;
1106
1107 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1108 //
1109 // Explanation:
1110 // Any kernel driver can create an ALSA client (visible to us).
1111 // With modern hardware, only rawmidi devices do this. Kernel
1112 // drivers create rawmidi devices and the rawmidi subsystem makes
1113 // the seq clients. But the OPL3 driver is special, it does not
1114 // make a rawmidi device but a seq client directly. (This is the
1115 // only one to worry about in the kernel code, as of 2015-03-23.)
1116 //
1117 // OPL3 is very old (but still possible to get in new
1118 // hardware). It is unlikely that new drivers would not use
1119 // rawmidi and defeat our heuristic.
1120 //
1121 // Longer term, support should be added in the kernel to expose a
1122 // direct link from card->client (or client->card) so that all
1123 // these heuristics will be obsolete. Once that is there, we can
1124 // assume our old heuristics will work on old kernels and the new
1125 // robust code will be used on new. Then we will not need to worry
1126 // about changes to kernel internals breaking our code.
1127 // See the TODO above at kMinimumClientIdForCards.
1128 for (int device = -1;
1129 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1130 err = snd_ctl_hwdep_info(handle, hwdep);
1131 if (err != 0) {
1132 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1133 continue;
1134 }
1135 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1136 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1137 iface == SND_HWDEP_IFACE_OPL4)
1138 ++midi_count;
1139 }
1140 snd_ctl_close(handle);
1141
mgiucad9af8452015-06-25 02:11:57 -07001142 if (midi_count > 0) {
danakj75afea02016-04-25 20:36:04 -07001143 std::unique_ptr<AlsaCard> card(
mgiucad9af8452015-06-25 02:11:57 -07001144 new AlsaCard(dev, name, longname, driver, midi_count));
limasdfe59d0392015-11-19 20:28:57 -08001145 alsa_cards_.insert(std::make_pair(number, std::move(card)));
mgiucad9af8452015-06-25 02:11:57 -07001146 alsa_card_midi_count_ += midi_count;
1147 }
agoodeb09423b2015-05-11 11:39:57 -07001148}
1149
1150void MidiManagerAlsa::RemoveCard(int number) {
1151 auto it = alsa_cards_.find(number);
1152 if (it == alsa_cards_.end())
1153 return;
1154
1155 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001156 alsa_cards_.erase(it);
1157}
1158
agoode99d63292015-04-13 08:39:25 -07001159void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001160 // Verify that our information from ALSA and udev are in sync. If
1161 // not, we cannot generate events right now.
1162 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1163 return;
1164
agoode99d63292015-04-13 08:39:25 -07001165 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001166 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001167
1168 // Disconnect any connected old ports that are now missing.
agoode5ebc4932015-12-01 08:25:12 -08001169 for (auto& old_port : port_state_) {
agoode99d63292015-04-13 08:39:25 -07001170 if (old_port->connected() &&
1171 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1172 old_port->set_connected(false);
Avi Drissman3528fd02015-12-18 20:11:31 -05001173 uint32_t web_port_index = old_port->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001174 switch (old_port->type()) {
1175 case MidiPort::Type::kInput:
1176 source_map_.erase(
1177 AddrToInt(old_port->client_id(), old_port->port_id()));
toyoshimec2570a2016-10-21 02:15:27 -07001178 SetInputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001179 break;
1180 case MidiPort::Type::kOutput:
1181 DeleteAlsaOutputPort(web_port_index);
toyoshimec2570a2016-10-21 02:15:27 -07001182 SetOutputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001183 break;
1184 }
1185 }
1186 }
1187
1188 // Reconnect or add new ports.
1189 auto it = new_port_state->begin();
1190 while (it != new_port_state->end()) {
agoode5ebc4932015-12-01 08:25:12 -08001191 auto& new_port = *it;
agoode99d63292015-04-13 08:39:25 -07001192 auto old_port = port_state_.Find(*new_port);
1193 if (old_port == port_state_.end()) {
1194 // Add new port.
agoode5ebc4932015-12-01 08:25:12 -08001195 const auto& opaque_key = new_port->OpaqueKey();
1196 const auto& manufacturer = new_port->manufacturer();
1197 const auto& port_name = new_port->port_name();
1198 const auto& version = new_port->version();
1199 const auto& type = new_port->type();
1200 const auto& client_id = new_port->client_id();
1201 const auto& port_id = new_port->port_id();
1202
dchengc2aeece2015-12-27 00:54:00 -08001203 uint32_t web_port_index = port_state_.push_back(std::move(new_port));
agoode5ebc4932015-12-01 08:25:12 -08001204 it = new_port_state->erase(it);
1205
1206 MidiPortInfo info(opaque_key, manufacturer, port_name, version,
toyoshimec2570a2016-10-21 02:15:27 -07001207 PortState::OPENED);
agoode5ebc4932015-12-01 08:25:12 -08001208 switch (type) {
agoode99d63292015-04-13 08:39:25 -07001209 case MidiPort::Type::kInput:
agoode5ebc4932015-12-01 08:25:12 -08001210 if (Subscribe(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001211 AddInputPort(info);
1212 break;
agoode99d63292015-04-13 08:39:25 -07001213 case MidiPort::Type::kOutput:
agoode5ebc4932015-12-01 08:25:12 -08001214 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001215 AddOutputPort(info);
1216 break;
1217 }
agoode99d63292015-04-13 08:39:25 -07001218 } else if (!(*old_port)->connected()) {
1219 // Reconnect.
Avi Drissman3528fd02015-12-18 20:11:31 -05001220 uint32_t web_port_index = (*old_port)->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001221 (*old_port)->Update(new_port->path(), new_port->client_id(),
1222 new_port->port_id(), new_port->client_name(),
1223 new_port->port_name(), new_port->manufacturer(),
1224 new_port->version());
1225 switch ((*old_port)->type()) {
1226 case MidiPort::Type::kInput:
1227 if (Subscribe(web_port_index, (*old_port)->client_id(),
1228 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001229 SetInputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001230 break;
agoode99d63292015-04-13 08:39:25 -07001231 case MidiPort::Type::kOutput:
1232 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1233 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001234 SetOutputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001235 break;
1236 }
1237 (*old_port)->set_connected(true);
1238 ++it;
1239 } else {
1240 ++it;
1241 }
1242 }
1243}
1244
agoode975043d2015-05-11 00:46:17 -07001245// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001246void MidiManagerAlsa::EnumerateAlsaPorts() {
1247 snd_seq_client_info_t* client_info;
1248 snd_seq_client_info_alloca(&client_info);
1249 snd_seq_port_info_t* port_info;
1250 snd_seq_port_info_alloca(&port_info);
1251
1252 // Enumerate clients.
1253 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001254 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001255 int client_id = snd_seq_client_info_get_client(client_info);
1256 ProcessClientStartEvent(client_id);
1257
1258 // Enumerate ports.
1259 snd_seq_port_info_set_client(port_info, client_id);
1260 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001261 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001262 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1263 ProcessPortStartEvent(*addr);
1264 }
1265 }
1266}
1267
agoode975043d2015-05-11 00:46:17 -07001268bool MidiManagerAlsa::EnumerateUdevCards() {
1269 int err;
1270
1271 device::ScopedUdevEnumeratePtr enumerate(
1272 device::udev_enumerate_new(udev_.get()));
1273 if (!enumerate.get()) {
1274 VLOG(1) << "udev_enumerate_new fails";
1275 return false;
1276 }
1277
1278 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1279 kUdevSubsystemSound);
1280 if (err) {
1281 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001282 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001283 return false;
1284 }
1285
1286 err = device::udev_enumerate_scan_devices(enumerate.get());
1287 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001288 VLOG(1) << "udev_enumerate_scan_devices fails: "
1289 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001290 return false;
1291 }
1292
1293 udev_list_entry* list_entry;
1294 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1295 udev_list_entry_foreach(list_entry, devices) {
1296 const char* path = device::udev_list_entry_get_name(list_entry);
1297 device::ScopedUdevDevicePtr dev(
1298 device::udev_device_new_from_syspath(udev_.get(), path));
1299 if (dev.get())
1300 ProcessUdevEvent(dev.get());
1301 }
1302
1303 return true;
1304}
1305
Avi Drissman3528fd02015-12-18 20:11:31 -05001306bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
agoode99d63292015-04-13 08:39:25 -07001307 int client_id,
1308 int port_id) {
1309 // Create the port.
1310 int out_port = snd_seq_create_simple_port(
agoode5a1aa112015-06-21 20:51:00 -07001311 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -07001312 if (out_port < 0) {
1313 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1314 return false;
1315 }
1316 // Activate port subscription.
1317 snd_seq_port_subscribe_t* subs;
1318 snd_seq_port_subscribe_alloca(&subs);
1319 snd_seq_addr_t sender;
1320 sender.client = out_client_id_;
1321 sender.port = out_port;
1322 snd_seq_port_subscribe_set_sender(subs, &sender);
1323 snd_seq_addr_t dest;
1324 dest.client = client_id;
1325 dest.port = port_id;
1326 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001327 int err = snd_seq_subscribe_port(out_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001328 if (err != 0) {
1329 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
agoode5a1aa112015-06-21 20:51:00 -07001330 snd_seq_delete_simple_port(out_client_.get(), out_port);
agoode99d63292015-04-13 08:39:25 -07001331 return false;
1332 }
1333
1334 // Update our map.
1335 base::AutoLock lock(out_ports_lock_);
1336 out_ports_[port_index] = out_port;
1337 return true;
1338}
1339
Avi Drissman3528fd02015-12-18 20:11:31 -05001340void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
agoode99d63292015-04-13 08:39:25 -07001341 base::AutoLock lock(out_ports_lock_);
1342 auto it = out_ports_.find(port_index);
1343 if (it == out_ports_.end())
1344 return;
1345
1346 int alsa_port = it->second;
agoode5a1aa112015-06-21 20:51:00 -07001347 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
agoode99d63292015-04-13 08:39:25 -07001348 out_ports_.erase(it);
1349}
1350
Avi Drissman3528fd02015-12-18 20:11:31 -05001351bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1352 int client_id,
1353 int port_id) {
agoode99d63292015-04-13 08:39:25 -07001354 // Activate port subscription.
1355 snd_seq_port_subscribe_t* subs;
1356 snd_seq_port_subscribe_alloca(&subs);
1357 snd_seq_addr_t sender;
1358 sender.client = client_id;
1359 sender.port = port_id;
1360 snd_seq_port_subscribe_set_sender(subs, &sender);
1361 snd_seq_addr_t dest;
1362 dest.client = in_client_id_;
1363 dest.port = in_port_id_;
1364 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001365 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001366 if (err != 0) {
1367 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1368 return false;
1369 }
1370
1371 // Update our map.
1372 source_map_[AddrToInt(client_id, port_id)] = port_index;
1373 return true;
1374}
1375
Takashi Toyoshima0ae49702017-09-01 04:59:51 +00001376MidiManagerAlsa::ScopedSndMidiEventPtr
1377MidiManagerAlsa::CreateScopedSndMidiEventPtr(size_t size) {
1378 snd_midi_event_t* coder;
1379 snd_midi_event_new(size, &coder);
1380 return ScopedSndMidiEventPtr(coder);
1381}
1382
toyoshimf4d61522017-02-10 02:03:32 -08001383MidiManager* MidiManager::Create(MidiService* service) {
1384 return new MidiManagerAlsa(service);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001385}
1386
toyoshime147c5e2015-05-07 21:58:31 -07001387} // namespace midi