haiku/src/servers/midi/DeviceWatcher.cpp

318 lines
6.8 KiB
C++

/*
* Copyright 2004-2009, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Matthijs Hollemans
* Jerome Leveque
* Philippe Houdoin
* Pete Goodeve
*/
#include "debug.h"
#include "DeviceWatcher.h"
#include "PortDrivers.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <new>
#include <Application.h>
#include <Bitmap.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <IconUtils.h>
#include <Path.h>
#include <PathMonitor.h>
#include <Resources.h>
#include <Roster.h>
using std::nothrow;
using namespace BPrivate;
using BPrivate::HashMap;
using BPrivate::HashString;
const char *kDevicesRoot = "/dev/midi";
class DeviceEndpoints {
public:
DeviceEndpoints(int fd, MidiPortConsumer* consumer, MidiPortProducer* producer)
: fFD(fd), fConsumer(consumer), fProducer(producer)
{
}
int fFD;
MidiPortConsumer* fConsumer;
MidiPortProducer* fProducer;
};
DeviceWatcher::DeviceWatcher()
: BLooper("MIDI devices watcher"),
fDeviceEndpointsMap(), fVectorIconData(NULL), fVectorIconDataSize(0),
fLargeIcon(NULL), fMiniIcon(NULL)
{
// Load midi endpoint vector icon data
app_info info;
be_app->GetAppInfo(&info);
BFile file(&info.ref, B_READ_ONLY);
BResources resources;
if (resources.SetTo(&file) == B_OK) {
size_t dataSize;
// Load MIDI port endpoint vector icon
const uint8* data = (const uint8*)resources.LoadResource(
B_VECTOR_ICON_TYPE, "endpoint_vector_icon", &dataSize);
if (data != NULL && dataSize > 0)
fVectorIconData = new(std::nothrow) uint8[dataSize];
if (fVectorIconData) {
// data is own by resources local object: copy its content for
// later use
memcpy(fVectorIconData, data, dataSize);
fVectorIconDataSize = dataSize;
}
}
// Render 32x32 and 16x16 B_CMAP8 icons for R5 compatibility
if (fVectorIconData != NULL) {
fLargeIcon = new(std::nothrow) BBitmap(BRect(0, 0, 31, 31), B_CMAP8);
fMiniIcon = new(std::nothrow) BBitmap(BRect(0, 0, 15, 15), B_CMAP8);
if (BIconUtils::GetVectorIcon(fVectorIconData, fVectorIconDataSize,
fLargeIcon) != B_OK) {
delete fLargeIcon;
fLargeIcon = NULL;
}
if (BIconUtils::GetVectorIcon(fVectorIconData, fVectorIconDataSize,
fMiniIcon) != B_OK) {
delete fMiniIcon;
fMiniIcon = NULL;
}
}
Start();
}
DeviceWatcher::~DeviceWatcher()
{
Stop();
delete fLargeIcon;
delete fMiniIcon;
delete[] fVectorIconData;
}
status_t
DeviceWatcher::Start()
{
// Do an initial scan
// We need to do this from a separate thread, otherwise we will deadlock.
// The reason is that we instantiate a BMidiRoster object, which sends a
// message to the midi_server to register itself, and blocks until it gets
// a response. But since we _are_ the midi_server we will never be able to
// send that response if our main thread is already blocking.
resume_thread(spawn_thread(_InitialDevicesScanThread,
"Initial devices scan", B_NORMAL_PRIORITY, this));
// And watch for any change
return BPathMonitor::StartWatching(kDevicesRoot,
B_WATCH_FILES_ONLY | B_WATCH_RECURSIVELY, this);
}
status_t
DeviceWatcher::Stop()
{
return BPathMonitor::StopWatching(kDevicesRoot, this);
}
void
DeviceWatcher::MessageReceived(BMessage* message)
{
if (message->what != B_PATH_MONITOR)
return;
int32 opcode;
if (message->FindInt32("opcode", &opcode) != B_OK)
return;
// message->PrintToStream();
const char* path;
if (message->FindString("path", &path) != B_OK)
return;
switch (opcode) {
case B_ENTRY_CREATED: {
_AddDevice(path);
break;
}
case B_ENTRY_REMOVED: {
_RemoveDevice(path);
break;
}
}
}
// #pragma mark -
/* static */
int32
DeviceWatcher::_InitialDevicesScanThread(void* data)
{
((DeviceWatcher*)data)->_ScanDevices(kDevicesRoot);
return 0;
}
void
DeviceWatcher::_ScanDevices(const char* path)
{
TRACE(("DeviceWatcher::_ScanDevices(\"%s\");\n", path));
BDirectory dir(path);
if (dir.InitCheck() != B_OK)
return;
BEntry entry;
while (dir.GetNextEntry(&entry) == B_OK) {
BPath name;
entry.GetPath(&name);
if (entry.IsDirectory())
_ScanDevices(name.Path());
else
_AddDevice(name.Path());
}
}
void
DeviceWatcher::_AddDevice(const char* path)
{
TRACE(("DeviceWatcher::_AddDevice(\"%s\");\n", path));
if (fDeviceEndpointsMap.ContainsKey(path)) {
// Already known
TRACE(("already known...!\n"));
return;
}
BEntry entry(path);
if (entry.IsDirectory())
// Invalid path!
return;
if (entry.IsSymLink()) {
BEntry symlink(path, true);
if (symlink.IsDirectory()) {
// Invalid path!
return;
}
}
int fd = open(path, O_RDWR | O_EXCL);
if (fd < 0) {
if (errno != EACCES)
return;
// maybe it's a input or output only port?
fd = open(path, O_RDONLY | O_EXCL);
if (fd < 0 && errno == EACCES)
fd = open(path, O_WRONLY | O_EXCL);
if (fd < 0)
return;
}
TRACE(("Doing _AddDevice(\"%s\"); fd=%d\n", path, fd));
MidiPortConsumer* consumer = NULL;
MidiPortProducer* producer = NULL;
int flags = fcntl(fd, F_GETFL);
if ((flags & O_ACCMODE) != O_RDONLY) {
consumer = new MidiPortConsumer(fd, path);
_SetIcons(consumer);
TRACE(("Register %s MidiPortConsumer\n", consumer->Name()));
consumer->Register();
}
if ((flags & O_ACCMODE) != O_WRONLY) {
producer = new MidiPortProducer(fd, path);
_SetIcons(producer);
TRACE(("Register %s MidiPortProducer\n", producer->Name()));
producer->Register();
}
fDeviceEndpointsMap.Put(path, new DeviceEndpoints(fd, consumer, producer));
TRACE(("Done _AddDevice(\"%s\")\n", path));
}
void
DeviceWatcher::_RemoveDevice(const char* path)
{
TRACE(("DeviceWatcher::_RemoveDevice(\"%s\");\n", path));
DeviceEndpoints* deviceEndpoints = fDeviceEndpointsMap.Get(path);
if (!deviceEndpoints) {
TRACE(("_RemoveDevice(\"%s\") didn't find endpoint in map!!\n", path));
return;
}
TRACE((" _RemoveDevice(\"%s\") unregistering\n", path));
if (deviceEndpoints->fConsumer)
deviceEndpoints->fConsumer->Unregister();
if (deviceEndpoints->fProducer)
deviceEndpoints->fProducer->Unregister();
TRACE((" _RemoveDevice(\"%s\") releasing\n", path));
if (deviceEndpoints->fConsumer)
deviceEndpoints->fConsumer->Release();
if (deviceEndpoints->fProducer)
deviceEndpoints->fProducer->Release();
TRACE((" _RemoveDevice(\"%s\") removing from map\n", path));
fDeviceEndpointsMap.Remove(path);
TRACE(("Done _RemoveDevice(\"%s\")\n", path));
}
void
DeviceWatcher::_SetIcons(BMidiEndpoint* endpoint)
{
BMessage msg;
if (fVectorIconData && fVectorIconDataSize > 0) {
msg.AddData("icon", B_VECTOR_ICON_TYPE, fVectorIconData,
fVectorIconDataSize);
}
if (fLargeIcon) {
msg.AddData("be:large_icon", B_LARGE_ICON_TYPE, fLargeIcon->Bits(),
fLargeIcon->BitsLength());
}
if (fMiniIcon) {
msg.AddData("be:mini_icon", B_MINI_ICON_TYPE, fMiniIcon->Bits(),
fMiniIcon->BitsLength());
}
endpoint->SetProperties(&msg);
}