/* * Copyright 2010-2017, Haiku, Inc. All Rights Reserved. * Copyright 2008-2009, Pier Luigi Fiorini. All Rights Reserved. * Copyright 2004-2008, Michael Davidson. All Rights Reserved. * Copyright 2004-2007, Mikael Eiman. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Michael Davidson, slaad@bong.com.au * Mikael Eiman, mikael@eiman.tv * Pier Luigi Fiorini, pierluigi.fiorini@gmail.com * Brian Hill, supernova@tycho.email */ #include "NotificationWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AppGroupView.h" #include "AppUsage.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "NotificationWindow" property_info main_prop_list[] = { {"message", {B_GET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0}, "get a message"}, {"message", {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0}, "count messages"}, {"message", {B_CREATE_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0}, "create a message"}, {"message", {B_SET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0}, "modify a message"}, { 0 } }; /** * Checks if notification position overlaps with * deskbar position */ static bool is_overlapping(deskbar_location deskbar, uint32 notification) { if (deskbar == B_DESKBAR_RIGHT_TOP && notification == (B_FOLLOW_RIGHT | B_FOLLOW_TOP)) return true; if (deskbar == B_DESKBAR_RIGHT_BOTTOM && notification == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)) return true; if (deskbar == B_DESKBAR_LEFT_TOP && notification == (B_FOLLOW_LEFT | B_FOLLOW_TOP)) return true; if (deskbar == B_DESKBAR_LEFT_BOTTOM && notification == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM)) return true; if (deskbar == B_DESKBAR_TOP && (notification == (B_FOLLOW_LEFT | B_FOLLOW_TOP) || notification == (B_FOLLOW_RIGHT | B_FOLLOW_TOP))) return true; if (deskbar == B_DESKBAR_BOTTOM && (notification == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM) || notification == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM))) return true; return false; } NotificationWindow::NotificationWindow() : BWindow(BRect(0, 0, -1, -1), B_TRANSLATE_MARK("Notification"), B_BORDERED_WINDOW_LOOK, B_FLOATING_ALL_WINDOW_FEEL, B_AVOID_FRONT | B_AVOID_FOCUS | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_NOT_RESIZABLE | B_NOT_MOVABLE | B_AUTO_UPDATE_SIZE_LIMITS, B_ALL_WORKSPACES), fShouldRun(true) { status_t result = find_directory(B_USER_CACHE_DIRECTORY, &fCachePath); fCachePath.Append("Notifications"); BDirectory cacheDir; result = cacheDir.SetTo(fCachePath.Path()); if (result == B_ENTRY_NOT_FOUND) cacheDir.CreateDirectory(fCachePath.Path(), NULL); SetLayout(new BGroupLayout(B_VERTICAL, 0)); _LoadSettings(true); // Start the message loop Hide(); Show(); } NotificationWindow::~NotificationWindow() { appfilter_t::iterator aIt; for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++) delete aIt->second; } bool NotificationWindow::QuitRequested() { appview_t::iterator aIt; for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { aIt->second->RemoveSelf(); delete aIt->second; } BMessenger(be_app).SendMessage(B_QUIT_REQUESTED); return BWindow::QuitRequested(); } void NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active) { // Ensure window is in the correct position if (active) SetPosition(); } void NotificationWindow::FrameResized(float width, float height) { SetPosition(); } void NotificationWindow::ScreenChanged(BRect frame, color_space mode) { SetPosition(); } void NotificationWindow::MessageReceived(BMessage* message) { switch (message->what) { case B_NODE_MONITOR: { _LoadSettings(); break; } case kNotificationMessage: { if (!fShouldRun) break; BMessage reply(B_REPLY); BNotification* notification = new BNotification(message); if (notification->InitCheck() == B_OK) { bigtime_t timeout; if (message->FindInt64("timeout", &timeout) != B_OK) timeout = fTimeout; BString sourceSignature(notification->SourceSignature()); BString sourceName(notification->SourceName()); bool allow = false; appfilter_t::iterator it = fAppFilters .find(sourceSignature.String()); AppUsage* appUsage = NULL; if (it == fAppFilters.end()) { if (sourceSignature.Length() > 0 && sourceName.Length() > 0) { appUsage = new AppUsage(sourceName.String(), sourceSignature.String(), true); fAppFilters[sourceSignature.String()] = appUsage; // TODO save back to settings file } allow = true; } else { appUsage = it->second; allow = appUsage->Allowed(); } if (allow) { BString groupName(notification->Group()); appview_t::iterator aIt = fAppViews.find(groupName); AppGroupView* group = NULL; if (aIt == fAppViews.end()) { group = new AppGroupView(this, groupName == "" ? NULL : groupName.String()); fAppViews[groupName] = group; GetLayout()->AddView(group); } else group = aIt->second; NotificationView* view = new NotificationView(notification, timeout, fIconSize); group->AddInfo(view); _ShowHide(); reply.AddInt32("error", B_OK); } else reply.AddInt32("error", B_NOT_ALLOWED); } else { reply.what = B_MESSAGE_NOT_UNDERSTOOD; reply.AddInt32("error", B_ERROR); } message->SendReply(&reply); break; } case kRemoveGroupView: { AppGroupView* view = NULL; if (message->FindPointer("view", (void**)&view) != B_OK) return; // It's possible that between sending this message, and us receiving // it, the view has become used again, in which case we shouldn't // delete it. if (view->HasChildren()) return; // this shouldn't happen if (fAppViews.erase(view->Group()) < 1) break; view->RemoveSelf(); delete view; _ShowHide(); break; } default: BWindow::MessageReceived(message); } } icon_size NotificationWindow::IconSize() { return fIconSize; } int32 NotificationWindow::Timeout() { return fTimeout; } float NotificationWindow::Width() { return fWidth; } void NotificationWindow::_ShowHide() { if (fAppViews.empty() && !IsHidden()) { Hide(); return; } if (IsHidden()) { SetPosition(); Show(); } } void NotificationWindow::SetPosition() { Layout(true); BRect bounds = DecoratorFrame(); float width = Bounds().Width() + 1; float height = Bounds().Height() + 1; float leftOffset = Frame().left - bounds.left; float topOffset = Frame().top - bounds.top + 1; float rightOffset = bounds.right - Frame().right; float bottomOffset = bounds.bottom - Frame().bottom; // Size of the borders around the window float x = Frame().left; float y = Frame().top; // If we cant guess, don't move... BPoint location(x, y); BDeskbar deskbar; // If notification and deskbar position are same // then follow deskbar position uint32 position = (is_overlapping(deskbar.Location(), fPosition)) ? B_FOLLOW_DESKBAR : fPosition; if (position == B_FOLLOW_DESKBAR) { BRect frame = deskbar.Frame(); switch (deskbar.Location()) { case B_DESKBAR_TOP: // In case of overlapping here or for bottom // use user's notification position y = frame.bottom + topOffset; x = (fPosition == (B_FOLLOW_LEFT | B_FOLLOW_TOP)) ? frame.left + rightOffset : frame.right - width + rightOffset; break; case B_DESKBAR_BOTTOM: y = frame.top - height - bottomOffset; x = (fPosition == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM)) ? frame.left + rightOffset : frame.right - width + rightOffset; break; case B_DESKBAR_RIGHT_TOP: y = frame.top - topOffset + 1; x = frame.left - width - rightOffset; break; case B_DESKBAR_LEFT_TOP: y = frame.top - topOffset + 1; x = frame.right + leftOffset; break; case B_DESKBAR_RIGHT_BOTTOM: y = frame.bottom - height + bottomOffset; x = frame.left - width - rightOffset; break; case B_DESKBAR_LEFT_BOTTOM: y = frame.bottom - height + bottomOffset; x = frame.right + leftOffset; break; default: break; } location = BPoint(x, y); } else if (position == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)) { location = BScreen().Frame().RightBottom(); location -= BPoint(width, height); } else if (position == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM)) { location = BScreen().Frame().LeftBottom(); location -= BPoint(0, height); } else if (position == (B_FOLLOW_RIGHT | B_FOLLOW_TOP)) { location = BScreen().Frame().RightTop(); location -= BPoint(width, 0); } else if (position == (B_FOLLOW_LEFT | B_FOLLOW_TOP)) { location = BScreen().Frame().LeftTop(); } MoveTo(location); } void NotificationWindow::_LoadSettings(bool startMonitor) { BPath path; BMessage settings; if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) return; path.Append(kSettingsFile); BFile file(path.Path(), B_READ_ONLY | B_CREATE_FILE); settings.Unflatten(&file); _LoadGeneralSettings(settings); _LoadDisplaySettings(settings); _LoadAppFilters(settings); if (startMonitor) { node_ref nref; BEntry entry(path.Path()); entry.GetNodeRef(&nref); if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { BAlert* alert = new BAlert(B_TRANSLATE("Warning"), B_TRANSLATE("Couldn't start general settings monitor.\n" "Live filter changes disabled."), B_TRANSLATE("OK")); alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); alert->Go(NULL); } } } void NotificationWindow::_LoadAppFilters(BMessage& settings) { type_code type; int32 count = 0; if (settings.GetInfo("app_usage", &type, &count) != B_OK) return; for (int32 i = 0; i < count; i++) { AppUsage* app = new AppUsage(); if (settings.FindFlat("app_usage", i, app) == B_OK) fAppFilters[app->Signature()] = app; else delete app; } } void NotificationWindow::_LoadGeneralSettings(BMessage& settings) { if (settings.FindBool(kAutoStartName, &fShouldRun) == B_OK) { if (fShouldRun == false) { // We should not start. Quit the app! be_app_messenger.SendMessage(B_QUIT_REQUESTED); } } else fShouldRun = true; if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK) fTimeout = kDefaultTimeout; fTimeout *= 1000000; // Convert from seconds to microseconds } void NotificationWindow::_LoadDisplaySettings(BMessage& settings) { int32 setting; float originalWidth = fWidth; if (settings.FindFloat(kWidthName, &fWidth) != B_OK) fWidth = kDefaultWidth; if (originalWidth != fWidth) GetLayout()->SetExplicitSize(BSize(fWidth, B_SIZE_UNSET)); if (settings.FindInt32(kIconSizeName, &setting) != B_OK) fIconSize = kDefaultIconSize; else fIconSize = (icon_size)setting; int32 position; if (settings.FindInt32(kNotificationPositionName, &position) != B_OK) fPosition = kDefaultNotificationPosition; else fPosition = position; // Notify the views about the change appview_t::iterator aIt; for (aIt = fAppViews.begin(); aIt != fAppViews.end(); ++aIt) { AppGroupView* view = aIt->second; view->Invalidate(); } }