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