blob: ed7e063596a48d27546e0975e95e31a5b2608d4f [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "midis/device.h"
#include <alsa/asoundlib.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <base/bind.h>
#include <base/callback.h>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include "midis/constants.h"
#include "midis/file_handler.h"
#include "midis/subdevice_client_fd_holder.h"
namespace {
const unsigned int kInputPortCaps =
SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
const unsigned int kOutputPortCaps =
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
} // namespace
namespace midis {
Device::Device(const std::string& name,
const std::string& manufacturer,
uint32_t card,
uint32_t device,
uint32_t num_subdevices,
uint32_t flags,
InPort::SubscribeCallback in_sub_cb,
OutPort::SubscribeCallback out_sub_cb,
InPort::DeletionCallback in_del_cb,
OutPort::DeletionCallback out_del_cb,
OutPort::SendMidiDataCallback send_data_cb,
const std::map<uint32_t, unsigned int>& port_caps)
: name_(name),
manufacturer_(manufacturer),
card_(card),
device_(device),
num_subdevices_(num_subdevices),
flags_(flags),
in_sub_cb_(in_sub_cb),
out_sub_cb_(out_sub_cb),
in_del_cb_(in_del_cb),
out_del_cb_(out_del_cb),
send_data_cb_(send_data_cb),
port_caps_(port_caps),
weak_factory_(this) {
LOG(INFO) << "Device created: " << name_;
}
Device::~Device() {
StopMonitoring();
}
void Device::StopMonitoring() {
// Cancel all the clients FDs who were listening / writing to this device.
client_fds_.clear();
in_ports_.clear();
out_ports_.clear();
}
bool Device::StartMonitoring() {
for (auto& it : port_caps_) {
if (it.second & kInputPortCaps) {
auto in_port = InPort::Create(device_, it.first, in_sub_cb_, in_del_cb_);
if (in_port) {
in_ports_.emplace(it.first, std::move(in_port));
LOG(INFO) << "Input Port created for port:" << it.first;
}
}
if (it.second & kOutputPortCaps) {
auto out_port = OutPort::Create(
device_, it.first, out_sub_cb_, out_del_cb_, send_data_cb_);
if (out_port) {
out_ports_.emplace(it.first, std::move(out_port));
LOG(INFO) << "Outpot Port created for port:" << it.first;
}
}
}
return true;
}
void Device::HandleReceiveData(const char* buffer,
uint32_t subdevice,
size_t buf_len) const {
// NOTE: We don't check whether this subdevice can actually receive data
// because the data is coming in from the a MIDI H/W port, and so if data is
// being generated here, it must be from a valid source.
auto list_it = client_fds_.find(subdevice);
if (list_it != client_fds_.end()) {
for (const auto& id_fd_entry : list_it->second) {
id_fd_entry->WriteDeviceDataToClient(buffer, buf_len);
}
}
}
void Device::RemoveClientFromDevice(uint32_t client_id) {
LOG(INFO) << "Removing the client: " << client_id
<< " from all device watchers for device: " << name_;
for (auto list_it = client_fds_.begin(); list_it != client_fds_.end();) {
// First remove all clients in a subdevice.
for (auto it = list_it->second.begin(); it != list_it->second.end();) {
if (it->get()->GetClientId() == client_id) {
LOG(INFO) << "Found client: " << client_id << " in list. deleting";
it = list_it->second.erase(it);
} else {
++it;
}
}
// If no clients remain, remove the subdevice entry from the map.
if (list_it->second.empty()) {
client_fds_.erase(list_it++);
} else {
++list_it;
}
}
if (client_fds_.empty()) {
StopMonitoring();
}
}
void Device::WriteClientDataToDevice(uint32_t subdevice_id,
const uint8_t* buffer,
size_t buf_len) {
// Check whether this port supports output, otherwise just drop the data, and
// print a warning.
auto it = out_ports_.find(subdevice_id);
if (it == out_ports_.end()) {
LOG(WARNING)
<< "Data received on port: " << subdevice_id
<< " which doesn't support writing to MIDI device; dropping data";
}
it->second->SendData(buffer, buf_len);
}
base::ScopedFD Device::AddClientToReadSubdevice(uint32_t client_id,
uint32_t subdevice_id) {
if (client_fds_.empty()) {
if (!StartMonitoring()) {
LOG(ERROR) << "Couldn't start monitoring device: " << name_;
StopMonitoring();
return base::ScopedFD();
}
}
int sock_fd[2];
int ret = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock_fd);
if (ret < 0) {
PLOG(ERROR) << "socketpair for client_id: " << client_id
<< " device_id: " << device_ << " subdevice: " << subdevice_id
<< "failed.";
return base::ScopedFD();
}
base::ScopedFD server_fd(sock_fd[0]);
base::ScopedFD client_fd(sock_fd[1]);
auto id_fd_list = client_fds_.find(subdevice_id);
if (id_fd_list == client_fds_.end()) {
std::vector<std::unique_ptr<SubDeviceClientFdHolder>> list_entries;
list_entries.emplace_back(SubDeviceClientFdHolder::Create(
client_id,
subdevice_id,
std::move(server_fd),
base::Bind(&Device::WriteClientDataToDevice,
weak_factory_.GetWeakPtr())));
client_fds_.emplace(subdevice_id, std::move(list_entries));
} else {
for (auto const& pair : id_fd_list->second) {
if (pair->GetClientId() == client_id) {
LOG(INFO) << "Client id: " << client_id
<< " already registered to"
" subdevice: "
<< subdevice_id << ".";
return base::ScopedFD();
}
}
id_fd_list->second.emplace_back(SubDeviceClientFdHolder::Create(
client_id,
subdevice_id,
std::move(server_fd),
base::Bind(&Device::WriteClientDataToDevice,
weak_factory_.GetWeakPtr())));
}
return client_fd;
}
} // namespace midis