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: 63 VolumeToolTip(int32 which = VOLUME_USE_MIXER) 64 : 65 BTextToolTip(""), 66 fWhich(which) 67 { 68 } 69 70 virtual ~VolumeToolTip() 71 { 72 } 73 74 virtual void AttachedToWindow() 75 { 76 Update(); 77 } 78 79 void SetWhich(int32 which) 80 { 81 fWhich = which; 82 } 83 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 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 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 179 MediaReplicant::MediaReplicant(BRect frame, const char* name, 180 uint32 resizeMask, uint32 flags) 181 : 182 BView(frame, name, resizeMask, flags), 183 fVolumeSlider(NULL), 184 fMuted(false) 185 { 186 _Init(); 187 } 188 189 190 MediaReplicant::MediaReplicant(BMessage* message) 191 : 192 BView(message), 193 fVolumeSlider(NULL), 194 fMuted(false) 195 { 196 _Init(); 197 } 198 199 200 MediaReplicant::~MediaReplicant() 201 { 202 delete fIcon; 203 _SaveSettings(); 204 _DisconnectMixer(); 205 } 206 207 208 MediaReplicant* 209 MediaReplicant::Instantiate(BMessage* data) 210 { 211 if (!validate_instantiation(data, kReplicantName)) 212 return NULL; 213 214 return new(std::nothrow) MediaReplicant(data); 215 } 216 217 218 status_t 219 MediaReplicant::Archive(BMessage* data, bool deep) const 220 { 221 status_t status = BView::Archive(data, deep); 222 if (status < B_OK) 223 return status; 224 225 return data->AddString("add_on", kAppSignature); 226 } 227 228 229 void 230 MediaReplicant::AttachedToWindow() 231 { 232 AdoptParentColors(); 233 234 _ConnectMixer(); 235 236 BView::AttachedToWindow(); 237 } 238 239 240 void 241 MediaReplicant::Draw(BRect rect) 242 { 243 SetDrawingMode(B_OP_OVER); 244 DrawBitmap(fMuted ? fMutedIcon : fIcon); 245 } 246 247 248 void 249 MediaReplicant::MouseDown(BPoint point) 250 { 251 int32 buttons = B_PRIMARY_MOUSE_BUTTON; 252 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 253 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 254 255 BPoint where = ConvertToScreen(point); 256 257 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { 258 BPopUpMenu* menu = new BPopUpMenu("", false, false); 259 menu->SetFont(be_plain_font); 260 261 menu->AddItem(new BMenuItem( 262 B_TRANSLATE("Media preferences" B_UTF8_ELLIPSIS), 263 new BMessage(kMsgOpenMediaSettings))); 264 menu->AddItem(new BMenuItem( 265 B_TRANSLATE("Sound preferences" B_UTF8_ELLIPSIS), 266 new BMessage(kMsgOpenSoundSettings))); 267 268 menu->AddSeparatorItem(); 269 270 menu->AddItem(new BMenuItem(B_TRANSLATE("Open MediaPlayer"), 271 new BMessage(kMsgOpenMediaPlayer))); 272 273 menu->AddSeparatorItem(); 274 275 BMenu* subMenu = new BMenu(B_TRANSLATE("Options")); 276 menu->AddItem(subMenu); 277 278 BMenuItem* item = new BMenuItem(B_TRANSLATE("Control physical output"), 279 new BMessage(kMsgVolumeWhich)); 280 item->SetMarked(fVolumeWhich == VOLUME_USE_PHYS_OUTPUT); 281 subMenu->AddItem(item); 282 283 item = new BMenuItem(B_TRANSLATE("Beep"), 284 new BMessage(kMsgToggleBeep)); 285 item->SetMarked(!fDontBeep); 286 subMenu->AddItem(item); 287 288 menu->SetTargetForItems(this); 289 subMenu->SetTargetForItems(this); 290 291 menu->Go(where, true, true, BRect(where - BPoint(4, 4), 292 where + BPoint(4, 4))); 293 294 } else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) { 295 if (fMixerControl != NULL) { 296 fMixerControl->SetMute(!fMuted); 297 fMuted = fMixerControl->Mute(); 298 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 299 if (tip != NULL) { 300 tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted"): NULL); 301 tip->Update(); 302 ShowToolTip(tip); 303 } 304 Invalidate(); 305 } 306 307 } else { 308 // Show VolumeWindow 309 fVolumeSlider = new VolumeWindow(BRect(where.x, where.y, 310 where.x + 207, where.y + 19), fDontBeep, fVolumeWhich); 311 fVolumeSlider->Show(); 312 } 313 } 314 315 316 void 317 MediaReplicant::MessageReceived(BMessage* message) 318 { 319 switch (message->what) { 320 case kMsgOpenMediaPlayer: 321 _Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer", 322 B_SYSTEM_APPS_DIRECTORY, "MediaPlayer"); 323 break; 324 325 case kMsgOpenMediaSettings: 326 _Launch("Media Preferences", "application/x-vnd.Haiku-Media", 327 B_SYSTEM_PREFERENCES_DIRECTORY, "Media"); 328 break; 329 330 case kMsgOpenSoundSettings: 331 _Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds", 332 B_SYSTEM_PREFERENCES_DIRECTORY, "Sounds"); 333 break; 334 335 case kMsgToggleBeep: 336 { 337 BMenuItem* item; 338 if (message->FindPointer("source", (void**)&item) != B_OK) 339 return; 340 341 item->SetMarked(!item->IsMarked()); 342 fDontBeep = !item->IsMarked(); 343 break; 344 } 345 346 case kMsgVolumeWhich: 347 { 348 BMenuItem* item; 349 if (message->FindPointer("source", (void**)&item) != B_OK) 350 return; 351 352 item->SetMarked(!item->IsMarked()); 353 fVolumeWhich = item->IsMarked() 354 ? VOLUME_USE_PHYS_OUTPUT : VOLUME_USE_MIXER; 355 356 if (_ConnectMixer() != B_OK 357 && fVolumeWhich == VOLUME_USE_PHYS_OUTPUT) { 358 // unable to switch to physical output 359 item->SetMarked(false); 360 fVolumeWhich = VOLUME_USE_MIXER; 361 _ConnectMixer(); 362 } 363 364 if (VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip())) { 365 tip->SetWhich(fVolumeWhich); 366 tip->Update(); 367 } 368 break; 369 } 370 371 case B_MOUSE_WHEEL_CHANGED: 372 { 373 float deltaY; 374 if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK 375 && deltaY != 0.0 && fMixerControl != NULL) { 376 fMixerControl->ChangeVolumeBy(deltaY < 0 ? 6 : -6); 377 378 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 379 if (tip != NULL) { 380 tip->Update(); 381 ShowToolTip(tip); 382 } 383 } 384 break; 385 } 386 387 case B_MEDIA_NEW_PARAMETER_VALUE: 388 { 389 if (fMixerControl != NULL && !fMixerControl->Connected()) 390 return; 391 392 bool setMuted = fMixerControl->Mute(); 393 if (setMuted != fMuted) { 394 fMuted = setMuted; 395 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 396 if (tip != NULL) { 397 tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted") : NULL); 398 tip->Update(); 399 } 400 Invalidate(); 401 } 402 break; 403 } 404 405 case B_MEDIA_SERVER_STARTED: 406 _ConnectMixer(); 407 break; 408 409 case B_MEDIA_NODE_CREATED: 410 { 411 // It's not enough to wait for B_MEDIA_SERVER_STARTED message, as 412 // the mixer will still be getting loaded by the media server 413 media_node mixerNode; 414 media_node_id mixerNodeID; 415 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 416 if (roster != NULL 417 && message->FindInt32("media_node_id",&mixerNodeID) == B_OK 418 && roster->GetNodeFor(mixerNodeID, &mixerNode) == B_OK) { 419 if (mixerNode.kind == B_SYSTEM_MIXER) { 420 _ConnectMixer(); 421 roster->ReleaseNode(mixerNode); 422 } 423 } 424 break; 425 } 426 427 default: 428 BView::MessageReceived(message); 429 break; 430 } 431 } 432 433 434 status_t 435 MediaReplicant::_LaunchByPath(const char* path) 436 { 437 entry_ref ref; 438 status_t status = get_ref_for_path(path, &ref); 439 if (status != B_OK) 440 return status; 441 442 status = be_roster->Launch(&ref); 443 if (status != B_ALREADY_RUNNING) 444 return status; 445 446 // The application runs already, bring it to front 447 448 app_info appInfo; 449 status = be_roster->GetAppInfo(&ref, &appInfo); 450 if (status != B_OK) 451 return status; 452 453 return be_roster->ActivateApp(appInfo.team); 454 } 455 456 457 status_t 458 MediaReplicant::_LaunchBySignature(const char* signature) 459 { 460 status_t status = be_roster->Launch(signature); 461 if (status != B_ALREADY_RUNNING) 462 return status; 463 464 // The application runs already, bring it to front 465 466 app_info appInfo; 467 status = be_roster->GetAppInfo(signature, &appInfo); 468 if (status != B_OK) 469 return status; 470 471 return be_roster->ActivateApp(appInfo.team); 472 } 473 474 475 void 476 MediaReplicant::_Launch(const char* prettyName, const char* signature, 477 directory_which base, const char* fileName) 478 { 479 BPath path; 480 status_t status = find_directory(base, &path); 481 if (status == B_OK) 482 path.Append(fileName); 483 484 // launch the application 485 if (_LaunchBySignature(signature) != B_OK 486 && _LaunchByPath(path.Path()) != B_OK) { 487 BString message = B_TRANSLATE("Couldn't launch "); 488 message << prettyName; 489 490 BAlert* alert = new BAlert(B_TRANSLATE("desklink"), message.String(), 491 B_TRANSLATE("OK")); 492 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 493 alert->Go(); 494 } 495 } 496 497 498 void 499 MediaReplicant::_LoadSettings() 500 { 501 fDontBeep = false; 502 fVolumeWhich = VOLUME_USE_MIXER; 503 504 BPath path; 505 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK) 506 return; 507 508 path.Append(kSettingsFile); 509 510 BFile settings(path.Path(), B_READ_ONLY); 511 if (settings.InitCheck() < B_OK) 512 return; 513 514 BMessage msg; 515 if (msg.Unflatten(&settings) < B_OK) 516 return; 517 518 msg.FindInt32("volwhich", &fVolumeWhich); 519 msg.FindBool("dontbeep", &fDontBeep); 520 } 521 522 523 void 524 MediaReplicant::_SaveSettings() 525 { 526 BPath path; 527 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK) 528 return; 529 530 path.Append(kSettingsFile); 531 532 BFile settings(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 533 if (settings.InitCheck() < B_OK) 534 return; 535 536 BMessage msg('CNFG'); 537 msg.AddInt32("volwhich", fVolumeWhich); 538 msg.AddBool("dontbeep", fDontBeep); 539 540 ssize_t size = 0; 541 msg.Flatten(&settings, &size); 542 } 543 544 545 void 546 MediaReplicant::_Init() 547 { 548 image_info info; 549 if (our_image(info) != B_OK) 550 return; 551 552 BFile file(info.name, B_READ_ONLY); 553 if (file.InitCheck() != B_OK) 554 return; 555 556 BResources resources(&file); 557 if (resources.InitCheck() != B_OK) 558 return; 559 560 fIcon = _LoadIcon(resources, "Speaker"); 561 fMutedIcon = _LoadIcon(resources, "SpeakerMuted"); 562 563 _LoadSettings(); 564 565 SetToolTip(new VolumeToolTip(fVolumeWhich)); 566 } 567 568 569 BBitmap* 570 MediaReplicant::_LoadIcon(BResources& resources, const char* name) 571 { 572 size_t size; 573 const void* data = resources.LoadResource(B_VECTOR_ICON_TYPE, name, &size); 574 if (data == NULL) 575 return NULL; 576 577 // Scale tray icon 578 BBitmap* icon = new BBitmap(Bounds(), B_RGBA32); 579 if (icon->InitCheck() != B_OK 580 || BIconUtils::GetVectorIcon((const uint8*)data, size, icon) != B_OK) { 581 delete icon; 582 return NULL; 583 } 584 return icon; 585 } 586 587 588 void 589 MediaReplicant::_DisconnectMixer() 590 { 591 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 592 if (roster == NULL) 593 return; 594 595 roster->StopWatching(this, B_MEDIA_SERVER_STARTED | B_MEDIA_NODE_CREATED); 596 597 if (fMixerControl->MuteNode() != media_node::null) { 598 roster->StopWatching(this, fMixerControl->MuteNode(), 599 B_MEDIA_NEW_PARAMETER_VALUE); 600 } 601 602 delete fMixerControl; 603 fMixerControl = NULL; 604 } 605 606 607 status_t 608 MediaReplicant::_ConnectMixer() 609 { 610 _DisconnectMixer(); 611 612 BMediaRoster* roster = BMediaRoster::Roster(); 613 if (roster == NULL) 614 return B_ERROR; 615 616 roster->StartWatching(this, B_MEDIA_SERVER_STARTED | B_MEDIA_NODE_CREATED); 617 618 fMixerControl = new MixerControl(fVolumeWhich); 619 620 const char* errorString = NULL; 621 float volume = 0.0; 622 fMixerControl->Connect(fVolumeWhich, &volume, &errorString); 623 624 if (errorString != NULL) { 625 SetToolTip(errorString); 626 return B_ERROR; 627 } 628 629 if (fMixerControl->MuteNode() != media_node::null) { 630 roster->StartWatching(this, fMixerControl->MuteNode(), 631 B_MEDIA_NEW_PARAMETER_VALUE); 632 fMuted = fMixerControl->Mute(); 633 } 634 635 return B_OK; 636 } 637 638 639 // #pragma mark - 640 641 642 extern "C" BView* 643 instantiate_deskbar_item(float maxWidth, float maxHeight) 644 { 645 return new MediaReplicant(BRect(0, 0, maxHeight - 1, maxHeight - 1), 646 kReplicantName); 647 } 648 649