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