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 break; 328 329 case ML_RESTART_MEDIA_SERVER: 330 { 331 fRestartThread = spawn_thread(&MediaWindow::_RestartMediaServices, 332 "restart_thread", B_NORMAL_PRIORITY, this); 333 if (fRestartThread < 0) 334 fprintf(stderr, "couldn't create restart thread\n"); 335 else 336 resume_thread(fRestartThread); 337 break; 338 } 339 case B_MEDIA_WEB_CHANGED: 340 case ML_SELECTED_NODE: 341 { 342 PRINT_OBJECT(*message); 343 344 MediaListItem* item = static_cast<MediaListItem*>( 345 fListView->ItemAt(fListView->CurrentSelection())); 346 if (item == NULL) 347 break; 348 349 fCurrentNode.SetTo(NULL); 350 _ClearParamView(); 351 item->AlterWindow(this); 352 break; 353 } 354 case B_MEDIA_SERVER_STARTED: 355 { 356 PRINT_OBJECT(*message); 357 _InitMedia(false); 358 break; 359 } 360 default: 361 BWindow::MessageReceived(message); 362 break; 363 } 364 } 365 366 367 // #pragma mark - private 368 369 370 void 371 MediaWindow::_InitWindow() 372 { 373 fListView = new BListView("media_list_view"); 374 fListView->SetSelectionMessage(new BMessage(ML_SELECTED_NODE)); 375 fListView->SetExplicitMinSize(BSize(140, B_SIZE_UNSET)); 376 377 // Add ScrollView to Media Menu for pretty border 378 BScrollView* scrollView = new BScrollView("listscroller", 379 fListView, 0, false, false, B_FANCY_BORDER); 380 381 // Create the Views 382 fTitleView = new BSeparatorView(B_HORIZONTAL, B_FANCY_BORDER); 383 fTitleView->SetLabel(B_TRANSLATE("Audio settings")); 384 fTitleView->SetFont(be_bold_font); 385 386 fContentLayout = new BCardLayout(); 387 new BView("content view", 0, fContentLayout); 388 fContentLayout->Owner()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 389 fContentLayout->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 390 391 fAudioView = new AudioSettingsView(); 392 fContentLayout->AddView(fAudioView); 393 394 fVideoView = new VideoSettingsView(); 395 fContentLayout->AddView(fVideoView); 396 397 fMidiView = new MidiSettingsView(); 398 fContentLayout->AddView(fMidiView); 399 400 // Layout all views 401 BLayoutBuilder::Group<>(this, B_HORIZONTAL) 402 .SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING, 403 B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING) 404 .Add(scrollView, 0.0f) 405 .AddGroup(B_VERTICAL) 406 .SetInsets(0, 0, 0, 0) 407 .Add(fTitleView) 408 .Add(fContentLayout); 409 410 // Start the window 411 fInitCheck = _InitMedia(true); 412 if (fInitCheck != B_OK) 413 PostMessage(B_QUIT_REQUESTED); 414 else if (IsHidden()) 415 Show(); 416 } 417 418 419 status_t 420 MediaWindow::_InitMedia(bool first) 421 { 422 status_t err = B_OK; 423 BMediaRoster* roster = BMediaRoster::Roster(&err); 424 425 if (first && err != B_OK) { 426 BAlert* alert = new BAlert("start_media_server", 427 B_TRANSLATE("Could not connect to the media server.\n" 428 "Would you like to start it ?"), 429 B_TRANSLATE("Quit"), 430 B_TRANSLATE("Start media server"), NULL, 431 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 432 alert->SetShortcut(0, B_ESCAPE); 433 if (alert->Go() == 0) 434 return B_ERROR; 435 436 Show(); 437 438 launch_media_server(); 439 } 440 441 Lock(); 442 443 bool isVideoSelected = true; 444 if (!first && fListView->ItemAt(0) != NULL 445 && fListView->ItemAt(0)->IsSelected()) 446 isVideoSelected = false; 447 448 while (fListView->CountItems() > 0) 449 delete fListView->RemoveItem((int32)0); 450 _EmptyNodeLists(); 451 452 // Grab Media Info 453 _FindNodes(); 454 455 // Add video nodes first. They might have an additional audio 456 // output or input, but still should be listed as video node. 457 _AddNodeItems(fVideoOutputs, MediaListItem::VIDEO_TYPE); 458 _AddNodeItems(fVideoInputs, MediaListItem::VIDEO_TYPE); 459 _AddNodeItems(fAudioOutputs, MediaListItem::AUDIO_TYPE); 460 _AddNodeItems(fAudioInputs, MediaListItem::AUDIO_TYPE); 461 462 fAudioView->AddOutputNodes(fAudioOutputs); 463 fAudioView->AddInputNodes(fAudioInputs); 464 fVideoView->AddOutputNodes(fVideoOutputs); 465 fVideoView->AddInputNodes(fVideoInputs); 466 467 // build our list view 468 DeviceListItem* audio = new DeviceListItem(B_TRANSLATE("Audio settings"), 469 MediaListItem::AUDIO_TYPE); 470 fListView->AddItem(audio); 471 472 MidiListItem* midi = new MidiListItem(B_TRANSLATE("MIDI Settings")); 473 fListView->AddItem(midi); 474 475 MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"), 476 MediaListItem::VIDEO_TYPE); 477 fListView->AddItem(video); 478 479 MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer")); 480 fListView->AddItem(mixer); 481 482 fListView->SortItems(&MediaListItem::Compare); 483 _UpdateListViewMinWidth(); 484 485 // Set default nodes for our setting views 486 media_node defaultNode; 487 dormant_node_info nodeInfo; 488 int32 outputID; 489 BString outputName; 490 491 if (roster->GetAudioInput(&defaultNode) == B_OK) { 492 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 493 fAudioView->SetDefaultInput(&nodeInfo); 494 // this causes our listview to be updated as well 495 } 496 497 if (roster->GetAudioOutput(&defaultNode, &outputID, &outputName) == B_OK) { 498 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 499 fAudioView->SetDefaultOutput(&nodeInfo); 500 fAudioView->SetDefaultChannel(outputID); 501 // this causes our listview to be updated as well 502 } 503 504 if (roster->GetVideoInput(&defaultNode) == B_OK) { 505 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 506 fVideoView->SetDefaultInput(&nodeInfo); 507 // this causes our listview to be updated as well 508 } 509 510 if (roster->GetVideoOutput(&defaultNode) == B_OK) { 511 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 512 fVideoView->SetDefaultOutput(&nodeInfo); 513 // this causes our listview to be updated as well 514 } 515 516 if (first) 517 fListView->Select(fListView->IndexOf(mixer)); 518 else if (isVideoSelected) 519 fListView->Select(fListView->IndexOf(video)); 520 else 521 fListView->Select(fListView->IndexOf(audio)); 522 523 Unlock(); 524 525 return B_OK; 526 } 527 528 529 void 530 MediaWindow::_FindNodes() 531 { 532 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 533 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 534 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 535 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 536 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 537 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 538 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 539 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 540 } 541 542 543 void 544 MediaWindow::_FindNodes(media_type type, uint64 kind, NodeList& into) 545 { 546 dormant_node_info nodeInfo[64]; 547 int32 nodeInfoCount = 64; 548 549 media_format format; 550 media_format* nodeInputFormat = NULL; 551 media_format* nodeOutputFormat = NULL; 552 format.type = type; 553 554 // output nodes must be BBufferConsumers => they have an input format 555 // input nodes must be BBufferProducers => they have an output format 556 if ((kind & B_PHYSICAL_OUTPUT) != 0) 557 nodeInputFormat = &format; 558 else if ((kind & B_PHYSICAL_INPUT) != 0) 559 nodeOutputFormat = &format; 560 else 561 return; 562 563 BMediaRoster* roster = BMediaRoster::Roster(); 564 565 if (roster->GetDormantNodes(nodeInfo, &nodeInfoCount, nodeInputFormat, 566 nodeOutputFormat, NULL, kind) != B_OK) { 567 // TODO: better error reporting! 568 fprintf(stderr, "error\n"); 569 return; 570 } 571 572 for (int32 i = 0; i < nodeInfoCount; i++) { 573 PRINT(("node : %s, media_addon %i, flavor_id %i\n", 574 nodeInfo[i].name, (int)nodeInfo[i].addon, 575 (int)nodeInfo[i].flavor_id)); 576 577 dormant_node_info* info = new dormant_node_info(); 578 strncpy(info->name, nodeInfo[i].name, B_MEDIA_NAME_LENGTH); 579 info->flavor_id = nodeInfo[i].flavor_id; 580 info->addon = nodeInfo[i].addon; 581 into.AddItem(info); 582 } 583 } 584 585 586 void 587 MediaWindow::_AddNodeItems(NodeList& list, MediaListItem::media_type type) 588 { 589 int32 count = list.CountItems(); 590 for (int32 i = 0; i < count; i++) { 591 dormant_node_info* info = list.ItemAt(i); 592 if (_FindNodeListItem(info) == NULL) 593 fListView->AddItem(new NodeListItem(info, type)); 594 } 595 } 596 597 598 void 599 MediaWindow::_EmptyNodeLists() 600 { 601 fAudioOutputs.MakeEmpty(); 602 fAudioInputs.MakeEmpty(); 603 fVideoOutputs.MakeEmpty(); 604 fVideoInputs.MakeEmpty(); 605 } 606 607 608 NodeListItem* 609 MediaWindow::_FindNodeListItem(dormant_node_info* info) 610 { 611 NodeListItem audioItem(info, MediaListItem::AUDIO_TYPE); 612 NodeListItem videoItem(info, MediaListItem::VIDEO_TYPE); 613 614 NodeListItem::Comparator audioComparator(&audioItem); 615 NodeListItem::Comparator videoComparator(&videoItem); 616 617 for (int32 i = 0; i < fListView->CountItems(); i++) { 618 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 619 item->Accept(audioComparator); 620 if (audioComparator.result == 0) 621 return static_cast<NodeListItem*>(item); 622 623 item->Accept(videoComparator); 624 if (videoComparator.result == 0) 625 return static_cast<NodeListItem*>(item); 626 } 627 return NULL; 628 } 629 630 631 void 632 MediaWindow::_UpdateListViewMinWidth() 633 { 634 float width = 0; 635 for (int32 i = 0; i < fListView->CountItems(); i++) { 636 BListItem* item = fListView->ItemAt(i); 637 width = max_c(width, item->Width()); 638 } 639 fListView->SetExplicitMinSize(BSize(width, B_SIZE_UNSET)); 640 fListView->InvalidateLayout(); 641 } 642 643 644 status_t 645 MediaWindow::_RestartMediaServices(void* data) 646 { 647 MediaWindow* window = (MediaWindow*)data; 648 649 shutdown_media_server(); 650 launch_media_server(); 651 652 return window->PostMessage(ML_RESTART_THREAD_FINISHED); 653 } 654 655 656 void 657 MediaWindow::_ClearParamView() 658 { 659 BLayoutItem* item = fContentLayout->VisibleItem(); 660 if (!item) 661 return; 662 663 BView* view = item->View(); 664 if (view != fVideoView && view != fAudioView && view != fMidiView) { 665 fContentLayout->RemoveItem(item); 666 delete item; 667 delete view; 668 delete fParamWeb; 669 fParamWeb = NULL; 670 } 671 } 672 673 674 void 675 MediaWindow::_MakeParamView() 676 { 677 if (!fCurrentNode.IsSet()) 678 return; 679 680 fParamWeb = NULL; 681 BMediaRoster* roster = BMediaRoster::Roster(); 682 if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) { 683 BRect hint(fContentLayout->Frame()); 684 BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint); 685 if (paramView) { 686 fContentLayout->AddView(paramView); 687 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 688 return; 689 } 690 } 691 692 _MakeEmptyParamView(); 693 } 694 695 696 void 697 MediaWindow::_MakeEmptyParamView() 698 { 699 fParamWeb = NULL; 700 701 BStringView* stringView = new BStringView("noControls", 702 B_TRANSLATE("This hardware has no controls.")); 703 704 BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 705 stringView->SetExplicitMaxSize(unlimited); 706 707 BAlignment centered(B_ALIGN_HORIZONTAL_CENTER, 708 B_ALIGN_VERTICAL_CENTER); 709 stringView->SetExplicitAlignment(centered); 710 stringView->SetAlignment(B_ALIGN_CENTER); 711 712 fContentLayout->AddView(stringView); 713 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 714 715 rgb_color panel = stringView->LowColor(); 716 stringView->SetHighColor(tint_color(panel, 717 B_DISABLED_LABEL_TINT)); 718 } 719 720