1 /* 2 * Copyright 2010, Haiku, Inc. All Rights Reserved. 3 * Copyright 2008-2009, Pier Luigi Fiorini. All Rights Reserved. 4 * Copyright 2004-2008, Michael Davidson. All Rights Reserved. 5 * Copyright 2004-2007, Mikael Eiman. All Rights Reserved. 6 * Distributed under the terms of the MIT License. 7 * 8 * Authors: 9 * Michael Davidson, slaad@bong.com.au 10 * Mikael Eiman, mikael@eiman.tv 11 * Pier Luigi Fiorini, pierluigi.fiorini@gmail.com 12 */ 13 14 #include <algorithm> 15 16 #include <Alert.h> 17 #include <Application.h> 18 #include <Debug.h> 19 #include <File.h> 20 #include <NodeMonitor.h> 21 #include <PropertyInfo.h> 22 23 #include "AppGroupView.h" 24 #include "AppUsage.h" 25 #include "BorderView.h" 26 #include "NotificationWindow.h" 27 28 property_info main_prop_list[] = { 29 { "message", {B_GET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0}, "get a message"}, 30 { "message", {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0}, "count messages"}, 31 { "message", {B_CREATE_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0}, "create a message"}, 32 { "message", {B_SET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0 }, "modify a message" }, 33 0 34 }; 35 36 const float kCloseSize = 8; 37 const float kExpandSize = 8; 38 const float kPenSize = 1; 39 const float kEdgePadding = 5; 40 const float kSmallPadding = 2; 41 42 43 NotificationWindow::NotificationWindow() 44 : 45 BWindow(BRect(10, 10, 30, 30), "Notification", B_BORDERED_WINDOW, 46 B_AVOID_FRONT | B_AVOID_FOCUS | B_NOT_CLOSABLE | B_NOT_ZOOMABLE 47 | B_NOT_MINIMIZABLE | B_NOT_RESIZABLE, B_ALL_WORKSPACES) 48 { 49 fBorder = new BorderView(Bounds(), "Notification"); 50 51 AddChild(fBorder); 52 53 Show(); 54 Hide(); 55 56 LoadSettings(true); 57 LoadAppFilters(true); 58 } 59 60 61 NotificationWindow::~NotificationWindow() 62 { 63 appfilter_t::iterator aIt; 64 for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++) 65 delete aIt->second; 66 } 67 68 69 bool 70 NotificationWindow::QuitRequested() 71 { 72 appview_t::iterator aIt; 73 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 74 aIt->second->RemoveSelf(); 75 delete aIt->second; 76 } 77 78 BMessenger(be_app).SendMessage(B_QUIT_REQUESTED); 79 return BWindow::QuitRequested(); 80 } 81 82 83 void 84 NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active) 85 { 86 // Ensure window is in the correct position 87 if (active) 88 ResizeAll(); 89 } 90 91 92 void 93 NotificationWindow::MessageReceived(BMessage* message) 94 { 95 switch (message->what) { 96 case B_NODE_MONITOR: 97 { 98 LoadSettings(); 99 LoadAppFilters(); 100 break; 101 } 102 case kResizeToFit: 103 ResizeAll(); 104 break; 105 case B_COUNT_PROPERTIES: 106 { 107 BMessage reply(B_REPLY); 108 BMessage specifier; 109 const char* property = NULL; 110 bool messageOkay = true; 111 112 if (message->FindMessage("specifiers", 0, &specifier) != B_OK) 113 messageOkay = false; 114 if (specifier.FindString("property", &property) != B_OK) 115 messageOkay = false; 116 if (strcmp(property, "message") != 0) 117 messageOkay = false; 118 119 if (messageOkay) 120 reply.AddInt32("result", fViews.size()); 121 else { 122 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 123 reply.AddInt32("error", B_ERROR); 124 } 125 126 message->SendReply(&reply); 127 break; 128 } 129 case B_CREATE_PROPERTY: 130 case kNotificationMessage: 131 { 132 int32 type; 133 const char* content = NULL; 134 const char* title = NULL; 135 const char* app = NULL; 136 BMessage reply(B_REPLY); 137 bool messageOkay = true; 138 139 if (message->FindInt32("type", &type) != B_OK) 140 type = B_INFORMATION_NOTIFICATION; 141 if (message->FindString("content", &content) != B_OK) 142 messageOkay = false; 143 if (message->FindString("title", &title) != B_OK) 144 messageOkay = false; 145 if (message->FindString("app", &app) != B_OK && message->FindString("appTitle", &app) != B_OK) 146 messageOkay = false; 147 148 if (messageOkay) { 149 NotificationView* view = new NotificationView(this, (notification_type)type, app, 150 title, content, new BMessage(*message)); 151 152 appfilter_t::iterator fIt = fAppFilters.find(app); 153 bool allow = false; 154 if (fIt == fAppFilters.end()) { 155 app_info info; 156 BMessenger messenger = message->ReturnAddress(); 157 if (messenger.IsValid()) 158 be_roster->GetRunningAppInfo(messenger.Team(), &info); 159 else 160 be_roster->GetAppInfo("application/x-vnd.Be-SHEL", &info); 161 162 AppUsage* appUsage = new AppUsage(info.ref, app, true); 163 fAppFilters[app] = appUsage; 164 165 appUsage->Allowed(title, (notification_type)type); 166 167 allow = true; 168 } else 169 allow = fIt->second->Allowed(title, (notification_type)type); 170 171 if (allow) { 172 appview_t::iterator aIt = fAppViews.find(app); 173 AppGroupView *group = NULL; 174 if (aIt == fAppViews.end()) { 175 group = new AppGroupView(this, app); 176 fAppViews[app] = group; 177 fBorder->AddChild(group); 178 } else { 179 group = aIt->second; 180 }; 181 group->AddInfo(view); 182 183 ResizeAll(); 184 185 reply.AddInt32("error", B_OK); 186 } else 187 reply.AddInt32("Error", B_ERROR); 188 } else { 189 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 190 reply.AddInt32("error", B_ERROR); 191 } 192 193 message->SendReply(&reply); 194 break; 195 } 196 case kRemoveView: 197 { 198 void* _ptr; 199 message->FindPointer("view", &_ptr); 200 201 NotificationView* info 202 = reinterpret_cast<NotificationView*>(_ptr); 203 204 fBorder->RemoveChild(info); 205 206 std::vector<NotificationView*>::iterator i 207 = find(fViews.begin(), fViews.end(), info); 208 if (i != fViews.end()) 209 fViews.erase(i); 210 211 delete info; 212 213 ResizeAll(); 214 break; 215 } 216 default: 217 BWindow::MessageReceived(message); 218 } 219 } 220 221 222 BHandler* 223 NotificationWindow::ResolveSpecifier(BMessage *msg, int32 index, 224 BMessage *spec, int32 form, const char *prop) 225 { 226 BPropertyInfo prop_info(main_prop_list); 227 BHandler* handler = NULL; 228 229 if (strcmp(prop,"message") == 0) { 230 switch (msg->what) { 231 case B_CREATE_PROPERTY: 232 { 233 msg->PopSpecifier(); 234 handler = this; 235 break; 236 } 237 case B_SET_PROPERTY: 238 case B_GET_PROPERTY: 239 { 240 int32 i; 241 242 if (spec->FindInt32("index", &i) != B_OK) 243 i = -1; 244 245 if (i >= 0 && i < (int32)fViews.size()) { 246 msg->PopSpecifier(); 247 handler = fViews[i]; 248 } else 249 handler = NULL; 250 break; 251 } 252 case B_COUNT_PROPERTIES: 253 msg->PopSpecifier(); 254 handler = this; 255 break; 256 default: 257 break; 258 } 259 } 260 261 if (!handler) 262 handler = BWindow::ResolveSpecifier(msg, index, spec, form, prop); 263 264 return handler; 265 } 266 267 268 icon_size 269 NotificationWindow::IconSize() 270 { 271 return fIconSize; 272 } 273 274 275 int32 276 NotificationWindow::Timeout() 277 { 278 return fTimeout; 279 } 280 281 282 283 infoview_layout 284 NotificationWindow::Layout() 285 { 286 return fLayout; 287 } 288 289 290 float 291 NotificationWindow::ViewWidth() 292 { 293 return fWidth; 294 } 295 296 297 void 298 NotificationWindow::ResizeAll() 299 { 300 if (fAppViews.empty()) { 301 if (!IsHidden()) 302 Hide(); 303 return; 304 } 305 306 appview_t::iterator aIt; 307 bool shouldHide = true; 308 309 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 310 AppGroupView *app = aIt->second; 311 if (app->HasChildren()) { 312 shouldHide = false; 313 break; 314 } 315 } 316 317 if (shouldHide) { 318 if (!IsHidden()) 319 Hide(); 320 return; 321 } 322 323 if (IsHidden()) 324 Show(); 325 326 float width = 0; 327 float height = 0; 328 329 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 330 AppGroupView* view = aIt->second; 331 float w = -1; 332 float h = -1; 333 334 if (!view->HasChildren()) { 335 if (!view->IsHidden()) 336 view->Hide(); 337 } else { 338 view->GetPreferredSize(&w, &h); 339 width = max_c(width, h); 340 341 view->ResizeToPreferred(); 342 view->MoveTo(0, height); 343 344 height += h; 345 346 if (view->IsHidden()) 347 view->Show(); 348 } 349 } 350 351 ResizeTo(ViewWidth(), height); 352 PopupAnimation(Bounds().Width(), Bounds().Height()); 353 } 354 355 356 void 357 NotificationWindow::PopupAnimation(float width, float height) 358 { 359 float x = 0, y = 0, sx, sy; 360 float pad = 0; 361 BDeskbar deskbar; 362 BRect frame = deskbar.Frame(); 363 364 switch (deskbar.Location()) { 365 case B_DESKBAR_TOP: 366 // Put it just under, top right corner 367 sx = frame.right; 368 sy = frame.bottom+pad; 369 y = sy; 370 x = sx-width-pad; 371 break; 372 case B_DESKBAR_BOTTOM: 373 // Put it just above, lower left corner 374 sx = frame.right; 375 sy = frame.top-height-pad; 376 y = sy; 377 x = sx - width-pad; 378 break; 379 case B_DESKBAR_LEFT_TOP: 380 // Put it just to the right of the deskbar 381 sx = frame.right+pad; 382 sy = frame.top-height; 383 x = sx; 384 y = frame.top+pad; 385 break; 386 case B_DESKBAR_RIGHT_TOP: 387 // Put it just to the left of the deskbar 388 sx = frame.left-width-pad; 389 sy = frame.top-height; 390 x = sx; 391 y = frame.top+pad; 392 break; 393 case B_DESKBAR_LEFT_BOTTOM: 394 // Put it to the right of the deskbar. 395 sx = frame.right+pad; 396 sy = frame.bottom; 397 x = sx; 398 y = sy-height-pad; 399 break; 400 case B_DESKBAR_RIGHT_BOTTOM: 401 // Put it to the left of the deskbar. 402 sx = frame.left-width-pad; 403 sy = frame.bottom; 404 y = sy-height-pad; 405 x = sx; 406 break; 407 default: 408 break; 409 } 410 411 MoveTo(x, y); 412 413 if (IsHidden() && fViews.size() != 0) 414 Show(); 415 //Activate();// it hides floaters from apps :-( 416 } 417 418 419 void 420 NotificationWindow::LoadSettings(bool startMonitor) 421 { 422 _LoadGeneralSettings(startMonitor); 423 _LoadDisplaySettings(startMonitor); 424 } 425 426 427 void 428 NotificationWindow::LoadAppFilters(bool startMonitor) 429 { 430 BPath path; 431 432 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 433 return; 434 435 path.Append(kSettingsDirectory); 436 437 if (create_directory(path.Path(), 0755) != B_OK) 438 return; 439 440 path.Append(kFiltersSettings); 441 442 BFile file(path.Path(), B_READ_ONLY); 443 BMessage settings; 444 if (settings.Unflatten(&file) != B_OK) 445 return; 446 447 type_code type; 448 int32 count = 0; 449 450 if (settings.GetInfo("app_usage", &type, &count) != B_OK) 451 return; 452 453 for (int32 i = 0; i < count; i++) { 454 AppUsage* app = new AppUsage(); 455 settings.FindFlat("app_usage", i, app); 456 fAppFilters[app->Name()] = app; 457 } 458 459 if (startMonitor) { 460 node_ref nref; 461 BEntry entry(path.Path()); 462 entry.GetNodeRef(&nref); 463 464 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 465 BAlert* alert = new BAlert("", "Couldn't start filter " 466 " monitor. Live filter changes disabled.", "Darn."); 467 alert->Go(); 468 } 469 } 470 } 471 472 473 void 474 NotificationWindow::SaveAppFilters() 475 { 476 BPath path; 477 478 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 479 return; 480 481 path.Append(kSettingsDirectory); 482 path.Append(kFiltersSettings); 483 484 BMessage settings; 485 BFile file(path.Path(), B_WRITE_ONLY); 486 487 appfilter_t::iterator fIt; 488 for (fIt = fAppFilters.begin(); fIt != fAppFilters.end(); fIt++) 489 settings.AddFlat("app_usage", fIt->second); 490 491 settings.Flatten(&file); 492 } 493 494 495 void 496 NotificationWindow::_LoadGeneralSettings(bool startMonitor) 497 { 498 BPath path; 499 BMessage settings; 500 501 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 502 return; 503 504 path.Append(kSettingsDirectory); 505 if (create_directory(path.Path(), 0755) == B_OK) { 506 path.Append(kGeneralSettings); 507 508 BFile file(path.Path(), B_READ_ONLY); 509 settings.Unflatten(&file); 510 } 511 512 if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK) 513 fTimeout = kDefaultTimeout; 514 515 // Notify the view about the change 516 views_t::iterator it; 517 for (it = fViews.begin(); it != fViews.end(); ++it) { 518 NotificationView* view = (*it); 519 view->SetText(view->Application(), view->Title(), view->Text()); 520 view->Invalidate(); 521 } 522 523 if (startMonitor) { 524 node_ref nref; 525 BEntry entry(path.Path()); 526 entry.GetNodeRef(&nref); 527 528 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 529 BAlert* alert = new BAlert("", "Couldn't start general settings " 530 " monitor.\nLive filter changes disabled.", "OK"); 531 alert->Go(); 532 } 533 } 534 } 535 536 537 void 538 NotificationWindow::_LoadDisplaySettings(bool startMonitor) 539 { 540 BPath path; 541 BMessage settings; 542 543 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 544 return; 545 546 path.Append(kSettingsDirectory); 547 if (create_directory(path.Path(), 0755) == B_OK) { 548 path.Append(kDisplaySettings); 549 550 BFile file(path.Path(), B_READ_ONLY); 551 settings.Unflatten(&file); 552 } 553 554 int32 setting; 555 556 if (settings.FindFloat(kWidthName, &fWidth) != B_OK) 557 fWidth = kDefaultWidth; 558 559 if (settings.FindInt32(kIconSizeName, &setting) != B_OK) 560 fIconSize = kDefaultIconSize; 561 else 562 fIconSize = (icon_size)setting; 563 564 if (settings.FindInt32(kLayoutName, &setting) != B_OK) 565 fLayout = kDefaultLayout; 566 else { 567 switch (setting) { 568 case 0: 569 fLayout = TitleAboveIcon; 570 break; 571 case 1: 572 fLayout = AllTextRightOfIcon; 573 break; 574 default: 575 fLayout = kDefaultLayout; 576 } 577 } 578 579 // Notify the view about the change 580 views_t::iterator it; 581 for (it = fViews.begin(); it != fViews.end(); ++it) { 582 NotificationView* view = (*it); 583 view->SetText(view->Application(), view->Title(), view->Text()); 584 view->Invalidate(); 585 } 586 587 if (startMonitor) { 588 node_ref nref; 589 BEntry entry(path.Path()); 590 entry.GetNodeRef(&nref); 591 592 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 593 BAlert* alert = new BAlert("", "Couldn't start display settings " 594 " monitor.\nLive filter changes disabled.", "OK"); 595 alert->Go(); 596 } 597 } 598 } 599