1 //
2 //	matrix.c
3 //
4 //	Matrix-window implementation
5 //
6 #include <windows.h>
7 #include <windowsx.h>
8 #include <tchar.h>
9 #include "globals.h"
10 #include "message.h"
11 #include "matrix.h"
12 #include "resource.h"
13 
14 void DoMatrixMessage(HDC hdc, MATRIX *matrix);
15 
16 // pseudo-random number generator, based on 16bit CRC algorithm
17 static WORD _crc_reg = 0;
18 int crc_rand()
19 {
20 	const  WORD mask = 0xb400;
21 
22 	if(_crc_reg & 1)
23 		_crc_reg = (_crc_reg >> 1) ^ mask;
24 	else
25 		_crc_reg = (_crc_reg >> 1);
26 
27 	return _crc_reg;
28 }
29 
30 int GlyphIntensity(GLYPH glyph)
31 {
32 	return (int)((glyph & 0x7f00) >> 8);
33 }
34 
35 GLYPH DarkenGlyph(GLYPH glyph)
36 {
37 	int intensity = GlyphIntensity(glyph);
38 
39 	if(intensity > 0)
40 		return GLYPH_REDRAW | ((intensity - 1) << 8) | (glyph & 0x00FF);
41 	else
42 		return glyph;
43 }
44 
45 GLYPH RandomGlyph(int intensity)
46 {
47 	return GLYPH_REDRAW | (intensity << 8) | (crc_rand() % NUM_GLYPHS);
48 }
49 
50 void RedrawBlip(GLYPH *glypharr, int blippos)
51 {
52 	glypharr[blippos+0] |= GLYPH_REDRAW;
53 	glypharr[blippos+1] |= GLYPH_REDRAW;
54 	glypharr[blippos+8] |= GLYPH_REDRAW;
55 	glypharr[blippos+9] |= GLYPH_REDRAW;
56 }
57 
58 void ScrollMatrixColumn(MATRIX_COLUMN *col)
59 {
60 	int y;
61 	GLYPH lastglyph = 0;
62 	GLYPH thisglyph;
63 
64 	// wait until we are allowed to scroll
65 	if(col->started == FALSE)
66 	{
67 		if(--col->countdown <= 0)
68 			col->started = TRUE;
69 
70 		return;
71 	}
72 
73 	// "seed" the glyph-run
74 	lastglyph = col->state ? (GLYPH)0 : (GLYPH)(MAX_INTENSITY << 8);
75 
76 	//
77 	// loop over the entire length of the column, looking for changes
78 	// in intensity/darkness. This change signifies the start/end
79 	// of a run of glyphs.
80 	//
81 	for(y = 0; y < col->length; y++)
82 	{
83 		thisglyph = col->glyph[y];
84 
85 		// bottom-most part of "run". Insert a new character (glyph)
86 		// at the end to lengthen the run down the screen..gives the
87 		// impression that the run is "falling" down the screen
88 		if(GlyphIntensity(thisglyph) < GlyphIntensity(lastglyph) &&
89 			GlyphIntensity(thisglyph) == 0)
90 		{
91 			col->glyph[y] = RandomGlyph(MAX_INTENSITY - 1);
92 			y++;
93 		}
94 		// top-most part of "run". Delete a character off the top by
95 		// darkening the glyph until it eventually disappears (turns black).
96 		// this gives the effect that the run as dropped downwards
97 		else if(GlyphIntensity(thisglyph) > GlyphIntensity(lastglyph))
98 		{
99 			col->glyph[y] = DarkenGlyph(thisglyph);
100 
101 			// if we've just darkened the last bit, skip on so
102 			// the whole run doesn't go dark
103 			if(GlyphIntensity(thisglyph) == MAX_INTENSITY - 1)
104 				y++;
105 		}
106 
107 		lastglyph = col->glyph[y];
108 	}
109 
110 	// change state from blanks <-> runs when the current run as expired
111 	if(--col->runlen <= 0)
112 	{
113 		if(col->state ^= 1)
114 			col->runlen = crc_rand() % (3 * DENSITY/2) + DENSITY_MIN;
115 		else
116 			col->runlen = crc_rand() % (DENSITY_MAX+1-DENSITY) + (DENSITY_MIN*2);
117 	}
118 
119 	//
120 	// make a "blip" run down this column at double-speed
121 	//
122 
123 	// mark current blip as redraw so it gets "erased"
124 	if(col->blippos >= 0 && col->blippos < col->length)
125 		RedrawBlip(col->glyph, col->blippos);
126 
127 	// advance down screen at double-speed
128 	col->blippos += 2;
129 
130 	// if the blip gets to the end of a run, start it again (for a random
131 	// length so that the blips never get synched together)
132 	if(col->blippos >= col->bliplen)
133 	{
134 		col->bliplen = col->length + crc_rand() % 50;
135 		col->blippos = 0;
136 	}
137 
138 	// now redraw blip at new position
139 	if(col->blippos >= 0 && col->blippos < col->length)
140 		RedrawBlip(col->glyph, col->blippos);
141 
142 }
143 
144 //
145 // randomly change a small collection glyphs in a column
146 //
147 void RandomMatrixColumn(MATRIX_COLUMN *col)
148 {
149 	int i, y;
150 
151 	for(i = 1, y = 0; i < 16; i++)
152 	{
153 		// find a run
154 		while(GlyphIntensity(col->glyph[y]) < MAX_INTENSITY-1 && y < col->length)
155 			y++;
156 
157 		if(y >= col->length)
158 			break;
159 
160 		col->glyph[y]  = (col->glyph[y] & 0xff00) | (crc_rand() % NUM_GLYPHS);
161 		col->glyph[y] |= GLYPH_REDRAW;
162 
163 		y += crc_rand() % 10;
164 	}
165 }
166 
167 void DrawGlyph(MATRIX *matrix, HDC hdc, int xpos, int ypos, GLYPH glyph)
168 {
169 	int intensity = GlyphIntensity(glyph);
170 	int glyphidx  = glyph & 0xff;
171 
172 	BitBlt(hdc, xpos, ypos, GLYPH_WIDTH, GLYPH_HEIGHT, matrix->hdcBitmap,
173 		glyphidx * GLYPH_WIDTH, intensity * GLYPH_HEIGHT, SRCCOPY);
174 }
175 
176 void RedrawMatrixColumn(MATRIX_COLUMN *col, MATRIX *matrix, HDC hdc, int xpos)
177 {
178 	int y;
179 
180 	// loop down the length of the column redrawing only what needs doing
181 	for(y = 0; y < col->length; y++)
182 	{
183 		GLYPH glyph = col->glyph[y];
184 
185 		// does this glyph (character) need to be redrawn?
186 		if(glyph & GLYPH_REDRAW)
187 		{
188 			if((y == col->blippos+0 || y == col->blippos+1 ||
189 				y == col->blippos+8 || y == col->blippos+9) &&
190 				GlyphIntensity(glyph) >= MAX_INTENSITY-1)
191 				glyph |= MAX_INTENSITY << 8;
192 
193 			DrawGlyph(matrix, hdc, xpos, y * GLYPH_HEIGHT, glyph);
194 
195 			// clear redraw state
196 			col->glyph[y] &= ~GLYPH_REDRAW;
197 		}
198 	}
199 }
200 
201 void DecodeMatrix(HWND hwnd, MATRIX *matrix)
202 {
203 	int x;
204 	HDC hdc = GetDC(hwnd);
205 
206 	for(x = 0; x < matrix->numcols; x++)
207 	{
208 		RandomMatrixColumn(&matrix->column[x]);
209 		ScrollMatrixColumn(&matrix->column[x]);
210 		RedrawMatrixColumn(&matrix->column[x], matrix, hdc, x * GLYPH_WIDTH);
211 	}
212 
213 	if(matrix->message)
214 		DoMatrixMessage(hdc, matrix);
215 
216 	ReleaseDC(hwnd, hdc);
217 }
218 
219 //
220 //	Allocate matrix structures
221 //
222 MATRIX *CreateMatrix(HWND hwnd, int width, int height)
223 {
224 	MATRIX *matrix;
225 	HDC hdc;
226 	int x, y;
227 
228 	int rows = height / GLYPH_HEIGHT + 1;
229 	int cols = width  / GLYPH_WIDTH  + 1;
230 
231 	// allocate matrix!
232 	if((matrix = malloc(sizeof(MATRIX) + sizeof(MATRIX_COLUMN) * cols)) == 0)
233 		return 0;
234 
235 	matrix->numcols = cols;
236 	matrix->numrows = rows;
237 	matrix->width   = width;
238 	matrix->height  = height;
239 
240 	for(x = 0; x < cols; x++)
241 	{
242 		matrix->column[x].length       = rows;
243 		matrix->column[x].started      = FALSE;
244 		matrix->column[x].countdown    = crc_rand() % 100;
245 		matrix->column[x].state        = crc_rand() % 2;
246 		matrix->column[x].runlen       = crc_rand() % 20 + 3;
247 
248 		matrix->column[x].glyph  = malloc(sizeof(GLYPH) * (rows+16));
249 
250 		for(y = 0; y < rows; y++)
251 			matrix->column[x].glyph[y] = 0;//;
252 	}
253 
254 	// Load bitmap!!
255 	hdc = GetDC(NULL);
256 	matrix->hbmBitmap = LoadBitmap(GetModuleHandle(0), MAKEINTRESOURCE(IDB_BITMAP1));
257 	matrix->hdcBitmap = CreateCompatibleDC(hdc);
258 	SelectObject(matrix->hdcBitmap, matrix->hbmBitmap);
259 	ReleaseDC(NULL, hdc);
260 
261 	// Create a message for this window...only if we are
262 	// screen-saving (not if in preview mode)
263 	if(GetParent(hwnd) == 0)
264 		matrix->message = InitMatrixMessage(hwnd, matrix->numcols, matrix->numrows);
265 	else
266 		matrix->message = 0;
267 
268 	return matrix;
269 }
270 
271 //
272 //	Free up matrix structures
273 //
274 void DestroyMatrix(MATRIX *matrix)
275 {
276 	int x;
277 
278 	// free the matrix columns
279 	for(x = 0; x < matrix->numcols; x++)
280 		free(matrix->column[x].glyph);
281 
282 	DeleteDC(matrix->hdcBitmap);
283 	DeleteObject(matrix->hbmBitmap);
284 
285 	// now delete the matrix!
286 	free(matrix);
287 }
288 
289 MATRIX *GetMatrix(HWND hwnd)
290 {
291 	return (MATRIX *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
292 }
293 
294 void SetMatrix(HWND hwnd, MATRIX *matrix)
295 {
296 	SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)matrix);
297 }
298 
299 //
300 //	Window procedure for one matrix (1 per screen)
301 //
302 LRESULT WINAPI MatrixWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
303 {
304 	static POINT ptLast;
305 	static POINT ptCursor;
306 	static BOOL  fFirstTime = TRUE;
307 
308 	MATRIX *matrix = GetMatrix(hwnd);
309 
310 	switch(msg)
311 	{
312 	// window creation
313 	case WM_NCCREATE:
314 
315 		// create the matrix based on how big this window is
316 		matrix = CreateMatrix(hwnd, ((CREATESTRUCT *)lParam)->cx, ((CREATESTRUCT *)lParam)->cy);
317 
318 		// failed to allocate? stop window creation!
319 		if(matrix == 0)
320 			return FALSE;
321 
322 		SetMatrix(hwnd, matrix);
323 
324 		// start off an animation timer
325 		SetTimer(hwnd, 0xdeadbeef, ((SPEED_MAX - g_nMatrixSpeed) + SPEED_MIN) * 10, 0);
326 
327 		return TRUE;
328 
329 	// window being destroyed, cleanup
330 	case WM_NCDESTROY:
331 		DestroyMatrix(matrix);
332 		PostQuitMessage(0);
333 		return 0;
334 
335 	// animation timer has gone off, redraw the matrix!
336 	case WM_TIMER:
337 		DecodeMatrix(hwnd, matrix);
338 		return 0;
339 
340 	// break out of screen-saver if any keyboard activity
341 	case WM_KEYDOWN:
342     case WM_SYSKEYDOWN:
343         PostMessage(hwnd, WM_CLOSE, 0, 0);
344         return 0;
345 
346 	// break out of screen-saver if any mouse activity
347 	case WM_LBUTTONDOWN:
348 	case WM_LBUTTONUP:
349 	case WM_RBUTTONDOWN:
350 	case WM_RBUTTONUP:
351 	case WM_MBUTTONDOWN:
352 	case WM_MBUTTONUP:
353 	case WM_MOUSEMOVE:
354 
355 		// If we've got a parent then we must be a preview
356 		if(GetParent(hwnd) != 0)
357 			return 0;
358 
359 		if(fFirstTime)
360 		{
361 			GetCursorPos(&ptLast);
362 			fFirstTime = FALSE;
363 		}
364 
365 		GetCursorPos(&ptCursor);
366 
367 		// if the mouse has moved more than 3 pixels then exit
368 		if(abs(ptCursor.x - ptLast.x) >= 3 || abs(ptCursor.y - ptLast.y) >= 3)
369 			PostMessage(hwnd, WM_CLOSE, 0, 0);
370 
371 		ptLast = ptCursor;
372 
373         return 0;
374 
375 	// someone wants to close us...see if it's ok
376 	case WM_CLOSE:
377 
378 		if(VerifyPassword(hwnd))
379 		{
380 			KillTimer(hwnd, 0xdeadbeef);
381 			DestroyWindow(hwnd);
382 		}
383 
384 		return 0;
385 	}
386 
387 	return DefWindowProc(hwnd, msg, wParam, lParam);
388 }
389 
390 HWND CreateScreenSaveWnd(HWND hwndParent, RECT *rect)
391 {
392 	DWORD dwStyle = hwndParent ? WS_CHILD : WS_POPUP;
393 
394 #ifdef _DEBUG
395 	DWORD dwStyleEx = 0;
396 #else
397 	DWORD dwStyleEx = WS_EX_TOPMOST;
398 #endif
399 
400 	if(hwndParent)
401 		GetClientRect(hwndParent, rect);
402 
403 	return CreateWindowEx(	dwStyleEx,
404 							APPNAME,
405 							0,
406 							WS_VISIBLE | dwStyle,
407 							rect->left,
408 							rect->top,
409 							rect->right - rect->left,
410 							rect->bottom - rect->top,
411 							hwndParent,
412 							0,
413 							GetModuleHandle(0),
414 							0
415 						);
416 }
417 
418 //
419 //	Initialize class for matrix window
420 //
421 void InitScreenSaveClass(BOOL fPreview)
422 {
423 	WNDCLASSEX	wcx;
424 
425 	wcx.cbSize			= sizeof(WNDCLASSEX);
426 	wcx.style			= 0;
427 	wcx.lpfnWndProc		= MatrixWndProc;
428 	wcx.cbClsExtra		= 0;
429 	wcx.cbWndExtra		= sizeof(MATRIX *);
430 	wcx.hInstance		= GetModuleHandle(0);
431 	wcx.hIcon			= 0;
432 	wcx.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
433 	wcx.lpszMenuName	= 0;
434 	wcx.lpszClassName	= APPNAME;
435 	wcx.hIconSm			= 0;
436 
437 	if(fPreview)
438 		wcx.hCursor			= LoadCursor(0, IDC_ARROW);
439 	else
440 		wcx.hCursor			= LoadCursor(wcx.hInstance, MAKEINTRESOURCE(IDC_BLANKCURSOR));
441 
442 	// initialize the crc register used for "random" number generation
443 	_crc_reg = (WORD)GetTickCount();
444 
445 	RegisterClassEx(&wcx);
446 }
447