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