1/*
2 * Copyright 2003-2009, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Michael Phipps
7 *		Jérôme Duval, jerome.duval@free.fr
8 *		Axel Dörfler, axeld@pinc-software.de
9 *		Ryan Leavengood, leavengood@gmail.com
10 */
11
12
13#include "ScreenSaverFilter.h"
14
15#include <Application.h>
16#include <Autolock.h>
17#include <FindDirectory.h>
18#include <MessageRunner.h>
19#include <NodeMonitor.h>
20#include <OS.h>
21#include <Roster.h>
22#include <Screen.h>
23
24#include <new>
25#include <strings.h>
26#include <syslog.h>
27
28
29static const int32 kNeverBlankCornerSize = 10;
30static const int32 kBlankCornerSize = 5;
31static const bigtime_t kCornerDelay = 1000000LL;
32	// one second
33
34static const int32 kMsgCheckTime = 'SSCT';
35static const int32 kMsgCornerInvoke = 'Scin';
36
37
38extern "C" _EXPORT BInputServerFilter* instantiate_input_filter();
39
40
41/** Required C func to build the IS Filter */
42BInputServerFilter*
43instantiate_input_filter()
44{
45	return new (std::nothrow) ScreenSaverFilter();
46}
47
48
49//	#pragma mark -
50
51
52ScreenSaverController::ScreenSaverController(ScreenSaverFilter *filter)
53	: BLooper("screensaver controller", B_LOW_PRIORITY),
54	fFilter(filter)
55{
56}
57
58
59void
60ScreenSaverController::MessageReceived(BMessage *message)
61{
62	switch (message->what) {
63		case B_NODE_MONITOR:
64			fFilter->ReloadSettings();
65			break;
66		case B_SOME_APP_LAUNCHED:
67		case B_SOME_APP_QUIT:
68		{
69			const char *signature;
70			if (message->FindString("be:signature", &signature) == B_OK
71				&& strcasecmp(signature, SCREEN_BLANKER_SIG) == 0)
72				fFilter->SetIsRunning(message->what == B_SOME_APP_LAUNCHED);
73			break;
74		}
75
76		case kMsgCheckTime:
77			fFilter->CheckTime();
78			break;
79
80		case kMsgCornerInvoke:
81			fFilter->CheckCornerInvoke();
82			break;
83
84		default:
85			BLooper::MessageReceived(message);
86	}
87}
88
89
90//	#pragma mark -
91
92
93ScreenSaverFilter::ScreenSaverFilter()
94	: BLocker("screen saver filter"),
95	fLastEventTime(system_time()),
96	fBlankTime(0),
97	fSnoozeTime(0),
98	fCurrentCorner(NO_CORNER),
99	fFrameNum(0),
100	fRunner(NULL),
101	fCornerRunner(NULL),
102	fWatchingDirectory(false),
103	fWatchingFile(false),
104	fIsRunning(false)
105{
106	fController = new (std::nothrow) ScreenSaverController(this);
107	if (fController == NULL)
108		return;
109
110	BAutolock _(this);
111
112	fController->Run();
113
114	ReloadSettings();
115	be_roster->StartWatching(fController);
116}
117
118
119ScreenSaverFilter::~ScreenSaverFilter()
120{
121	be_roster->StopWatching(fController);
122
123	if (fWatchingFile || fWatchingDirectory)
124		watch_node(&fNodeRef, B_STOP_WATCHING, fController);
125
126	// We must quit our controller without being locked, or else we might
127	// deadlock; when the controller is gone, there is no reason to lock
128	// anymore, anyway.
129	if (fController->Lock())
130		fController->Quit();
131
132	delete fCornerRunner;
133	delete fRunner;
134}
135
136
137/*!	Starts watching the settings file, or if that doesn't exist, the directory
138	the settings file will be placed into.
139*/
140void
141ScreenSaverFilter::_WatchSettings()
142{
143	BEntry entry(fSettings.Path().Path());
144	if (entry.Exists()) {
145		if (fWatchingFile)
146			return;
147
148		if (fWatchingDirectory) {
149			watch_node(&fNodeRef, B_STOP_WATCHING, fController);
150			fWatchingDirectory = false;
151		}
152		if (entry.GetNodeRef(&fNodeRef) == B_OK
153			&& watch_node(&fNodeRef, B_WATCH_ALL, fController) == B_OK)
154			fWatchingFile = true;
155	} else {
156		if (fWatchingDirectory)
157			return;
158
159		if (fWatchingFile) {
160			watch_node(&fNodeRef, B_STOP_WATCHING, fController);
161			fWatchingFile = false;
162		}
163		BEntry dir;
164		if (entry.GetParent(&dir) == B_OK
165			&& dir.GetNodeRef(&fNodeRef) == B_OK
166			&& watch_node(&fNodeRef, B_WATCH_DIRECTORY, fController) == B_OK)
167			fWatchingDirectory = true;
168	}
169}
170
171
172/*!	Starts the screen saver if allowed */
173void
174ScreenSaverFilter::_Invoke()
175{
176	if ((fCurrentCorner == fNeverBlankCorner && fNeverBlankCorner != NO_CORNER)
177		|| (fSettings.TimeFlags() & ENABLE_SAVER) == 0
178		|| fIsRunning
179		|| be_roster->IsRunning(SCREEN_BLANKER_SIG))
180		return;
181
182	if (be_roster->Launch(SCREEN_BLANKER_SIG) == B_OK) {
183		// Already set the running state to avoid launching
184		// the blanker twice in any case.
185		fIsRunning = true;
186		return;
187	}
188
189	// Try really hard to launch it. It's very likely that this fails,
190	// when we run from the CD and there is only an incomplete mime
191	// database for example...
192	BPath path;
193	if (find_directory(B_SYSTEM_BIN_DIRECTORY, &path) != B_OK
194		|| path.Append("screen_blanker") != B_OK)
195		path.SetTo("/bin/screen_blanker");
196	BEntry entry(path.Path());
197	entry_ref ref;
198	if (entry.GetRef(&ref) == B_OK
199		&& be_roster->Launch(&ref) == B_OK)
200		fIsRunning = true;
201}
202
203
204void
205ScreenSaverFilter::ReloadSettings()
206{
207	BAutolock _(this);
208	bool isFirst = !fWatchingDirectory && !fWatchingFile;
209
210	_WatchSettings();
211
212	if (fWatchingDirectory && !isFirst) {
213		// there is no settings file yet
214		return;
215	}
216
217	delete fCornerRunner;
218	delete fRunner;
219	fRunner = fCornerRunner = NULL;
220
221	fSettings.Load();
222
223	fBlankCorner = fSettings.BlankCorner();
224	fNeverBlankCorner = fSettings.NeverBlankCorner();
225	fBlankTime = fSnoozeTime = fSettings.BlankTime();
226	CheckTime();
227
228	if (fBlankCorner != NO_CORNER || fNeverBlankCorner != NO_CORNER) {
229		BMessage invoke(kMsgCornerInvoke);
230		fCornerRunner = new (std::nothrow) BMessageRunner(fController,
231			&invoke, B_INFINITE_TIMEOUT);
232			// will be reset in Filter()
233	}
234
235	BMessage check(kMsgCheckTime);
236	fRunner = new (std::nothrow) BMessageRunner(fController, &check,
237		fSnoozeTime);
238	if (fRunner == NULL || fRunner->InitCheck() != B_OK)
239		syslog(LOG_ERR, "screen saver filter runner init failed\n");
240}
241
242
243void
244ScreenSaverFilter::SetIsRunning(bool isRunning)
245{
246	// called from the controller BLooper
247	BAutolock _(this);
248	fIsRunning = isRunning;
249}
250
251
252void
253ScreenSaverFilter::CheckTime()
254{
255	BAutolock _(this);
256
257	bigtime_t now = system_time();
258	if (now >= fLastEventTime + fBlankTime)
259		_Invoke();
260
261	// TODO: this doesn't work correctly - since the BMessageRunner is not
262	// restarted, the next check will be too far away
263
264	// If the screen saver is on OR it was time to come on but it didn't (corner),
265	// snooze for blankTime.
266	// Otherwise, there was an event in the middle of the last snooze, so snooze
267	// for the remainder.
268	if (fIsRunning || fLastEventTime + fBlankTime <= now)
269		fSnoozeTime = fBlankTime;
270	else
271		fSnoozeTime = fLastEventTime + fBlankTime - now;
272
273	if (fRunner != NULL)
274		fRunner->SetInterval(fSnoozeTime);
275}
276
277
278void
279ScreenSaverFilter::CheckCornerInvoke()
280{
281	BAutolock _(this);
282
283	bigtime_t inactivity = system_time() - fLastEventTime;
284
285	if (fCurrentCorner == fBlankCorner && fBlankCorner != NO_CORNER
286		&& inactivity >= kCornerDelay)
287		_Invoke();
288}
289
290
291void
292ScreenSaverFilter::_UpdateRectangles()
293{
294	fBlankRect = _ScreenCorner(fBlankCorner, kBlankCornerSize);
295	fNeverBlankRect = _ScreenCorner(fNeverBlankCorner, kNeverBlankCornerSize);
296}
297
298
299BRect
300ScreenSaverFilter::_ScreenCorner(screen_corner corner, uint32 cornerSize)
301{
302	BRect frame = BScreen().Frame();
303
304	switch (corner) {
305		case UP_LEFT_CORNER:
306			return BRect(frame.left, frame.top, frame.left + cornerSize - 1,
307				frame.top + cornerSize - 1);
308
309		case UP_RIGHT_CORNER:
310			return BRect(frame.right - cornerSize + 1, frame.top, frame.right,
311				frame.top + cornerSize - 1);
312
313		case DOWN_RIGHT_CORNER:
314			return BRect(frame.right - cornerSize + 1, frame.bottom - cornerSize + 1,
315				frame.right, frame.bottom);
316
317		case DOWN_LEFT_CORNER:
318			return BRect(frame.left, frame.bottom - cornerSize + 1,
319				frame.left + cornerSize - 1, frame.bottom);
320
321		default:
322			// return an invalid rectangle
323			return BRect(-1, -1, -2, -2);
324	}
325}
326
327
328filter_result
329ScreenSaverFilter::Filter(BMessage *message, BList *outList)
330{
331	BAutolock _(this);
332
333	fLastEventTime = system_time();
334
335	switch (message->what) {
336		case B_MOUSE_MOVED:
337		{
338			BPoint where;
339			if (message->FindPoint("where", &where) != B_OK)
340				break;
341
342			if ((fFrameNum++ % 32) == 0) {
343				// Every so many frames, update
344				// Used so that we don't update the screen coord's so often
345				// Ideally, we would get a message when the screen changes.
346				// R5 doesn't do this.
347				_UpdateRectangles();
348			}
349
350			if (fBlankRect.Contains(where)) {
351				fCurrentCorner = fBlankCorner;
352
353				// start screen blanker after one second of inactivity
354				if (fCornerRunner != NULL
355					&& fCornerRunner->SetInterval(kCornerDelay) != B_OK)
356					_Invoke();
357				break;
358			}
359
360			if (fNeverBlankRect.Contains(where))
361				fCurrentCorner = fNeverBlankCorner;
362			else
363				fCurrentCorner = NO_CORNER;
364			break;
365		}
366	}
367
368	return B_DISPATCH_MESSAGE;
369}
370
371