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