vfs: check userland buffers before reading them.
[haiku.git] / src / bin / desklink / VolumeControl.cpp
bloba7a0fe565c25f3cf86f89f59759c0f7286bf1dc5
1 /*
2 * Copyright 2003-2010, Haiku, Inc.
3 * Distributed under the terms of the MIT license.
5 * Authors:
6 * Jérôme Duval
7 * François Revol
8 * Axel Dörfler, axeld@pinc-software.de.
9 */
12 #include "VolumeControl.h"
14 #include <string.h>
15 #include <stdio.h>
17 #include <Application.h>
18 #include <Beep.h>
19 #include <Catalog.h>
20 #include <ControlLook.h>
21 #include <Dragger.h>
22 #include <MediaRoster.h>
23 #include <MessageRunner.h>
25 #include <AppMisc.h>
27 #include "desklink.h"
28 #include "MixerControl.h"
29 #include "VolumeWindow.h"
32 #undef B_TRANSLATION_CONTEXT
33 #define B_TRANSLATION_CONTEXT "VolumeControl"
36 static const uint32 kMsgReconnectVolume = 'rcms';
39 VolumeControl::VolumeControl(int32 volumeWhich, bool beep, BMessage* message)
41 BSlider("VolumeControl", B_TRANSLATE("Volume"),
42 message, 0, 1, B_HORIZONTAL),
43 fMixerControl(new MixerControl(volumeWhich)),
44 fBeep(beep),
45 fSnapping(false),
46 fConnectRetries(0)
48 font_height fontHeight;
49 GetFontHeight(&fontHeight);
50 SetBarThickness(ceilf((fontHeight.ascent + fontHeight.descent) * 0.7));
52 BRect rect(Bounds());
53 rect.top = rect.bottom - 7;
54 rect.left = rect.right - 7;
55 BDragger* dragger = new BDragger(rect, this,
56 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
57 AddChild(dragger);
61 VolumeControl::VolumeControl(BMessage* archive)
63 BSlider(archive),
64 fMixerControl(NULL),
65 fSnapping(false),
66 fConnectRetries(0)
68 if (archive->FindBool("beep", &fBeep) != B_OK)
69 fBeep = false;
71 int32 volumeWhich;
72 if (archive->FindInt32("volume which", &volumeWhich) != B_OK)
73 volumeWhich = VOLUME_USE_MIXER;
75 fMixerControl = new MixerControl(volumeWhich);
77 BMessage msg(B_QUIT_REQUESTED);
78 archive->SendReply(&msg);
82 VolumeControl::~VolumeControl()
84 delete fMixerControl;
88 status_t
89 VolumeControl::Archive(BMessage* into, bool deep) const
91 status_t status;
93 status = BView::Archive(into, deep);
94 if (status < B_OK)
95 return status;
97 status = into->AddString("add_on", kAppSignature);
98 if (status < B_OK)
99 return status;
101 status = into->AddBool("beep", fBeep);
102 if (status != B_OK)
103 return status;
105 return into->AddInt32("volume which", fMixerControl->VolumeWhich());
109 VolumeControl*
110 VolumeControl::Instantiate(BMessage* archive)
112 if (!validate_instantiation(archive, "VolumeControl"))
113 return NULL;
115 return new VolumeControl(archive);
119 void
120 VolumeControl::AttachedToWindow()
122 BSlider::AttachedToWindow();
124 if (_IsReplicant())
125 SetEventMask(0, 0);
126 else
127 SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
129 BMediaRoster* roster = BMediaRoster::Roster();
130 roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_STARTED);
131 roster->StartWatching(BMessenger(this), B_MEDIA_SERVER_QUIT);
133 _ConnectVolume();
135 if (!fMixerControl->Connected()) {
136 // Wait a bit, and try again - the media server might not have been
137 // ready yet
138 BMessage reconnect(kMsgReconnectVolume);
139 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
140 fConnectRetries = 3;
145 void
146 VolumeControl::DetachedFromWindow()
148 _DisconnectVolume();
150 BMediaRoster* roster = BMediaRoster::CurrentRoster();
151 roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_STARTED);
152 roster->StopWatching(BMessenger(this), B_MEDIA_SERVER_QUIT);
156 /*! Since we have set a mouse event mask, we don't want to forward all
157 mouse downs to the slider - instead, we only invoke it, which causes a
158 message to our target. Within the VolumeWindow, this will actually
159 cause the window to close.
160 Also, we need to mask out the dragger in this case, or else dragging
161 us will also cause a volume update.
163 void
164 VolumeControl::MouseDown(BPoint where)
166 // Ignore clicks on the dragger
167 int32 viewToken;
168 if (Bounds().Contains(where) && Looper()->CurrentMessage() != NULL
169 && Looper()->CurrentMessage()->FindInt32("_view_token",
170 &viewToken) == B_OK
171 && viewToken != _get_object_token_(this))
172 return;
174 // TODO: investigate why this does not work as expected (the dragger
175 // frame seems to be off)
176 #if 0
177 if (BView* dragger = ChildAt(0)) {
178 if (!dragger->IsHidden() && dragger->Frame().Contains(where))
179 return;
181 #endif
183 if (!IsEnabled() || !Bounds().Contains(where)) {
184 Invoke();
185 return;
188 BSlider::MouseDown(where);
192 void
193 VolumeControl::MouseUp(BPoint where)
195 fSnapping = false;
196 BSlider::MouseUp(where);
200 /*! Override the BSlider functionality to be able to grab the knob when
201 it's over 0 dB for some pixels.
203 void
204 VolumeControl::MouseMoved(BPoint where, uint32 transit,
205 const BMessage* dragMessage)
207 if (!IsTracking()) {
208 BSlider::MouseMoved(where, transit, dragMessage);
209 return;
212 float cursorPosition = Orientation() == B_HORIZONTAL ? where.x : where.y;
214 if (fSnapping && cursorPosition >= fMinSnap && cursorPosition <= fMaxSnap) {
215 // Don't move the slider, keep the current value for a few
216 // more pixels
217 return;
220 fSnapping = false;
222 int32 oldValue = Value();
223 int32 newValue = ValueForPoint(where);
224 if (oldValue == newValue) {
225 BSlider::MouseMoved(where, transit, dragMessage);
226 return;
229 // Check if there is a 0 dB transition at all
230 if ((oldValue < 0 && newValue >= 0) || (oldValue > 0 && newValue <= 0)) {
231 SetValue(0);
232 if (ModificationMessage() != NULL)
233 Messenger().SendMessage(ModificationMessage());
235 float snapPoint = _PointForValue(0);
236 const float kMinSnapOffset = 6;
238 if (oldValue > newValue) {
239 // movement from right to left
240 fMinSnap = _PointForValue(-4);
241 if (fabs(snapPoint - fMinSnap) < kMinSnapOffset)
242 fMinSnap = snapPoint - kMinSnapOffset;
244 fMaxSnap = _PointForValue(1);
245 } else {
246 // movement from left to right
247 fMinSnap = _PointForValue(-1);
248 fMaxSnap = _PointForValue(4);
249 if (fabs(snapPoint - fMaxSnap) < kMinSnapOffset)
250 fMaxSnap = snapPoint + kMinSnapOffset;
253 fSnapping = true;
254 return;
257 BSlider::MouseMoved(where, transit, dragMessage);
261 void
262 VolumeControl::MessageReceived(BMessage* msg)
264 switch (msg->what) {
265 case B_MOUSE_WHEEL_CHANGED:
267 if (!fMixerControl->Connected())
268 return;
270 // Even though the volume bar is horizontal, we use the more common
271 // vertical mouse wheel change
272 float deltaY = 0.0f;
274 msg->FindFloat("be:wheel_delta_y", &deltaY);
276 if (deltaY == 0.0f)
277 return;
279 int32 currentValue = Value();
280 int32 newValue = currentValue - int32(deltaY) * 3;
282 if (newValue != currentValue) {
283 SetValue(newValue);
284 InvokeNotify(ModificationMessage(), B_CONTROL_MODIFIED);
286 break;
289 case B_MEDIA_NEW_PARAMETER_VALUE:
290 if (IsTracking())
291 break;
293 SetValue((int32)fMixerControl->Volume());
294 break;
296 case B_MEDIA_SERVER_STARTED:
298 BMessage reconnect(kMsgReconnectVolume);
299 BMessageRunner::StartSending(this, &reconnect, 1000000LL, 1);
300 fConnectRetries = 3;
301 break;
304 case B_MEDIA_SERVER_QUIT:
306 // No media server around
307 SetLabel(B_TRANSLATE("No media server running"));
308 SetEnabled(false);
309 break;
312 case B_QUIT_REQUESTED:
313 Window()->MessageReceived(msg);
314 break;
316 case kMsgReconnectVolume:
317 _ConnectVolume();
318 if (!fMixerControl->Connected() && --fConnectRetries > 1) {
319 BMessage reconnect(kMsgReconnectVolume);
320 BMessageRunner::StartSending(this, &reconnect,
321 6000000LL / fConnectRetries, 1);
323 break;
325 default:
326 return BView::MessageReceived(msg);
331 status_t
332 VolumeControl::Invoke(BMessage* message)
334 if (fBeep && fOriginalValue != Value() && message == NULL) {
335 beep();
336 fOriginalValue = Value();
339 fMixerControl->SetVolume(Value());
341 return BSlider::Invoke(message);
345 void
346 VolumeControl::DrawBar()
348 BRect frame = BarFrame();
349 BView* view = OffscreenView();
351 if (be_control_look != NULL) {
352 uint32 flags = be_control_look->Flags(this);
353 rgb_color base = LowColor();
354 rgb_color rightFillColor = (rgb_color){255, 109, 38, 255};
355 rgb_color leftFillColor = (rgb_color){116, 224, 0, 255};
357 int32 min, max;
358 GetLimits(&min, &max);
359 float position = (float)min / (min - max);
361 be_control_look->DrawSliderBar(view, frame, frame, base, leftFillColor,
362 rightFillColor, position, flags, Orientation());
363 return;
366 BSlider::DrawBar();
370 const char*
371 VolumeControl::UpdateText() const
373 if (!IsEnabled())
374 return NULL;
376 fText.SetToFormat(B_TRANSLATE("%" B_PRId32 " dB"), Value());
377 return fText.String();
381 void
382 VolumeControl::_DisconnectVolume()
384 BMediaRoster* roster = BMediaRoster::CurrentRoster();
385 if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
386 roster->StopWatching(this, fMixerControl->GainNode(),
387 B_MEDIA_NEW_PARAMETER_VALUE);
392 void
393 VolumeControl::_ConnectVolume()
395 _DisconnectVolume();
397 const char* errorString = NULL;
398 float volume = 0.0;
399 fMixerControl->Connect(fMixerControl->VolumeWhich(), &volume, &errorString);
401 if (errorString != NULL) {
402 SetLabel(errorString);
403 SetLimits(-60, 18);
404 } else {
405 SetLabel(B_TRANSLATE("Volume"));
406 SetLimits((int32)floorf(fMixerControl->Minimum()),
407 (int32)ceilf(fMixerControl->Maximum()));
409 BMediaRoster* roster = BMediaRoster::CurrentRoster();
410 if (roster != NULL && fMixerControl->GainNode() != media_node::null) {
411 roster->StartWatching(this, fMixerControl->GainNode(),
412 B_MEDIA_NEW_PARAMETER_VALUE);
416 SetEnabled(errorString == NULL);
418 fOriginalValue = (int32)volume;
419 SetValue((int32)volume);
423 float
424 VolumeControl::_PointForValue(int32 value) const
426 int32 min, max;
427 GetLimits(&min, &max);
429 if (Orientation() == B_HORIZONTAL) {
430 return ceilf(1.0f * (value - min) / (max - min)
431 * (BarFrame().Width() - 2) + BarFrame().left + 1);
434 return ceilf(BarFrame().top - 1.0f * (value - min) / (max - min)
435 * BarFrame().Height());
439 bool
440 VolumeControl::_IsReplicant() const
442 return dynamic_cast<VolumeWindow*>(Window()) == NULL;