xref: /haiku/src/servers/notification/NotificationWindow.cpp (revision 6aff37d1c79e20748c683ae224bd629f88a5b0be)
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