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