1 /* 2 * Copyright 2010-2017, 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 * Brian Hill, supernova@tycho.email 13 */ 14 #include "NotificationWindow.h" 15 16 #include <algorithm> 17 18 #include <Alert.h> 19 #include <Application.h> 20 #include <Catalog.h> 21 #include <Deskbar.h> 22 #include <Directory.h> 23 #include <File.h> 24 #include <FindDirectory.h> 25 #include <GroupLayout.h> 26 #include <NodeMonitor.h> 27 #include <Notifications.h> 28 #include <Path.h> 29 #include <PropertyInfo.h> 30 31 #include "AppGroupView.h" 32 #include "AppUsage.h" 33 34 35 #undef B_TRANSLATION_CONTEXT 36 #define B_TRANSLATION_CONTEXT "NotificationWindow" 37 38 39 property_info main_prop_list[] = { 40 {"message", {B_GET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0}, 41 "get a message"}, 42 {"message", {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0}, 43 "count messages"}, 44 {"message", {B_CREATE_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0}, 45 "create a message"}, 46 {"message", {B_SET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0}, 47 "modify a message"}, 48 49 { 0 } 50 }; 51 52 53 NotificationWindow::NotificationWindow() 54 : 55 BWindow(BRect(0, 0, -1, -1), B_TRANSLATE_MARK("Notification"), 56 B_BORDERED_WINDOW_LOOK, B_FLOATING_ALL_WINDOW_FEEL, B_AVOID_FRONT 57 | B_AVOID_FOCUS | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE 58 | B_NOT_RESIZABLE | B_NOT_MOVABLE | B_AUTO_UPDATE_SIZE_LIMITS, 59 B_ALL_WORKSPACES), 60 fShouldRun(true) 61 { 62 status_t result = find_directory(B_USER_CACHE_DIRECTORY, &fCachePath); 63 fCachePath.Append("Notifications"); 64 BDirectory cacheDir; 65 result = cacheDir.SetTo(fCachePath.Path()); 66 if(result == B_ENTRY_NOT_FOUND) 67 cacheDir.CreateDirectory(fCachePath.Path(), NULL); 68 69 SetLayout(new BGroupLayout(B_VERTICAL, 0)); 70 71 _LoadSettings(true); 72 73 // Start the message loop 74 Hide(); 75 Show(); 76 } 77 78 79 NotificationWindow::~NotificationWindow() 80 { 81 appfilter_t::iterator aIt; 82 for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++) 83 delete aIt->second; 84 } 85 86 87 bool 88 NotificationWindow::QuitRequested() 89 { 90 appview_t::iterator aIt; 91 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 92 aIt->second->RemoveSelf(); 93 delete aIt->second; 94 } 95 96 BMessenger(be_app).SendMessage(B_QUIT_REQUESTED); 97 return BWindow::QuitRequested(); 98 } 99 100 101 void 102 NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active) 103 { 104 // Ensure window is in the correct position 105 if (active) 106 SetPosition(); 107 } 108 109 110 void 111 NotificationWindow::FrameResized(float width, float height) 112 { 113 SetPosition(); 114 } 115 116 117 void 118 NotificationWindow::ScreenChanged(BRect frame, color_space mode) 119 { 120 SetPosition(); 121 } 122 123 124 void 125 NotificationWindow::MessageReceived(BMessage* message) 126 { 127 switch (message->what) { 128 case B_NODE_MONITOR: 129 { 130 _LoadSettings(); 131 break; 132 } 133 case kNotificationMessage: 134 { 135 if (!fShouldRun) 136 break; 137 138 BMessage reply(B_REPLY); 139 BNotification* notification = new BNotification(message); 140 141 if (notification->InitCheck() == B_OK) { 142 bigtime_t timeout; 143 if (message->FindInt64("timeout", &timeout) != B_OK) 144 timeout = fTimeout; 145 BString sourceSignature(notification->SourceSignature()); 146 BString sourceName(notification->SourceName()); 147 148 bool allow = false; 149 appfilter_t::iterator it = 150 fAppFilters.find(sourceSignature.String()); 151 152 AppUsage* appUsage = NULL; 153 if (it == fAppFilters.end()) { 154 if (sourceSignature.Length() > 0 155 && sourceName.Length() > 0) { 156 appUsage = new AppUsage(sourceName.String(), 157 sourceSignature.String(), true); 158 fAppFilters[sourceSignature.String()] = appUsage; 159 // TODO save back to settings file 160 } 161 allow = true; 162 } else { 163 appUsage = it->second; 164 allow = appUsage->Allowed(); 165 } 166 167 if (allow) { 168 BString groupName(notification->Group()); 169 appview_t::iterator aIt = fAppViews.find(groupName); 170 AppGroupView* group = NULL; 171 if (aIt == fAppViews.end()) { 172 group = new AppGroupView(this, 173 groupName == "" ? NULL : groupName.String()); 174 fAppViews[groupName] = group; 175 GetLayout()->AddView(group); 176 } else 177 group = aIt->second; 178 179 NotificationView* view = new NotificationView(notification, 180 timeout, fIconSize); 181 182 group->AddInfo(view); 183 184 _ShowHide(); 185 186 reply.AddInt32("error", B_OK); 187 } else 188 reply.AddInt32("error", B_NOT_ALLOWED); 189 } else { 190 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 191 reply.AddInt32("error", B_ERROR); 192 } 193 194 message->SendReply(&reply); 195 break; 196 } 197 case kRemoveGroupView: 198 { 199 AppGroupView* view = NULL; 200 if (message->FindPointer("view", (void**)&view) != B_OK) 201 return; 202 203 // It's possible that between sending this message, and us receiving 204 // it, the view has become used again, in which case we shouldn't 205 // delete it. 206 if (view->HasChildren()) 207 return; 208 209 // this shouldn't happen 210 if (fAppViews.erase(view->Group()) < 1) 211 break; 212 213 view->RemoveSelf(); 214 delete view; 215 216 _ShowHide(); 217 break; 218 } 219 default: 220 BWindow::MessageReceived(message); 221 } 222 } 223 224 225 icon_size 226 NotificationWindow::IconSize() 227 { 228 return fIconSize; 229 } 230 231 232 int32 233 NotificationWindow::Timeout() 234 { 235 return fTimeout; 236 } 237 238 239 float 240 NotificationWindow::Width() 241 { 242 return fWidth; 243 } 244 245 246 void 247 NotificationWindow::_ShowHide() 248 { 249 if (fAppViews.empty() && !IsHidden()) { 250 Hide(); 251 return; 252 } 253 254 if (IsHidden()) { 255 SetPosition(); 256 Show(); 257 } 258 } 259 260 261 void 262 NotificationWindow::SetPosition() 263 { 264 Layout(true); 265 266 BRect bounds = DecoratorFrame(); 267 float width = Bounds().Width() + 1; 268 float height = Bounds().Height() + 1; 269 270 float leftOffset = Frame().left - bounds.left; 271 float topOffset = Frame().top - bounds.top + 1; 272 float rightOffset = bounds.right - Frame().right; 273 float bottomOffset = bounds.bottom - Frame().bottom; 274 // Size of the borders around the window 275 276 float x = Frame().left; 277 float y = Frame().top; 278 // If we can't guess, don't move... 279 280 BDeskbar deskbar; 281 BRect frame = deskbar.Frame(); 282 283 switch (deskbar.Location()) { 284 case B_DESKBAR_TOP: 285 // Put it just under, top right corner 286 y = frame.bottom + topOffset; 287 x = frame.right - width + rightOffset; 288 break; 289 case B_DESKBAR_BOTTOM: 290 // Put it just above, lower left corner 291 y = frame.top - height - bottomOffset; 292 x = frame.right - width + rightOffset; 293 break; 294 case B_DESKBAR_RIGHT_TOP: 295 x = frame.left - width - rightOffset; 296 y = frame.top - topOffset + 1; 297 break; 298 case B_DESKBAR_LEFT_TOP: 299 x = frame.right + leftOffset; 300 y = frame.top - topOffset + 1; 301 break; 302 case B_DESKBAR_RIGHT_BOTTOM: 303 y = frame.bottom - height + bottomOffset; 304 x = frame.left - width - rightOffset; 305 break; 306 case B_DESKBAR_LEFT_BOTTOM: 307 y = frame.bottom - height + bottomOffset; 308 x = frame.right + leftOffset; 309 break; 310 default: 311 break; 312 } 313 314 MoveTo(x, y); 315 } 316 317 318 void 319 NotificationWindow::_LoadSettings(bool startMonitor) 320 { 321 BPath path; 322 BMessage settings; 323 324 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 325 return; 326 327 path.Append(kSettingsFile); 328 329 BFile file(path.Path(), B_READ_ONLY | B_CREATE_FILE); 330 settings.Unflatten(&file); 331 332 _LoadGeneralSettings(settings); 333 _LoadDisplaySettings(settings); 334 _LoadAppFilters(settings); 335 336 if (startMonitor) { 337 node_ref nref; 338 BEntry entry(path.Path()); 339 entry.GetNodeRef(&nref); 340 341 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 342 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 343 B_TRANSLATE("Couldn't start general settings monitor.\n" 344 "Live filter changes disabled."), B_TRANSLATE("OK")); 345 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 346 alert->Go(NULL); 347 } 348 } 349 } 350 351 352 void 353 NotificationWindow::_LoadAppFilters(BMessage& settings) 354 { 355 type_code type; 356 int32 count = 0; 357 358 if (settings.GetInfo("app_usage", &type, &count) != B_OK) 359 return; 360 361 for (int32 i = 0; i < count; i++) { 362 AppUsage* app = new AppUsage(); 363 if (settings.FindFlat("app_usage", i, app) == B_OK) 364 fAppFilters[app->Signature()] = app; 365 else 366 delete app; 367 } 368 } 369 370 371 void 372 NotificationWindow::_LoadGeneralSettings(BMessage& settings) 373 { 374 if (settings.FindBool(kAutoStartName, &fShouldRun) == B_OK) { 375 if (fShouldRun == false) { 376 // We should not start. Quit the app! 377 be_app_messenger.SendMessage(B_QUIT_REQUESTED); 378 } 379 } else 380 fShouldRun = true; 381 382 if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK) 383 fTimeout = kDefaultTimeout; 384 fTimeout *= 1000000; 385 // Convert from seconds to microseconds 386 } 387 388 389 void 390 NotificationWindow::_LoadDisplaySettings(BMessage& settings) 391 { 392 int32 setting; 393 float originalWidth = fWidth; 394 395 if (settings.FindFloat(kWidthName, &fWidth) != B_OK) 396 fWidth = kDefaultWidth; 397 if (originalWidth != fWidth) 398 GetLayout()->SetExplicitSize(BSize(fWidth, B_SIZE_UNSET)); 399 400 if (settings.FindInt32(kIconSizeName, &setting) != B_OK) 401 fIconSize = kDefaultIconSize; 402 else 403 fIconSize = (icon_size)setting; 404 405 // Notify the views about the change 406 appview_t::iterator aIt; 407 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); ++aIt) { 408 AppGroupView* view = aIt->second; 409 view->Invalidate(); 410 } 411 } 412