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