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