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