xref: /haiku/src/bin/desklink/MediaReplicant.cpp (revision 4bd0c1066b227cec4b79883bdef697c7a27f2e90)
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