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