1 /*
2 * Copyright 2003-2018, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Jérôme Duval
7 * François Revol
8 * Marcus Overhagen
9 * Jonas Sundström
10 * Axel Dörfler, axeld@pinc-software.de.
11 * Stephan Aßmus <superstippi@gmx.de>
12 * Puck Meerburg, puck@puckipedia.nl
13 */
14
15
16 //! Volume control, and media shortcuts in Deskbar
17
18
19 #include <new>
20 #include <stdio.h>
21
22 #include <Alert.h>
23 #include <Bitmap.h>
24 #include <Catalog.h>
25 #include <Entry.h>
26 #include <File.h>
27 #include <FindDirectory.h>
28 #include <IconUtils.h>
29 #include <MenuItem.h>
30 #include <Path.h>
31 #include <PopUpMenu.h>
32 #include <Resources.h>
33 #include <Roster.h>
34 #include <String.h>
35 #include <StringView.h>
36 #include <TextView.h>
37 #include <ToolTip.h>
38 #include <ToolTipManager.h>
39
40 #include "desklink.h"
41 #include "MixerControl.h"
42 #include "VolumeWindow.h"
43
44
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "MediaReplicant"
47
48
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';
54
55 static const char* kReplicantName = "MediaReplicant";
56 // R5 name needed, Media prefs manel removes by name
57
58 static const char* kSettingsFile = "x-vnd.Haiku-desklink";
59
60
61 class VolumeToolTip : public BTextToolTip {
62 public:
VolumeToolTip(int32 which=VOLUME_USE_MIXER)63 VolumeToolTip(int32 which = VOLUME_USE_MIXER)
64 :
65 BTextToolTip(""),
66 fWhich(which)
67 {
68 }
69
~VolumeToolTip()70 virtual ~VolumeToolTip()
71 {
72 }
73
AttachedToWindow()74 virtual void AttachedToWindow()
75 {
76 Update();
77 }
78
SetWhich(int32 which)79 void SetWhich(int32 which)
80 {
81 fWhich = which;
82 }
83
Update()84 void Update()
85 {
86 if (!Lock())
87 return;
88
89 BTextView* view = (BTextView*)View();
90
91 if (fMuteMessage.Length() != 0)
92 view->SetText(fMuteMessage.String());
93 else {
94 MixerControl control;
95 control.Connect(fWhich);
96
97 BString text;
98 text.SetToFormat(B_TRANSLATE("%g dB"), control.Volume());
99 view->SetText(text.String());
100 }
101 Unlock();
102 }
103
SetMuteMessage(const char * message)104 void SetMuteMessage(const char* message)
105 {
106 fMuteMessage = message == NULL ? "" : message;
107 }
108
109 private:
110 int32 fWhich;
111 BString fMuteMessage;
112 };
113
114
115 class MediaReplicant : public BView {
116 public:
117 MediaReplicant(BRect frame, const char* name,
118 uint32 resizeMask = B_FOLLOW_ALL,
119 uint32 flags = B_WILL_DRAW | B_NAVIGABLE
120 | B_PULSE_NEEDED);
121 MediaReplicant(BMessage* archive);
122
123 virtual ~MediaReplicant();
124
125 // archiving overrides
126 static MediaReplicant* Instantiate(BMessage* data);
127 virtual status_t Archive(BMessage* data, bool deep = true) const;
128
129 // BView overrides
130 virtual void AttachedToWindow();
131 virtual void MouseDown(BPoint point);
132 virtual void Draw(BRect updateRect);
133 virtual void MessageReceived(BMessage* message);
134
135 private:
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();
143 void _Init();
144 BBitmap* _LoadIcon(BResources& resources, const char* name);
145
146 void _DisconnectMixer();
147 status_t _ConnectMixer();
148
149 MixerControl* fMixerControl;
150
151 BBitmap* fIcon;
152 BBitmap* fMutedIcon;
153 VolumeWindow* fVolumeSlider;
154 bool fDontBeep;
155 // don't beep on volume change
156 int32 fVolumeWhich;
157 // which volume parameter to act on (Mixer/Phys.Output)
158 bool fMuted;
159 };
160
161
162 status_t
our_image(image_info & image)163 our_image(image_info& image)
164 {
165 int32 cookie = 0;
166 while (get_next_image_info(B_CURRENT_TEAM, &cookie, &image) == B_OK) {
167 if ((char*)our_image >= (char*)image.text
168 && (char*)our_image <= (char*)image.text + image.text_size)
169 return B_OK;
170 }
171
172 return B_ERROR;
173 }
174
175
176 // #pragma mark -
177
178
MediaReplicant(BRect frame,const char * name,uint32 resizeMask,uint32 flags)179 MediaReplicant::MediaReplicant(BRect frame, const char* name,
180 uint32 resizeMask, uint32 flags)
181 :
182 BView(frame, name, resizeMask, flags),
183 fMixerControl(NULL),
184 fVolumeSlider(NULL),
185 fMuted(false)
186 {
187 _Init();
188 }
189
190
MediaReplicant(BMessage * message)191 MediaReplicant::MediaReplicant(BMessage* message)
192 :
193 BView(message),
194 fMixerControl(NULL),
195 fVolumeSlider(NULL),
196 fMuted(false)
197 {
198 _Init();
199 }
200
201
~MediaReplicant()202 MediaReplicant::~MediaReplicant()
203 {
204 delete fIcon;
205 _SaveSettings();
206 _DisconnectMixer();
207 }
208
209
210 MediaReplicant*
Instantiate(BMessage * data)211 MediaReplicant::Instantiate(BMessage* data)
212 {
213 if (!validate_instantiation(data, kReplicantName))
214 return NULL;
215
216 return new(std::nothrow) MediaReplicant(data);
217 }
218
219
220 status_t
Archive(BMessage * data,bool deep) const221 MediaReplicant::Archive(BMessage* data, bool deep) const
222 {
223 status_t status = BView::Archive(data, deep);
224 if (status < B_OK)
225 return status;
226
227 return data->AddString("add_on", kAppSignature);
228 }
229
230
231 void
AttachedToWindow()232 MediaReplicant::AttachedToWindow()
233 {
234 AdoptParentColors();
235
236 _ConnectMixer();
237
238 BView::AttachedToWindow();
239 }
240
241
242 void
Draw(BRect rect)243 MediaReplicant::Draw(BRect rect)
244 {
245 SetDrawingMode(B_OP_OVER);
246 DrawBitmap(fMuted ? fMutedIcon : fIcon);
247 }
248
249
250 void
MouseDown(BPoint point)251 MediaReplicant::MouseDown(BPoint point)
252 {
253 int32 buttons = B_PRIMARY_MOUSE_BUTTON;
254 if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
255 Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
256
257 BPoint where = ConvertToScreen(point);
258
259 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
260 BPopUpMenu* menu = new BPopUpMenu("", false, false);
261 menu->SetFont(be_plain_font);
262
263 menu->AddItem(new BMenuItem(
264 B_TRANSLATE("Media preferences" B_UTF8_ELLIPSIS),
265 new BMessage(kMsgOpenMediaSettings)));
266 menu->AddItem(new BMenuItem(
267 B_TRANSLATE("Sounds preferences" B_UTF8_ELLIPSIS),
268 new BMessage(kMsgOpenSoundSettings)));
269
270 menu->AddSeparatorItem();
271
272 menu->AddItem(new BMenuItem(B_TRANSLATE("Open MediaPlayer"),
273 new BMessage(kMsgOpenMediaPlayer)));
274
275 menu->AddSeparatorItem();
276
277 BMenu* subMenu = new BMenu(B_TRANSLATE("Options"));
278 menu->AddItem(subMenu);
279
280 BMenuItem* item = new BMenuItem(B_TRANSLATE("Control physical output"),
281 new BMessage(kMsgVolumeWhich));
282 item->SetMarked(fVolumeWhich == VOLUME_USE_PHYS_OUTPUT);
283 subMenu->AddItem(item);
284
285 item = new BMenuItem(B_TRANSLATE("Beep"),
286 new BMessage(kMsgToggleBeep));
287 item->SetMarked(!fDontBeep);
288 subMenu->AddItem(item);
289
290 menu->SetTargetForItems(this);
291 subMenu->SetTargetForItems(this);
292
293 menu->Go(where, true, true, BRect(where - BPoint(4, 4),
294 where + BPoint(4, 4)));
295
296 } else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
297 if (fMixerControl != NULL) {
298 fMixerControl->SetMute(!fMuted);
299 fMuted = fMixerControl->Mute();
300 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
301 if (tip != NULL) {
302 tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted"): NULL);
303 tip->Update();
304 ShowToolTip(tip);
305 }
306 Invalidate();
307 }
308
309 } else {
310 // Show VolumeWindow
311 fVolumeSlider = new VolumeWindow(BRect(where.x, where.y,
312 where.x + 207, where.y + 19), fDontBeep, fVolumeWhich);
313 fVolumeSlider->Show();
314 }
315 }
316
317
318 void
MessageReceived(BMessage * message)319 MediaReplicant::MessageReceived(BMessage* message)
320 {
321 switch (message->what) {
322 case kMsgOpenMediaPlayer:
323 _Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer",
324 B_SYSTEM_APPS_DIRECTORY, "MediaPlayer");
325 break;
326
327 case kMsgOpenMediaSettings:
328 _Launch("Media Preferences", "application/x-vnd.Haiku-Media",
329 B_SYSTEM_PREFERENCES_DIRECTORY, "Media");
330 break;
331
332 case kMsgOpenSoundSettings:
333 _Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds",
334 B_SYSTEM_PREFERENCES_DIRECTORY, "Sounds");
335 break;
336
337 case kMsgToggleBeep:
338 {
339 BMenuItem* item;
340 if (message->FindPointer("source", (void**)&item) != B_OK)
341 return;
342
343 item->SetMarked(!item->IsMarked());
344 fDontBeep = !item->IsMarked();
345 break;
346 }
347
348 case kMsgVolumeWhich:
349 {
350 BMenuItem* item;
351 if (message->FindPointer("source", (void**)&item) != B_OK)
352 return;
353
354 item->SetMarked(!item->IsMarked());
355 fVolumeWhich = item->IsMarked()
356 ? VOLUME_USE_PHYS_OUTPUT : VOLUME_USE_MIXER;
357
358 if (_ConnectMixer() != B_OK
359 && fVolumeWhich == VOLUME_USE_PHYS_OUTPUT) {
360 // unable to switch to physical output
361 item->SetMarked(false);
362 fVolumeWhich = VOLUME_USE_MIXER;
363 _ConnectMixer();
364 }
365
366 if (VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip())) {
367 tip->SetWhich(fVolumeWhich);
368 tip->Update();
369 }
370 break;
371 }
372
373 case B_MOUSE_WHEEL_CHANGED:
374 {
375 float deltaY;
376 if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
377 && deltaY != 0.0 && fMixerControl != NULL) {
378 fMixerControl->ChangeVolumeBy(deltaY < 0 ? 6 : -6);
379
380 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
381 if (tip != NULL) {
382 tip->Update();
383 ShowToolTip(tip);
384 }
385 }
386 break;
387 }
388
389 case B_MEDIA_NEW_PARAMETER_VALUE:
390 {
391 if (fMixerControl != NULL && !fMixerControl->Connected())
392 return;
393
394 bool setMuted = fMixerControl->Mute();
395 if (setMuted != fMuted) {
396 fMuted = setMuted;
397 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip());
398 if (tip != NULL) {
399 tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted") : NULL);
400 tip->Update();
401 }
402 Invalidate();
403 }
404 break;
405 }
406
407 case B_MEDIA_SERVER_STARTED:
408 _ConnectMixer();
409 break;
410
411 case B_MEDIA_SERVER_QUIT:
412 _DisconnectMixer();
413 break;
414
415 case B_MEDIA_NODE_CREATED:
416 {
417 // It's not enough to wait for B_MEDIA_SERVER_STARTED message, as
418 // the mixer will still be getting loaded by the media server
419 media_node mixerNode;
420 media_node_id mixerNodeID;
421 BMediaRoster* roster = BMediaRoster::CurrentRoster();
422 if (roster != NULL
423 && message->FindInt32("media_node_id", &mixerNodeID) == B_OK
424 && roster->GetNodeFor(mixerNodeID, &mixerNode) == B_OK) {
425 if (mixerNode.kind == B_SYSTEM_MIXER)
426 _ConnectMixer();
427 roster->ReleaseNode(mixerNode);
428 }
429 break;
430 }
431
432 default:
433 BView::MessageReceived(message);
434 break;
435 }
436 }
437
438
439 status_t
_LaunchByPath(const char * path)440 MediaReplicant::_LaunchByPath(const char* path)
441 {
442 entry_ref ref;
443 status_t status = get_ref_for_path(path, &ref);
444 if (status != B_OK)
445 return status;
446
447 status = be_roster->Launch(&ref);
448 if (status != B_ALREADY_RUNNING)
449 return status;
450
451 // The application runs already, bring it to front
452
453 app_info appInfo;
454 status = be_roster->GetAppInfo(&ref, &appInfo);
455 if (status != B_OK)
456 return status;
457
458 return be_roster->ActivateApp(appInfo.team);
459 }
460
461
462 status_t
_LaunchBySignature(const char * signature)463 MediaReplicant::_LaunchBySignature(const char* signature)
464 {
465 status_t status = be_roster->Launch(signature);
466 if (status != B_ALREADY_RUNNING)
467 return status;
468
469 // The application runs already, bring it to front
470
471 app_info appInfo;
472 status = be_roster->GetAppInfo(signature, &appInfo);
473 if (status != B_OK)
474 return status;
475
476 return be_roster->ActivateApp(appInfo.team);
477 }
478
479
480 void
_Launch(const char * prettyName,const char * signature,directory_which base,const char * fileName)481 MediaReplicant::_Launch(const char* prettyName, const char* signature,
482 directory_which base, const char* fileName)
483 {
484 BPath path;
485 status_t status = find_directory(base, &path);
486 if (status == B_OK)
487 path.Append(fileName);
488
489 // launch the application
490 if (_LaunchBySignature(signature) != B_OK
491 && _LaunchByPath(path.Path()) != B_OK) {
492 BString message = B_TRANSLATE("Couldn't launch ");
493 message << prettyName;
494
495 BAlert* alert = new BAlert(
496 B_TRANSLATE_COMMENT("desklink", "Title of an alert box"),
497 message.String(), B_TRANSLATE("OK"));
498 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
499 alert->Go();
500 }
501 }
502
503
504 void
_LoadSettings()505 MediaReplicant::_LoadSettings()
506 {
507 fDontBeep = false;
508 fVolumeWhich = VOLUME_USE_MIXER;
509
510 BPath path;
511 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
512 return;
513
514 path.Append(kSettingsFile);
515
516 BFile settings(path.Path(), B_READ_ONLY);
517 if (settings.InitCheck() < B_OK)
518 return;
519
520 BMessage msg;
521 if (msg.Unflatten(&settings) < B_OK)
522 return;
523
524 msg.FindInt32("volwhich", &fVolumeWhich);
525 msg.FindBool("dontbeep", &fDontBeep);
526 }
527
528
529 void
_SaveSettings()530 MediaReplicant::_SaveSettings()
531 {
532 BPath path;
533 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK)
534 return;
535
536 path.Append(kSettingsFile);
537
538 BFile settings(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
539 if (settings.InitCheck() < B_OK)
540 return;
541
542 BMessage msg('CNFG');
543 msg.AddInt32("volwhich", fVolumeWhich);
544 msg.AddBool("dontbeep", fDontBeep);
545
546 ssize_t size = 0;
547 msg.Flatten(&settings, &size);
548 }
549
550
551 void
_Init()552 MediaReplicant::_Init()
553 {
554 image_info info;
555 if (our_image(info) != B_OK)
556 return;
557
558 BFile file(info.name, B_READ_ONLY);
559 if (file.InitCheck() != B_OK)
560 return;
561
562 BResources resources(&file);
563 if (resources.InitCheck() != B_OK)
564 return;
565
566 fIcon = _LoadIcon(resources, "Speaker");
567 fMutedIcon = _LoadIcon(resources, "SpeakerMuted");
568
569 _LoadSettings();
570
571 SetToolTip(new VolumeToolTip(fVolumeWhich));
572 }
573
574
575 BBitmap*
_LoadIcon(BResources & resources,const char * name)576 MediaReplicant::_LoadIcon(BResources& resources, const char* name)
577 {
578 size_t size;
579 const void* data = resources.LoadResource(B_VECTOR_ICON_TYPE, name, &size);
580 if (data == NULL)
581 return NULL;
582
583 // Scale tray icon
584 BBitmap* icon = new BBitmap(Bounds(), B_RGBA32);
585 if (icon->InitCheck() != B_OK
586 || BIconUtils::GetVectorIcon((const uint8*)data, size, icon) != B_OK) {
587 delete icon;
588 return NULL;
589 }
590 return icon;
591 }
592
593
594 void
_DisconnectMixer()595 MediaReplicant::_DisconnectMixer()
596 {
597 BMediaRoster* roster = BMediaRoster::CurrentRoster();
598 if (roster == NULL)
599 return;
600
601 roster->StopWatching(this, B_MEDIA_SERVER_STARTED);
602 roster->StopWatching(this, B_MEDIA_SERVER_QUIT);
603 roster->StopWatching(this, B_MEDIA_NODE_CREATED);
604
605 if (fMixerControl == NULL)
606 return;
607
608 if (fMixerControl->MuteNode() != media_node::null) {
609 roster->StopWatching(this, fMixerControl->MuteNode(),
610 B_MEDIA_NEW_PARAMETER_VALUE);
611 }
612
613 delete fMixerControl;
614 fMixerControl = NULL;
615 }
616
617
618 status_t
_ConnectMixer()619 MediaReplicant::_ConnectMixer()
620 {
621 _DisconnectMixer();
622
623 BMediaRoster* roster = BMediaRoster::Roster();
624 if (roster == NULL)
625 return B_ERROR;
626
627 roster->StartWatching(this, B_MEDIA_SERVER_STARTED);
628 roster->StartWatching(this, B_MEDIA_SERVER_QUIT);
629 roster->StartWatching(this, B_MEDIA_NODE_CREATED);
630
631 fMixerControl = new MixerControl(fVolumeWhich);
632
633 const char* errorString = NULL;
634 float volume = 0.0;
635 fMixerControl->Connect(fVolumeWhich, &volume, &errorString);
636
637 if (errorString != NULL) {
638 SetToolTip(errorString);
639 delete fMixerControl;
640 fMixerControl = NULL;
641 return B_ERROR;
642 }
643
644 if (fMixerControl->MuteNode() != media_node::null) {
645 roster->StartWatching(this, fMixerControl->MuteNode(),
646 B_MEDIA_NEW_PARAMETER_VALUE);
647 fMuted = fMixerControl->Mute();
648 }
649
650 return B_OK;
651 }
652
653
654 // #pragma mark -
655
656
657 extern "C" BView*
instantiate_deskbar_item(float maxWidth,float maxHeight)658 instantiate_deskbar_item(float maxWidth, float maxHeight)
659 {
660 return new MediaReplicant(BRect(0, 0, maxHeight - 1, maxHeight - 1),
661 kReplicantName);
662 }
663
664