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.
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
14 #include "NotificationWindow.h"
19 #include <Application.h>
22 #include <Directory.h>
24 #include <FindDirectory.h>
25 #include <GroupLayout.h>
26 #include <NodeMonitor.h>
27 #include <Notifications.h>
30 #include <PropertyInfo.h>
33 #include "AppGroupView.h"
37 #undef B_TRANSLATION_CONTEXT
38 #define B_TRANSLATION_CONTEXT "NotificationWindow"
41 property_info main_prop_list
[] = {
42 {"message", {B_GET_PROPERTY
, 0}, {B_INDEX_SPECIFIER
, 0},
44 {"message", {B_COUNT_PROPERTIES
, 0}, {B_DIRECT_SPECIFIER
, 0},
46 {"message", {B_CREATE_PROPERTY
, 0}, {B_DIRECT_SPECIFIER
, 0},
48 {"message", {B_SET_PROPERTY
, 0}, {B_INDEX_SPECIFIER
, 0},
56 * Checks if notification position overlaps with
60 is_overlapping(deskbar_location deskbar
,
61 uint32 notification
) {
62 if (deskbar
== B_DESKBAR_RIGHT_TOP
63 && notification
== (B_FOLLOW_RIGHT
| B_FOLLOW_TOP
))
65 if (deskbar
== B_DESKBAR_RIGHT_BOTTOM
66 && notification
== (B_FOLLOW_RIGHT
| B_FOLLOW_BOTTOM
))
68 if (deskbar
== B_DESKBAR_LEFT_TOP
69 && notification
== (B_FOLLOW_LEFT
| B_FOLLOW_TOP
))
71 if (deskbar
== B_DESKBAR_LEFT_BOTTOM
72 && notification
== (B_FOLLOW_LEFT
| B_FOLLOW_BOTTOM
))
74 if (deskbar
== B_DESKBAR_TOP
75 && (notification
== (B_FOLLOW_LEFT
| B_FOLLOW_TOP
)
76 || notification
== (B_FOLLOW_RIGHT
| B_FOLLOW_TOP
)))
78 if (deskbar
== B_DESKBAR_BOTTOM
79 && (notification
== (B_FOLLOW_LEFT
| B_FOLLOW_BOTTOM
)
80 || notification
== (B_FOLLOW_RIGHT
| B_FOLLOW_BOTTOM
)))
86 NotificationWindow::NotificationWindow()
88 BWindow(BRect(0, 0, -1, -1), B_TRANSLATE_MARK("Notification"),
89 B_BORDERED_WINDOW_LOOK
, B_FLOATING_ALL_WINDOW_FEEL
, B_AVOID_FRONT
90 | B_AVOID_FOCUS
| B_NOT_CLOSABLE
| B_NOT_ZOOMABLE
| B_NOT_MINIMIZABLE
91 | B_NOT_RESIZABLE
| B_NOT_MOVABLE
| B_AUTO_UPDATE_SIZE_LIMITS
,
95 status_t result
= find_directory(B_USER_CACHE_DIRECTORY
, &fCachePath
);
96 fCachePath
.Append("Notifications");
98 result
= cacheDir
.SetTo(fCachePath
.Path());
99 if (result
== B_ENTRY_NOT_FOUND
)
100 cacheDir
.CreateDirectory(fCachePath
.Path(), NULL
);
102 SetLayout(new BGroupLayout(B_VERTICAL
, 0));
106 // Start the message loop
112 NotificationWindow::~NotificationWindow()
114 appfilter_t::iterator aIt
;
115 for (aIt
= fAppFilters
.begin(); aIt
!= fAppFilters
.end(); aIt
++)
121 NotificationWindow::QuitRequested()
123 appview_t::iterator aIt
;
124 for (aIt
= fAppViews
.begin(); aIt
!= fAppViews
.end(); aIt
++) {
125 aIt
->second
->RemoveSelf();
129 BMessenger(be_app
).SendMessage(B_QUIT_REQUESTED
);
130 return BWindow::QuitRequested();
135 NotificationWindow::WorkspaceActivated(int32
/*workspace*/, bool active
)
137 // Ensure window is in the correct position
144 NotificationWindow::FrameResized(float width
, float height
)
151 NotificationWindow::ScreenChanged(BRect frame
, color_space mode
)
158 NotificationWindow::MessageReceived(BMessage
* message
)
160 switch (message
->what
) {
166 case kNotificationMessage
:
171 BMessage
reply(B_REPLY
);
172 BNotification
* notification
= new BNotification(message
);
174 if (notification
->InitCheck() == B_OK
) {
176 if (message
->FindInt64("timeout", &timeout
) != B_OK
)
178 BString
sourceSignature(notification
->SourceSignature());
179 BString
sourceName(notification
->SourceName());
182 appfilter_t::iterator it
= fAppFilters
183 .find(sourceSignature
.String());
185 AppUsage
* appUsage
= NULL
;
186 if (it
== fAppFilters
.end()) {
187 if (sourceSignature
.Length() > 0
188 && sourceName
.Length() > 0) {
189 appUsage
= new AppUsage(sourceName
.String(),
190 sourceSignature
.String(), true);
191 fAppFilters
[sourceSignature
.String()] = appUsage
;
192 // TODO save back to settings file
196 appUsage
= it
->second
;
197 allow
= appUsage
->Allowed();
201 BString
groupName(notification
->Group());
202 appview_t::iterator aIt
= fAppViews
.find(groupName
);
203 AppGroupView
* group
= NULL
;
204 if (aIt
== fAppViews
.end()) {
205 group
= new AppGroupView(this,
206 groupName
== "" ? NULL
: groupName
.String());
207 fAppViews
[groupName
] = group
;
208 GetLayout()->AddView(group
);
212 NotificationView
* view
= new NotificationView(notification
,
215 group
->AddInfo(view
);
219 reply
.AddInt32("error", B_OK
);
221 reply
.AddInt32("error", B_NOT_ALLOWED
);
223 reply
.what
= B_MESSAGE_NOT_UNDERSTOOD
;
224 reply
.AddInt32("error", B_ERROR
);
227 message
->SendReply(&reply
);
230 case kRemoveGroupView
:
232 AppGroupView
* view
= NULL
;
233 if (message
->FindPointer("view", (void**)&view
) != B_OK
)
236 // It's possible that between sending this message, and us receiving
237 // it, the view has become used again, in which case we shouldn't
239 if (view
->HasChildren())
242 // this shouldn't happen
243 if (fAppViews
.erase(view
->Group()) < 1)
253 BWindow::MessageReceived(message
);
259 NotificationWindow::IconSize()
266 NotificationWindow::Timeout()
273 NotificationWindow::Width()
280 NotificationWindow::_ShowHide()
282 if (fAppViews
.empty() && !IsHidden()) {
295 NotificationWindow::SetPosition()
299 BRect bounds
= DecoratorFrame();
300 float width
= Bounds().Width() + 1;
301 float height
= Bounds().Height() + 1;
303 float leftOffset
= Frame().left
- bounds
.left
;
304 float topOffset
= Frame().top
- bounds
.top
+ 1;
305 float rightOffset
= bounds
.right
- Frame().right
;
306 float bottomOffset
= bounds
.bottom
- Frame().bottom
;
307 // Size of the borders around the window
309 float x
= Frame().left
;
310 float y
= Frame().top
;
311 // If we cant guess, don't move...
312 BPoint
location(x
, y
);
316 // If notification and deskbar position are same
317 // then follow deskbar position
318 uint32 position
= (is_overlapping(deskbar
.Location(), fPosition
))
323 if (position
== B_FOLLOW_DESKBAR
) {
324 BRect frame
= deskbar
.Frame();
325 switch (deskbar
.Location()) {
327 // In case of overlapping here or for bottom
328 // use user's notification position
329 y
= frame
.bottom
+ topOffset
;
330 x
= (fPosition
== (B_FOLLOW_LEFT
| B_FOLLOW_TOP
))
331 ? frame
.left
+ rightOffset
332 : frame
.right
- width
+ rightOffset
;
334 case B_DESKBAR_BOTTOM
:
335 y
= frame
.top
- height
- bottomOffset
;
336 x
= (fPosition
== (B_FOLLOW_LEFT
| B_FOLLOW_BOTTOM
))
337 ? frame
.left
+ rightOffset
338 : frame
.right
- width
+ rightOffset
;
340 case B_DESKBAR_RIGHT_TOP
:
341 y
= frame
.top
- topOffset
+ 1;
342 x
= frame
.left
- width
- rightOffset
;
344 case B_DESKBAR_LEFT_TOP
:
345 y
= frame
.top
- topOffset
+ 1;
346 x
= frame
.right
+ leftOffset
;
348 case B_DESKBAR_RIGHT_BOTTOM
:
349 y
= frame
.bottom
- height
+ bottomOffset
;
350 x
= frame
.left
- width
- rightOffset
;
352 case B_DESKBAR_LEFT_BOTTOM
:
353 y
= frame
.bottom
- height
+ bottomOffset
;
354 x
= frame
.right
+ leftOffset
;
359 location
= BPoint(x
, y
);
360 } else if (position
== (B_FOLLOW_RIGHT
| B_FOLLOW_BOTTOM
)) {
361 location
= BScreen().Frame().RightBottom();
362 location
-= BPoint(width
, height
);
363 } else if (position
== (B_FOLLOW_LEFT
| B_FOLLOW_BOTTOM
)) {
364 location
= BScreen().Frame().LeftBottom();
365 location
-= BPoint(0, height
);
366 } else if (position
== (B_FOLLOW_RIGHT
| B_FOLLOW_TOP
)) {
367 location
= BScreen().Frame().RightTop();
368 location
-= BPoint(width
, 0);
369 } else if (position
== (B_FOLLOW_LEFT
| B_FOLLOW_TOP
)) {
370 location
= BScreen().Frame().LeftTop();
378 NotificationWindow::_LoadSettings(bool startMonitor
)
383 if (find_directory(B_USER_SETTINGS_DIRECTORY
, &path
) != B_OK
)
386 path
.Append(kSettingsFile
);
388 BFile
file(path
.Path(), B_READ_ONLY
| B_CREATE_FILE
);
389 settings
.Unflatten(&file
);
391 _LoadGeneralSettings(settings
);
392 _LoadDisplaySettings(settings
);
393 _LoadAppFilters(settings
);
397 BEntry
entry(path
.Path());
398 entry
.GetNodeRef(&nref
);
400 if (watch_node(&nref
, B_WATCH_ALL
, BMessenger(this)) != B_OK
) {
401 BAlert
* alert
= new BAlert(B_TRANSLATE("Warning"),
402 B_TRANSLATE("Couldn't start general settings monitor.\n"
403 "Live filter changes disabled."), B_TRANSLATE("OK"));
404 alert
->SetFlags(alert
->Flags() | B_CLOSE_ON_ESCAPE
);
412 NotificationWindow::_LoadAppFilters(BMessage
& settings
)
417 if (settings
.GetInfo("app_usage", &type
, &count
) != B_OK
)
420 for (int32 i
= 0; i
< count
; i
++) {
421 AppUsage
* app
= new AppUsage();
422 if (settings
.FindFlat("app_usage", i
, app
) == B_OK
)
423 fAppFilters
[app
->Signature()] = app
;
431 NotificationWindow::_LoadGeneralSettings(BMessage
& settings
)
433 if (settings
.FindBool(kAutoStartName
, &fShouldRun
) == B_OK
) {
434 if (fShouldRun
== false) {
435 // We should not start. Quit the app!
436 be_app_messenger
.SendMessage(B_QUIT_REQUESTED
);
441 if (settings
.FindInt32(kTimeoutName
, &fTimeout
) != B_OK
)
442 fTimeout
= kDefaultTimeout
;
444 // Convert from seconds to microseconds
449 NotificationWindow::_LoadDisplaySettings(BMessage
& settings
)
452 float originalWidth
= fWidth
;
454 if (settings
.FindFloat(kWidthName
, &fWidth
) != B_OK
)
455 fWidth
= kDefaultWidth
;
456 if (originalWidth
!= fWidth
)
457 GetLayout()->SetExplicitSize(BSize(fWidth
, B_SIZE_UNSET
));
459 if (settings
.FindInt32(kIconSizeName
, &setting
) != B_OK
)
460 fIconSize
= kDefaultIconSize
;
462 fIconSize
= (icon_size
)setting
;
465 if (settings
.FindInt32(kNotificationPositionName
, &position
) != B_OK
)
466 fPosition
= kDefaultNotificationPosition
;
468 fPosition
= position
;
470 // Notify the views about the change
471 appview_t::iterator aIt
;
472 for (aIt
= fAppViews
.begin(); aIt
!= fAppViews
.end(); ++aIt
) {
473 AppGroupView
* view
= aIt
->second
;