1/*
2 * Copyright 2003-2013 Haiku, Inc. All rights reserved
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel Dörfler, axeld@pinc-software.de
7 *		Jérôme Duval, jerome.duval@free.fr
8 *		Michael Phipps
9 *		John Scipione, jscipione@gmail.com
10 */
11
12
13#include "ScreenSaverRunner.h"
14
15#include <stdio.h>
16
17#include <DirectWindow.h>
18#include <FindDirectory.h>
19#include <Message.h>
20#include <Window.h>
21
22
23ScreenSaverRunner::ScreenSaverRunner(BWindow* window, BView* view,
24	ScreenSaverSettings& settings)
25	:
26	fWindow(window),
27	fView(view),
28	fIsDirectDraw(dynamic_cast<BDirectWindow*>(window) != NULL),
29	fSettings(settings),
30	fSaver(NULL),
31	fAddonImage(-1),
32	fThread(-1),
33	fQuitting(false)
34{
35	_LoadAddOn();
36}
37
38
39ScreenSaverRunner::~ScreenSaverRunner()
40{
41	if (!fQuitting)
42		Quit();
43
44	_CleanUp();
45}
46
47
48status_t
49ScreenSaverRunner::Run()
50{
51	fThread = spawn_thread(&_ThreadFunc, "ScreenSaverRenderer", B_LOW_PRIORITY,
52		this);
53	Resume();
54
55	return fThread >= B_OK ? B_OK : fThread;
56}
57
58
59void
60ScreenSaverRunner::Quit()
61{
62	fQuitting = true;
63	Resume();
64
65	if (fThread >= 0) {
66		status_t returnValue;
67		wait_for_thread(fThread, &returnValue);
68	}
69}
70
71
72status_t
73ScreenSaverRunner::Suspend()
74{
75	return suspend_thread(fThread);
76}
77
78
79status_t
80ScreenSaverRunner::Resume()
81{
82	return resume_thread(fThread);
83}
84
85
86void
87ScreenSaverRunner::_LoadAddOn()
88{
89	// This is a new set of preferences. Free up what we did have
90	// TODO: this is currently not meant to be used after creation
91	if (fThread >= B_OK) {
92		Suspend();
93		if (fSaver != NULL)
94			fSaver->StopSaver();
95	}
96	_CleanUp();
97
98	const char* moduleName = fSettings.ModuleName();
99	if (moduleName == NULL || *moduleName == '\0') {
100		Resume();
101		return;
102	}
103
104	BScreenSaver* (*instantiate)(BMessage*, image_id);
105
106	// try each directory until one succeeds
107
108	directory_which which[] = {
109		B_USER_NONPACKAGED_ADDONS_DIRECTORY,
110		B_USER_ADDONS_DIRECTORY,
111		B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
112		B_SYSTEM_ADDONS_DIRECTORY,
113	};
114	BPath path;
115
116	for (uint32 i = 0; i < sizeof(which) / sizeof(which[0]); i++) {
117		if (find_directory(which[i], &path, false) != B_OK)
118			continue;
119		else if (path.Append("Screen Savers") != B_OK)
120			continue;
121		else if (path.Append(fSettings.ModuleName()) != B_OK)
122			continue;
123
124		fAddonImage = load_add_on(path.Path());
125		if (fAddonImage > 0)
126			break;
127	}
128
129	if (fAddonImage > 0) {
130		// look for the one C function that should exist,
131		// instantiate_screen_saver()
132		if (get_image_symbol(fAddonImage, "instantiate_screen_saver",
133				B_SYMBOL_TYPE_TEXT, (void **)&instantiate) != B_OK) {
134			fprintf(stderr, "Unable to find the instantiation function.\n");
135		} else {
136			BMessage state;
137			fSettings.GetModuleState(moduleName, &state);
138			fSaver = instantiate(&state, fAddonImage);
139		}
140
141		if (fSaver == NULL) {
142			fprintf(stderr, "Screen saver initialization failed.\n");
143			_CleanUp();
144		} else if (fSaver->InitCheck() != B_OK) {
145			fprintf(stderr, "Screen saver initialization failed: %s.\n",
146				strerror(fSaver->InitCheck()));
147			_CleanUp();
148		}
149	} else
150		fprintf(stderr, "Unable to open add-on %s.\n", path.Path());
151
152	Resume();
153}
154
155
156void
157ScreenSaverRunner::_CleanUp()
158{
159	delete fSaver;
160	fSaver = NULL;
161
162	if (fAddonImage >= 0) {
163		status_t result = unload_add_on(fAddonImage);
164		if (result != B_OK) {
165			fprintf(stderr, "Unable to unload screen saver add-on: %s.\n",
166				strerror(result));
167		}
168		fAddonImage = -1;
169	}
170}
171
172
173status_t
174ScreenSaverRunner::_Run()
175{
176	static const uint32 kInitialTickRate = 50000;
177
178	// TODO: This code is getting awfully complicated and should
179	// probably be refactored.
180	uint32 tickBase = kInitialTickRate;
181	int32 snoozeCount = 0;
182	int32 frame = 0;
183	bigtime_t lastTickTime = 0;
184	bigtime_t tick = fSaver != NULL ? fSaver->TickSize() : tickBase;
185
186	while (!fQuitting) {
187		// break the idle time up into ticks so that we can evaluate
188		// the quit condition with greater responsiveness
189		// otherwise a screen saver that sets, say, a 30 second tick
190		// will result in the screen saver not responding to deactivation
191		// for that length of time
192		snooze(tickBase);
193		if (system_time() - lastTickTime < tick)
194			continue;
195		else {
196			// re-evaluate the tick time after each successful wakeup
197			// screensavers can adjust it on the fly, and we must be
198			// prepared to accomodate that
199			tick = fSaver != NULL ? fSaver->TickSize() : tickBase;
200
201			if (tick < tickBase) {
202				if (tick < 0)
203					tick = 0;
204				tickBase = tick;
205			} else if (tickBase < kInitialTickRate
206				&& tick >= kInitialTickRate) {
207				tickBase = kInitialTickRate;
208			}
209
210			lastTickTime = system_time();
211		}
212
213		if (snoozeCount) {
214			// if we are sleeping, do nothing
215			snoozeCount--;
216		} else if (fSaver != NULL) {
217			if (fSaver->LoopOnCount() && frame >= fSaver->LoopOnCount()) {
218				// Time to nap
219				frame = 0;
220				snoozeCount = fSaver->LoopOffCount();
221			} else if (fWindow->LockWithTimeout(5000LL) == B_OK) {
222				if (!fQuitting) {
223					// NOTE: BeOS R5 really calls DirectDraw()
224					// and then Draw() for the same frame
225					if (fIsDirectDraw)
226						fSaver->DirectDraw(frame);
227					fSaver->Draw(fView, frame);
228					fView->Sync();
229					frame++;
230				}
231				fWindow->Unlock();
232			}
233		} else
234			snoozeCount = 1000;
235	}
236
237	if (fSaver != NULL)
238		fSaver->StopSaver();
239
240	return B_OK;
241}
242
243
244status_t
245ScreenSaverRunner::_ThreadFunc(void* data)
246{
247	ScreenSaverRunner* runner = (ScreenSaverRunner*)data;
248	return runner->_Run();
249}
250