1/*
2 * Copyright 2004-2009, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Matthijs Hollemans
7 *		Jerome Leveque
8 *		Philippe Houdoin
9 *		Pete Goodeve
10 */
11
12#include "debug.h"
13#include "DeviceWatcher.h"
14#include "PortDrivers.h"
15
16#include <errno.h>
17#include <fcntl.h>
18#include <stdio.h>
19#include <new>
20
21#include <Application.h>
22#include <Bitmap.h>
23#include <Directory.h>
24#include <Entry.h>
25#include <File.h>
26#include <IconUtils.h>
27#include <Path.h>
28#include <PathMonitor.h>
29#include <Resources.h>
30#include <Roster.h>
31
32using std::nothrow;
33
34using namespace BPrivate;
35using BPrivate::HashMap;
36using BPrivate::HashString;
37
38
39const char *kDevicesRoot = "/dev/midi";
40
41
42class DeviceEndpoints {
43public:
44	DeviceEndpoints(int fd, MidiPortConsumer* consumer, MidiPortProducer* producer)
45		:  fFD(fd), fConsumer(consumer), fProducer(producer)
46	{
47	}
48
49	int 				fFD;
50	MidiPortConsumer*	fConsumer;
51	MidiPortProducer*	fProducer;
52};
53
54
55
56DeviceWatcher::DeviceWatcher()
57	: BLooper("MIDI devices watcher"),
58	fDeviceEndpointsMap(), fVectorIconData(NULL), fVectorIconDataSize(0),
59	fLargeIcon(NULL), fMiniIcon(NULL)
60{
61	// Load midi endpoint vector icon data
62	app_info info;
63	be_app->GetAppInfo(&info);
64	BFile file(&info.ref, B_READ_ONLY);
65
66	BResources resources;
67	if (resources.SetTo(&file) == B_OK) {
68		size_t dataSize;
69		// Load MIDI port endpoint vector icon
70		const uint8* data = (const uint8*)resources.LoadResource(
71			B_VECTOR_ICON_TYPE,	"endpoint_vector_icon", &dataSize);
72
73		if (data != NULL && dataSize > 0)
74			fVectorIconData = new(std::nothrow) uint8[dataSize];
75
76		if (fVectorIconData) {
77			// data is own by resources local object: copy its content for
78			// later use
79			memcpy(fVectorIconData, data, dataSize);
80			fVectorIconDataSize = dataSize;
81		}
82	}
83
84	// Render 32x32 and 16x16 B_CMAP8 icons for R5 compatibility
85	if (fVectorIconData != NULL) {
86		fLargeIcon = new(std::nothrow) BBitmap(BRect(0, 0, 31, 31), B_CMAP8);
87		fMiniIcon  = new(std::nothrow) BBitmap(BRect(0, 0, 15, 15), B_CMAP8);
88
89		if (BIconUtils::GetVectorIcon(fVectorIconData, fVectorIconDataSize,
90			fLargeIcon) != B_OK) {
91			delete fLargeIcon;
92			fLargeIcon = NULL;
93		}
94		if (BIconUtils::GetVectorIcon(fVectorIconData, fVectorIconDataSize,
95			fMiniIcon) != B_OK) {
96			delete fMiniIcon;
97			fMiniIcon = NULL;
98		}
99	}
100
101	Start();
102}
103
104
105DeviceWatcher::~DeviceWatcher()
106{
107	Stop();
108
109	delete fLargeIcon;
110	delete fMiniIcon;
111	delete[] fVectorIconData;
112}
113
114
115status_t
116DeviceWatcher::Start()
117{
118	// Do an initial scan
119
120	// We need to do this from a separate thread, otherwise we will deadlock.
121	// The reason is that we instantiate a BMidiRoster object, which sends a
122	// message to the midi_server to register itself, and blocks until it gets
123	// a response. But since we _are_ the midi_server we will never be able to
124	// send that response if our main thread is already blocking.
125
126    resume_thread(spawn_thread(_InitialDevicesScanThread,
127		"Initial devices scan", B_NORMAL_PRIORITY, this));
128
129	// And watch for any change
130	return BPathMonitor::StartWatching(kDevicesRoot,
131		B_WATCH_FILES_ONLY | B_WATCH_RECURSIVELY, this);
132}
133
134
135status_t
136DeviceWatcher::Stop()
137{
138	return BPathMonitor::StopWatching(kDevicesRoot, this);
139}
140
141
142void
143DeviceWatcher::MessageReceived(BMessage* message)
144{
145	if (message->what != B_PATH_MONITOR)
146		return;
147
148	int32 opcode;
149	if (message->FindInt32("opcode", &opcode) != B_OK)
150		return;
151
152	// message->PrintToStream();
153
154	const char* path;
155	if (message->FindString("path", &path) != B_OK)
156		return;
157
158	switch (opcode) {
159		case B_ENTRY_CREATED: {
160			_AddDevice(path);
161			break;
162		}
163		case B_ENTRY_REMOVED: {
164			_RemoveDevice(path);
165			break;
166		}
167	}
168}
169
170
171// #pragma mark -
172
173
174/* static  */
175int32
176DeviceWatcher::_InitialDevicesScanThread(void* data)
177{
178	((DeviceWatcher*)data)->_ScanDevices(kDevicesRoot);
179	return 0;
180}
181
182
183void
184DeviceWatcher::_ScanDevices(const char* path)
185{
186	TRACE(("DeviceWatcher::_ScanDevices(\"%s\");\n", path));
187
188	BDirectory dir(path);
189	if (dir.InitCheck() != B_OK)
190		return;
191
192	BEntry entry;
193	while (dir.GetNextEntry(&entry) == B_OK)  {
194		BPath name;
195		entry.GetPath(&name);
196		if (entry.IsDirectory())
197			_ScanDevices(name.Path());
198		else
199           	_AddDevice(name.Path());
200	}
201}
202
203
204void
205DeviceWatcher::_AddDevice(const char* path)
206{
207	TRACE(("DeviceWatcher::_AddDevice(\"%s\");\n", path));
208
209	if (fDeviceEndpointsMap.ContainsKey(path)) {
210		// Already known
211		TRACE(("already known...!\n"));
212		return;
213	}
214
215	BEntry entry(path);
216	if (entry.IsDirectory())
217		// Invalid path!
218		return;
219
220	if (entry.IsSymLink()) {
221		BEntry symlink(path, true);
222		if (symlink.IsDirectory()) {
223			// Invalid path!
224			return;
225		}
226	}
227
228	int fd = open(path, O_RDWR | O_EXCL);
229	if (fd < 0) {
230		if (errno != EACCES)
231			return;
232
233		// maybe it's a input or output only port?
234		fd = open(path, O_RDONLY | O_EXCL);
235		if (fd < 0 && errno == EACCES)
236			fd = open(path, O_WRONLY | O_EXCL);
237		if (fd < 0)
238			return;
239	}
240
241	TRACE(("Doing _AddDevice(\"%s\"); fd=%d\n", path, fd));
242
243	MidiPortConsumer* consumer = NULL;
244	MidiPortProducer* producer = NULL;
245
246	int flags = fcntl(fd, F_GETFL);
247
248	if ((flags & O_ACCMODE) != O_RDONLY) {
249		consumer = new MidiPortConsumer(fd, path);
250		_SetIcons(consumer);
251		TRACE(("Register %s MidiPortConsumer\n", consumer->Name()));
252		consumer->Register();
253	}
254
255	if ((flags & O_ACCMODE) != O_WRONLY) {
256		producer = new MidiPortProducer(fd, path);
257		_SetIcons(producer);
258		TRACE(("Register %s MidiPortProducer\n", producer->Name()));
259		producer->Register();
260	}
261
262	fDeviceEndpointsMap.Put(path, new DeviceEndpoints(fd, consumer, producer));
263	TRACE(("Done _AddDevice(\"%s\")\n", path));
264}
265
266
267void
268DeviceWatcher::_RemoveDevice(const char* path)
269{
270	TRACE(("DeviceWatcher::_RemoveDevice(\"%s\");\n", path));
271
272	DeviceEndpoints* deviceEndpoints = fDeviceEndpointsMap.Get(path);
273	if (!deviceEndpoints) {
274		TRACE(("_RemoveDevice(\"%s\") didn't find endpoint in map!!\n", path));
275		return;
276	}
277
278	TRACE((" _RemoveDevice(\"%s\") unregistering\n", path));
279	if (deviceEndpoints->fConsumer)
280		deviceEndpoints->fConsumer->Unregister();
281	if (deviceEndpoints->fProducer)
282		deviceEndpoints->fProducer->Unregister();
283
284	TRACE((" _RemoveDevice(\"%s\") releasing\n", path));
285	if (deviceEndpoints->fConsumer)
286		deviceEndpoints->fConsumer->Release();
287	if (deviceEndpoints->fProducer)
288		deviceEndpoints->fProducer->Release();
289
290	TRACE((" _RemoveDevice(\"%s\") removing from map\n", path));
291	fDeviceEndpointsMap.Remove(path);
292	TRACE(("Done _RemoveDevice(\"%s\")\n", path));
293}
294
295
296void
297DeviceWatcher::_SetIcons(BMidiEndpoint* endpoint)
298{
299	BMessage msg;
300
301	if (fVectorIconData && fVectorIconDataSize > 0) {
302		msg.AddData("icon", B_VECTOR_ICON_TYPE, fVectorIconData,
303			fVectorIconDataSize);
304	}
305
306	if (fLargeIcon) {
307		msg.AddData("be:large_icon", B_LARGE_ICON_TYPE, fLargeIcon->Bits(),
308			fLargeIcon->BitsLength());
309	}
310
311	if (fMiniIcon) {
312		msg.AddData("be:mini_icon", B_MINI_ICON_TYPE, fMiniIcon->Bits(),
313			fMiniIcon->BitsLength());
314	}
315
316	endpoint->SetProperties(&msg);
317}
318