2 * Copyright 2003-2013, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
10 * Axel Dörfler, axeld@pinc-software.de.
11 * Stephan Aßmus <superstippi@gmx.de>
12 * Puck Meerburg, puck@puckipedia.nl
16 //! Volume control, and media shortcuts in Deskbar
27 #include <FindDirectory.h>
28 #include <IconUtils.h>
31 #include <PopUpMenu.h>
34 #include <StringView.h>
37 #include <ToolTipManager.h>
41 #include "MixerControl.h"
42 #include "VolumeWindow.h"
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "MediaReplicant"
49 static const uint32 kMsgOpenMediaSettings
= 'mese';
50 static const uint32 kMsgOpenSoundSettings
= 'sose';
51 static const uint32 kMsgOpenMediaPlayer
= 'omep';
52 static const uint32 kMsgToggleBeep
= 'tdbp';
53 static const uint32 kMsgVolumeWhich
= 'svwh';
55 static const char* kReplicantName
= "MediaReplicant";
56 // R5 name needed, Media prefs manel removes by name
58 static const char* kSettingsFile
= "x-vnd.Haiku-desklink";
61 class VolumeToolTip
: public BTextToolTip
{
63 VolumeToolTip(int32 which
= VOLUME_USE_MIXER
)
70 virtual ~VolumeToolTip()
74 virtual void AttachedToWindow()
79 void SetWhich(int32 which
)
89 BTextView
* view
= (BTextView
*)View();
91 if (fMuteMessage
.Length() != 0)
92 view
->SetText(fMuteMessage
.String());
95 control
.Connect(fWhich
);
98 text
.SetToFormat(B_TRANSLATE("%g dB"), control
.Volume());
99 view
->SetText(text
.String());
104 void SetMuteMessage(const char* message
)
106 fMuteMessage
= message
== NULL
? "" : message
;
111 BString fMuteMessage
;
115 class MediaReplicant
: public BView
{
117 MediaReplicant(BRect frame
, const char* name
,
118 uint32 resizeMask
= B_FOLLOW_ALL
,
119 uint32 flags
= B_WILL_DRAW
| B_NAVIGABLE
121 MediaReplicant(BMessage
* archive
);
123 virtual ~MediaReplicant();
125 // archiving overrides
126 static MediaReplicant
* Instantiate(BMessage
* data
);
127 virtual status_t
Archive(BMessage
* data
, bool deep
= true) const;
130 virtual void AttachedToWindow();
131 virtual void MouseDown(BPoint point
);
132 virtual void Draw(BRect updateRect
);
133 virtual void MessageReceived(BMessage
* message
);
136 status_t
_LaunchByPath(const char* path
);
137 status_t
_LaunchBySignature(const char* signature
);
138 void _Launch(const char* prettyName
,
139 const char* signature
, directory_which base
,
140 const char* fileName
);
141 void _LoadSettings();
142 void _SaveSettings();
145 void _DisconnectMixer();
146 status_t
_ConnectMixer();
148 MixerControl
* fMixerControl
;
152 VolumeWindow
* fVolumeSlider
;
154 // don't beep on volume change
156 // which volume parameter to act on (Mixer/Phys.Output)
161 MediaReplicant::MediaReplicant(BRect frame
, const char* name
,
162 uint32 resizeMask
, uint32 flags
)
164 BView(frame
, name
, resizeMask
, flags
),
172 MediaReplicant::MediaReplicant(BMessage
* message
)
182 MediaReplicant::~MediaReplicant()
191 MediaReplicant::Instantiate(BMessage
* data
)
193 if (!validate_instantiation(data
, kReplicantName
))
196 return new(std::nothrow
) MediaReplicant(data
);
201 MediaReplicant::Archive(BMessage
* data
, bool deep
) const
203 status_t status
= BView::Archive(data
, deep
);
207 return data
->AddString("add_on", kAppSignature
);
212 MediaReplicant::AttachedToWindow()
218 BView::AttachedToWindow();
223 MediaReplicant::Draw(BRect rect
)
225 SetDrawingMode(B_OP_OVER
);
226 DrawBitmap(fMuted
? fMutedIcon
: fIcon
);
231 MediaReplicant::MouseDown(BPoint point
)
233 int32 buttons
= B_PRIMARY_MOUSE_BUTTON
;
234 if (Looper() != NULL
&& Looper()->CurrentMessage() != NULL
)
235 Looper()->CurrentMessage()->FindInt32("buttons", &buttons
);
237 BPoint where
= ConvertToScreen(point
);
239 if ((buttons
& B_SECONDARY_MOUSE_BUTTON
) != 0) {
240 BPopUpMenu
* menu
= new BPopUpMenu("", false, false);
241 menu
->SetFont(be_plain_font
);
243 menu
->AddItem(new BMenuItem(
244 B_TRANSLATE("Media preferences" B_UTF8_ELLIPSIS
),
245 new BMessage(kMsgOpenMediaSettings
)));
246 menu
->AddItem(new BMenuItem(
247 B_TRANSLATE("Sound preferences" B_UTF8_ELLIPSIS
),
248 new BMessage(kMsgOpenSoundSettings
)));
250 menu
->AddSeparatorItem();
252 menu
->AddItem(new BMenuItem(B_TRANSLATE("Open MediaPlayer"),
253 new BMessage(kMsgOpenMediaPlayer
)));
255 menu
->AddSeparatorItem();
257 BMenu
* subMenu
= new BMenu(B_TRANSLATE("Options"));
258 menu
->AddItem(subMenu
);
260 BMenuItem
* item
= new BMenuItem(B_TRANSLATE("Control physical output"),
261 new BMessage(kMsgVolumeWhich
));
262 item
->SetMarked(fVolumeWhich
== VOLUME_USE_PHYS_OUTPUT
);
263 subMenu
->AddItem(item
);
265 item
= new BMenuItem(B_TRANSLATE("Beep"),
266 new BMessage(kMsgToggleBeep
));
267 item
->SetMarked(!fDontBeep
);
268 subMenu
->AddItem(item
);
270 menu
->SetTargetForItems(this);
271 subMenu
->SetTargetForItems(this);
273 menu
->Go(where
, true, true, BRect(where
- BPoint(4, 4),
274 where
+ BPoint(4, 4)));
276 } else if ((buttons
& B_TERTIARY_MOUSE_BUTTON
) != 0) {
277 if (fMixerControl
!= NULL
) {
278 fMixerControl
->SetMute(!fMuted
);
279 fMuted
= fMixerControl
->Mute();
280 VolumeToolTip
* tip
= dynamic_cast<VolumeToolTip
*>(ToolTip());
282 tip
->SetMuteMessage(fMuted
? B_TRANSLATE("Muted"): NULL
);
291 fVolumeSlider
= new VolumeWindow(BRect(where
.x
, where
.y
,
292 where
.x
+ 207, where
.y
+ 19), fDontBeep
, fVolumeWhich
);
293 fVolumeSlider
->Show();
299 MediaReplicant::MessageReceived(BMessage
* message
)
301 switch (message
->what
) {
302 case kMsgOpenMediaPlayer
:
303 _Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer",
304 B_SYSTEM_APPS_DIRECTORY
, "MediaPlayer");
307 case kMsgOpenMediaSettings
:
308 _Launch("Media Preferences", "application/x-vnd.Haiku-Media",
309 B_SYSTEM_PREFERENCES_DIRECTORY
, "Media");
312 case kMsgOpenSoundSettings
:
313 _Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds",
314 B_SYSTEM_PREFERENCES_DIRECTORY
, "Sounds");
320 if (message
->FindPointer("source", (void**)&item
) != B_OK
)
323 item
->SetMarked(!item
->IsMarked());
324 fDontBeep
= !item
->IsMarked();
328 case kMsgVolumeWhich
:
331 if (message
->FindPointer("source", (void**)&item
) != B_OK
)
334 item
->SetMarked(!item
->IsMarked());
335 fVolumeWhich
= item
->IsMarked()
336 ? VOLUME_USE_PHYS_OUTPUT
: VOLUME_USE_MIXER
;
338 if (_ConnectMixer() != B_OK
339 && fVolumeWhich
== VOLUME_USE_PHYS_OUTPUT
) {
340 // unable to switch to physical output
341 item
->SetMarked(false);
342 fVolumeWhich
= VOLUME_USE_MIXER
;
346 if (VolumeToolTip
* tip
= dynamic_cast<VolumeToolTip
*>(ToolTip())) {
347 tip
->SetWhich(fVolumeWhich
);
353 case B_MOUSE_WHEEL_CHANGED
:
356 if (message
->FindFloat("be:wheel_delta_y", &deltaY
) == B_OK
357 && deltaY
!= 0.0 && fMixerControl
!= NULL
) {
358 fMixerControl
->ChangeVolumeBy(deltaY
< 0 ? 6 : -6);
360 VolumeToolTip
* tip
= dynamic_cast<VolumeToolTip
*>(ToolTip());
369 case B_MEDIA_NEW_PARAMETER_VALUE
:
371 if (fMixerControl
!= NULL
&& !fMixerControl
->Connected())
374 bool setMuted
= fMixerControl
->Mute();
375 if (setMuted
!= fMuted
) {
377 VolumeToolTip
* tip
= dynamic_cast<VolumeToolTip
*>(ToolTip());
379 tip
->SetMuteMessage(fMuted
? B_TRANSLATE("Muted") : NULL
);
387 case B_MEDIA_SERVER_STARTED
:
391 case B_MEDIA_NODE_CREATED
:
393 // It's not enough to wait for B_MEDIA_SERVER_STARTED message, as
394 // the mixer will still be getting loaded by the media server
395 media_node mixerNode
;
396 media_node_id mixerNodeID
;
397 BMediaRoster
* roster
= BMediaRoster::CurrentRoster();
399 && message
->FindInt32("media_node_id",&mixerNodeID
) == B_OK
400 && roster
->GetNodeFor(mixerNodeID
, &mixerNode
) == B_OK
) {
401 if (mixerNode
.kind
== B_SYSTEM_MIXER
) {
403 roster
->ReleaseNode(mixerNode
);
410 BView::MessageReceived(message
);
417 MediaReplicant::_LaunchByPath(const char* path
)
420 status_t status
= get_ref_for_path(path
, &ref
);
424 status
= be_roster
->Launch(&ref
);
425 if (status
!= B_ALREADY_RUNNING
)
428 // The application runs already, bring it to front
431 status
= be_roster
->GetAppInfo(&ref
, &appInfo
);
435 return be_roster
->ActivateApp(appInfo
.team
);
440 MediaReplicant::_LaunchBySignature(const char* signature
)
442 status_t status
= be_roster
->Launch(signature
);
443 if (status
!= B_ALREADY_RUNNING
)
446 // The application runs already, bring it to front
449 status
= be_roster
->GetAppInfo(signature
, &appInfo
);
453 return be_roster
->ActivateApp(appInfo
.team
);
458 MediaReplicant::_Launch(const char* prettyName
, const char* signature
,
459 directory_which base
, const char* fileName
)
462 status_t status
= find_directory(base
, &path
);
464 path
.Append(fileName
);
466 // launch the application
467 if (_LaunchBySignature(signature
) != B_OK
468 && _LaunchByPath(path
.Path()) != B_OK
) {
469 BString message
= B_TRANSLATE("Couldn't launch ");
470 message
<< prettyName
;
472 BAlert
* alert
= new BAlert(B_TRANSLATE("desklink"), message
.String(),
474 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
481 MediaReplicant::_LoadSettings()
484 fVolumeWhich
= VOLUME_USE_MIXER
;
487 if (find_directory(B_USER_SETTINGS_DIRECTORY
, &path
, false) < B_OK
)
490 path
.Append(kSettingsFile
);
492 BFile
settings(path
.Path(), B_READ_ONLY
);
493 if (settings
.InitCheck() < B_OK
)
497 if (msg
.Unflatten(&settings
) < B_OK
)
500 msg
.FindInt32("volwhich", &fVolumeWhich
);
501 msg
.FindBool("dontbeep", &fDontBeep
);
506 MediaReplicant::_SaveSettings()
509 if (find_directory(B_USER_SETTINGS_DIRECTORY
, &path
, false) < B_OK
)
512 path
.Append(kSettingsFile
);
514 BFile
settings(path
.Path(), B_WRITE_ONLY
| B_CREATE_FILE
| B_ERASE_FILE
);
515 if (settings
.InitCheck() < B_OK
)
518 BMessage
msg('CNFG');
519 msg
.AddInt32("volwhich", fVolumeWhich
);
520 msg
.AddBool("dontbeep", fDontBeep
);
523 msg
.Flatten(&settings
, &size
);
528 MediaReplicant::_Init()
530 fIcon
= new BBitmap(BRect(0, 0, kSpeakerWidth
- 1, kSpeakerHeight
- 1),
532 BIconUtils::GetVectorIcon(kSpeakerIcon
, sizeof(kSpeakerIcon
), fIcon
);
534 fMutedIcon
= new BBitmap(BRect(0, 0, kSpeakerWidth
- 1, kSpeakerHeight
- 1),
536 BIconUtils::GetVectorIcon(kMutedSpeakerIcon
, sizeof(kMutedSpeakerIcon
),
541 SetToolTip(new VolumeToolTip(fVolumeWhich
));
546 MediaReplicant::_DisconnectMixer()
548 BMediaRoster
* roster
= BMediaRoster::CurrentRoster();
552 roster
->StopWatching(this, B_MEDIA_SERVER_STARTED
| B_MEDIA_NODE_CREATED
);
554 if (fMixerControl
->MuteNode() != media_node::null
) {
555 roster
->StopWatching(this, fMixerControl
->MuteNode(),
556 B_MEDIA_NEW_PARAMETER_VALUE
);
559 delete fMixerControl
;
560 fMixerControl
= NULL
;
565 MediaReplicant::_ConnectMixer()
569 BMediaRoster
* roster
= BMediaRoster::Roster();
573 roster
->StartWatching(this, B_MEDIA_SERVER_STARTED
| B_MEDIA_NODE_CREATED
);
575 fMixerControl
= new MixerControl(fVolumeWhich
);
577 const char* errorString
= NULL
;
579 fMixerControl
->Connect(fVolumeWhich
, &volume
, &errorString
);
581 if (errorString
!= NULL
) {
582 SetToolTip(errorString
);
586 if (fMixerControl
->MuteNode() != media_node::null
) {
587 roster
->StartWatching(this, fMixerControl
->MuteNode(),
588 B_MEDIA_NEW_PARAMETER_VALUE
);
589 fMuted
= fMixerControl
->Mute();
600 instantiate_deskbar_item(void)
602 return new MediaReplicant(BRect(0, 0, 16, 16), kReplicantName
);