1 /* 2 * Copyright 2004-2018, Haiku Inc. All Rights Reserved. 3 * Copyright 2001 Dr. Zoidberg Enterprises. All rights reserved. 4 * 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 //! mail_daemon's deskbar menu and view 10 11 12 #include "DeskbarView.h" 13 14 #include <stdio.h> 15 #include <malloc.h> 16 17 #include <Bitmap.h> 18 #include <Catalog.h> 19 #include <Deskbar.h> 20 #include <Directory.h> 21 #include <Entry.h> 22 #include <FindDirectory.h> 23 #include <IconUtils.h> 24 #include <kernel/fs_info.h> 25 #include <kernel/fs_index.h> 26 #include <MenuItem.h> 27 #include <Messenger.h> 28 #include <NodeInfo.h> 29 #include <NodeMonitor.h> 30 #include <OpenWithTracker.h> 31 #include <Path.h> 32 #include <PopUpMenu.h> 33 #include <Query.h> 34 #include <Rect.h> 35 #include <Resources.h> 36 #include <Roster.h> 37 #include <String.h> 38 #include <StringFormat.h> 39 #include <SymLink.h> 40 #include <VolumeRoster.h> 41 #include <Window.h> 42 43 #include <E-mail.h> 44 #include <MailDaemon.h> 45 #include <MailSettings.h> 46 47 #include <MailPrivate.h> 48 49 #include "DeskbarViewIcons.h" 50 51 52 #undef B_TRANSLATION_CONTEXT 53 #define B_TRANSLATION_CONTEXT "DeskbarView" 54 55 56 const char* kTrackerSignature = "application/x-vnd.Be-TRAK"; 57 58 59 extern "C" _EXPORT BView* instantiate_deskbar_item(float maxWidth, 60 float maxHeight); 61 62 63 static status_t 64 our_image(image_info& image) 65 { 66 int32 cookie = 0; 67 while (get_next_image_info(B_CURRENT_TEAM, &cookie, &image) == B_OK) { 68 if ((char *)our_image >= (char *)image.text 69 && (char *)our_image <= (char *)image.text + image.text_size) 70 return B_OK; 71 } 72 73 return B_ERROR; 74 } 75 76 77 BView* 78 instantiate_deskbar_item(float maxWidth, float maxHeight) 79 { 80 return new DeskbarView(BRect(0, 0, maxHeight - 1, maxHeight - 1)); 81 } 82 83 84 // #pragma mark - 85 86 87 DeskbarView::DeskbarView(BRect frame) 88 : 89 BView(frame, "mail_daemon", B_FOLLOW_NONE, B_WILL_DRAW | B_PULSE_NEEDED), 90 fStatus(kStatusNoMail), 91 fLastButtons(0) 92 { 93 _InitBitmaps(); 94 } 95 96 97 DeskbarView::DeskbarView(BMessage *message) 98 : 99 BView(message), 100 fStatus(kStatusNoMail), 101 fLastButtons(0) 102 { 103 _InitBitmaps(); 104 } 105 106 107 DeskbarView::~DeskbarView() 108 { 109 for (int i = 0; i < kStatusCount; i++) 110 delete fBitmaps[i]; 111 112 for (int32 i = 0; i < fNewMailQueries.CountItems(); i++) 113 delete ((BQuery *)(fNewMailQueries.ItemAt(i))); 114 } 115 116 117 void DeskbarView::AttachedToWindow() 118 { 119 BView::AttachedToWindow(); 120 AdoptParentColors(); 121 122 if (ViewUIColor() == B_NO_COLOR) 123 SetLowColor(ViewColor()); 124 else 125 SetLowUIColor(ViewUIColor()); 126 127 if (be_roster->IsRunning(B_MAIL_DAEMON_SIGNATURE)) { 128 _RefreshMailQuery(); 129 } else { 130 BDeskbar deskbar; 131 deskbar.RemoveItem("mail_daemon"); 132 } 133 } 134 135 136 bool DeskbarView::_EntryInTrash(const entry_ref* ref) 137 { 138 BEntry entry(ref); 139 BVolume volume(ref->device); 140 BPath path; 141 if (volume.InitCheck() != B_OK 142 || find_directory(B_TRASH_DIRECTORY, &path, false, &volume) != B_OK) 143 return false; 144 145 BDirectory trash(path.Path()); 146 return trash.Contains(&entry); 147 } 148 149 150 void DeskbarView::_RefreshMailQuery() 151 { 152 for (int32 i = 0; i < fNewMailQueries.CountItems(); i++) 153 delete ((BQuery *)(fNewMailQueries.ItemAt(i))); 154 fNewMailQueries.MakeEmpty(); 155 156 BVolumeRoster volumes; 157 BVolume volume; 158 fNewMessages = 0; 159 160 while (volumes.GetNextVolume(&volume) == B_OK) { 161 BQuery *newMailQuery = new BQuery; 162 newMailQuery->SetTarget(this); 163 newMailQuery->SetVolume(&volume); 164 newMailQuery->PushAttr(B_MAIL_ATTR_STATUS); 165 newMailQuery->PushString("New"); 166 newMailQuery->PushOp(B_EQ); 167 newMailQuery->Fetch(); 168 169 BEntry entry; 170 while (newMailQuery->GetNextEntry(&entry) == B_OK) { 171 if (entry.InitCheck() == B_OK) { 172 entry_ref ref; 173 entry.GetRef(&ref); 174 if (!_EntryInTrash(&ref)) 175 fNewMessages++; 176 } 177 } 178 179 fNewMailQueries.AddItem(newMailQuery); 180 } 181 182 fStatus = (fNewMessages > 0) ? kStatusNewMail : kStatusNoMail; 183 Invalidate(); 184 } 185 186 187 DeskbarView* DeskbarView::Instantiate(BMessage *data) 188 { 189 if (!validate_instantiation(data, "DeskbarView")) 190 return NULL; 191 192 return new DeskbarView(data); 193 } 194 195 196 status_t DeskbarView::Archive(BMessage *data,bool deep) const 197 { 198 BView::Archive(data, deep); 199 200 data->AddString("add_on", B_MAIL_DAEMON_SIGNATURE); 201 return B_NO_ERROR; 202 } 203 204 205 void 206 DeskbarView::Draw(BRect /*updateRect*/) 207 { 208 if (fBitmaps[fStatus] == NULL) 209 return; 210 211 SetDrawingMode(B_OP_ALPHA); 212 DrawBitmap(fBitmaps[fStatus]); 213 SetDrawingMode(B_OP_COPY); 214 } 215 216 217 void 218 DeskbarView::MessageReceived(BMessage* message) 219 { 220 switch (message->what) { 221 case MD_CHECK_SEND_NOW: 222 // also happens in DeskbarView::MouseUp() with 223 // B_TERTIARY_MOUSE_BUTTON pressed 224 BMailDaemon().CheckAndSendQueuedMail(); 225 break; 226 case MD_CHECK_FOR_MAILS: 227 BMailDaemon().CheckMail(message->FindInt32("account")); 228 break; 229 case MD_SEND_MAILS: 230 BMailDaemon().SendQueuedMail(); 231 break; 232 233 case MD_OPEN_NEW: 234 { 235 char* argv[] = {(char *)"New Message", (char *)"mailto:"}; 236 be_roster->Launch("text/x-email", 2, argv); 237 break; 238 } 239 case MD_OPEN_PREFS: 240 be_roster->Launch("application/x-vnd.Haiku-Mail"); 241 break; 242 243 case MD_REFRESH_QUERY: 244 _RefreshMailQuery(); 245 break; 246 247 case B_QUERY_UPDATE: 248 { 249 int32 what; 250 dev_t device; 251 ino_t directory; 252 const char *name; 253 entry_ref ref; 254 message->FindInt32("opcode", &what); 255 message->FindInt32("device", &device); 256 message->FindInt64("directory", &directory); 257 switch (what) { 258 case B_ENTRY_CREATED: 259 if (message->FindString("name", &name) == B_OK) { 260 ref.device = device; 261 ref.directory = directory; 262 ref.set_name(name); 263 if (!_EntryInTrash(&ref)) 264 fNewMessages++; 265 } 266 break; 267 case B_ENTRY_REMOVED: 268 node_ref node; 269 node.device = device; 270 node.node = directory; 271 BDirectory dir(&node); 272 BEntry entry(&dir, NULL); 273 entry.GetRef(&ref); 274 if (!_EntryInTrash(&ref)) 275 fNewMessages--; 276 break; 277 } 278 fStatus = fNewMessages > 0 ? kStatusNewMail : kStatusNoMail; 279 Invalidate(); 280 break; 281 } 282 case B_QUIT_REQUESTED: 283 BMailDaemon().Quit(); 284 break; 285 286 // open received files in the standard mail application 287 case B_REFS_RECEIVED: 288 { 289 BMessage argv(B_ARGV_RECEIVED); 290 argv.AddString("argv", "E-mail"); 291 292 entry_ref ref; 293 BPath path; 294 int i = 0; 295 296 while (message->FindRef("refs", i++, &ref) == B_OK 297 && path.SetTo(&ref) == B_OK) { 298 //fprintf(stderr,"got %s\n", path.Path()); 299 argv.AddString("argv", path.Path()); 300 } 301 302 if (i > 1) { 303 argv.AddInt32("argc", i); 304 be_roster->Launch("text/x-email", &argv); 305 } 306 break; 307 } 308 default: 309 BView::MessageReceived(message); 310 } 311 } 312 313 314 void 315 DeskbarView::_InitBitmaps() 316 { 317 for (int i = 0; i < kStatusCount; i++) 318 fBitmaps[i] = NULL; 319 320 image_info info; 321 if (our_image(info) != B_OK) 322 return; 323 324 BFile file(info.name, B_READ_ONLY); 325 if (file.InitCheck() != B_OK) 326 return; 327 328 BResources resources(&file); 329 if (resources.InitCheck() != B_OK) 330 return; 331 332 for (int i = 0; i < kStatusCount; i++) { 333 const void* data = NULL; 334 size_t size; 335 data = resources.LoadResource(B_VECTOR_ICON_TYPE, 336 kIconNoMail + i, &size); 337 if (data != NULL) { 338 BBitmap* icon = new BBitmap(Bounds(), B_RGBA32); 339 if (icon->InitCheck() == B_OK 340 && BIconUtils::GetVectorIcon((const uint8 *)data, 341 size, icon) == B_OK) { 342 fBitmaps[i] = icon; 343 } else 344 delete icon; 345 } 346 } 347 } 348 349 350 void 351 DeskbarView::Pulse() 352 { 353 // TODO: Check if mail_daemon is still running 354 } 355 356 357 void 358 DeskbarView::MouseUp(BPoint pos) 359 { 360 if ((fLastButtons & B_PRIMARY_MOUSE_BUTTON) !=0 361 && OpenWithTracker(B_USER_SETTINGS_DIRECTORY, "Mail/mailbox") != B_OK) { 362 entry_ref ref; 363 _GetNewQueryRef(ref); 364 365 BMessenger trackerMessenger(kTrackerSignature); 366 BMessage message(B_REFS_RECEIVED); 367 message.AddRef("refs", &ref); 368 369 trackerMessenger.SendMessage(&message); 370 } 371 372 if ((fLastButtons & B_TERTIARY_MOUSE_BUTTON) != 0) 373 BMailDaemon().CheckMail(); 374 } 375 376 377 void 378 DeskbarView::MouseDown(BPoint pos) 379 { 380 Looper()->CurrentMessage()->FindInt32("buttons", &fLastButtons); 381 382 if ((fLastButtons & B_SECONDARY_MOUSE_BUTTON) != 0) { 383 ConvertToScreen(&pos); 384 385 BPopUpMenu* menu = _BuildMenu(); 386 menu->Go(pos, true, true, BRect(pos.x - 2, pos.y - 2, 387 pos.x + 2, pos.y + 2), true); 388 } 389 } 390 391 392 bool 393 DeskbarView::_CreateMenuLinks(BDirectory& directory, BPath& path) 394 { 395 status_t status = directory.SetTo(path.Path()); 396 if (status == B_OK) 397 return true; 398 399 // Check if the directory has to be created (and do it in this case, 400 // filling it with some standard links). Normally the installer will 401 // create the directory and fill it with links, so normally this doesn't 402 // get used. 403 404 BEntry entry(path.Path()); 405 if (status != B_ENTRY_NOT_FOUND 406 || entry.GetParent(&directory) < B_OK 407 || directory.CreateDirectory(path.Leaf(), NULL) < B_OK 408 || directory.SetTo(path.Path()) < B_OK) 409 return false; 410 411 BPath targetPath; 412 find_directory(B_USER_DIRECTORY, &targetPath); 413 targetPath.Append("mail/in"); 414 415 directory.CreateSymLink("Open Inbox Folder", targetPath.Path(), NULL); 416 targetPath.GetParent(&targetPath); 417 directory.CreateSymLink("Open Mail Folder", targetPath.Path(), NULL); 418 419 // create the draft query 420 421 BFile file; 422 if (directory.CreateFile("Open Draft", &file) < B_OK) 423 return true; 424 425 BString string("MAIL:draft==1"); 426 file.WriteAttrString("_trk/qrystr", &string); 427 string = "E-mail"; 428 file.WriteAttrString("_trk/qryinitmime", &string); 429 BNodeInfo(&file).SetType("application/x-vnd.Be-query"); 430 431 return true; 432 } 433 434 435 void 436 DeskbarView::_CreateNewMailQuery(BEntry& query) 437 { 438 BFile file(&query, B_READ_WRITE | B_CREATE_FILE); 439 if (file.InitCheck() != B_OK) 440 return; 441 442 BString string(B_MAIL_ATTR_STATUS "==\"New\""); 443 file.WriteAttrString("_trk/qrystr", &string); 444 file.WriteAttrString("_trk/qryinitstr", &string); 445 int32 mode = 'Fbyq'; 446 file.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0, &mode, sizeof(int32)); 447 string = "E-mail"; 448 file.WriteAttrString("_trk/qryinitmime", &string); 449 BNodeInfo(&file).SetType("application/x-vnd.Be-query"); 450 } 451 452 453 BPopUpMenu* 454 DeskbarView::_BuildMenu() 455 { 456 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 457 menu->SetFont(be_plain_font); 458 459 menu->AddItem(new BMenuItem(B_TRANSLATE("Create new message" 460 B_UTF8_ELLIPSIS), new BMessage(MD_OPEN_NEW))); 461 menu->AddSeparatorItem(); 462 463 BMessenger tracker(kTrackerSignature); 464 BNavMenu* navMenu; 465 BMenuItem* item; 466 BMessage* msg; 467 entry_ref ref; 468 469 BPath path; 470 find_directory(B_USER_SETTINGS_DIRECTORY, &path); 471 path.Append("Mail/Menu Links"); 472 473 BDirectory directory; 474 if (_CreateMenuLinks(directory, path)) { 475 int32 count = 0; 476 477 while (directory.GetNextRef(&ref) == B_OK) { 478 count++; 479 480 path.SetTo(&ref); 481 // the true here dereferences the symlinks all the way :) 482 BEntry entry(&ref, true); 483 484 // do we want to use the NavMenu, or just an ordinary BMenuItem? 485 // we are using the NavMenu only for directories and queries 486 bool useNavMenu = false; 487 488 if (entry.InitCheck() == B_OK) { 489 if (entry.IsDirectory()) 490 useNavMenu = true; 491 else if (entry.IsFile()) { 492 // Files should use the BMenuItem unless they are queries 493 char mimeString[B_MIME_TYPE_LENGTH]; 494 BNode node(&entry); 495 BNodeInfo info(&node); 496 if (info.GetType(mimeString) == B_OK 497 && strcmp(mimeString, "application/x-vnd.Be-query") 498 == 0) 499 useNavMenu = true; 500 } 501 // clobber the existing ref only if the symlink derefernces 502 // completely, otherwise we'll stick with what we have 503 entry.GetRef(&ref); 504 } 505 506 msg = new BMessage(B_REFS_RECEIVED); 507 msg->AddRef("refs", &ref); 508 509 if (useNavMenu) { 510 item = new BMenuItem(navMenu = new BNavMenu(path.Leaf(), 511 B_REFS_RECEIVED, tracker), msg); 512 navMenu->SetNavDir(&ref); 513 } else 514 item = new BMenuItem(path.Leaf(), msg); 515 516 menu->AddItem(item); 517 if (entry.InitCheck() != B_OK) 518 item->SetEnabled(false); 519 } 520 if (count > 0) 521 menu->AddSeparatorItem(); 522 } 523 524 // Hack for R5's buggy Query Notification 525 #ifdef HAIKU_TARGET_PLATFORM_BEOS 526 menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh New Mail Count"), 527 new BMessage(MD_REFRESH_QUERY))); 528 #endif 529 530 // The New E-mail query 531 532 if (fNewMessages > 0) { 533 static BStringFormat format(B_TRANSLATE( 534 "{0, plural, one{# new message} other{# new messages}}")); 535 BString string; 536 format.Format(string, fNewMessages); 537 538 _GetNewQueryRef(ref); 539 540 item = new BMenuItem(navMenu = new BNavMenu(string.String(), 541 B_REFS_RECEIVED, BMessenger(kTrackerSignature)), 542 msg = new BMessage(B_REFS_RECEIVED)); 543 msg->AddRef("refs", &ref); 544 navMenu->SetNavDir(&ref); 545 546 menu->AddItem(item); 547 } else { 548 menu->AddItem(item = new BMenuItem(B_TRANSLATE("No new messages"), 549 NULL)); 550 item->SetEnabled(false); 551 } 552 553 BMailAccounts accounts; 554 if ((modifiers() & B_SHIFT_KEY) != 0) { 555 BMenu *accountMenu = new BMenu(B_TRANSLATE("Check for mails only")); 556 BFont font; 557 menu->GetFont(&font); 558 accountMenu->SetFont(&font); 559 560 for (int32 i = 0; i < accounts.CountAccounts(); i++) { 561 BMailAccountSettings* account = accounts.AccountAt(i); 562 563 BMessage* message = new BMessage(MD_CHECK_FOR_MAILS); 564 message->AddInt32("account", account->AccountID()); 565 566 accountMenu->AddItem(new BMenuItem(account->Name(), message)); 567 } 568 if (accounts.CountAccounts() == 0) { 569 item = new BMenuItem(B_TRANSLATE("<no accounts>"), NULL); 570 item->SetEnabled(false); 571 accountMenu->AddItem(item); 572 } 573 accountMenu->SetTargetForItems(this); 574 menu->AddItem(new BMenuItem(accountMenu, 575 new BMessage(MD_CHECK_FOR_MAILS))); 576 577 // Not used: 578 // menu->AddItem(new BMenuItem(B_TRANSLATE("Check For Mails Only"), 579 // new BMessage(MD_CHECK_FOR_MAILS))); 580 menu->AddItem(new BMenuItem(B_TRANSLATE("Send pending mails"), 581 new BMessage(MD_SEND_MAILS))); 582 } else { 583 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Check for mail now"), 584 new BMessage(MD_CHECK_SEND_NOW))); 585 if (accounts.CountAccounts() == 0) 586 item->SetEnabled(false); 587 } 588 589 menu->AddSeparatorItem(); 590 menu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), 591 new BMessage(MD_OPEN_PREFS))); 592 593 if (modifiers() & B_SHIFT_KEY) { 594 menu->AddItem(new BMenuItem(B_TRANSLATE("Shutdown mail services"), 595 new BMessage(B_QUIT_REQUESTED))); 596 } 597 598 // Reset Item Targets (only those which aren't already set) 599 600 for (int32 i = menu->CountItems(); i-- > 0;) { 601 item = menu->ItemAt(i); 602 if (item != NULL && (msg = item->Message()) != NULL) { 603 if (msg->what == B_REFS_RECEIVED) 604 item->SetTarget(tracker); 605 else 606 item->SetTarget(this); 607 } 608 } 609 return menu; 610 } 611 612 613 status_t 614 DeskbarView::_GetNewQueryRef(entry_ref& ref) 615 { 616 BPath path; 617 find_directory(B_USER_SETTINGS_DIRECTORY, &path); 618 path.Append("Mail/New E-mail"); 619 BEntry query(path.Path()); 620 if (!query.Exists()) 621 _CreateNewMailQuery(query); 622 return query.GetRef(&ref); 623 } 624