1 /* 2 * Copyright 2003-2013, 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 <Roster.h> 33 #include <String.h> 34 #include <StringView.h> 35 #include <TextView.h> 36 #include <ToolTip.h> 37 #include <ToolTipManager.h> 38 39 #include "desklink.h" 40 #include "iconfile.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 145 void _DisconnectMixer(); 146 status_t _ConnectMixer(); 147 148 MixerControl* fMixerControl; 149 150 BBitmap* fIcon; 151 BBitmap* fMutedIcon; 152 VolumeWindow* fVolumeSlider; 153 bool fDontBeep; 154 // don't beep on volume change 155 int32 fVolumeWhich; 156 // which volume parameter to act on (Mixer/Phys.Output) 157 bool fMuted; 158 }; 159 160 161 MediaReplicant::MediaReplicant(BRect frame, const char* name, 162 uint32 resizeMask, uint32 flags) 163 : 164 BView(frame, name, resizeMask, flags), 165 fVolumeSlider(NULL), 166 fMuted(false) 167 { 168 _Init(); 169 } 170 171 172 MediaReplicant::MediaReplicant(BMessage* message) 173 : 174 BView(message), 175 fVolumeSlider(NULL), 176 fMuted(false) 177 { 178 _Init(); 179 } 180 181 182 MediaReplicant::~MediaReplicant() 183 { 184 delete fIcon; 185 _SaveSettings(); 186 _DisconnectMixer(); 187 } 188 189 190 MediaReplicant* 191 MediaReplicant::Instantiate(BMessage* data) 192 { 193 if (!validate_instantiation(data, kReplicantName)) 194 return NULL; 195 196 return new(std::nothrow) MediaReplicant(data); 197 } 198 199 200 status_t 201 MediaReplicant::Archive(BMessage* data, bool deep) const 202 { 203 status_t status = BView::Archive(data, deep); 204 if (status < B_OK) 205 return status; 206 207 return data->AddString("add_on", kAppSignature); 208 } 209 210 211 void 212 MediaReplicant::AttachedToWindow() 213 { 214 AdoptParentColors(); 215 216 _ConnectMixer(); 217 218 BView::AttachedToWindow(); 219 } 220 221 222 void 223 MediaReplicant::Draw(BRect rect) 224 { 225 SetDrawingMode(B_OP_OVER); 226 DrawBitmap(fMuted ? fMutedIcon : fIcon); 227 } 228 229 230 void 231 MediaReplicant::MouseDown(BPoint point) 232 { 233 int32 buttons = B_PRIMARY_MOUSE_BUTTON; 234 if (Looper() != NULL && Looper()->CurrentMessage() != NULL) 235 Looper()->CurrentMessage()->FindInt32("buttons", &buttons); 236 237 BPoint where = ConvertToScreen(point); 238 239 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { 240 BPopUpMenu* menu = new BPopUpMenu("", false, false); 241 menu->SetFont(be_plain_font); 242 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))); 249 250 menu->AddSeparatorItem(); 251 252 menu->AddItem(new BMenuItem(B_TRANSLATE("Open MediaPlayer"), 253 new BMessage(kMsgOpenMediaPlayer))); 254 255 menu->AddSeparatorItem(); 256 257 BMenu* subMenu = new BMenu(B_TRANSLATE("Options")); 258 menu->AddItem(subMenu); 259 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); 264 265 item = new BMenuItem(B_TRANSLATE("Beep"), 266 new BMessage(kMsgToggleBeep)); 267 item->SetMarked(!fDontBeep); 268 subMenu->AddItem(item); 269 270 menu->SetTargetForItems(this); 271 subMenu->SetTargetForItems(this); 272 273 menu->Go(where, true, true, BRect(where - BPoint(4, 4), 274 where + BPoint(4, 4))); 275 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()); 281 if (tip != NULL) { 282 tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted"): NULL); 283 tip->Update(); 284 ShowToolTip(tip); 285 } 286 Invalidate(); 287 } 288 289 } else { 290 // Show VolumeWindow 291 fVolumeSlider = new VolumeWindow(BRect(where.x, where.y, 292 where.x + 207, where.y + 19), fDontBeep, fVolumeWhich); 293 fVolumeSlider->Show(); 294 } 295 } 296 297 298 void 299 MediaReplicant::MessageReceived(BMessage* message) 300 { 301 switch (message->what) { 302 case kMsgOpenMediaPlayer: 303 _Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer", 304 B_SYSTEM_APPS_DIRECTORY, "MediaPlayer"); 305 break; 306 307 case kMsgOpenMediaSettings: 308 _Launch("Media Preferences", "application/x-vnd.Haiku-Media", 309 B_SYSTEM_PREFERENCES_DIRECTORY, "Media"); 310 break; 311 312 case kMsgOpenSoundSettings: 313 _Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds", 314 B_SYSTEM_PREFERENCES_DIRECTORY, "Sounds"); 315 break; 316 317 case kMsgToggleBeep: 318 { 319 BMenuItem* item; 320 if (message->FindPointer("source", (void**)&item) != B_OK) 321 return; 322 323 item->SetMarked(!item->IsMarked()); 324 fDontBeep = !item->IsMarked(); 325 break; 326 } 327 328 case kMsgVolumeWhich: 329 { 330 BMenuItem* item; 331 if (message->FindPointer("source", (void**)&item) != B_OK) 332 return; 333 334 item->SetMarked(!item->IsMarked()); 335 fVolumeWhich = item->IsMarked() 336 ? VOLUME_USE_PHYS_OUTPUT : VOLUME_USE_MIXER; 337 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; 343 _ConnectMixer(); 344 } 345 346 if (VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip())) { 347 tip->SetWhich(fVolumeWhich); 348 tip->Update(); 349 } 350 break; 351 } 352 353 case B_MOUSE_WHEEL_CHANGED: 354 { 355 float deltaY; 356 if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK 357 && deltaY != 0.0 && fMixerControl != NULL) { 358 fMixerControl->ChangeVolumeBy(deltaY < 0 ? 6 : -6); 359 360 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 361 if (tip != NULL) { 362 tip->Update(); 363 ShowToolTip(tip); 364 } 365 } 366 break; 367 } 368 369 case B_MEDIA_NEW_PARAMETER_VALUE: 370 { 371 if (fMixerControl != NULL && !fMixerControl->Connected()) 372 return; 373 374 bool setMuted = fMixerControl->Mute(); 375 if (setMuted != fMuted) { 376 fMuted = setMuted; 377 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 378 if (tip != NULL) { 379 tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted") : NULL); 380 tip->Update(); 381 } 382 Invalidate(); 383 } 384 break; 385 } 386 387 case B_MEDIA_SERVER_STARTED: 388 _ConnectMixer(); 389 break; 390 391 case B_MEDIA_NODE_CREATED: 392 { 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(); 398 if (roster != NULL 399 && message->FindInt32("media_node_id",&mixerNodeID) == B_OK 400 && roster->GetNodeFor(mixerNodeID, &mixerNode) == B_OK) { 401 if (mixerNode.kind == B_SYSTEM_MIXER) { 402 _ConnectMixer(); 403 roster->ReleaseNode(mixerNode); 404 } 405 } 406 break; 407 } 408 409 default: 410 BView::MessageReceived(message); 411 break; 412 } 413 } 414 415 416 status_t 417 MediaReplicant::_LaunchByPath(const char* path) 418 { 419 entry_ref ref; 420 status_t status = get_ref_for_path(path, &ref); 421 if (status != B_OK) 422 return status; 423 424 status = be_roster->Launch(&ref); 425 if (status != B_ALREADY_RUNNING) 426 return status; 427 428 // The application runs already, bring it to front 429 430 app_info appInfo; 431 status = be_roster->GetAppInfo(&ref, &appInfo); 432 if (status != B_OK) 433 return status; 434 435 return be_roster->ActivateApp(appInfo.team); 436 } 437 438 439 status_t 440 MediaReplicant::_LaunchBySignature(const char* signature) 441 { 442 status_t status = be_roster->Launch(signature); 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(signature, &appInfo); 450 if (status != B_OK) 451 return status; 452 453 return be_roster->ActivateApp(appInfo.team); 454 } 455 456 457 void 458 MediaReplicant::_Launch(const char* prettyName, const char* signature, 459 directory_which base, const char* fileName) 460 { 461 BPath path; 462 status_t status = find_directory(base, &path); 463 if (status == B_OK) 464 path.Append(fileName); 465 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; 471 472 BAlert* alert = new BAlert(B_TRANSLATE("desklink"), message.String(), 473 B_TRANSLATE("OK")); 474 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 475 alert->Go(); 476 } 477 } 478 479 480 void 481 MediaReplicant::_LoadSettings() 482 { 483 fDontBeep = false; 484 fVolumeWhich = VOLUME_USE_MIXER; 485 486 BPath path; 487 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK) 488 return; 489 490 path.Append(kSettingsFile); 491 492 BFile settings(path.Path(), B_READ_ONLY); 493 if (settings.InitCheck() < B_OK) 494 return; 495 496 BMessage msg; 497 if (msg.Unflatten(&settings) < B_OK) 498 return; 499 500 msg.FindInt32("volwhich", &fVolumeWhich); 501 msg.FindBool("dontbeep", &fDontBeep); 502 } 503 504 505 void 506 MediaReplicant::_SaveSettings() 507 { 508 BPath path; 509 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK) 510 return; 511 512 path.Append(kSettingsFile); 513 514 BFile settings(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 515 if (settings.InitCheck() < B_OK) 516 return; 517 518 BMessage msg('CNFG'); 519 msg.AddInt32("volwhich", fVolumeWhich); 520 msg.AddBool("dontbeep", fDontBeep); 521 522 ssize_t size = 0; 523 msg.Flatten(&settings, &size); 524 } 525 526 527 void 528 MediaReplicant::_Init() 529 { 530 fIcon = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1), 531 B_RGBA32); 532 BIconUtils::GetVectorIcon(kSpeakerIcon, sizeof(kSpeakerIcon), fIcon); 533 534 fMutedIcon = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1), 535 B_RGBA32); 536 BIconUtils::GetVectorIcon(kMutedSpeakerIcon, sizeof(kMutedSpeakerIcon), 537 fMutedIcon); 538 539 _LoadSettings(); 540 541 SetToolTip(new VolumeToolTip(fVolumeWhich)); 542 } 543 544 545 void 546 MediaReplicant::_DisconnectMixer() 547 { 548 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 549 if (roster == NULL) 550 return; 551 552 roster->StopWatching(this, B_MEDIA_SERVER_STARTED | B_MEDIA_NODE_CREATED); 553 554 if (fMixerControl->MuteNode() != media_node::null) { 555 roster->StopWatching(this, fMixerControl->MuteNode(), 556 B_MEDIA_NEW_PARAMETER_VALUE); 557 } 558 559 delete fMixerControl; 560 fMixerControl = NULL; 561 } 562 563 564 status_t 565 MediaReplicant::_ConnectMixer() 566 { 567 _DisconnectMixer(); 568 569 BMediaRoster* roster = BMediaRoster::Roster(); 570 if (roster == NULL) 571 return B_ERROR; 572 573 roster->StartWatching(this, B_MEDIA_SERVER_STARTED | B_MEDIA_NODE_CREATED); 574 575 fMixerControl = new MixerControl(fVolumeWhich); 576 577 const char* errorString = NULL; 578 float volume = 0.0; 579 fMixerControl->Connect(fVolumeWhich, &volume, &errorString); 580 581 if (errorString != NULL) { 582 SetToolTip(errorString); 583 return B_ERROR; 584 } 585 586 if (fMixerControl->MuteNode() != media_node::null) { 587 roster->StartWatching(this, fMixerControl->MuteNode(), 588 B_MEDIA_NEW_PARAMETER_VALUE); 589 fMuted = fMixerControl->Mute(); 590 } 591 592 return B_OK; 593 } 594 595 596 // #pragma mark - 597 598 599 extern "C" BView* 600 instantiate_deskbar_item(void) 601 { 602 return new MediaReplicant(BRect(0, 0, 16, 16), kReplicantName); 603 } 604 605