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