blob: 2a8f924aeb0c11505bbe843e1d6d2128458d78c7 [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"
Lei Zhang21a44532021-05-26 19:26:48 +000017#include "base/cxx17_backports.h"
agoodef212b2a2015-03-19 12:53:23 -070018#include "base/json/json_string_value_serializer.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019#include "base/logging.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"
agoodef212b2a2015-03-19 12:53:23 -070022#include "base/strings/string_number_conversions.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000023#include "base/strings/stringprintf.h"
Patrick Monette3f9156f2021-10-15 19:13:42 +000024#include "base/task/single_thread_task_runner.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"
toyoshimf4d61522017-02-10 02:03:32 -080028#include "media/midi/midi_service.h"
Adithya Srinivasan33252732018-10-17 15:59:40 +000029#include "media/midi/midi_service.mojom.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 Toyoshimae8240ab2018-10-03 09:30:11 +0000166 {
167 base::AutoLock lock(out_client_lock_);
168 // Close the out client. This will trigger the event thread to stop,
169 // because of SND_SEQ_EVENT_CLIENT_EXIT.
170 out_client_.reset();
171 }
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000172 // Ensure that no task is running any more.
Takashi Toyoshima88b4ac02019-02-12 11:25:56 +0000173 if (!service()->task_service()->UnbindInstance())
174 return;
175
176 // |out_client_| should be reset before UnbindInstance() call to avoid
177 // a deadlock, but other finalization steps should be implemented after the
178 // UnbindInstance() call above, if we need.
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000179}
180
toyoshim@chromium.org51c7f532014-05-01 17:17:32 +0000181void MidiManagerAlsa::StartInitialization() {
Takashi Toyoshima88b4ac02019-02-12 11:25:56 +0000182 if (!service()->task_service()->BindInstance())
toyoshimefcee5e2017-06-14 07:14:39 -0700183 return CompleteInitialization(Result::INITIALIZATION_ERROR);
toyoshimf4d61522017-02-10 02:03:32 -0800184
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000185 // Create client handles and name the clients.
186 int err;
187 {
188 snd_seq_t* in_seq = nullptr;
189 err = snd_seq_open(&in_seq, kAlsaHw, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
190 if (err != 0) {
191 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
192 return CompleteInitialization(Result::INITIALIZATION_ERROR);
193 }
194 in_client_ = ScopedSndSeqPtr(in_seq);
195 in_client_id_ = snd_seq_client_id(in_client_.get());
196 err = snd_seq_set_client_name(in_client_.get(), "Chrome (input)");
197 if (err != 0) {
198 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
199 return CompleteInitialization(Result::INITIALIZATION_ERROR);
200 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000201 }
agoodeb2ead822016-03-11 12:14:35 -0800202
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000203 {
204 snd_seq_t* out_seq = nullptr;
205 err = snd_seq_open(&out_seq, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
206 if (err != 0) {
207 VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
208 return CompleteInitialization(Result::INITIALIZATION_ERROR);
209 }
210 base::AutoLock lock(out_client_lock_);
211 out_client_ = ScopedSndSeqPtr(out_seq);
212 out_client_id_ = snd_seq_client_id(out_client_.get());
213 err = snd_seq_set_client_name(out_client_.get(), "Chrome (output)");
214 if (err != 0) {
215 VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
216 return CompleteInitialization(Result::INITIALIZATION_ERROR);
217 }
agoode@chromium.org25227512014-06-08 05:12:05 +0000218 }
219
220 // Create input port.
agoode99d63292015-04-13 08:39:25 -0700221 in_port_id_ = snd_seq_create_simple_port(
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000222 in_client_.get(), NULL, kCreateInputPortCaps, kCreatePortType);
agoode99d63292015-04-13 08:39:25 -0700223 if (in_port_id_ < 0) {
224 VLOG(1) << "snd_seq_create_simple_port fails: "
225 << snd_strerror(in_port_id_);
toyoshimf1b88962015-07-09 14:14:51 -0700226 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000227 }
228
229 // Subscribe to the announce port.
230 snd_seq_port_subscribe_t* subs;
231 snd_seq_port_subscribe_alloca(&subs);
232 snd_seq_addr_t announce_sender;
233 snd_seq_addr_t announce_dest;
234 announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
235 announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
agoode99d63292015-04-13 08:39:25 -0700236 announce_dest.client = in_client_id_;
237 announce_dest.port = in_port_id_;
agoode@chromium.org25227512014-06-08 05:12:05 +0000238 snd_seq_port_subscribe_set_sender(subs, &announce_sender);
239 snd_seq_port_subscribe_set_dest(subs, &announce_dest);
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000240 err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode@chromium.org25227512014-06-08 05:12:05 +0000241 if (err != 0) {
242 VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
243 << snd_strerror(err);
toyoshimf1b88962015-07-09 14:14:51 -0700244 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode@chromium.org25227512014-06-08 05:12:05 +0000245 }
246
agoodeb2ead822016-03-11 12:14:35 -0800247 // Initialize decoder.
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000248 decoder_ = CreateScopedSndMidiEventPtr(0);
249 snd_midi_event_no_status(decoder_.get(), 1);
agoode@chromium.org25227512014-06-08 05:12:05 +0000250
agoodeb2ead822016-03-11 12:14:35 -0800251 // Initialize udev and monitor.
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000252 udev_ = device::ScopedUdevPtr(device::udev_new());
253 udev_monitor_ = device::ScopedUdevMonitorPtr(
254 device::udev_monitor_new_from_netlink(udev_.get(), kUdev));
255 if (!udev_monitor_.get()) {
agoode975043d2015-05-11 00:46:17 -0700256 VLOG(1) << "udev_monitor_new_from_netlink fails";
toyoshimf1b88962015-07-09 14:14:51 -0700257 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700258 }
259 err = device::udev_monitor_filter_add_match_subsystem_devtype(
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000260 udev_monitor_.get(), kUdevSubsystemSound, nullptr);
agoode975043d2015-05-11 00:46:17 -0700261 if (err != 0) {
262 VLOG(1) << "udev_monitor_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -0700263 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700264 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700265 }
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +0000266 err = device::udev_monitor_enable_receiving(udev_monitor_.get());
agoode975043d2015-05-11 00:46:17 -0700267 if (err != 0) {
brettwf7f870f2015-06-09 11:05:24 -0700268 VLOG(1) << "udev_monitor_enable_receiving fails: "
269 << base::safe_strerror(-err);
toyoshimf1b88962015-07-09 14:14:51 -0700270 return CompleteInitialization(Result::INITIALIZATION_ERROR);
agoode975043d2015-05-11 00:46:17 -0700271 }
272
agoodeb2ead822016-03-11 12:14:35 -0800273 // Generate hotplug events for existing ports.
274 // TODO(agoode): Check the return value for failure.
275 EnumerateAlsaPorts();
276
277 // Generate hotplug events for existing udev devices. This must be done
278 // after udev_monitor_enable_receiving() is called. See the algorithm
279 // at http://www.signal11.us/oss/udev/.
agoode975043d2015-05-11 00:46:17 -0700280 EnumerateUdevCards();
281
agoodeb2ead822016-03-11 12:14:35 -0800282 // Start processing events. Don't do this before enumeration of both
283 // ALSA and udev.
toyoshimefcee5e2017-06-14 07:14:39 -0700284 service()->task_service()->PostBoundTask(
285 kEventTaskRunner,
286 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
agoode@chromium.org25227512014-06-08 05:12:05 +0000287
toyoshimf1b88962015-07-09 14:14:51 -0700288 CompleteInitialization(Result::OK);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000289}
290
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000291void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
Avi Drissman3528fd02015-12-18 20:11:31 -0500292 uint32_t port_index,
293 const std::vector<uint8_t>& data,
tzik925e2c62018-02-02 07:39:45 +0000294 base::TimeTicks timestamp) {
toyoshimefcee5e2017-06-14 07:14:39 -0700295 service()->task_service()->PostBoundDelayedTask(
296 kSendTaskRunner,
297 base::BindOnce(&MidiManagerAlsa::SendMidiData, base::Unretained(this),
298 client, port_index, data),
Takashi Toyoshimaafb27d52017-09-13 11:50:41 +0000299 MidiService::TimestampToTimeDeltaDelay(timestamp));
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000300}
301
agooded87fc0f2015-05-21 08:29:31 -0700302MidiManagerAlsa::MidiPort::Id::Id() = default;
303
304MidiManagerAlsa::MidiPort::Id::Id(const std::string& bus,
305 const std::string& vendor_id,
306 const std::string& model_id,
307 const std::string& usb_interface_num,
308 const std::string& serial)
309 : bus_(bus),
310 vendor_id_(vendor_id),
311 model_id_(model_id),
312 usb_interface_num_(usb_interface_num),
313 serial_(serial) {
314}
315
316MidiManagerAlsa::MidiPort::Id::Id(const Id&) = default;
317
318MidiManagerAlsa::MidiPort::Id::~Id() = default;
319
320bool MidiManagerAlsa::MidiPort::Id::operator==(const Id& rhs) const {
321 return (bus_ == rhs.bus_) && (vendor_id_ == rhs.vendor_id_) &&
322 (model_id_ == rhs.model_id_) &&
323 (usb_interface_num_ == rhs.usb_interface_num_) &&
324 (serial_ == rhs.serial_);
325}
326
327bool MidiManagerAlsa::MidiPort::Id::empty() const {
328 return bus_.empty() && vendor_id_.empty() && model_id_.empty() &&
329 usb_interface_num_.empty() && serial_.empty();
330}
331
agoode99d63292015-04-13 08:39:25 -0700332MidiManagerAlsa::MidiPort::MidiPort(const std::string& path,
agooded87fc0f2015-05-21 08:29:31 -0700333 const Id& id,
agoode99d63292015-04-13 08:39:25 -0700334 int client_id,
335 int port_id,
336 int midi_device,
337 const std::string& client_name,
338 const std::string& port_name,
339 const std::string& manufacturer,
340 const std::string& version,
341 Type type)
342 : id_(id),
343 midi_device_(midi_device),
344 type_(type),
345 path_(path),
346 client_id_(client_id),
347 port_id_(port_id),
348 client_name_(client_name),
349 port_name_(port_name),
350 manufacturer_(manufacturer),
agoode5a1aa112015-06-21 20:51:00 -0700351 version_(version) {
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000352}
353
agoodeb0582872015-05-20 05:22:24 -0700354MidiManagerAlsa::MidiPort::~MidiPort() = default;
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000355
agoode99d63292015-04-13 08:39:25 -0700356// Note: keep synchronized with the MidiPort::Match* methods.
danakj75afea02016-04-25 20:36:04 -0700357std::unique_ptr<base::Value> MidiManagerAlsa::MidiPort::Value() const {
358 std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue);
agoode99d63292015-04-13 08:39:25 -0700359
360 std::string type;
361 switch (type_) {
362 case Type::kInput:
363 type = "input";
364 break;
agoode99d63292015-04-13 08:39:25 -0700365 case Type::kOutput:
366 type = "output";
367 break;
368 }
369 value->SetString("type", type);
370 SetStringIfNonEmpty(value.get(), "path", path_);
agoode99d63292015-04-13 08:39:25 -0700371 SetStringIfNonEmpty(value.get(), "clientName", client_name_);
372 SetStringIfNonEmpty(value.get(), "portName", port_name_);
373 value->SetInteger("clientId", client_id_);
374 value->SetInteger("portId", port_id_);
375 value->SetInteger("midiDevice", midi_device_);
376
agooded87fc0f2015-05-21 08:29:31 -0700377 // Flatten id fields.
378 SetStringIfNonEmpty(value.get(), "bus", id_.bus());
379 SetStringIfNonEmpty(value.get(), "vendorId", id_.vendor_id());
380 SetStringIfNonEmpty(value.get(), "modelId", id_.model_id());
381 SetStringIfNonEmpty(value.get(), "usbInterfaceNum", id_.usb_interface_num());
382 SetStringIfNonEmpty(value.get(), "serial", id_.serial());
383
dchengc2aeece2015-12-27 00:54:00 -0800384 return std::move(value);
agoodebd4be9b2015-03-16 19:17:25 -0700385}
agoode@chromium.org25227512014-06-08 05:12:05 +0000386
agoode99d63292015-04-13 08:39:25 -0700387std::string MidiManagerAlsa::MidiPort::JSONValue() const {
388 std::string json;
389 JSONStringValueSerializer serializer(&json);
390 serializer.Serialize(*Value().get());
391 return json;
agoodef212b2a2015-03-19 12:53:23 -0700392}
393
agoode99d63292015-04-13 08:39:25 -0700394// TODO(agoode): Do not use SHA256 here. Instead store a persistent
395// mapping and just use a UUID or other random string.
396// http://crbug.com/465320
397std::string MidiManagerAlsa::MidiPort::OpaqueKey() const {
Avi Drissman3528fd02015-12-18 20:11:31 -0500398 uint8_t hash[crypto::kSHA256Length];
agoode99d63292015-04-13 08:39:25 -0700399 crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
400 return base::HexEncode(&hash, sizeof(hash));
agoodebd4be9b2015-03-16 19:17:25 -0700401}
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000402
agoode99d63292015-04-13 08:39:25 -0700403bool MidiManagerAlsa::MidiPort::MatchConnected(const MidiPort& query) const {
404 // Matches on:
405 // connected == true
406 // type
407 // path
408 // id
409 // client_id
410 // port_id
411 // midi_device
412 // client_name
413 // port_name
414 return connected() && (type() == query.type()) && (path() == query.path()) &&
415 (id() == query.id()) && (client_id() == query.client_id()) &&
416 (port_id() == query.port_id()) &&
417 (midi_device() == query.midi_device()) &&
418 (client_name() == query.client_name()) &&
419 (port_name() == query.port_name());
agoodebd4be9b2015-03-16 19:17:25 -0700420}
421
agoode99d63292015-04-13 08:39:25 -0700422bool MidiManagerAlsa::MidiPort::MatchCardPass1(const MidiPort& query) const {
423 // Matches on:
424 // connected == false
425 // type
426 // path
427 // id
428 // port_id
429 // midi_device
430 return MatchCardPass2(query) && (path() == query.path());
agoodebd4be9b2015-03-16 19:17:25 -0700431}
432
agoode99d63292015-04-13 08:39:25 -0700433bool MidiManagerAlsa::MidiPort::MatchCardPass2(const MidiPort& query) const {
434 // Matches on:
435 // connected == false
436 // type
437 // id
438 // port_id
439 // midi_device
440 return !connected() && (type() == query.type()) && (id() == query.id()) &&
441 (port_id() == query.port_id()) &&
442 (midi_device() == query.midi_device());
agoodef212b2a2015-03-19 12:53:23 -0700443}
444
agoode99d63292015-04-13 08:39:25 -0700445bool MidiManagerAlsa::MidiPort::MatchNoCardPass1(const MidiPort& query) const {
446 // Matches on:
447 // connected == false
448 // type
449 // path.empty(), for both this and query
450 // id.empty(), for both this and query
451 // client_id
452 // port_id
453 // client_name
454 // port_name
455 // midi_device == -1, for both this and query
456 return MatchNoCardPass2(query) && (client_id() == query.client_id());
agoodef212b2a2015-03-19 12:53:23 -0700457}
458
agoode99d63292015-04-13 08:39:25 -0700459bool MidiManagerAlsa::MidiPort::MatchNoCardPass2(const MidiPort& query) const {
460 // Matches on:
461 // connected == false
462 // type
463 // path.empty(), for both this and query
464 // id.empty(), for both this and query
465 // port_id
466 // client_name
467 // port_name
468 // midi_device == -1, for both this and query
469 return !connected() && (type() == query.type()) && path().empty() &&
470 query.path().empty() && id().empty() && query.id().empty() &&
471 (port_id() == query.port_id()) &&
472 (client_name() == query.client_name()) &&
473 (port_name() == query.port_name()) && (midi_device() == -1) &&
474 (query.midi_device() == -1);
toyoshim@chromium.org4a8657c2014-02-06 11:23:09 +0000475}
476
agoodeb0582872015-05-20 05:22:24 -0700477MidiManagerAlsa::MidiPortStateBase::~MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700478
479MidiManagerAlsa::MidiPortStateBase::iterator
480MidiManagerAlsa::MidiPortStateBase::Find(
481 const MidiManagerAlsa::MidiPort& port) {
482 auto result = FindConnected(port);
483 if (result == end())
484 result = FindDisconnected(port);
485 return result;
486}
487
488MidiManagerAlsa::MidiPortStateBase::iterator
489MidiManagerAlsa::MidiPortStateBase::FindConnected(
490 const MidiManagerAlsa::MidiPort& port) {
491 // Exact match required for connected ports.
danakj75afea02016-04-25 20:36:04 -0700492 auto it = std::find_if(ports_.begin(), ports_.end(),
493 [&port](std::unique_ptr<MidiPort>& p) {
494 return p->MatchConnected(port);
495 });
agoode99d63292015-04-13 08:39:25 -0700496 return it;
497}
498
499MidiManagerAlsa::MidiPortStateBase::iterator
500MidiManagerAlsa::MidiPortStateBase::FindDisconnected(
501 const MidiManagerAlsa::MidiPort& port) {
502 // Always match on:
503 // type
504 // Possible things to match on:
505 // path
506 // id
507 // client_id
508 // port_id
509 // midi_device
510 // client_name
511 // port_name
512
513 if (!port.path().empty()) {
514 // If path is present, then we have a card-based client.
515
516 // Pass 1. Match on path, id, midi_device, port_id.
517 // This is the best possible match for hardware card-based clients.
518 // This will also match the empty id correctly for devices without an id.
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->MatchCardPass1(port);
522 });
agoode99d63292015-04-13 08:39:25 -0700523 if (it != ports_.end())
524 return it;
525
526 if (!port.id().empty()) {
527 // Pass 2. Match on id, midi_device, port_id.
528 // This will give us a high-confidence match when a user moves a device to
529 // another USB/Firewire/Thunderbolt/etc port, but only works if the device
530 // has a hardware id.
danakj75afea02016-04-25 20:36:04 -0700531 it = std::find_if(ports_.begin(), ports_.end(),
532 [&port](std::unique_ptr<MidiPort>& p) {
533 return p->MatchCardPass2(port);
534 });
agoode99d63292015-04-13 08:39:25 -0700535 if (it != ports_.end())
536 return it;
537 }
538 } else {
539 // Else, we have a non-card-based client.
540 // Pass 1. Match on client_id, port_id, client_name, port_name.
541 // This will give us a reasonably good match.
danakj75afea02016-04-25 20:36:04 -0700542 auto it = std::find_if(ports_.begin(), ports_.end(),
543 [&port](std::unique_ptr<MidiPort>& p) {
544 return p->MatchNoCardPass1(port);
545 });
agoode99d63292015-04-13 08:39:25 -0700546 if (it != ports_.end())
547 return it;
548
549 // Pass 2. Match on port_id, client_name, port_name.
550 // This is weaker but similar to pass 2 in the hardware card-based clients
551 // match.
danakj75afea02016-04-25 20:36:04 -0700552 it = std::find_if(ports_.begin(), ports_.end(),
553 [&port](std::unique_ptr<MidiPort>& p) {
554 return p->MatchNoCardPass2(port);
555 });
agoode99d63292015-04-13 08:39:25 -0700556 if (it != ports_.end())
557 return it;
558 }
559
560 // No match.
561 return ports_.end();
562}
563
agoodeb0582872015-05-20 05:22:24 -0700564MidiManagerAlsa::MidiPortStateBase::MidiPortStateBase() = default;
agoode99d63292015-04-13 08:39:25 -0700565
agoodedf1b9ff2015-06-25 18:14:50 -0700566MidiManagerAlsa::MidiPortState::MidiPortState() = default;
agoode99d63292015-04-13 08:39:25 -0700567
danakj75afea02016-04-25 20:36:04 -0700568uint32_t MidiManagerAlsa::MidiPortState::push_back(
569 std::unique_ptr<MidiPort> port) {
agoode99d63292015-04-13 08:39:25 -0700570 // Add the web midi index.
Avi Drissman3528fd02015-12-18 20:11:31 -0500571 uint32_t web_port_index = 0;
agoode99d63292015-04-13 08:39:25 -0700572 switch (port->type()) {
573 case MidiPort::Type::kInput:
574 web_port_index = num_input_ports_++;
575 break;
576 case MidiPort::Type::kOutput:
577 web_port_index = num_output_ports_++;
578 break;
579 }
580 port->set_web_port_index(web_port_index);
dchengc2aeece2015-12-27 00:54:00 -0800581 MidiPortStateBase::push_back(std::move(port));
agoode99d63292015-04-13 08:39:25 -0700582 return web_port_index;
583}
584
agoodedf1b9ff2015-06-25 18:14:50 -0700585MidiManagerAlsa::AlsaSeqState::AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700586
agoodeb0582872015-05-20 05:22:24 -0700587MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() = default;
agoode99d63292015-04-13 08:39:25 -0700588
589void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id,
590 const std::string& client_name,
591 snd_seq_client_type_t type) {
592 ClientExit(client_id);
ricea37e45762016-08-25 02:43:39 -0700593 clients_.insert(
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000594 std::make_pair(client_id, std::make_unique<Client>(client_name, type)));
agoodeb09423b2015-05-11 11:39:57 -0700595 if (IsCardClient(type, client_id))
596 ++card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700597}
598
599bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) {
600 return clients_.find(client_id) != clients_.end();
601}
602
603void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) {
604 auto it = clients_.find(client_id);
605 if (it != clients_.end()) {
agoodeb09423b2015-05-11 11:39:57 -0700606 if (IsCardClient(it->second->type(), client_id))
607 --card_client_count_;
agoode99d63292015-04-13 08:39:25 -0700608 clients_.erase(it);
609 }
610}
611
612void MidiManagerAlsa::AlsaSeqState::PortStart(
613 int client_id,
614 int port_id,
615 const std::string& port_name,
616 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
617 bool midi) {
618 auto it = clients_.find(client_id);
619 if (it != clients_.end())
620 it->second->AddPort(port_id,
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000621 std::make_unique<Port>(port_name, direction, midi));
agoode99d63292015-04-13 08:39:25 -0700622}
623
624void MidiManagerAlsa::AlsaSeqState::PortExit(int client_id, int port_id) {
625 auto it = clients_.find(client_id);
626 if (it != clients_.end())
627 it->second->RemovePort(port_id);
628}
629
630snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType(
631 int client_id) const {
632 auto it = clients_.find(client_id);
633 if (it == clients_.end())
634 return SND_SEQ_USER_CLIENT;
635 return it->second->type();
636}
637
danakj75afea02016-04-25 20:36:04 -0700638std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState>
agoodeb09423b2015-05-11 11:39:57 -0700639MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) {
danakj75afea02016-04-25 20:36:04 -0700640 std::unique_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports(
agoode99d63292015-04-13 08:39:25 -0700641 new TemporaryMidiPortState);
agoodeb09423b2015-05-11 11:39:57 -0700642 auto card_it = alsa_cards.begin();
agoode99d63292015-04-13 08:39:25 -0700643
agoodeb09423b2015-05-11 11:39:57 -0700644 int card_midi_device = -1;
agoode99d63292015-04-13 08:39:25 -0700645 for (const auto& client_pair : clients_) {
646 int client_id = client_pair.first;
vmpstr0205abb2016-06-28 18:50:56 -0700647 auto* client = client_pair.second.get();
agoode99d63292015-04-13 08:39:25 -0700648
649 // Get client metadata.
650 const std::string client_name = client->name();
651 std::string manufacturer;
652 std::string driver;
653 std::string path;
agooded87fc0f2015-05-21 08:29:31 -0700654 MidiPort::Id id;
agoode99d63292015-04-13 08:39:25 -0700655 std::string card_name;
656 std::string card_longname;
657 int midi_device = -1;
658
agoodeb09423b2015-05-11 11:39:57 -0700659 if (IsCardClient(client->type(), client_id)) {
660 auto& card = card_it->second;
661 if (card_midi_device == -1)
662 card_midi_device = 0;
663
664 manufacturer = card->manufacturer();
agooded87fc0f2015-05-21 08:29:31 -0700665 path = card->path();
666 id = MidiPort::Id(card->bus(), card->vendor_id(), card->model_id(),
667 card->usb_interface_num(), card->serial());
668 card_name = card->name();
669 card_longname = card->longname();
agoodeb09423b2015-05-11 11:39:57 -0700670 midi_device = card_midi_device;
671
672 ++card_midi_device;
673 if (card_midi_device >= card->midi_device_count()) {
674 card_midi_device = -1;
675 ++card_it;
676 }
677 }
678
agoode99d63292015-04-13 08:39:25 -0700679 for (const auto& port_pair : *client) {
680 int port_id = port_pair.first;
681 const auto& port = port_pair.second;
682
683 if (port->midi()) {
684 std::string version;
685 if (!driver.empty()) {
686 version = driver + " / ";
687 }
688 version +=
689 base::StringPrintf("ALSA library version %d.%d.%d", SND_LIB_MAJOR,
690 SND_LIB_MINOR, SND_LIB_SUBMINOR);
691 PortDirection direction = port->direction();
692 if (direction == PortDirection::kInput ||
693 direction == PortDirection::kDuplex) {
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000694 midi_ports->push_back(std::make_unique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700695 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700696 port->name(), manufacturer, version, MidiPort::Type::kInput));
agoode99d63292015-04-13 08:39:25 -0700697 }
698 if (direction == PortDirection::kOutput ||
699 direction == PortDirection::kDuplex) {
Gyuyoung Kim34e191a2018-01-10 09:48:42 +0000700 midi_ports->push_back(std::make_unique<MidiPort>(
agoode99d63292015-04-13 08:39:25 -0700701 path, id, client_id, port_id, midi_device, client->name(),
ricea37e45762016-08-25 02:43:39 -0700702 port->name(), manufacturer, version, MidiPort::Type::kOutput));
agoode99d63292015-04-13 08:39:25 -0700703 }
704 }
705 }
706 }
707
dchengc2aeece2015-12-27 00:54:00 -0800708 return midi_ports;
agoode99d63292015-04-13 08:39:25 -0700709}
710
711MidiManagerAlsa::AlsaSeqState::Port::Port(
712 const std::string& name,
713 MidiManagerAlsa::AlsaSeqState::PortDirection direction,
714 bool midi)
715 : name_(name), direction_(direction), midi_(midi) {
716}
717
agoodeb0582872015-05-20 05:22:24 -0700718MidiManagerAlsa::AlsaSeqState::Port::~Port() = default;
agoode99d63292015-04-13 08:39:25 -0700719
720MidiManagerAlsa::AlsaSeqState::Client::Client(const std::string& name,
721 snd_seq_client_type_t type)
mgiucad9af8452015-06-25 02:11:57 -0700722 : name_(name), type_(type) {
agoode99d63292015-04-13 08:39:25 -0700723}
724
agoodeb0582872015-05-20 05:22:24 -0700725MidiManagerAlsa::AlsaSeqState::Client::~Client() = default;
agoode99d63292015-04-13 08:39:25 -0700726
danakj75afea02016-04-25 20:36:04 -0700727void MidiManagerAlsa::AlsaSeqState::Client::AddPort(
728 int addr,
729 std::unique_ptr<Port> port) {
limasdfe59d0392015-11-19 20:28:57 -0800730 ports_[addr] = std::move(port);
agoode99d63292015-04-13 08:39:25 -0700731}
732
733void MidiManagerAlsa::AlsaSeqState::Client::RemovePort(int addr) {
mgiucad9af8452015-06-25 02:11:57 -0700734 ports_.erase(addr);
agoode99d63292015-04-13 08:39:25 -0700735}
736
737MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
738MidiManagerAlsa::AlsaSeqState::Client::begin() const {
739 return ports_.begin();
740}
741
742MidiManagerAlsa::AlsaSeqState::Client::PortMap::const_iterator
743MidiManagerAlsa::AlsaSeqState::Client::end() const {
744 return ports_.end();
agoodeaf6e9f52015-03-24 10:23:49 -0700745}
746
agoodeb09423b2015-05-11 11:39:57 -0700747MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev,
agooded87fc0f2015-05-21 08:29:31 -0700748 const std::string& name,
749 const std::string& longname,
750 const std::string& driver,
agoodeb09423b2015-05-11 11:39:57 -0700751 int midi_device_count)
agooded87fc0f2015-05-21 08:29:31 -0700752 : name_(name),
753 longname_(longname),
754 driver_(driver),
755 path_(device::UdevDeviceGetPropertyValue(dev, kUdevIdPath)),
756 bus_(device::UdevDeviceGetPropertyValue(dev, kUdevIdBus)),
757 vendor_id_(
758 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor)),
759 model_id_(
760 UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel)),
761 usb_interface_num_(
762 device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum)),
763 serial_(UdevDeviceGetPropertyOrSysattr(dev,
764 kUdevIdSerialShort,
765 kSysattrGuid)),
766 midi_device_count_(midi_device_count),
767 manufacturer_(ExtractManufacturerString(
768 GetVendor(dev),
769 vendor_id_,
770 device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase),
771 name,
772 longname)) {
agoodeb09423b2015-05-11 11:39:57 -0700773}
774
agoodeb0582872015-05-20 05:22:24 -0700775MidiManagerAlsa::AlsaCard::~AlsaCard() = default;
agoodeb09423b2015-05-11 11:39:57 -0700776
agoode55a8b522015-03-08 12:40:17 -0700777// static
agoodeb09423b2015-05-11 11:39:57 -0700778std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString(
agoode5e4e9cd2015-03-09 12:34:24 -0700779 const std::string& udev_id_vendor,
agoode55a8b522015-03-08 12:40:17 -0700780 const std::string& udev_id_vendor_id,
781 const std::string& udev_id_vendor_from_database,
782 const std::string& alsa_name,
783 const std::string& alsa_longname) {
784 // Let's try to determine the manufacturer. Here is the ordered preference
785 // in extraction:
agoodef212b2a2015-03-19 12:53:23 -0700786 // 1. Vendor name from the hardware device string, from udev properties
787 // or sysattrs.
agoode5e4e9cd2015-03-09 12:34:24 -0700788 // 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
agoode55a8b522015-03-08 12:40:17 -0700789 // 3. Heuristic from ALSA.
790
agoodee83758c2015-03-23 22:07:54 -0700791 // Is the vendor string present and not just the vendor hex id?
792 if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
agoode55a8b522015-03-08 12:40:17 -0700793 return udev_id_vendor;
794 }
795
796 // Is there a vendor string in the hardware database?
797 if (!udev_id_vendor_from_database.empty()) {
798 return udev_id_vendor_from_database;
799 }
800
801 // Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
802 // We assume that card longname is in the format of
803 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
804 // a manufacturer name here.
805 size_t at_index = alsa_longname.rfind(" at ");
agoodef212b2a2015-03-19 12:53:23 -0700806 if (at_index && at_index != std::string::npos) {
agoode55a8b522015-03-08 12:40:17 -0700807 size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
agoodef212b2a2015-03-19 12:53:23 -0700808 if (name_index && name_index != std::string::npos)
agoode55a8b522015-03-08 12:40:17 -0700809 return alsa_longname.substr(0, name_index - 1);
810 }
811
812 // Failure.
813 return "";
814}
815
toyoshimefcee5e2017-06-14 07:14:39 -0700816void MidiManagerAlsa::SendMidiData(MidiManagerClient* client,
toyoshimf4d61522017-02-10 02:03:32 -0800817 uint32_t port_index,
Avi Drissman3528fd02015-12-18 20:11:31 -0500818 const std::vector<uint8_t>& data) {
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000819 ScopedSndMidiEventPtr encoder = CreateScopedSndMidiEventPtr(kSendBufferSize);
agoode5a1aa112015-06-21 20:51:00 -0700820 for (const auto datum : data) {
agoodebd4be9b2015-03-16 19:17:25 -0700821 snd_seq_event_t event;
Takashi Toyoshima0ae49702017-09-01 04:59:51 +0000822 int result = snd_midi_event_encode_byte(encoder.get(), datum, &event);
agoodebd4be9b2015-03-16 19:17:25 -0700823 if (result == 1) {
824 // Full event, send it.
Peter Kasting44e4cb92021-11-02 17:52:49 +0000825 base::AutoLock ports_lock(out_ports_lock_);
agoode99d63292015-04-13 08:39:25 -0700826 auto it = out_ports_.find(port_index);
827 if (it != out_ports_.end()) {
Peter Kasting44e4cb92021-11-02 17:52:49 +0000828 base::AutoLock client_lock(out_client_lock_);
Takashi Toyoshima9687b7c2017-07-19 09:54:14 +0000829 if (!out_client_)
830 return;
agoode99d63292015-04-13 08:39:25 -0700831 snd_seq_ev_set_source(&event, it->second);
832 snd_seq_ev_set_subs(&event);
833 snd_seq_ev_set_direct(&event);
agoode5a1aa112015-06-21 20:51:00 -0700834 snd_seq_event_output_direct(out_client_.get(), &event);
agoode99d63292015-04-13 08:39:25 -0700835 }
agoodebd4be9b2015-03-16 19:17:25 -0700836 }
837 }
toyoshimf4d61522017-02-10 02:03:32 -0800838
839 // Acknowledge send.
840 AccumulateMidiBytesSent(client, data.size());
agoodebd4be9b2015-03-16 19:17:25 -0700841}
842
toyoshimefcee5e2017-06-14 07:14:39 -0700843void MidiManagerAlsa::EventLoop() {
agoode975043d2015-05-11 00:46:17 -0700844 bool loop_again = true;
agoodebd4be9b2015-03-16 19:17:25 -0700845
agoode975043d2015-05-11 00:46:17 -0700846 struct pollfd pfd[2];
agoode5a1aa112015-06-21 20:51:00 -0700847 snd_seq_poll_descriptors(in_client_.get(), &pfd[0], 1, POLLIN);
agoode975043d2015-05-11 00:46:17 -0700848 pfd[1].fd = device::udev_monitor_get_fd(udev_monitor_.get());
849 pfd[1].events = POLLIN;
agoodebd4be9b2015-03-16 19:17:25 -0700850
Avi Drissman2c637192018-12-25 20:26:39 +0000851 int err = HANDLE_EINTR(poll(pfd, base::size(pfd), -1));
agoode975043d2015-05-11 00:46:17 -0700852 if (err < 0) {
brettwf7f870f2015-06-09 11:05:24 -0700853 VLOG(1) << "poll fails: " << base::safe_strerror(errno);
agoode975043d2015-05-11 00:46:17 -0700854 loop_again = false;
agoodeaf6e9f52015-03-24 10:23:49 -0700855 } else {
agoode975043d2015-05-11 00:46:17 -0700856 if (pfd[0].revents & POLLIN) {
857 // Read available incoming MIDI data.
858 int remaining;
tzik925e2c62018-02-02 07:39:45 +0000859 base::TimeTicks timestamp = base::TimeTicks::Now();
agoode975043d2015-05-11 00:46:17 -0700860 do {
861 snd_seq_event_t* event;
agoode5a1aa112015-06-21 20:51:00 -0700862 err = snd_seq_event_input(in_client_.get(), &event);
863 remaining = snd_seq_event_input_pending(in_client_.get(), 0);
agoode975043d2015-05-11 00:46:17 -0700864
865 if (err == -ENOSPC) {
866 // Handle out of space error.
867 VLOG(1) << "snd_seq_event_input detected buffer overrun";
868 // We've lost events: check another way to see if we need to shut
869 // down.
agoode975043d2015-05-11 00:46:17 -0700870 } else if (err == -EAGAIN) {
871 // We've read all the data.
872 } else if (err < 0) {
873 // Handle other errors.
874 VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
875 // TODO(agoode): Use RecordAction() or similar to log this.
876 loop_again = false;
877 } else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
878 event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
879 // Handle announce events.
880 switch (event->type) {
881 case SND_SEQ_EVENT_PORT_START:
882 // Don't use SND_SEQ_EVENT_CLIENT_START because the
883 // client name may not be set by the time we query
884 // it. It should be set by the time ports are made.
885 ProcessClientStartEvent(event->data.addr.client);
886 ProcessPortStartEvent(event->data.addr);
887 break;
888 case SND_SEQ_EVENT_CLIENT_EXIT:
889 // Check for disconnection of our "out" client. This means "shut
890 // down".
891 if (event->data.addr.client == out_client_id_) {
892 loop_again = false;
893 remaining = 0;
894 } else
895 ProcessClientExitEvent(event->data.addr);
896 break;
897 case SND_SEQ_EVENT_PORT_EXIT:
898 ProcessPortExitEvent(event->data.addr);
899 break;
900 }
901 } else {
902 // Normal operation.
903 ProcessSingleEvent(event, timestamp);
904 }
905 } while (remaining > 0);
906 }
907 if (pfd[1].revents & POLLIN) {
908 device::ScopedUdevDevicePtr dev(
909 device::udev_monitor_receive_device(udev_monitor_.get()));
910 if (dev.get())
911 ProcessUdevEvent(dev.get());
912 else
913 VLOG(1) << "udev_monitor_receive_device fails";
914 }
agoodebd4be9b2015-03-16 19:17:25 -0700915 }
916
agoodebd4be9b2015-03-16 19:17:25 -0700917 // Do again.
toyoshimf4d61522017-02-10 02:03:32 -0800918 if (loop_again) {
toyoshimefcee5e2017-06-14 07:14:39 -0700919 service()->task_service()->PostBoundTask(
920 kEventTaskRunner,
921 base::BindOnce(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
toyoshimf4d61522017-02-10 02:03:32 -0800922 }
agoodebd4be9b2015-03-16 19:17:25 -0700923}
924
925void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
tzik925e2c62018-02-02 07:39:45 +0000926 base::TimeTicks timestamp) {
agoode99d63292015-04-13 08:39:25 -0700927 auto source_it =
928 source_map_.find(AddrToInt(event->source.client, event->source.port));
agoodebd4be9b2015-03-16 19:17:25 -0700929 if (source_it != source_map_.end()) {
Avi Drissman3528fd02015-12-18 20:11:31 -0500930 uint32_t source = source_it->second;
agoodebd4be9b2015-03-16 19:17:25 -0700931 if (event->type == SND_SEQ_EVENT_SYSEX) {
932 // Special! Variable-length sysex.
Avi Drissman3528fd02015-12-18 20:11:31 -0500933 ReceiveMidiData(source, static_cast<const uint8_t*>(event->data.ext.ptr),
agoodebd4be9b2015-03-16 19:17:25 -0700934 event->data.ext.len, timestamp);
935 } else {
936 // Otherwise, decode this and send that on.
937 unsigned char buf[12];
agoode5a1aa112015-06-21 20:51:00 -0700938 long count =
939 snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
agoodebd4be9b2015-03-16 19:17:25 -0700940 if (count <= 0) {
941 if (count != -ENOENT) {
942 // ENOENT means that it's not a MIDI message, which is not an
943 // error, but other negative values are errors for us.
944 VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
945 // TODO(agoode): Record this failure.
946 }
947 } else {
948 ReceiveMidiData(source, buf, count, timestamp);
949 }
950 }
951 }
952}
953
agoode99d63292015-04-13 08:39:25 -0700954void MidiManagerAlsa::ProcessClientStartEvent(int client_id) {
955 // Ignore if client is already started.
956 if (alsa_seq_state_.ClientStarted(client_id))
957 return;
958
959 snd_seq_client_info_t* client_info;
960 snd_seq_client_info_alloca(&client_info);
agoode5a1aa112015-06-21 20:51:00 -0700961 int err =
962 snd_seq_get_any_client_info(in_client_.get(), client_id, client_info);
agoode99d63292015-04-13 08:39:25 -0700963 if (err != 0)
964 return;
965
966 // Skip our own clients.
967 if ((client_id == in_client_id_) || (client_id == out_client_id_))
968 return;
969
970 // Update our view of ALSA seq state.
971 alsa_seq_state_.ClientStart(client_id,
972 snd_seq_client_info_get_name(client_info),
973 snd_seq_client_info_get_type(client_info));
974
975 // Generate Web MIDI events.
976 UpdatePortStateAndGenerateEvents();
977}
978
979void MidiManagerAlsa::ProcessPortStartEvent(const snd_seq_addr_t& addr) {
980 snd_seq_port_info_t* port_info;
981 snd_seq_port_info_alloca(&port_info);
agoode5a1aa112015-06-21 20:51:00 -0700982 int err = snd_seq_get_any_port_info(in_client_.get(), addr.client, addr.port,
983 port_info);
agoode99d63292015-04-13 08:39:25 -0700984 if (err != 0)
985 return;
986
987 unsigned int caps = snd_seq_port_info_get_capability(port_info);
988 bool input = (caps & kRequiredInputPortCaps) == kRequiredInputPortCaps;
989 bool output = (caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps;
990 AlsaSeqState::PortDirection direction;
991 if (input && output)
992 direction = AlsaSeqState::PortDirection::kDuplex;
993 else if (input)
994 direction = AlsaSeqState::PortDirection::kInput;
995 else if (output)
996 direction = AlsaSeqState::PortDirection::kOutput;
997 else
998 return;
999
1000 // Update our view of ALSA seq state.
1001 alsa_seq_state_.PortStart(
1002 addr.client, addr.port, snd_seq_port_info_get_name(port_info), direction,
1003 snd_seq_port_info_get_type(port_info) & SND_SEQ_PORT_TYPE_MIDI_GENERIC);
1004 // Generate Web MIDI events.
1005 UpdatePortStateAndGenerateEvents();
1006}
1007
1008void MidiManagerAlsa::ProcessClientExitEvent(const snd_seq_addr_t& addr) {
1009 // Update our view of ALSA seq state.
1010 alsa_seq_state_.ClientExit(addr.client);
1011 // Generate Web MIDI events.
1012 UpdatePortStateAndGenerateEvents();
1013}
1014
1015void MidiManagerAlsa::ProcessPortExitEvent(const snd_seq_addr_t& addr) {
1016 // Update our view of ALSA seq state.
1017 alsa_seq_state_.PortExit(addr.client, addr.port);
1018 // Generate Web MIDI events.
1019 UpdatePortStateAndGenerateEvents();
1020}
1021
agoode975043d2015-05-11 00:46:17 -07001022void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) {
1023 // Only card devices have this property set, and only when they are
1024 // fully initialized.
1025 if (!device::udev_device_get_property_value(dev,
1026 kUdevPropertySoundInitialized))
1027 return;
1028
1029 // Get the action. If no action, then we are doing first time enumeration
1030 // and the device is treated as new.
1031 const char* action = device::udev_device_get_action(dev);
1032 if (!action)
1033 action = kUdevActionChange;
1034
1035 if (strcmp(action, kUdevActionChange) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001036 AddCard(dev);
1037 // Generate Web MIDI events.
1038 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001039 } else if (strcmp(action, kUdevActionRemove) == 0) {
agoodeb09423b2015-05-11 11:39:57 -07001040 RemoveCard(GetCardNumber(dev));
1041 // Generate Web MIDI events.
1042 UpdatePortStateAndGenerateEvents();
agoode975043d2015-05-11 00:46:17 -07001043 }
1044}
1045
agoodeb09423b2015-05-11 11:39:57 -07001046void MidiManagerAlsa::AddCard(udev_device* dev) {
1047 int number = GetCardNumber(dev);
1048 if (number == -1)
1049 return;
1050
1051 RemoveCard(number);
1052
1053 snd_ctl_card_info_t* card;
1054 snd_hwdep_info_t* hwdep;
1055 snd_ctl_card_info_alloca(&card);
1056 snd_hwdep_info_alloca(&hwdep);
1057 const std::string id = base::StringPrintf("hw:CARD=%i", number);
1058 snd_ctl_t* handle;
1059 int err = snd_ctl_open(&handle, id.c_str(), 0);
1060 if (err != 0) {
1061 VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err);
1062 return;
1063 }
1064 err = snd_ctl_card_info(handle, card);
1065 if (err != 0) {
1066 VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
1067 snd_ctl_close(handle);
1068 return;
1069 }
1070 std::string name = snd_ctl_card_info_get_name(card);
1071 std::string longname = snd_ctl_card_info_get_longname(card);
1072 std::string driver = snd_ctl_card_info_get_driver(card);
1073
1074 // Count rawmidi devices (not subdevices).
1075 int midi_count = 0;
1076 for (int device = -1;
1077 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;)
1078 ++midi_count;
1079
1080 // Count any hwdep synths that become MIDI devices outside of rawmidi.
1081 //
1082 // Explanation:
1083 // Any kernel driver can create an ALSA client (visible to us).
1084 // With modern hardware, only rawmidi devices do this. Kernel
1085 // drivers create rawmidi devices and the rawmidi subsystem makes
1086 // the seq clients. But the OPL3 driver is special, it does not
1087 // make a rawmidi device but a seq client directly. (This is the
1088 // only one to worry about in the kernel code, as of 2015-03-23.)
1089 //
1090 // OPL3 is very old (but still possible to get in new
1091 // hardware). It is unlikely that new drivers would not use
1092 // rawmidi and defeat our heuristic.
1093 //
1094 // Longer term, support should be added in the kernel to expose a
1095 // direct link from card->client (or client->card) so that all
1096 // these heuristics will be obsolete. Once that is there, we can
1097 // assume our old heuristics will work on old kernels and the new
1098 // robust code will be used on new. Then we will not need to worry
1099 // about changes to kernel internals breaking our code.
1100 // See the TODO above at kMinimumClientIdForCards.
1101 for (int device = -1;
1102 !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) {
1103 err = snd_ctl_hwdep_info(handle, hwdep);
1104 if (err != 0) {
1105 VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err);
1106 continue;
1107 }
1108 snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep);
1109 if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 ||
1110 iface == SND_HWDEP_IFACE_OPL4)
1111 ++midi_count;
1112 }
1113 snd_ctl_close(handle);
1114
mgiucad9af8452015-06-25 02:11:57 -07001115 if (midi_count > 0) {
Peter Kasting44e4cb92021-11-02 17:52:49 +00001116 auto alsa_card =
1117 std::make_unique<AlsaCard>(dev, name, longname, driver, midi_count);
1118 alsa_cards_.insert(std::make_pair(number, std::move(alsa_card)));
mgiucad9af8452015-06-25 02:11:57 -07001119 alsa_card_midi_count_ += midi_count;
1120 }
agoodeb09423b2015-05-11 11:39:57 -07001121}
1122
1123void MidiManagerAlsa::RemoveCard(int number) {
1124 auto it = alsa_cards_.find(number);
1125 if (it == alsa_cards_.end())
1126 return;
1127
1128 alsa_card_midi_count_ -= it->second->midi_device_count();
agoodeb09423b2015-05-11 11:39:57 -07001129 alsa_cards_.erase(it);
1130}
1131
agoode99d63292015-04-13 08:39:25 -07001132void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() {
agoodeb09423b2015-05-11 11:39:57 -07001133 // Verify that our information from ALSA and udev are in sync. If
1134 // not, we cannot generate events right now.
1135 if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count())
1136 return;
1137
agoode99d63292015-04-13 08:39:25 -07001138 // Generate new port state.
agoodeb09423b2015-05-11 11:39:57 -07001139 auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_);
agoode99d63292015-04-13 08:39:25 -07001140
1141 // Disconnect any connected old ports that are now missing.
agoode5ebc4932015-12-01 08:25:12 -08001142 for (auto& old_port : port_state_) {
agoode99d63292015-04-13 08:39:25 -07001143 if (old_port->connected() &&
1144 (new_port_state->FindConnected(*old_port) == new_port_state->end())) {
1145 old_port->set_connected(false);
Avi Drissman3528fd02015-12-18 20:11:31 -05001146 uint32_t web_port_index = old_port->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001147 switch (old_port->type()) {
1148 case MidiPort::Type::kInput:
1149 source_map_.erase(
1150 AddrToInt(old_port->client_id(), old_port->port_id()));
toyoshimec2570a2016-10-21 02:15:27 -07001151 SetInputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001152 break;
1153 case MidiPort::Type::kOutput:
1154 DeleteAlsaOutputPort(web_port_index);
toyoshimec2570a2016-10-21 02:15:27 -07001155 SetOutputPortState(web_port_index, PortState::DISCONNECTED);
agoode99d63292015-04-13 08:39:25 -07001156 break;
1157 }
1158 }
1159 }
1160
1161 // Reconnect or add new ports.
1162 auto it = new_port_state->begin();
1163 while (it != new_port_state->end()) {
agoode5ebc4932015-12-01 08:25:12 -08001164 auto& new_port = *it;
agoode99d63292015-04-13 08:39:25 -07001165 auto old_port = port_state_.Find(*new_port);
1166 if (old_port == port_state_.end()) {
1167 // Add new port.
agoode5ebc4932015-12-01 08:25:12 -08001168 const auto& opaque_key = new_port->OpaqueKey();
1169 const auto& manufacturer = new_port->manufacturer();
1170 const auto& port_name = new_port->port_name();
1171 const auto& version = new_port->version();
1172 const auto& type = new_port->type();
1173 const auto& client_id = new_port->client_id();
1174 const auto& port_id = new_port->port_id();
1175
dchengc2aeece2015-12-27 00:54:00 -08001176 uint32_t web_port_index = port_state_.push_back(std::move(new_port));
agoode5ebc4932015-12-01 08:25:12 -08001177 it = new_port_state->erase(it);
1178
Adithya Srinivasan33252732018-10-17 15:59:40 +00001179 mojom::PortInfo info(opaque_key, manufacturer, port_name, version,
1180 PortState::OPENED);
agoode5ebc4932015-12-01 08:25:12 -08001181 switch (type) {
agoode99d63292015-04-13 08:39:25 -07001182 case MidiPort::Type::kInput:
agoode5ebc4932015-12-01 08:25:12 -08001183 if (Subscribe(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001184 AddInputPort(info);
1185 break;
agoode99d63292015-04-13 08:39:25 -07001186 case MidiPort::Type::kOutput:
agoode5ebc4932015-12-01 08:25:12 -08001187 if (CreateAlsaOutputPort(web_port_index, client_id, port_id))
agoode99d63292015-04-13 08:39:25 -07001188 AddOutputPort(info);
1189 break;
1190 }
agoode99d63292015-04-13 08:39:25 -07001191 } else if (!(*old_port)->connected()) {
1192 // Reconnect.
Avi Drissman3528fd02015-12-18 20:11:31 -05001193 uint32_t web_port_index = (*old_port)->web_port_index();
agoode99d63292015-04-13 08:39:25 -07001194 (*old_port)->Update(new_port->path(), new_port->client_id(),
1195 new_port->port_id(), new_port->client_name(),
1196 new_port->port_name(), new_port->manufacturer(),
1197 new_port->version());
1198 switch ((*old_port)->type()) {
1199 case MidiPort::Type::kInput:
1200 if (Subscribe(web_port_index, (*old_port)->client_id(),
1201 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001202 SetInputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001203 break;
agoode99d63292015-04-13 08:39:25 -07001204 case MidiPort::Type::kOutput:
1205 if (CreateAlsaOutputPort(web_port_index, (*old_port)->client_id(),
1206 (*old_port)->port_id()))
toyoshimec2570a2016-10-21 02:15:27 -07001207 SetOutputPortState(web_port_index, PortState::OPENED);
agoode99d63292015-04-13 08:39:25 -07001208 break;
1209 }
1210 (*old_port)->set_connected(true);
1211 ++it;
1212 } else {
1213 ++it;
1214 }
1215 }
1216}
1217
agoode975043d2015-05-11 00:46:17 -07001218// TODO(agoode): return false on failure.
agoode99d63292015-04-13 08:39:25 -07001219void MidiManagerAlsa::EnumerateAlsaPorts() {
1220 snd_seq_client_info_t* client_info;
1221 snd_seq_client_info_alloca(&client_info);
1222 snd_seq_port_info_t* port_info;
1223 snd_seq_port_info_alloca(&port_info);
1224
1225 // Enumerate clients.
1226 snd_seq_client_info_set_client(client_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001227 while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
agoode99d63292015-04-13 08:39:25 -07001228 int client_id = snd_seq_client_info_get_client(client_info);
1229 ProcessClientStartEvent(client_id);
1230
1231 // Enumerate ports.
1232 snd_seq_port_info_set_client(port_info, client_id);
1233 snd_seq_port_info_set_port(port_info, -1);
agoode5a1aa112015-06-21 20:51:00 -07001234 while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
agoode99d63292015-04-13 08:39:25 -07001235 const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
1236 ProcessPortStartEvent(*addr);
1237 }
1238 }
1239}
1240
agoode975043d2015-05-11 00:46:17 -07001241bool MidiManagerAlsa::EnumerateUdevCards() {
1242 int err;
1243
1244 device::ScopedUdevEnumeratePtr enumerate(
1245 device::udev_enumerate_new(udev_.get()));
1246 if (!enumerate.get()) {
1247 VLOG(1) << "udev_enumerate_new fails";
1248 return false;
1249 }
1250
1251 err = device::udev_enumerate_add_match_subsystem(enumerate.get(),
1252 kUdevSubsystemSound);
1253 if (err) {
1254 VLOG(1) << "udev_enumerate_add_match_subsystem fails: "
brettwf7f870f2015-06-09 11:05:24 -07001255 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001256 return false;
1257 }
1258
1259 err = device::udev_enumerate_scan_devices(enumerate.get());
1260 if (err) {
brettwf7f870f2015-06-09 11:05:24 -07001261 VLOG(1) << "udev_enumerate_scan_devices fails: "
1262 << base::safe_strerror(-err);
agoode975043d2015-05-11 00:46:17 -07001263 return false;
1264 }
1265
1266 udev_list_entry* list_entry;
1267 auto* devices = device::udev_enumerate_get_list_entry(enumerate.get());
1268 udev_list_entry_foreach(list_entry, devices) {
1269 const char* path = device::udev_list_entry_get_name(list_entry);
1270 device::ScopedUdevDevicePtr dev(
1271 device::udev_device_new_from_syspath(udev_.get(), path));
1272 if (dev.get())
1273 ProcessUdevEvent(dev.get());
1274 }
1275
1276 return true;
1277}
1278
Avi Drissman3528fd02015-12-18 20:11:31 -05001279bool MidiManagerAlsa::CreateAlsaOutputPort(uint32_t port_index,
agoode99d63292015-04-13 08:39:25 -07001280 int client_id,
1281 int port_id) {
1282 // Create the port.
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +00001283 int out_port;
1284 {
1285 base::AutoLock lock(out_client_lock_);
1286 out_port = snd_seq_create_simple_port(
1287 out_client_.get(), NULL, kCreateOutputPortCaps, kCreatePortType);
1288
1289 if (out_port < 0) {
1290 VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(out_port);
1291 return false;
1292 }
1293
1294 // Activate port subscription.
1295 snd_seq_port_subscribe_t* subs;
1296 snd_seq_port_subscribe_alloca(&subs);
1297 snd_seq_addr_t sender;
1298 sender.client = out_client_id_;
1299 sender.port = out_port;
1300 snd_seq_port_subscribe_set_sender(subs, &sender);
1301 snd_seq_addr_t dest;
1302 dest.client = client_id;
1303 dest.port = port_id;
1304 snd_seq_port_subscribe_set_dest(subs, &dest);
1305 int err = snd_seq_subscribe_port(out_client_.get(), subs);
1306 if (err != 0) {
1307 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1308 snd_seq_delete_simple_port(out_client_.get(), out_port);
1309 return false;
1310 }
agoode99d63292015-04-13 08:39:25 -07001311 }
1312
1313 // Update our map.
1314 base::AutoLock lock(out_ports_lock_);
1315 out_ports_[port_index] = out_port;
1316 return true;
1317}
1318
Avi Drissman3528fd02015-12-18 20:11:31 -05001319void MidiManagerAlsa::DeleteAlsaOutputPort(uint32_t port_index) {
Takashi Toyoshimae8240ab2018-10-03 09:30:11 +00001320 int alsa_port;
1321 {
1322 base::AutoLock lock(out_ports_lock_);
1323 auto it = out_ports_.find(port_index);
1324 if (it == out_ports_.end())
1325 return;
1326 alsa_port = it->second;
1327 out_ports_.erase(it);
1328 }
1329 {
1330 base::AutoLock lock(out_client_lock_);
1331 snd_seq_delete_simple_port(out_client_.get(), alsa_port);
1332 }
agoode99d63292015-04-13 08:39:25 -07001333}
1334
Avi Drissman3528fd02015-12-18 20:11:31 -05001335bool MidiManagerAlsa::Subscribe(uint32_t port_index,
1336 int client_id,
1337 int port_id) {
agoode99d63292015-04-13 08:39:25 -07001338 // Activate port subscription.
1339 snd_seq_port_subscribe_t* subs;
1340 snd_seq_port_subscribe_alloca(&subs);
1341 snd_seq_addr_t sender;
1342 sender.client = client_id;
1343 sender.port = port_id;
1344 snd_seq_port_subscribe_set_sender(subs, &sender);
1345 snd_seq_addr_t dest;
1346 dest.client = in_client_id_;
1347 dest.port = in_port_id_;
1348 snd_seq_port_subscribe_set_dest(subs, &dest);
agoode5a1aa112015-06-21 20:51:00 -07001349 int err = snd_seq_subscribe_port(in_client_.get(), subs);
agoode99d63292015-04-13 08:39:25 -07001350 if (err != 0) {
1351 VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
1352 return false;
1353 }
1354
1355 // Update our map.
1356 source_map_[AddrToInt(client_id, port_id)] = port_index;
1357 return true;
1358}
1359
Takashi Toyoshima0ae49702017-09-01 04:59:51 +00001360MidiManagerAlsa::ScopedSndMidiEventPtr
1361MidiManagerAlsa::CreateScopedSndMidiEventPtr(size_t size) {
1362 snd_midi_event_t* coder;
1363 snd_midi_event_new(size, &coder);
1364 return ScopedSndMidiEventPtr(coder);
1365}
1366
toyoshimf4d61522017-02-10 02:03:32 -08001367MidiManager* MidiManager::Create(MidiService* service) {
1368 return new MidiManagerAlsa(service);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001369}
1370
toyoshime147c5e2015-05-07 21:58:31 -07001371} // namespace midi