1 /* 2 * Copyright 2003-2012, Haiku, Inc. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Sikosis, Jérôme Duval 7 * yourpalal, Alex Wilson 8 */ 9 10 11 #include "MediaWindow.h" 12 13 #include <stdio.h> 14 15 #include <Alert.h> 16 #include <Application.h> 17 #include <Autolock.h> 18 #include <Button.h> 19 #include <CardLayout.h> 20 #include <Catalog.h> 21 #include <Debug.h> 22 #include <Deskbar.h> 23 #include <IconUtils.h> 24 #include <LayoutBuilder.h> 25 #include <Locale.h> 26 #include <MediaRoster.h> 27 #include <MediaTheme.h> 28 #include <Resources.h> 29 #include <Roster.h> 30 #include <Screen.h> 31 #include <ScrollView.h> 32 #include <SeparatorView.h> 33 #include <SpaceLayoutItem.h> 34 #include <StorageKit.h> 35 #include <String.h> 36 #include <TextView.h> 37 38 #include "Media.h" 39 #include "MediaIcons.h" 40 #include "MidiSettingsView.h" 41 42 #undef B_TRANSLATION_CONTEXT 43 #define B_TRANSLATION_CONTEXT "Media Window" 44 45 46 const uint32 ML_SELECTED_NODE = 'MlSN'; 47 const uint32 ML_RESTART_THREAD_FINISHED = 'MlRF'; 48 49 50 class NodeListItemUpdater : public MediaListItem::Visitor { 51 public: 52 typedef void (NodeListItem::*UpdateMethod)(bool); 53 54 NodeListItemUpdater(NodeListItem* target, UpdateMethod action) 55 : 56 fComparator(target), 57 fAction(action) 58 { 59 } 60 61 62 virtual void Visit(AudioMixerListItem*){} 63 virtual void Visit(DeviceListItem*){} 64 virtual void Visit(MidiListItem*){} 65 virtual void Visit(NodeListItem* item) 66 { 67 item->Accept(fComparator); 68 (item->*(fAction))(fComparator.result == 0); 69 } 70 71 private: 72 73 NodeListItem::Comparator fComparator; 74 UpdateMethod fAction; 75 }; 76 77 78 MediaWindow::SmartNode::SmartNode(const BMessenger& notifyHandler) 79 : 80 fNode(NULL), 81 fMessenger(notifyHandler) 82 { 83 } 84 85 86 MediaWindow::SmartNode::~SmartNode() 87 { 88 _FreeNode(); 89 } 90 91 92 void 93 MediaWindow::SmartNode::SetTo(const dormant_node_info* info) 94 { 95 _FreeNode(); 96 if (!info) 97 return; 98 99 fNode = new media_node(); 100 BMediaRoster* roster = BMediaRoster::Roster(); 101 102 status_t status = B_OK; 103 media_node_id node_id; 104 if (roster->GetInstancesFor(info->addon, info->flavor_id, &node_id) == B_OK) 105 status = roster->GetNodeFor(node_id, fNode); 106 else 107 status = roster->InstantiateDormantNode(*info, fNode, B_FLAVOR_IS_GLOBAL); 108 109 if (status != B_OK) { 110 fprintf(stderr, "SmartNode::SetTo error with node %" B_PRId32 111 ": %s\n", fNode->node, strerror(status)); 112 } 113 114 status = roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD); 115 if (status != B_OK) { 116 fprintf(stderr, "SmartNode::SetTo can't start watching for" 117 " node %" B_PRId32 "\n", fNode->node); 118 } 119 } 120 121 122 void 123 MediaWindow::SmartNode::SetTo(const media_node& node) 124 { 125 _FreeNode(); 126 fNode = new media_node(node); 127 BMediaRoster* roster = BMediaRoster::Roster(); 128 roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD); 129 } 130 131 132 bool 133 MediaWindow::SmartNode::IsSet() 134 { 135 return fNode != NULL; 136 } 137 138 139 MediaWindow::SmartNode::operator media_node() 140 { 141 if (fNode) 142 return *fNode; 143 media_node node; 144 return node; 145 } 146 147 148 void 149 MediaWindow::SmartNode::_FreeNode() 150 { 151 if (!IsSet()) 152 return; 153 154 BMediaRoster* roster = BMediaRoster::Roster(); 155 if (roster != NULL) { 156 status_t status = roster->StopWatching(fMessenger, 157 *fNode, B_MEDIA_WILDCARD); 158 if (status != B_OK) { 159 fprintf(stderr, "SmartNode::_FreeNode can't unwatch" 160 " media services for node %" B_PRId32 "\n", fNode->node); 161 } 162 163 roster->ReleaseNode(*fNode); 164 if (status != B_OK) { 165 fprintf(stderr, "SmartNode::_FreeNode can't release" 166 " node %" B_PRId32 "\n", fNode->node); 167 } 168 } 169 delete fNode; 170 fNode = NULL; 171 } 172 173 174 // #pragma mark - 175 176 177 MediaWindow::MediaWindow(BRect frame) 178 : 179 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Media"), B_TITLED_WINDOW, 180 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 181 fCurrentNode(BMessenger(this)), 182 fParamWeb(NULL), 183 fAudioInputs(5, true), 184 fAudioOutputs(5, true), 185 fVideoInputs(5, true), 186 fVideoOutputs(5, true), 187 fInitCheck(B_OK), 188 fRestartThread(-1) 189 { 190 _InitWindow(); 191 192 BMediaRoster* roster = BMediaRoster::Roster(); 193 roster->StartWatching(BMessenger(this, this), 194 B_MEDIA_SERVER_STARTED); 195 } 196 197 198 MediaWindow::~MediaWindow() 199 { 200 _EmptyNodeLists(); 201 _ClearParamView(); 202 203 char buffer[512]; 204 BRect rect = Frame(); 205 PRINT_OBJECT(rect); 206 snprintf(buffer, 512, "# MediaPrefs Settings\n rect = %i,%i,%i,%i\n", 207 int(rect.left), int(rect.top), int(rect.right), int(rect.bottom)); 208 209 BPath path; 210 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 211 path.Append(SETTINGS_FILE); 212 BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); 213 if (file.InitCheck() == B_OK) 214 file.Write(buffer, strlen(buffer)); 215 } 216 217 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 218 roster->StopWatching(BMessenger(this, this), 219 B_MEDIA_SERVER_STARTED); 220 } 221 222 223 status_t 224 MediaWindow::InitCheck() 225 { 226 return fInitCheck; 227 } 228 229 230 void 231 MediaWindow::SelectNode(const dormant_node_info* node) 232 { 233 fCurrentNode.SetTo(node); 234 _MakeParamView(); 235 fTitleView->SetLabel(node->name); 236 } 237 238 239 void 240 MediaWindow::SelectAudioSettings(const char* title) 241 { 242 fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fAudioView)); 243 fTitleView->SetLabel(title); 244 } 245 246 247 void 248 MediaWindow::SelectVideoSettings(const char* title) 249 { 250 fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fVideoView)); 251 fTitleView->SetLabel(title); 252 } 253 254 255 void 256 MediaWindow::SelectAudioMixer(const char* title) 257 { 258 media_node mixerNode; 259 BMediaRoster* roster = BMediaRoster::Roster(); 260 roster->GetAudioMixer(&mixerNode); 261 fCurrentNode.SetTo(mixerNode); 262 _MakeParamView(); 263 fTitleView->SetLabel(title); 264 } 265 266 267 void 268 MediaWindow::SelectMidiSettings(const char* title) 269 { 270 fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fMidiView)); 271 fTitleView->SetLabel(title); 272 } 273 274 275 void 276 MediaWindow::UpdateInputListItem(MediaListItem::media_type type, 277 const dormant_node_info* node) 278 { 279 NodeListItem compareTo(node, type); 280 NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultInput); 281 for (int32 i = 0; i < fListView->CountItems(); i++) { 282 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 283 item->Accept(updater); 284 } 285 fListView->Invalidate(); 286 } 287 288 289 void 290 MediaWindow::UpdateOutputListItem(MediaListItem::media_type type, 291 const dormant_node_info* node) 292 { 293 NodeListItem compareTo(node, type); 294 NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultOutput); 295 for (int32 i = 0; i < fListView->CountItems(); i++) { 296 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 297 item->Accept(updater); 298 } 299 fListView->Invalidate(); 300 } 301 302 303 bool 304 MediaWindow::QuitRequested() 305 { 306 status_t exit = B_OK; 307 if (fRestartThread > 0) { 308 wait_for_thread(fRestartThread, &exit); 309 if (exit != B_OK) { 310 fprintf(stderr, "MediaWindow::QuitRequested wait_for_thread" 311 " returned with an error: %s\n", strerror(exit)); 312 } 313 } 314 // Stop watching the MediaRoster 315 fCurrentNode.SetTo(NULL); 316 be_app->PostMessage(B_QUIT_REQUESTED); 317 return true; 318 } 319 320 321 void 322 MediaWindow::MessageReceived(BMessage* message) 323 { 324 switch (message->what) { 325 case ML_RESTART_THREAD_FINISHED: 326 fRestartThread = -1; 327 _InitMedia(false); 328 break; 329 330 case ML_RESTART_MEDIA_SERVER: 331 { 332 fRestartThread = spawn_thread(&MediaWindow::_RestartMediaServices, 333 "restart_thread", B_NORMAL_PRIORITY, this); 334 if (fRestartThread < 0) 335 fprintf(stderr, "couldn't create restart thread\n"); 336 else 337 resume_thread(fRestartThread); 338 break; 339 } 340 case B_MEDIA_WEB_CHANGED: 341 case ML_SELECTED_NODE: 342 { 343 PRINT_OBJECT(*message); 344 345 MediaListItem* item = static_cast<MediaListItem*>( 346 fListView->ItemAt(fListView->CurrentSelection())); 347 if (item == NULL) 348 break; 349 350 fCurrentNode.SetTo(NULL); 351 _ClearParamView(); 352 item->AlterWindow(this); 353 break; 354 } 355 case B_MEDIA_SERVER_STARTED: 356 { 357 PRINT_OBJECT(*message); 358 _InitMedia(false); 359 break; 360 } 361 default: 362 BWindow::MessageReceived(message); 363 break; 364 } 365 } 366 367 368 // #pragma mark - private 369 370 371 void 372 MediaWindow::_InitWindow() 373 { 374 fListView = new BListView("media_list_view"); 375 fListView->SetSelectionMessage(new BMessage(ML_SELECTED_NODE)); 376 fListView->SetExplicitMinSize(BSize(140, B_SIZE_UNSET)); 377 378 // Add ScrollView to Media Menu for pretty border 379 BScrollView* scrollView = new BScrollView("listscroller", 380 fListView, 0, false, false, B_FANCY_BORDER); 381 382 // Create the Views 383 fTitleView = new BSeparatorView(B_HORIZONTAL, B_FANCY_BORDER); 384 fTitleView->SetLabel(B_TRANSLATE("Audio settings")); 385 fTitleView->SetFont(be_bold_font); 386 387 fContentLayout = new BCardLayout(); 388 new BView("content view", 0, fContentLayout); 389 fContentLayout->Owner()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 390 fContentLayout->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 391 392 fAudioView = new AudioSettingsView(); 393 fContentLayout->AddView(fAudioView); 394 395 fVideoView = new VideoSettingsView(); 396 fContentLayout->AddView(fVideoView); 397 398 fMidiView = new MidiSettingsView(); 399 fContentLayout->AddView(fMidiView); 400 401 // Layout all views 402 BLayoutBuilder::Group<>(this, B_HORIZONTAL) 403 .SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING, 404 B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING) 405 .Add(scrollView, 0.0f) 406 .AddGroup(B_VERTICAL) 407 .SetInsets(0, 0, 0, 0) 408 .Add(fTitleView) 409 .Add(fContentLayout); 410 411 // Start the window 412 fInitCheck = _InitMedia(true); 413 if (fInitCheck != B_OK) 414 PostMessage(B_QUIT_REQUESTED); 415 else if (IsHidden()) 416 Show(); 417 } 418 419 420 status_t 421 MediaWindow::_InitMedia(bool first) 422 { 423 status_t err = B_OK; 424 BMediaRoster* roster = BMediaRoster::Roster(&err); 425 426 if (first && err != B_OK) { 427 BAlert* alert = new BAlert("start_media_server", 428 B_TRANSLATE("Could not connect to the media server.\n" 429 "Would you like to start it ?"), 430 B_TRANSLATE("Quit"), 431 B_TRANSLATE("Start media server"), NULL, 432 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 433 alert->SetShortcut(0, B_ESCAPE); 434 if (alert->Go() == 0) 435 return B_ERROR; 436 437 Show(); 438 439 launch_media_server(); 440 } 441 442 Lock(); 443 444 bool isVideoSelected = true; 445 if (!first && fListView->ItemAt(0) != NULL 446 && fListView->ItemAt(0)->IsSelected()) 447 isVideoSelected = false; 448 449 while (fListView->CountItems() > 0) 450 delete fListView->RemoveItem((int32)0); 451 _EmptyNodeLists(); 452 453 // Grab Media Info 454 _FindNodes(); 455 456 // Add video nodes first. They might have an additional audio 457 // output or input, but still should be listed as video node. 458 _AddNodeItems(fVideoOutputs, MediaListItem::VIDEO_TYPE); 459 _AddNodeItems(fVideoInputs, MediaListItem::VIDEO_TYPE); 460 _AddNodeItems(fAudioOutputs, MediaListItem::AUDIO_TYPE); 461 _AddNodeItems(fAudioInputs, MediaListItem::AUDIO_TYPE); 462 463 fAudioView->AddOutputNodes(fAudioOutputs); 464 fAudioView->AddInputNodes(fAudioInputs); 465 fVideoView->AddOutputNodes(fVideoOutputs); 466 fVideoView->AddInputNodes(fVideoInputs); 467 468 // build our list view 469 DeviceListItem* audio = new DeviceListItem(B_TRANSLATE("Audio settings"), 470 MediaListItem::AUDIO_TYPE); 471 fListView->AddItem(audio); 472 473 MidiListItem* midi = new MidiListItem(B_TRANSLATE("MIDI Settings")); 474 fListView->AddItem(midi); 475 476 MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"), 477 MediaListItem::VIDEO_TYPE); 478 fListView->AddItem(video); 479 480 MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer")); 481 fListView->AddItem(mixer); 482 483 fListView->SortItems(&MediaListItem::Compare); 484 _UpdateListViewMinWidth(); 485 486 // Set default nodes for our setting views 487 media_node defaultNode; 488 dormant_node_info nodeInfo; 489 int32 outputID; 490 BString outputName; 491 492 if (roster->GetAudioInput(&defaultNode) == B_OK) { 493 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 494 fAudioView->SetDefaultInput(&nodeInfo); 495 // this causes our listview to be updated as well 496 } 497 498 if (roster->GetAudioOutput(&defaultNode, &outputID, &outputName) == B_OK) { 499 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 500 fAudioView->SetDefaultOutput(&nodeInfo); 501 fAudioView->SetDefaultChannel(outputID); 502 // this causes our listview to be updated as well 503 } 504 505 if (roster->GetVideoInput(&defaultNode) == B_OK) { 506 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 507 fVideoView->SetDefaultInput(&nodeInfo); 508 // this causes our listview to be updated as well 509 } 510 511 if (roster->GetVideoOutput(&defaultNode) == B_OK) { 512 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 513 fVideoView->SetDefaultOutput(&nodeInfo); 514 // this causes our listview to be updated as well 515 } 516 517 if (first) 518 fListView->Select(fListView->IndexOf(mixer)); 519 else if (isVideoSelected) 520 fListView->Select(fListView->IndexOf(video)); 521 else 522 fListView->Select(fListView->IndexOf(audio)); 523 524 Unlock(); 525 526 return B_OK; 527 } 528 529 530 void 531 MediaWindow::_FindNodes() 532 { 533 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 534 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 535 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 536 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 537 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 538 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 539 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 540 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 541 } 542 543 544 void 545 MediaWindow::_FindNodes(media_type type, uint64 kind, NodeList& into) 546 { 547 dormant_node_info nodeInfo[64]; 548 int32 nodeInfoCount = 64; 549 550 media_format format; 551 media_format* nodeInputFormat = NULL; 552 media_format* nodeOutputFormat = NULL; 553 format.type = type; 554 555 // output nodes must be BBufferConsumers => they have an input format 556 // input nodes must be BBufferProducers => they have an output format 557 if ((kind & B_PHYSICAL_OUTPUT) != 0) 558 nodeInputFormat = &format; 559 else if ((kind & B_PHYSICAL_INPUT) != 0) 560 nodeOutputFormat = &format; 561 else 562 return; 563 564 BMediaRoster* roster = BMediaRoster::Roster(); 565 566 if (roster->GetDormantNodes(nodeInfo, &nodeInfoCount, nodeInputFormat, 567 nodeOutputFormat, NULL, kind) != B_OK) { 568 // TODO: better error reporting! 569 fprintf(stderr, "error\n"); 570 return; 571 } 572 573 for (int32 i = 0; i < nodeInfoCount; i++) { 574 PRINT(("node : %s, media_addon %i, flavor_id %i\n", 575 nodeInfo[i].name, (int)nodeInfo[i].addon, 576 (int)nodeInfo[i].flavor_id)); 577 578 dormant_node_info* info = new dormant_node_info(); 579 strncpy(info->name, nodeInfo[i].name, B_MEDIA_NAME_LENGTH); 580 info->flavor_id = nodeInfo[i].flavor_id; 581 info->addon = nodeInfo[i].addon; 582 into.AddItem(info); 583 } 584 } 585 586 587 void 588 MediaWindow::_AddNodeItems(NodeList& list, MediaListItem::media_type type) 589 { 590 int32 count = list.CountItems(); 591 for (int32 i = 0; i < count; i++) { 592 dormant_node_info* info = list.ItemAt(i); 593 if (_FindNodeListItem(info) == NULL) 594 fListView->AddItem(new NodeListItem(info, type)); 595 } 596 } 597 598 599 void 600 MediaWindow::_EmptyNodeLists() 601 { 602 fAudioOutputs.MakeEmpty(); 603 fAudioInputs.MakeEmpty(); 604 fVideoOutputs.MakeEmpty(); 605 fVideoInputs.MakeEmpty(); 606 } 607 608 609 NodeListItem* 610 MediaWindow::_FindNodeListItem(dormant_node_info* info) 611 { 612 NodeListItem audioItem(info, MediaListItem::AUDIO_TYPE); 613 NodeListItem videoItem(info, MediaListItem::VIDEO_TYPE); 614 615 NodeListItem::Comparator audioComparator(&audioItem); 616 NodeListItem::Comparator videoComparator(&videoItem); 617 618 for (int32 i = 0; i < fListView->CountItems(); i++) { 619 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 620 item->Accept(audioComparator); 621 if (audioComparator.result == 0) 622 return static_cast<NodeListItem*>(item); 623 624 item->Accept(videoComparator); 625 if (videoComparator.result == 0) 626 return static_cast<NodeListItem*>(item); 627 } 628 return NULL; 629 } 630 631 632 void 633 MediaWindow::_UpdateListViewMinWidth() 634 { 635 float width = 0; 636 for (int32 i = 0; i < fListView->CountItems(); i++) { 637 BListItem* item = fListView->ItemAt(i); 638 width = max_c(width, item->Width()); 639 } 640 fListView->SetExplicitMinSize(BSize(width, B_SIZE_UNSET)); 641 fListView->InvalidateLayout(); 642 } 643 644 645 status_t 646 MediaWindow::_RestartMediaServices(void* data) 647 { 648 MediaWindow* window = (MediaWindow*)data; 649 650 shutdown_media_server(); 651 launch_media_server(); 652 653 return window->PostMessage(ML_RESTART_THREAD_FINISHED); 654 } 655 656 657 void 658 MediaWindow::_ClearParamView() 659 { 660 BLayoutItem* item = fContentLayout->VisibleItem(); 661 if (!item) 662 return; 663 664 BView* view = item->View(); 665 if (view != fVideoView && view != fAudioView && view != fMidiView) { 666 fContentLayout->RemoveItem(item); 667 delete item; 668 delete view; 669 delete fParamWeb; 670 fParamWeb = NULL; 671 } 672 } 673 674 675 void 676 MediaWindow::_MakeParamView() 677 { 678 if (!fCurrentNode.IsSet()) 679 return; 680 681 fParamWeb = NULL; 682 BMediaRoster* roster = BMediaRoster::Roster(); 683 if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) { 684 BRect hint(fContentLayout->Frame()); 685 BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint); 686 if (paramView) { 687 fContentLayout->AddView(paramView); 688 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 689 return; 690 } 691 } 692 693 _MakeEmptyParamView(); 694 } 695 696 697 void 698 MediaWindow::_MakeEmptyParamView() 699 { 700 fParamWeb = NULL; 701 702 BStringView* stringView = new BStringView("noControls", 703 B_TRANSLATE("This hardware has no controls.")); 704 705 BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 706 stringView->SetExplicitMaxSize(unlimited); 707 708 BAlignment centered(B_ALIGN_HORIZONTAL_CENTER, 709 B_ALIGN_VERTICAL_CENTER); 710 stringView->SetExplicitAlignment(centered); 711 stringView->SetAlignment(B_ALIGN_CENTER); 712 713 fContentLayout->AddView(stringView); 714 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 715 716 rgb_color panel = stringView->LowColor(); 717 stringView->SetHighColor(tint_color(panel, 718 B_DISABLED_LABEL_TINT)); 719 } 720 721