]> git.saurik.com Git - apple/boot.git/blob - i386/boot2/graphics.c
boot-132.tar.gz
[apple/boot.git] / i386 / boot2 / graphics.c
1 /*
2 * Copyright (c) 1999-2003 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 2.0 (the "License"). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
12 * this file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
20 * under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24 /*
25 * Copyright 1993 NeXT, Inc.
26 * All rights reserved.
27 */
28
29 #include "boot.h"
30 #include "vbe.h"
31 #include "appleClut8.h"
32 #include "appleboot.h"
33 #include "bootstruct.h"
34
35 /*
36 * for spinning disk
37 */
38 static int currentIndicator = 0;
39
40 static unsigned long lookUpCLUTIndex( unsigned char index,
41 unsigned char depth );
42
43 void drawColorRectangle( unsigned short x,
44 unsigned short y,
45 unsigned short width,
46 unsigned short height,
47 unsigned char colorIndex );
48
49 void drawDataRectangle( unsigned short x,
50 unsigned short y,
51 unsigned short width,
52 unsigned short height,
53 unsigned char * data );
54
55 static void setBorderColor( unsigned char colorIndex );
56
57 int
58 convertImage( unsigned short width,
59 unsigned short height,
60 const unsigned char *imageData,
61 unsigned char **newImageData );
62
63 #define VIDEO(x) (bootArgs->Video.v_ ## x)
64
65 #define MIN(x, y) ((x) < (y) ? (x) : (y))
66
67 //==========================================================================
68 // printVBEInfo
69
70 void printVBEInfo()
71 {
72 VBEInfoBlock vbeInfo;
73 int err;
74 int small;
75
76 // Fetch VBE Controller Info.
77
78 bzero( &vbeInfo, sizeof(vbeInfo) );
79 err = getVBEInfo( &vbeInfo );
80 if ( err != errSuccess )
81 return;
82
83 // Check presence of VESA signature.
84
85 if ( strncmp( (char *)vbeInfo.VESASignature, "VESA", 4 ) )
86 return;
87
88 // Announce controller properties.
89
90 small = (vbeInfo.TotalMemory < 16);
91
92 printf("VESA v%d.%d %d%s (%s)\n",
93 vbeInfo.VESAVersion >> 8,
94 vbeInfo.VESAVersion & 0xf,
95 small ? (vbeInfo.TotalMemory * 64) : (vbeInfo.TotalMemory / 16),
96 small ? "KB" : "MB",
97 VBEDecodeFP(const char *, vbeInfo.OEMStringPtr) );
98 }
99
100 //==========================================================================
101 //
102
103 void
104 printVBEModeInfo()
105 {
106 VBEInfoBlock vbeInfo;
107 unsigned short * modePtr;
108 VBEModeInfoBlock modeInfo;
109 int err;
110 int line;
111
112 err = getVBEInfo( &vbeInfo );
113 if ( err != errSuccess )
114 return;
115
116 line = 0;
117
118 // Activate and clear page 1
119 setActiveDisplayPage(1);
120 clearScreenRows(0, 24);
121 setCursorPosition( 0, 0, 1 );
122
123 printVBEInfo();
124 printf("Video modes supported:\n", VBEDecodeFP(const char *, vbeInfo.OEMStringPtr));
125
126 // Loop through the mode list, and find the matching mode.
127
128 for ( modePtr = VBEDecodeFP( unsigned short *, vbeInfo.VideoModePtr );
129 *modePtr != modeEndOfList; modePtr++ )
130 {
131 // Get mode information.
132
133 bzero( &modeInfo, sizeof(modeInfo) );
134 err = getVBEModeInfo( *modePtr, &modeInfo );
135 if ( err != errSuccess )
136 {
137 continue;
138 }
139
140 printf("Mode %x: %dx%dx%d mm:%d attr:%x\n",
141 *modePtr, modeInfo.XResolution, modeInfo.YResolution,
142 modeInfo.BitsPerPixel, modeInfo.MemoryModel,
143 modeInfo.ModeAttributes);
144
145 if (line++ >= 20) {
146 printf("(Press a key to continue...)");
147 getc();
148 line = 0;
149 clearScreenRows(0, 24);
150 setCursorPosition( 0, 0, 1 );
151 }
152 }
153 if (line != 0) {
154 printf("(Press a key to continue...)");
155 getc();
156 }
157 setActiveDisplayPage(0);
158 }
159
160
161 //==========================================================================
162 // getVESAModeWithProperties
163 //
164 // Return the VESA mode that matches the properties specified.
165 // If a mode is not found, then return the "best" available mode.
166
167 static unsigned short
168 getVESAModeWithProperties( unsigned short width,
169 unsigned short height,
170 unsigned char bitsPerPixel,
171 unsigned short attributesSet,
172 unsigned short attributesClear,
173 VBEModeInfoBlock * outModeInfo,
174 unsigned short * vesaVersion )
175 {
176 VBEInfoBlock vbeInfo;
177 unsigned short * modePtr;
178 VBEModeInfoBlock modeInfo;
179 unsigned char modeBitsPerPixel;
180 unsigned short matchedMode = modeEndOfList;
181 int err;
182
183 // Clear output mode info.
184
185 bzero( outModeInfo, sizeof(*outModeInfo) );
186
187 // Get VBE controller info containing the list of supported modes.
188
189 bzero( &vbeInfo, sizeof(vbeInfo) );
190 err = getVBEInfo( &vbeInfo );
191 if ( err != errSuccess )
192 {
193 return modeEndOfList;
194 }
195
196 // Report the VESA major/minor version number.
197
198 if (vesaVersion) *vesaVersion = vbeInfo.VESAVersion;
199
200 // Loop through the mode list, and find the matching mode.
201
202 for ( modePtr = VBEDecodeFP( unsigned short *, vbeInfo.VideoModePtr );
203 *modePtr != modeEndOfList; modePtr++ )
204 {
205 // Get mode information.
206
207 bzero( &modeInfo, sizeof(modeInfo) );
208 err = getVBEModeInfo( *modePtr, &modeInfo );
209 if ( err != errSuccess )
210 {
211 continue;
212 }
213
214 #if DEBUG
215 printf("Mode %x: %dx%dx%d mm:%d attr:%x\n",
216 *modePtr, modeInfo.XResolution, modeInfo.YResolution,
217 modeInfo.BitsPerPixel, modeInfo.MemoryModel,
218 modeInfo.ModeAttributes);
219 #endif
220
221 // Filter out unwanted modes based on mode attributes.
222
223 if ( ( ( modeInfo.ModeAttributes & attributesSet ) != attributesSet )
224 || ( ( modeInfo.ModeAttributes & attributesClear ) != 0 ) )
225 {
226 continue;
227 }
228
229 // Pixel depth in bits.
230
231 modeBitsPerPixel = modeInfo.BitsPerPixel;
232
233 if ( ( modeBitsPerPixel == 4 ) && ( modeInfo.MemoryModel == 0 ) )
234 {
235 // Text mode, 16 colors.
236 }
237 else if ( ( modeBitsPerPixel == 8 ) && ( modeInfo.MemoryModel == 4 ) )
238 {
239 // Packed pixel, 256 colors.
240 }
241 else if ( ( ( modeBitsPerPixel == 16 ) || ( modeBitsPerPixel == 15 ) )
242 && ( modeInfo.MemoryModel == 6 )
243 && ( modeInfo.RedMaskSize == 5 )
244 && ( modeInfo.GreenMaskSize == 5 )
245 && ( modeInfo.BlueMaskSize == 5 ) )
246 {
247 // Direct color, 16 bpp (1:5:5:5).
248 modeInfo.BitsPerPixel = modeBitsPerPixel = 16;
249 }
250 else if ( ( modeBitsPerPixel == 32 )
251 && ( modeInfo.MemoryModel == 6 )
252 && ( modeInfo.RedMaskSize == 8 )
253 && ( modeInfo.GreenMaskSize == 8 )
254 && ( modeInfo.BlueMaskSize == 8 ) )
255 {
256 // Direct color, 32 bpp (8:8:8:8).
257 }
258 else
259 {
260 continue; // Not a supported mode.
261 }
262
263 // Modes larger than the specified dimensions are skipped.
264
265 if ( ( modeInfo.XResolution > width ) ||
266 ( modeInfo.YResolution > height ) )
267 {
268 continue;
269 }
270
271 // Perfect match, we're done looking.
272
273 if ( ( modeInfo.XResolution == width ) &&
274 ( modeInfo.YResolution == height ) &&
275 ( modeBitsPerPixel == bitsPerPixel ) )
276 {
277 matchedMode = *modePtr;
278 bcopy( &modeInfo, outModeInfo, sizeof(modeInfo) );
279 break;
280 }
281
282 // Save the next "best" mode in case a perfect match is not found.
283
284 if ( modeInfo.XResolution == outModeInfo->XResolution &&
285 modeInfo.YResolution == outModeInfo->YResolution &&
286 modeBitsPerPixel <= outModeInfo->BitsPerPixel )
287 {
288 continue; // Saved mode has more depth.
289 }
290 if ( modeInfo.XResolution < outModeInfo->XResolution ||
291 modeInfo.YResolution < outModeInfo->YResolution ||
292 modeBitsPerPixel < outModeInfo->BitsPerPixel )
293 {
294 continue; // Saved mode has more resolution.
295 }
296
297 matchedMode = *modePtr;
298 bcopy( &modeInfo, outModeInfo, sizeof(modeInfo) );
299 }
300
301 return matchedMode;
302 }
303
304 //==========================================================================
305 // setupPalette
306
307 static void setupPalette( VBEPalette * p, const unsigned char * g )
308 {
309 int i;
310 unsigned char * source = (unsigned char *) g;
311
312 for (i = 0; i < 256; i++)
313 {
314 (*p)[i] = 0;
315 (*p)[i] |= ((unsigned long)((*source++) >> 2)) << 16; // Red
316 (*p)[i] |= ((unsigned long)((*source++) >> 2)) << 8; // Green
317 (*p)[i] |= ((unsigned long)((*source++) >> 2)); // Blue
318 }
319 }
320
321 //==========================================================================
322 // Simple decompressor for boot images encoded in RLE format.
323
324 char * decodeRLE( const void * rleData, int rleBlocks, int outBytes )
325 {
326 char *out, *cp;
327
328 struct RLEBlock {
329 unsigned char count;
330 unsigned char value;
331 } * bp = (struct RLEBlock *) rleData;
332
333 out = cp = (char *) malloc( outBytes );
334 if ( out == NULL ) return NULL;
335
336 while ( rleBlocks-- )
337 {
338 memset( cp, bp->value, bp->count );
339 cp += bp->count;
340 bp++;
341 }
342
343 return out;
344 }
345
346 //==========================================================================
347 // setVESAGraphicsMode
348
349 static int
350 setVESAGraphicsMode( unsigned short width,
351 unsigned short height,
352 unsigned char bitsPerPixel,
353 unsigned short refreshRate )
354 {
355 VBEModeInfoBlock minfo;
356 unsigned short mode;
357 unsigned short vesaVersion;
358 int err = errFuncNotSupported;
359
360 do {
361 mode = getVESAModeWithProperties( width, height, bitsPerPixel,
362 maColorModeBit |
363 maModeIsSupportedBit |
364 maGraphicsModeBit |
365 maLinearFrameBufferAvailBit,
366 0,
367 &minfo, &vesaVersion );
368 if ( mode == modeEndOfList )
369 {
370 break;
371 }
372
373 if ( (vesaVersion >> 8) >= 3 && refreshRate >= 60 &&
374 (gBootMode & kBootModeSafe) == 0 )
375 {
376 VBECRTCInfoBlock timing;
377
378 // Generate CRTC timing for given refresh rate.
379
380 generateCRTCTiming( minfo.XResolution, minfo.YResolution,
381 refreshRate, kCRTCParamRefreshRate,
382 &timing );
383
384 // Find the actual pixel clock supported by the hardware.
385
386 getVBEPixelClock( mode, &timing.PixelClock );
387
388 // Re-compute CRTC timing based on actual pixel clock.
389
390 generateCRTCTiming( minfo.XResolution, minfo.YResolution,
391 timing.PixelClock, kCRTCParamPixelClock,
392 &timing );
393
394 // Set the video mode and use specified CRTC timing.
395
396 err = setVBEMode( mode | kLinearFrameBufferBit |
397 kCustomRefreshRateBit, &timing );
398 }
399 else
400 {
401 // Set the mode with default refresh rate.
402
403 err = setVBEMode( mode | kLinearFrameBufferBit, NULL );
404 }
405 if ( err != errSuccess )
406 {
407 break;
408 }
409
410 // Set 8-bit color palette.
411
412 if ( minfo.BitsPerPixel == 8 )
413 {
414 VBEPalette palette;
415 setupPalette( &palette, appleClut8 );
416 if ((err = setVBEPalette(palette)) != errSuccess)
417 {
418 break;
419 }
420 }
421
422 // Is this required for buggy Video BIOS implementations?
423 // On which adapter?
424
425 if ( minfo.BytesPerScanline == 0 )
426 minfo.BytesPerScanline = ( minfo.XResolution *
427 minfo.BitsPerPixel ) >> 3;
428
429 // Update KernBootStruct using info provided by the selected
430 // VESA mode.
431
432 bootArgs->Video.v_display = GRAPHICS_MODE;
433 bootArgs->Video.v_width = minfo.XResolution;
434 bootArgs->Video.v_height = minfo.YResolution;
435 bootArgs->Video.v_depth = minfo.BitsPerPixel;
436 bootArgs->Video.v_rowBytes = minfo.BytesPerScanline;
437 bootArgs->Video.v_baseAddr = VBEMakeUInt32(minfo.PhysBasePtr);
438
439 }
440 while ( 0 );
441
442 return err;
443 }
444
445 int
446 convertImage( unsigned short width,
447 unsigned short height,
448 const unsigned char *imageData,
449 unsigned char **newImageData )
450 {
451 int cnt;
452 unsigned char *img = 0;
453 unsigned short *img16;
454 unsigned long *img32;
455
456 switch ( VIDEO(depth) ) {
457 case 16 :
458 img16 = malloc(width * height * 2);
459 if ( !img16 ) break;
460 for (cnt = 0; cnt < (width * height); cnt++)
461 img16[cnt] = lookUpCLUTIndex(imageData[cnt], 16);
462 img = (unsigned char *)img16;
463 break;
464
465 case 32 :
466 img32 = malloc(width * height * 4);
467 if ( !img32 ) break;
468 for (cnt = 0; cnt < (width * height); cnt++)
469 img32[cnt] = lookUpCLUTIndex(imageData[cnt], 32);
470 img = (unsigned char *)img32;
471 break;
472
473 default :
474 img = malloc(width * height);
475 bcopy(imageData, img, width * height);
476 break;
477 }
478 *newImageData = img;
479 return 0;
480 }
481
482 //==========================================================================
483 // drawBootGraphics
484
485 void
486 drawBootGraphics( void )
487 {
488 unsigned char * imageData = 0;
489 long x, y;
490 char * appleBootPict;
491
492 do {
493 // Fill the background to 75% grey (same as BootX).
494
495 drawColorRectangle( 0, 0, VIDEO(width), VIDEO(height),
496 0x01 /* color index */ );
497
498 setBorderColor( 0x01 /* color index */ );
499
500 appleBootPict = decodeRLE( gAppleBootPictRLE, kAppleBootRLEBlocks,
501 kAppleBootWidth * kAppleBootHeight );
502
503 // Prepare the data for the happy mac.
504
505 if ( appleBootPict )
506 {
507 convertImage(kAppleBootWidth, kAppleBootHeight,
508 (unsigned char *)appleBootPict, &imageData);
509
510 x = ( VIDEO(width) - kAppleBootWidth ) / 2;
511 y = ( VIDEO(height) - kAppleBootHeight ) / 2 + kAppleBootOffset;
512
513 // Draw the happy mac in the center of the display.
514
515 if ( imageData )
516 {
517 drawDataRectangle( x, y, kAppleBootWidth, kAppleBootHeight,
518 imageData );
519 free( imageData );
520 }
521 free( appleBootPict );
522 }
523
524 } while (0);
525 }
526
527 //==========================================================================
528 // LookUpCLUTIndex
529
530 static unsigned long lookUpCLUTIndex( unsigned char index,
531 unsigned char depth )
532 {
533 long result, red, green, blue;
534
535 red = appleClut8[index * 3 + 0];
536 green = appleClut8[index * 3 + 1];
537 blue = appleClut8[index * 3 + 2];
538
539 switch (depth) {
540 case 16 :
541 result = ((red & 0xF8) << 7) |
542 ((green & 0xF8) << 2) |
543 ((blue & 0xF8) >> 3);
544 result |= (result << 16);
545 break;
546
547 case 32 :
548 result = (red << 16) | (green << 8) | blue;
549 break;
550
551 default :
552 result = index | (index << 8);
553 result |= (result << 16);
554 break;
555 }
556
557 return result;
558 }
559
560 //==========================================================================
561 // drawColorRectangle
562
563 static void * stosl(void * dst, long val, long len)
564 {
565 asm volatile ( "rep; stosl"
566 : "=c" (len), "=D" (dst)
567 : "0" (len), "1" (dst), "a" (val)
568 : "memory" );
569
570 return dst;
571 }
572
573 void drawColorRectangle( unsigned short x,
574 unsigned short y,
575 unsigned short width,
576 unsigned short height,
577 unsigned char colorIndex )
578 {
579 long pixelBytes;
580 long color = lookUpCLUTIndex( colorIndex, VIDEO(depth) );
581 char * vram;
582
583 pixelBytes = VIDEO(depth) / 8;
584 vram = (char *) VIDEO(baseAddr) +
585 VIDEO(rowBytes) * y + pixelBytes * x;
586
587 width = MIN(width, VIDEO(width) - x);
588 height = MIN(height, VIDEO(height) - y);
589
590 while ( height-- )
591 {
592 int rem = ( pixelBytes * width ) % 4;
593 if ( rem ) bcopy( &color, vram, rem );
594 stosl( vram + rem, color, pixelBytes * width / 4 );
595 vram += VIDEO(rowBytes);
596 }
597 }
598
599 //==========================================================================
600 // drawDataRectangle
601
602 void drawDataRectangle( unsigned short x,
603 unsigned short y,
604 unsigned short width,
605 unsigned short height,
606 unsigned char * data )
607 {
608 unsigned short drawWidth;
609 long pixelBytes = VIDEO(depth) / 8;
610 unsigned char * vram = (unsigned char *) VIDEO(baseAddr) +
611 VIDEO(rowBytes) * y + pixelBytes * x;
612
613 drawWidth = MIN(width, VIDEO(width) - x);
614 height = MIN(height, VIDEO(height) - y);
615 while ( height-- ) {
616 bcopy( data, vram, drawWidth * pixelBytes );
617 vram += VIDEO(rowBytes);
618 data += width * pixelBytes;
619 }
620 }
621
622
623 //==========================================================================
624 // setBorderColor
625
626 static void
627 setBorderColor( unsigned char colorIndex )
628 {
629 long color = lookUpCLUTIndex( colorIndex, 32 );
630 VBEInfoBlock vbeInfo;
631 int err;
632
633 // Get VBE controller info.
634
635 bzero( &vbeInfo, sizeof(vbeInfo) );
636 err = getVBEInfo( &vbeInfo );
637 if ( err != errSuccess )
638 {
639 return;
640 }
641
642 }
643
644
645 //==========================================================================
646 // setVESATextMode
647
648 static int
649 setVESATextMode( unsigned short cols,
650 unsigned short rows,
651 unsigned char bitsPerPixel )
652 {
653 VBEModeInfoBlock minfo;
654 unsigned short mode = modeEndOfList;
655
656 if ( (cols != 80) || (rows != 25) ) // not 80x25 mode
657 {
658 mode = getVESAModeWithProperties( cols, rows, bitsPerPixel,
659 maColorModeBit |
660 maModeIsSupportedBit,
661 maGraphicsModeBit,
662 &minfo, NULL );
663 }
664
665 if ( ( mode == modeEndOfList ) || ( setVBEMode(mode, NULL) != errSuccess ) )
666 {
667 video_mode( 2 ); // VGA BIOS, 80x25 text mode.
668 minfo.XResolution = 80;
669 minfo.YResolution = 25;
670 }
671
672 // Update KernBootStruct using info provided by the selected
673 // VESA mode.
674
675 bootArgs->Video.v_display = VGA_TEXT_MODE;
676 bootArgs->Video.v_baseAddr = 0xb8000;
677 bootArgs->Video.v_width = minfo.XResolution;
678 bootArgs->Video.v_height = minfo.YResolution;
679 bootArgs->Video.v_depth = 8;
680 bootArgs->Video.v_rowBytes = 0x8000;
681
682 return errSuccess; // always return success
683 }
684
685 //==========================================================================
686 // getNumberArrayFromProperty
687
688 static int
689 getNumberArrayFromProperty( const char * propKey,
690 unsigned long numbers[],
691 unsigned long maxArrayCount )
692 {
693 char * propStr;
694 unsigned long count = 0;
695
696 #define _isdigit(c) ((c) >= '0' && (c) <= '9')
697
698 propStr = newStringForKey( (char *) propKey );
699 if ( propStr )
700 {
701 char * delimiter = propStr;
702 char * p = propStr;
703
704 while ( count < maxArrayCount && *p != '\0' )
705 {
706 unsigned long val = strtoul( p, &delimiter, 10 );
707 if ( p != delimiter )
708 {
709 numbers[count++] = val;
710 p = delimiter;
711 }
712 while ( ( *p != '\0' ) && !_isdigit(*p) )
713 p++;
714 }
715
716 free( propStr );
717 }
718
719 return count;
720 }
721
722 //==========================================================================
723 // setVideoMode
724 //
725 // Set the video mode to VGA_TEXT_MODE or GRAPHICS_MODE.
726
727 void
728 setVideoMode( int mode )
729 {
730 unsigned long params[4];
731 int count;
732 int err = errSuccess;
733
734 if ( mode == GRAPHICS_MODE )
735 {
736 params[3] = 0;
737 count = getNumberArrayFromProperty( kGraphicsModeKey, params, 4 );
738 if ( count < 3 )
739 {
740 params[0] = 1024; // Default graphics mode is 1024x768x32.
741 params[1] = 768;
742 params[2] = 32;
743 }
744
745 // Map from pixel format to bits per pixel.
746
747 if ( params[2] == 256 ) params[2] = 8;
748 if ( params[2] == 555 ) params[2] = 16;
749 if ( params[2] == 888 ) params[2] = 32;
750
751 err = setVESAGraphicsMode( params[0], params[1], params[2], params[3] );
752 if ( err == errSuccess )
753 {
754 if (gVerboseMode) {
755 // Tell the kernel to use text mode on a linear frame buffer display
756 bootArgs->Video.v_display = FB_TEXT_MODE;
757 } else {
758 bootArgs->Video.v_display = GRAPHICS_MODE;
759 drawBootGraphics();
760 }
761 }
762 }
763
764 if ( (mode == VGA_TEXT_MODE) || (err != errSuccess) )
765 {
766 count = getNumberArrayFromProperty( kTextModeKey, params, 2 );
767 if ( count < 2 )
768 {
769 params[0] = 80; // Default text mode is 80x25.
770 params[1] = 25;
771 }
772
773 setVESATextMode( params[0], params[1], 4 );
774 bootArgs->Video.v_display = VGA_TEXT_MODE;
775 }
776
777 currentIndicator = 0;
778 }
779
780 //==========================================================================
781 // Return the current video mode, VGA_TEXT_MODE or GRAPHICS_MODE.
782
783 int getVideoMode(void)
784 {
785 return bootArgs->Video.v_display;
786 }
787
788 //==========================================================================
789 // Display and clear the activity indicator.
790
791 static char indicator[] = {'-', '\\', '|', '/', '-', '\\', '|', '/', '\0'};
792 #define kNumIndicators (sizeof(indicator) - 1)
793
794 // To prevent a ridiculously fast-spinning indicator,
795 // ensure a minimum of 1/9 sec between animation frames.
796 #define MIN_TICKS 2
797
798 void
799 spinActivityIndicator( void )
800 {
801 static unsigned long lastTickTime = 0;
802 unsigned long currentTickTime = time18();
803 static char string[3] = {'\0', '\b', '\0'};
804
805 if (currentTickTime < lastTickTime + MIN_TICKS)
806 return;
807 else
808 lastTickTime = currentTickTime;
809
810 if ( getVideoMode() == VGA_TEXT_MODE )
811 {
812 if (currentIndicator >= kNumIndicators) currentIndicator = 0;
813 string[0] = indicator[currentIndicator++];
814 printf(string);
815 }
816 }
817
818 void
819 clearActivityIndicator( void )
820 {
821 if ( getVideoMode() == VGA_TEXT_MODE )
822 {
823 printf(" \b");
824 }
825 }
826