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
32 using std::nothrow;
33
34 using namespace BPrivate;
35 using BPrivate::HashMap;
36 using BPrivate::HashString;
37
38
39 const char *kDevicesRoot = "/dev/midi";
40
41
42 class DeviceEndpoints {
43 public:
DeviceEndpoints(int fd,MidiPortConsumer * consumer,MidiPortProducer * producer)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
DeviceWatcher()56 DeviceWatcher::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
~DeviceWatcher()105 DeviceWatcher::~DeviceWatcher()
106 {
107 Stop();
108
109 delete fLargeIcon;
110 delete fMiniIcon;
111 delete[] fVectorIconData;
112 }
113
114
115 status_t
Start()116 DeviceWatcher::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
135 status_t
Stop()136 DeviceWatcher::Stop()
137 {
138 return BPathMonitor::StopWatching(kDevicesRoot, this);
139 }
140
141
142 void
MessageReceived(BMessage * message)143 DeviceWatcher::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 */
175 int32
_InitialDevicesScanThread(void * data)176 DeviceWatcher::_InitialDevicesScanThread(void* data)
177 {
178 ((DeviceWatcher*)data)->_ScanDevices(kDevicesRoot);
179 return 0;
180 }
181
182
183 void
_ScanDevices(const char * path)184 DeviceWatcher::_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
204 void
_AddDevice(const char * path)205 DeviceWatcher::_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
267 void
_RemoveDevice(const char * path)268 DeviceWatcher::_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
296 void
_SetIcons(BMidiEndpoint * endpoint)297 DeviceWatcher::_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