vfs: check userland buffers before reading them.
[haiku.git] / src / servers / notification / NotificationWindow.cpp
blobd94359942f95c091cb9dbd3843d8b17357c96b67
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.
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
14 #include "NotificationWindow.h"
16 #include <algorithm>
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 <Point.h>
30 #include <PropertyInfo.h>
31 #include <Screen.h>
33 #include "AppGroupView.h"
34 #include "AppUsage.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},
43 "get a message"},
44 {"message", {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0},
45 "count messages"},
46 {"message", {B_CREATE_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0},
47 "create a message"},
48 {"message", {B_SET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0},
49 "modify a message"},
51 { 0 }
55 /**
56 * Checks if notification position overlaps with
57 * deskbar position
59 static bool
60 is_overlapping(deskbar_location deskbar,
61 uint32 notification) {
62 if (deskbar == B_DESKBAR_RIGHT_TOP
63 && notification == (B_FOLLOW_RIGHT | B_FOLLOW_TOP))
64 return true;
65 if (deskbar == B_DESKBAR_RIGHT_BOTTOM
66 && notification == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM))
67 return true;
68 if (deskbar == B_DESKBAR_LEFT_TOP
69 && notification == (B_FOLLOW_LEFT | B_FOLLOW_TOP))
70 return true;
71 if (deskbar == B_DESKBAR_LEFT_BOTTOM
72 && notification == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM))
73 return true;
74 if (deskbar == B_DESKBAR_TOP
75 && (notification == (B_FOLLOW_LEFT | B_FOLLOW_TOP)
76 || notification == (B_FOLLOW_RIGHT | B_FOLLOW_TOP)))
77 return true;
78 if (deskbar == B_DESKBAR_BOTTOM
79 && (notification == (B_FOLLOW_LEFT | B_FOLLOW_BOTTOM)
80 || notification == (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)))
81 return true;
82 return false;
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,
92 B_ALL_WORKSPACES),
93 fShouldRun(true)
95 status_t result = find_directory(B_USER_CACHE_DIRECTORY, &fCachePath);
96 fCachePath.Append("Notifications");
97 BDirectory cacheDir;
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));
104 _LoadSettings(true);
106 // Start the message loop
107 Hide();
108 Show();
112 NotificationWindow::~NotificationWindow()
114 appfilter_t::iterator aIt;
115 for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++)
116 delete aIt->second;
120 bool
121 NotificationWindow::QuitRequested()
123 appview_t::iterator aIt;
124 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) {
125 aIt->second->RemoveSelf();
126 delete aIt->second;
129 BMessenger(be_app).SendMessage(B_QUIT_REQUESTED);
130 return BWindow::QuitRequested();
134 void
135 NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active)
137 // Ensure window is in the correct position
138 if (active)
139 SetPosition();
143 void
144 NotificationWindow::FrameResized(float width, float height)
146 SetPosition();
150 void
151 NotificationWindow::ScreenChanged(BRect frame, color_space mode)
153 SetPosition();
157 void
158 NotificationWindow::MessageReceived(BMessage* message)
160 switch (message->what) {
161 case B_NODE_MONITOR:
163 _LoadSettings();
164 break;
166 case kNotificationMessage:
168 if (!fShouldRun)
169 break;
171 BMessage reply(B_REPLY);
172 BNotification* notification = new BNotification(message);
174 if (notification->InitCheck() == B_OK) {
175 bigtime_t timeout;
176 if (message->FindInt64("timeout", &timeout) != B_OK)
177 timeout = fTimeout;
178 BString sourceSignature(notification->SourceSignature());
179 BString sourceName(notification->SourceName());
181 bool allow = false;
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
194 allow = true;
195 } else {
196 appUsage = it->second;
197 allow = appUsage->Allowed();
200 if (allow) {
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);
209 } else
210 group = aIt->second;
212 NotificationView* view = new NotificationView(notification,
213 timeout, fIconSize);
215 group->AddInfo(view);
217 _ShowHide();
219 reply.AddInt32("error", B_OK);
220 } else
221 reply.AddInt32("error", B_NOT_ALLOWED);
222 } else {
223 reply.what = B_MESSAGE_NOT_UNDERSTOOD;
224 reply.AddInt32("error", B_ERROR);
227 message->SendReply(&reply);
228 break;
230 case kRemoveGroupView:
232 AppGroupView* view = NULL;
233 if (message->FindPointer("view", (void**)&view) != B_OK)
234 return;
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
238 // delete it.
239 if (view->HasChildren())
240 return;
242 // this shouldn't happen
243 if (fAppViews.erase(view->Group()) < 1)
244 break;
246 view->RemoveSelf();
247 delete view;
249 _ShowHide();
250 break;
252 default:
253 BWindow::MessageReceived(message);
258 icon_size
259 NotificationWindow::IconSize()
261 return fIconSize;
265 int32
266 NotificationWindow::Timeout()
268 return fTimeout;
272 float
273 NotificationWindow::Width()
275 return fWidth;
279 void
280 NotificationWindow::_ShowHide()
282 if (fAppViews.empty() && !IsHidden()) {
283 Hide();
284 return;
287 if (IsHidden()) {
288 SetPosition();
289 Show();
294 void
295 NotificationWindow::SetPosition()
297 Layout(true);
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);
314 BDeskbar deskbar;
316 // If notification and deskbar position are same
317 // then follow deskbar position
318 uint32 position = (is_overlapping(deskbar.Location(), fPosition))
319 ? B_FOLLOW_DESKBAR
320 : fPosition;
323 if (position == B_FOLLOW_DESKBAR) {
324 BRect frame = deskbar.Frame();
325 switch (deskbar.Location()) {
326 case B_DESKBAR_TOP:
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;
333 break;
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;
339 break;
340 case B_DESKBAR_RIGHT_TOP:
341 y = frame.top - topOffset + 1;
342 x = frame.left - width - rightOffset;
343 break;
344 case B_DESKBAR_LEFT_TOP:
345 y = frame.top - topOffset + 1;
346 x = frame.right + leftOffset;
347 break;
348 case B_DESKBAR_RIGHT_BOTTOM:
349 y = frame.bottom - height + bottomOffset;
350 x = frame.left - width - rightOffset;
351 break;
352 case B_DESKBAR_LEFT_BOTTOM:
353 y = frame.bottom - height + bottomOffset;
354 x = frame.right + leftOffset;
355 break;
356 default:
357 break;
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();
373 MoveTo(location);
377 void
378 NotificationWindow::_LoadSettings(bool startMonitor)
380 BPath path;
381 BMessage settings;
383 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
384 return;
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);
395 if (startMonitor) {
396 node_ref nref;
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);
405 alert->Go(NULL);
411 void
412 NotificationWindow::_LoadAppFilters(BMessage& settings)
414 type_code type;
415 int32 count = 0;
417 if (settings.GetInfo("app_usage", &type, &count) != B_OK)
418 return;
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;
424 else
425 delete app;
430 void
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);
438 } else
439 fShouldRun = true;
441 if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK)
442 fTimeout = kDefaultTimeout;
443 fTimeout *= 1000000;
444 // Convert from seconds to microseconds
448 void
449 NotificationWindow::_LoadDisplaySettings(BMessage& settings)
451 int32 setting;
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;
461 else
462 fIconSize = (icon_size)setting;
464 int32 position;
465 if (settings.FindInt32(kNotificationPositionName, &position) != B_OK)
466 fPosition = kDefaultNotificationPosition;
467 else
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;
474 view->Invalidate();