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 <ToolTip.h> 36 #include <ToolTipManager.h> 37 38 #include "desklink.h" 39 #include "iconfile.h" 40 #include "MixerControl.h" 41 #include "VolumeWindow.h" 42 43 44 #undef B_TRANSLATION_CONTEXT 45 #define B_TRANSLATION_CONTEXT "MediaReplicant" 46 47 48 static const uint32 kMsgOpenMediaSettings = 'mese'; 49 static const uint32 kMsgOpenSoundSettings = 'sose'; 50 static const uint32 kMsgOpenMediaPlayer = 'omep'; 51 static const uint32 kMsgToggleBeep = 'tdbp'; 52 static const uint32 kMsgVolumeWhich = 'svwh'; 53 54 static const char* kReplicantName = "MediaReplicant"; 55 // R5 name needed, Media prefs manel removes by name 56 57 static const char* kSettingsFile = "x-vnd.Haiku-desklink"; 58 59 60 class VolumeToolTip : public BToolTip { 61 public: 62 VolumeToolTip(int32 which = VOLUME_USE_MIXER) 63 : 64 fWhich(which) 65 { 66 fView = new BStringView("", ""); 67 } 68 69 virtual ~VolumeToolTip() 70 { 71 delete fView; 72 } 73 74 virtual BView* View() const 75 { 76 return fView; 77 } 78 79 virtual void AttachedToWindow() 80 { 81 Update(); 82 } 83 84 void SetWhich(int32 which) 85 { 86 fWhich = which; 87 } 88 89 void Update() 90 { 91 if (!Lock()) 92 return; 93 94 if (fMuteMessage.Length() != 0) 95 fView->SetText(fMuteMessage.String()); 96 else { 97 MixerControl control; 98 control.Connect(fWhich); 99 100 BString text; 101 text.SetToFormat(B_TRANSLATE("%g dB"), control.Volume()); 102 fView->SetText(text.String()); 103 } 104 105 Unlock(); 106 } 107 108 void SetMuteMessage(const char* message) 109 { 110 fMuteMessage = message == NULL ? "" : message; 111 } 112 113 private: 114 BStringView* fView; 115 int32 fWhich; 116 BString fMuteMessage; 117 }; 118 119 120 class MediaReplicant : public BView { 121 public: 122 MediaReplicant(BRect frame, const char* name, 123 uint32 resizeMask = B_FOLLOW_ALL, 124 uint32 flags = B_WILL_DRAW | B_NAVIGABLE 125 | B_PULSE_NEEDED); 126 MediaReplicant(BMessage* archive); 127 128 virtual ~MediaReplicant(); 129 130 // archiving overrides 131 static MediaReplicant* Instantiate(BMessage* data); 132 virtual status_t Archive(BMessage* data, bool deep = true) const; 133 134 // BView overrides 135 virtual void AttachedToWindow(); 136 virtual void MouseDown(BPoint point); 137 virtual void Draw(BRect updateRect); 138 virtual void MessageReceived(BMessage* message); 139 virtual void Pulse(); 140 141 private: 142 status_t _LaunchByPath(const char* path); 143 status_t _LaunchBySignature(const char* signature); 144 void _Launch(const char* prettyName, 145 const char* signature, directory_which base, 146 const char* fileName); 147 void _LoadSettings(); 148 void _SaveSettings(); 149 void _Init(); 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 MediaReplicant::MediaReplicant(BRect frame, const char* name, 163 uint32 resizeMask, uint32 flags) 164 : 165 BView(frame, name, resizeMask, flags), 166 fVolumeSlider(NULL), 167 fMuted(false) 168 { 169 _Init(); 170 } 171 172 173 MediaReplicant::MediaReplicant(BMessage* message) 174 : 175 BView(message), 176 fVolumeSlider(NULL), 177 fMuted(false) 178 { 179 _Init(); 180 } 181 182 183 MediaReplicant::~MediaReplicant() 184 { 185 delete fIcon; 186 _SaveSettings(); 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 BView* parent = Parent(); 215 if (parent) 216 SetViewColor(parent->ViewColor()); 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 MixerControl mixerControl; 278 if (mixerControl.Connect(fVolumeWhich)) { 279 mixerControl.SetMute(!fMuted); 280 fMuted = mixerControl.Mute(); 281 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 282 if (tip != NULL) { 283 tip->SetMuteMessage(fMuted ? B_TRANSLATE("Muted"): NULL); 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::Pulse() 300 { 301 bool setMuted = false; 302 MixerControl mixerControl; 303 const char* errorString = NULL; 304 if (!mixerControl.Connect(fVolumeWhich, NULL, &errorString)) { 305 fMuted = true; 306 errorString = NULL; 307 } else 308 setMuted = mixerControl.Mute(); 309 310 if (setMuted != fMuted) { 311 fMuted = setMuted; 312 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 313 if (tip != NULL) 314 tip->SetMuteMessage(errorString); 315 Invalidate(); 316 } 317 } 318 319 320 void 321 MediaReplicant::MessageReceived(BMessage* message) 322 { 323 switch (message->what) { 324 case kMsgOpenMediaPlayer: 325 _Launch("MediaPlayer", "application/x-vnd.Haiku-MediaPlayer", 326 B_SYSTEM_APPS_DIRECTORY, "MediaPlayer"); 327 break; 328 329 case kMsgOpenMediaSettings: 330 _Launch("Media Preferences", "application/x-vnd.Haiku-Media", 331 B_SYSTEM_PREFERENCES_DIRECTORY, "Media"); 332 break; 333 334 case kMsgOpenSoundSettings: 335 _Launch("Sounds Preferences", "application/x-vnd.Haiku-Sounds", 336 B_SYSTEM_PREFERENCES_DIRECTORY, "Sounds"); 337 break; 338 339 case kMsgToggleBeep: 340 { 341 BMenuItem* item; 342 if (message->FindPointer("source", (void**)&item) != B_OK) 343 return; 344 345 item->SetMarked(!item->IsMarked()); 346 fDontBeep = !item->IsMarked(); 347 break; 348 } 349 350 case kMsgVolumeWhich: 351 { 352 BMenuItem* item; 353 if (message->FindPointer("source", (void**)&item) != B_OK) 354 return; 355 356 item->SetMarked(!item->IsMarked()); 357 fVolumeWhich = item->IsMarked() 358 ? VOLUME_USE_PHYS_OUTPUT : VOLUME_USE_MIXER; 359 360 if (VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip())) 361 tip->SetWhich(fVolumeWhich); 362 break; 363 } 364 365 case B_MOUSE_WHEEL_CHANGED: 366 { 367 float deltaY; 368 if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK 369 && deltaY != 0.0) { 370 MixerControl mixerControl; 371 mixerControl.Connect(fVolumeWhich); 372 mixerControl.ChangeVolumeBy(deltaY < 0 ? 6 : -6); 373 374 VolumeToolTip* tip = dynamic_cast<VolumeToolTip*>(ToolTip()); 375 if (tip != NULL) { 376 tip->Update(); 377 ShowToolTip(tip); 378 } 379 } 380 break; 381 } 382 383 default: 384 BView::MessageReceived(message); 385 break; 386 } 387 } 388 389 390 status_t 391 MediaReplicant::_LaunchByPath(const char* path) 392 { 393 entry_ref ref; 394 status_t status = get_ref_for_path(path, &ref); 395 if (status != B_OK) 396 return status; 397 398 status = be_roster->Launch(&ref); 399 if (status != B_ALREADY_RUNNING) 400 return status; 401 402 // The application runs already, bring it to front 403 404 app_info appInfo; 405 status = be_roster->GetAppInfo(&ref, &appInfo); 406 if (status != B_OK) 407 return status; 408 409 return be_roster->ActivateApp(appInfo.team); 410 } 411 412 413 status_t 414 MediaReplicant::_LaunchBySignature(const char* signature) 415 { 416 status_t status = be_roster->Launch(signature); 417 if (status != B_ALREADY_RUNNING) 418 return status; 419 420 // The application runs already, bring it to front 421 422 app_info appInfo; 423 status = be_roster->GetAppInfo(signature, &appInfo); 424 if (status != B_OK) 425 return status; 426 427 return be_roster->ActivateApp(appInfo.team); 428 } 429 430 431 void 432 MediaReplicant::_Launch(const char* prettyName, const char* signature, 433 directory_which base, const char* fileName) 434 { 435 BPath path; 436 status_t status = find_directory(base, &path); 437 if (status == B_OK) 438 path.Append(fileName); 439 440 // launch the application 441 if (_LaunchBySignature(signature) != B_OK 442 && _LaunchByPath(path.Path()) != B_OK) { 443 BString message = B_TRANSLATE("Couldn't launch "); 444 message << prettyName; 445 446 BAlert* alert = new BAlert(B_TRANSLATE("desklink"), message.String(), 447 B_TRANSLATE("OK")); 448 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 449 alert->Go(); 450 } 451 } 452 453 454 void 455 MediaReplicant::_LoadSettings() 456 { 457 fDontBeep = false; 458 fVolumeWhich = VOLUME_USE_MIXER; 459 460 BPath path; 461 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK) 462 return; 463 464 path.Append(kSettingsFile); 465 466 BFile settings(path.Path(), B_READ_ONLY); 467 if (settings.InitCheck() < B_OK) 468 return; 469 470 BMessage msg; 471 if (msg.Unflatten(&settings) < B_OK) 472 return; 473 474 msg.FindInt32("volwhich", &fVolumeWhich); 475 msg.FindBool("dontbeep", &fDontBeep); 476 } 477 478 479 void 480 MediaReplicant::_SaveSettings() 481 { 482 BPath path; 483 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, false) < B_OK) 484 return; 485 486 path.Append(kSettingsFile); 487 488 BFile settings(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 489 if (settings.InitCheck() < B_OK) 490 return; 491 492 BMessage msg('CNFG'); 493 msg.AddInt32("volwhich", fVolumeWhich); 494 msg.AddBool("dontbeep", fDontBeep); 495 496 ssize_t size = 0; 497 msg.Flatten(&settings, &size); 498 } 499 500 501 void 502 MediaReplicant::_Init() 503 { 504 fIcon = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1), 505 B_RGBA32); 506 BIconUtils::GetVectorIcon(kSpeakerIcon, sizeof(kSpeakerIcon), fIcon); 507 508 fMutedIcon = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1), 509 B_RGBA32); 510 BIconUtils::GetVectorIcon(kMutedSpeakerIcon, sizeof(kMutedSpeakerIcon), 511 fMutedIcon); 512 513 _LoadSettings(); 514 515 SetToolTip(new VolumeToolTip(fVolumeWhich)); 516 } 517 518 519 // #pragma mark - 520 521 522 extern "C" BView* 523 instantiate_deskbar_item(void) 524 { 525 return new MediaReplicant(BRect(0, 0, 16, 16), kReplicantName); 526 } 527 528