xref: /haiku/src/bin/desklink/desklink.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
1 /*
2  * Copyright 2003-2008, 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  */
11 
12 //! VolumeControl and link items in Deskbar
13 
14 #include "VolumeSlider.h"
15 #include "DeskButton.h"
16 #include "iconfile.h"
17 
18 #include <Alert.h>
19 #include <Application.h>
20 #include <Bitmap.h>
21 #include <Debug.h>
22 #include <Deskbar.h>
23 #include <Dragger.h>
24 #include <File.h>
25 #include <FindDirectory.h>
26 #include <List.h>
27 #include <MenuItem.h>
28 #include <Message.h>
29 #include <MimeType.h>
30 #include <Path.h>
31 #include <PopUpMenu.h>
32 #include <Roster.h>
33 #include <String.h>
34 #include <View.h>
35 
36 #include <stdio.h>
37 #include <strings.h>
38 
39 #define MEDIA_SETTINGS 'mese'
40 #define SOUND_SETTINGS 'sose'
41 #define OPEN_MEDIA_PLAYER 'omep'
42 #define TOGGLE_DONT_BEEP 'tdbp'
43 #define SET_VOLUME_WHICH 'svwh'
44 
45 #define VOLUME_CTL_NAME "MediaReplicant"
46 	// R5 name needed, Media prefs manel removes by name
47 
48 #define SETTINGS_FILE "x-vnd.Haiku-desklink"
49 
50 const char *kAppSignature = "application/x-vnd.Haiku-desklink";
51 	// the application signature used by the replicant to find the
52 	// supporting code
53 
54 
55 class _EXPORT MediaReplicant;
56 	// the dragger part has to be exported
57 
58 class MediaReplicant : public BView {
59 public:
60 	MediaReplicant(BRect frame, const char *name,
61 		uint32 resizeMask = B_FOLLOW_ALL,
62 		uint32 flags = B_WILL_DRAW | B_NAVIGABLE);
63 	MediaReplicant(BMessage *);
64 		// BMessage * based constructor needed to support archiving
65 	virtual ~MediaReplicant();
66 
67 	// archiving overrides
68 	static MediaReplicant *Instantiate(BMessage *data);
69 	virtual	status_t Archive(BMessage *data, bool deep = true) const;
70 
71 	// misc BView overrides
72 	virtual void AttachedToWindow();
73 	virtual void MouseDown(BPoint);
74 	virtual void MouseUp(BPoint);
75 	virtual void Draw(BRect updateRect);
76 	virtual void MessageReceived(BMessage* message);
77 
78 private:
79 	void _AlertFindDirectory(status_t status, const char *where);
80 	status_t _LaunchByPath(const char *path);
81 	status_t _LaunchBySig(const char *sig);
82 	void _LoadSettings();
83 	void _SaveSettings();
84 
85 	BBitmap*		fSegments;
86 	VolumeSlider*	fVolumeSlider;
87 	bool 			fDontBeep;
88 		// don't beep on volume change
89 	int32 			fVolumeWhich;
90 		// which volume parameter to act on (Mixer/Phys.Output)
91 };
92 
93 //
94 //	This is the exported function that will be used by Deskbar
95 //	to create and add the replicant
96 //
97 extern "C" _EXPORT BView* instantiate_deskbar_item();
98 
99 BView *
100 instantiate_deskbar_item()
101 {
102 	return new MediaReplicant(BRect(0, 0, 16, 16), VOLUME_CTL_NAME);
103 }
104 
105 
106 MediaReplicant::MediaReplicant(BRect frame, const char *name,
107 		uint32 resizeMask, uint32 flags)
108 	: BView(frame, name, resizeMask, flags),
109 	fVolumeSlider(NULL)
110 {
111 	// Background Bitmap
112 	fSegments = new BBitmap(BRect(0, 0, kSpeakerWidth - 1, kSpeakerHeight - 1), B_CMAP8);
113 	fSegments->SetBits(kSpeakerBits, kSpeakerWidth*kSpeakerHeight, 0, B_CMAP8);
114 	_LoadSettings();
115 }
116 
117 
118 MediaReplicant::MediaReplicant(BMessage *message)
119 	: BView(message),
120 	fVolumeSlider(NULL)
121 {
122 	// Background Bitmap
123 	fSegments = new BBitmap(BRect(0, 0, 16 - 1, 16 - 1), B_CMAP8);
124 	fSegments->SetBits(kSpeakerBits, 16*16, 0, B_CMAP8);
125 	_LoadSettings();
126 }
127 
128 
129 MediaReplicant::~MediaReplicant()
130 {
131 	delete fSegments;
132 	_SaveSettings();
133 }
134 
135 
136 MediaReplicant *
137 MediaReplicant::Instantiate(BMessage *data)
138 {
139 	if (!validate_instantiation(data, VOLUME_CTL_NAME))
140 		return NULL;
141 
142 	return new MediaReplicant(data);
143 }
144 
145 
146 status_t
147 MediaReplicant::Archive(BMessage *data, bool deep) const
148 {
149 	status_t status = BView::Archive(data, deep);
150 	if (status < B_OK)
151 		return status;
152 
153 	return data->AddString("add_on", kAppSignature);
154 }
155 
156 
157 void
158 MediaReplicant::MessageReceived(BMessage *message)
159 {
160 	switch (message->what) {
161 	case B_ABOUT_REQUESTED:
162 		(new BAlert("About Volume Control", "Volume Control (Replicant)\n"
163 			    "  Brought to you by Jérôme DUVAL.\n\n"
164 			    "Copyright " B_UTF8_COPYRIGHT "2003-2008, Haiku","OK"))->Go(NULL);
165 		break;
166 
167 	case OPEN_MEDIA_PLAYER:
168 	{
169 		BPath mediaPlayerPath;
170 		status_t status = find_directory(B_BEOS_APPS_DIRECTORY, &mediaPlayerPath);
171 		if (status != B_OK) {
172 			_AlertFindDirectory(status, __PRETTY_FUNCTION__);
173 			break;
174 		}
175 		mediaPlayerPath.Append("MediaPlayer");
176 
177 		// launch the media player app
178 		if (_LaunchBySig("application/x-vnd.Haiku-MediaPlayer") == B_OK
179 			|| _LaunchBySig("application/x-vnd.Be.MediaPlayer") == B_OK
180 			|| _LaunchByPath(mediaPlayerPath.Path()) == B_OK)
181 			break;
182 
183 		(new BAlert("desklink", "Couldn't launch MediaPlayer", "OK"))->Go();
184 		break;
185 	}
186 
187 	case MEDIA_SETTINGS:
188 	{
189 		BPath mediaPrefsPath;
190 		status_t status = find_directory(B_BEOS_PREFERENCES_DIRECTORY,
191 			&mediaPrefsPath);
192 		if (status != B_OK) {
193 			_AlertFindDirectory(status, __PRETTY_FUNCTION__);
194 			break;
195 		}
196 		mediaPrefsPath.Append("Media");
197 
198 		// launch the media prefs app
199 		if (_LaunchBySig("application/x-vnd.Haiku-Media") == B_OK
200 			|| _LaunchBySig("application/x-vnd.Be.MediaPrefs") == B_OK
201 			|| _LaunchByPath(mediaPrefsPath.Path()) == B_OK)
202 			break;
203 
204 		(new BAlert("desklink", "Couldn't launch Media Preferences", "OK"))->Go();
205 		break;
206 	}
207 
208 	case SOUND_SETTINGS:
209 	{
210 		BPath soundsPrefsPath;
211 		status_t status = find_directory(B_BEOS_PREFERENCES_DIRECTORY,
212 			&soundsPrefsPath);
213 		if (status != B_OK) {
214 			_AlertFindDirectory(status, __PRETTY_FUNCTION__);
215 			break;
216 		}
217 		soundsPrefsPath.Append("Sounds");
218 
219 		// launch the sounds prefs app
220 		if (_LaunchBySig("application/x-vnd.Haiku-Sounds") == B_OK
221 			|| _LaunchBySig("application/x-vnd.Be.SoundsPrefs") == B_OK
222 			|| _LaunchByPath(soundsPrefsPath.Path()) == B_OK)
223 			break;
224 
225 		(new BAlert("desklink", "Couldn't launch Sounds Preferences", "OK"))->Go();
226 		break;
227 	}
228 
229 	case TOGGLE_DONT_BEEP:
230 		fDontBeep = !fDontBeep;
231 		break;
232 
233 	case SET_VOLUME_WHICH:
234 		message->FindInt32("volwhich", &fVolumeWhich);
235 		break;
236 
237 	default:
238 		BView::MessageReceived(message);
239 		break;
240 	}
241 }
242 
243 
244 status_t
245 MediaReplicant::_LaunchByPath(const char *path)
246 {
247 	BEntry ent;
248 	entry_ref ref;
249 	app_info appInfo;
250 	status_t err;
251 
252 	err = ent.SetTo(path);
253 	if (err)
254 		return err;
255 	err = ent.GetRef(&ref);
256 	if (err)
257 		return err;
258 	err = be_roster->Launch(&ref);
259 	if (err != B_ALREADY_RUNNING)
260 		return err; // should be B_OK or fatal error
261 	err = be_roster->GetAppInfo(&ref, &appInfo);
262 	if (err)
263 		return err;
264 	return be_roster->ActivateApp(appInfo.team);
265 }
266 
267 
268 status_t
269 MediaReplicant::_LaunchBySig(const char *sig)
270 {
271 	app_info appInfo;
272 	status_t err;
273 
274 	err = be_roster->Launch(sig);
275 	if (err != B_ALREADY_RUNNING)
276 		return err; // should be B_OK or fatal error
277 	err = be_roster->GetAppInfo(sig, &appInfo);
278 	if (err)
279 		return err;
280 	return be_roster->ActivateApp(appInfo.team);
281 }
282 
283 
284 void
285 MediaReplicant::AttachedToWindow()
286 {
287 	BView *parent = Parent();
288 	if (parent)
289 		SetViewColor(parent->ViewColor());
290 
291 	BView::AttachedToWindow();
292 }
293 
294 
295 void
296 MediaReplicant::Draw(BRect rect)
297 {
298 	BView::Draw(rect);
299 
300 	SetDrawingMode(B_OP_OVER);
301 	DrawBitmap(fSegments);
302 }
303 
304 
305 void
306 MediaReplicant::MouseDown(BPoint point)
307 {
308 	uint32 mouseButtons;
309 	BPoint where;
310 	GetMouse(&where, &mouseButtons, true);
311 
312 	where = ConvertToScreen(point);
313 
314 	if (mouseButtons & B_SECONDARY_MOUSE_BUTTON) {
315 		BPopUpMenu *menu = new BPopUpMenu("", false, false);
316 		menu->SetFont(be_plain_font);
317 		menu->AddItem(new BMenuItem("Media Preferences" B_UTF8_ELLIPSIS, new BMessage(MEDIA_SETTINGS)));
318 		menu->AddItem(new BMenuItem("Sound Preferences" B_UTF8_ELLIPSIS, new BMessage(SOUND_SETTINGS)));
319 		menu->AddSeparatorItem();
320 		menu->AddItem(new BMenuItem("Open MediaPlayer", new BMessage(OPEN_MEDIA_PLAYER)));
321 		menu->AddSeparatorItem();
322 		BMenuItem *tmpItem = new BMenuItem("Don't beep", new BMessage(TOGGLE_DONT_BEEP));
323 		menu->AddItem(tmpItem);
324 		tmpItem->SetMarked(fDontBeep);
325 		BMenu *volMenu = new BMenu("Act On");
326 		volMenu->SetFont(be_plain_font);
327 		BMessage *msg;
328 		msg = new BMessage(SET_VOLUME_WHICH);
329 		msg->AddInt32("volwhich", VOLUME_USE_MIXER);
330 		tmpItem = new BMenuItem("System Mixer", msg);
331 		tmpItem->SetMarked(fVolumeWhich == VOLUME_USE_MIXER);
332 		volMenu->AddItem(tmpItem);
333 		msg = new BMessage(SET_VOLUME_WHICH);
334 		msg->AddInt32("volwhich", VOLUME_USE_PHYS_OUTPUT);
335 		tmpItem = new BMenuItem("Physical Output", msg);
336 		volMenu->AddItem(tmpItem);
337 		tmpItem->SetMarked(fVolumeWhich == VOLUME_USE_PHYS_OUTPUT);
338 		menu->AddItem(volMenu);
339 
340 		menu->AddSeparatorItem();
341 		menu->SetFont(be_plain_font);
342 		menu->AddItem(new BMenuItem("About" B_UTF8_ELLIPSIS,
343 			new BMessage(B_ABOUT_REQUESTED)));
344 
345 		menu->SetTargetForItems(this);
346 		volMenu->SetTargetForItems(this);
347 		menu->Go(where, true, true, BRect(where - BPoint(4, 4),
348 			where + BPoint(4, 4)));
349 	} else if (mouseButtons & B_PRIMARY_MOUSE_BUTTON) {
350 		// Show VolumeSlider
351 		fVolumeSlider = new VolumeSlider(BRect(where.x, where.y, where.x + 207, where.y + 19),
352 			fDontBeep, fVolumeWhich);
353 		fVolumeSlider->Show();
354 	}
355 }
356 
357 
358 void
359 MediaReplicant::MouseUp(BPoint point)
360 {
361 	// don't Quit() ! thanks for FFM users
362 }
363 
364 
365 void
366 MediaReplicant::_LoadSettings()
367 {
368 	fDontBeep = false;
369 	fVolumeWhich = VOLUME_USE_MIXER;
370 
371 	BPath p;
372 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &p, false) < B_OK)
373 		return;
374 	p.SetTo(p.Path(), SETTINGS_FILE);
375 	BFile settings(p.Path(), B_READ_ONLY);
376 	if (settings.InitCheck() < B_OK)
377 		return;
378 	BMessage msg;
379 	if (msg.Unflatten(&settings) < B_OK)
380 		return;
381 	msg.FindInt32("volwhich", &fVolumeWhich);
382 	msg.FindBool("dontbeep", &fDontBeep);
383 }
384 
385 
386 void
387 MediaReplicant::_SaveSettings()
388 {
389 	BPath p;
390 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &p, false) < B_OK)
391 		return;
392 	p.SetTo(p.Path(), SETTINGS_FILE);
393 	BFile settings(p.Path(), B_WRITE_ONLY|B_CREATE_FILE|B_ERASE_FILE);
394 	if (settings.InitCheck() < B_OK)
395 		return;
396 	BMessage msg('CNFG');
397 	msg.AddInt32("volwhich", fVolumeWhich);
398 	msg.AddBool("dontbeep", fDontBeep);
399 	ssize_t len=0;
400 	if (msg.Flatten(&settings, &len) < B_OK)
401 		return;
402 }
403 
404 
405 void
406 MediaReplicant::_AlertFindDirectory(status_t status, const char *where)
407 {
408 	BString errorMessage;
409 	errorMessage << "At " << where << "\n";
410 	errorMessage << "find_directory() failed. \nReason: ";
411 	errorMessage << strerror(status);
412 	(new BAlert("AlertError", errorMessage.String(), "OK", NULL, NULL,
413 		B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go();
414 }
415 
416 
417 int
418 main(int, char **argv)
419 {
420 	BApplication app(kAppSignature);
421 	bool atLeastOnePath = false;
422 	BList titleList;
423 	BList actionList;
424 	BDeskbar deskbar;
425 	status_t err = B_OK;
426 
427 	for (int32 i = 1; argv[i]!=NULL; i++) {
428 		if (strcmp(argv[i], "--help") == 0)
429 			break;
430 
431 		if (strcmp(argv[i], "--list") == 0) {
432 			int32 i, found = 0, count;
433 			count = deskbar.CountItems();
434 			printf("Deskbar items:\n");
435 			// the API is doomed, so don't try to enum for too long
436 			for (i = 0; (found < count) && (i >= 0) && (i < 5000); i++) {
437 				const char scratch[2] = ""; // BDeskbar is buggy
438 				const char *name=scratch;
439 				if (deskbar.GetItemInfo(i, &name) >= B_OK) {
440 					found++;
441 					printf("Item %ld: '%s'\n", i, name);
442 					free((void *)name); // INTENDED
443 				}
444 			}
445 			return 0;
446 		}
447 
448 		if (strncmp(argv[i], "--remove", 8) == 0) {
449 			BString replicant = "DeskButton";
450 			if (strncmp(argv[i] + 8, "=", 1) == 0) {
451 				if (strlen(argv[i] + 9) > 0) {
452 					replicant = argv[i] + 9;
453 				} else {
454 					printf("desklink: Missing replicant name.\n");
455 					return 1;
456 				}
457 			}
458 			int32 found = 0;
459 			int32 found_id;
460 			while (deskbar.GetItemInfo(replicant.String(), &found_id) == B_OK) {
461 				err = deskbar.RemoveItem(found_id);
462 				if (err != B_OK) {
463 					printf("desklink: Error removing replicant id %ld: %s\n",
464 						found_id, strerror(err));
465 					break;
466 				}
467 				found++;
468 			}
469 			printf("Removed %ld items.\n", found);
470 			return err;
471 		}
472 
473 		if (strncmp(argv[i], "cmd=", 4) == 0) {
474 			BString *title = new BString(argv[i] + 4);
475 			int32 index = title->FindFirst(':');
476 			if (index <= 0) {
477 				printf("desklink: usage: cmd=title:action\n");
478 			} else {
479 				title->Truncate(index);
480 				BString *action = new BString(argv[i] + 4);
481 				action->Remove(0, index+1);
482 				titleList.AddItem(title);
483 				actionList.AddItem(action);
484 			}
485 			continue;
486 		}
487 
488 		atLeastOnePath = true;
489 
490 		BEntry entry(argv[i], true);
491 		entry_ref ref;
492 
493 		if (entry.Exists()) {
494 			entry.GetRef(&ref);
495 		} else if (BMimeType::IsValid(argv[i])) {
496 			if (be_roster->FindApp(argv[i], &ref) != B_OK) {
497 				printf("desklink: cannot find '%s'\n", argv[i]);
498 				return 1;
499 			}
500 		} else {
501 			printf("desklink: cannot find '%s'\n", argv[i]);
502 			return 1;
503 		}
504 
505 		err = deskbar.AddItem(&ref);
506 		if (err != B_OK) {
507 			err = deskbar.AddItem(new DeskButton(BRect(0, 0, 15, 15),
508 				&ref, "DeskButton", titleList, actionList));
509 			if (err != B_OK) {
510 				printf("desklink: Deskbar refuses link to '%s': %s\n", argv[i], strerror(err));
511 				return 1;
512 			}
513 		}
514 
515 		titleList.MakeEmpty();
516 		actionList.MakeEmpty();
517 	}
518 
519 	if (!atLeastOnePath) {
520 		printf(	"usage: desklink { [ --list|--remove|[cmd=title:action ... ] [ path|signature ] } ...\n"
521 			"--list: list all Deskbar addons.\n"
522 			"--remove: remove all desklink addons.\n"
523 			"--remove=name: remove all 'name' addons.\n");
524 		return 1;
525 	}
526 
527 	return 0;
528 }
529