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