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