]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
Miscellaneous minor code cleanup.
[apple2] / src / apple2.cpp
1 //
2 // Apple 2 SDL Portable Apple Emulator
3 //
4 // by James Hammons
5 // © 2014 Underground Software
6 //
7 // Loosely based on AppleWin by Tom Charlesworth which was based on AppleWin by
8 // Oliver Schmidt which was based on AppleWin by Michael O'Brien. :-) Parts are
9 // also derived from ApplePC. Too bad it was closed source--it could have been
10 // *the* premier Apple II emulator out there.
11 //
12 // JLH = James Hammons <jlhamm@acm.org>
13 //
14 // WHO  WHEN        WHAT
15 // ---  ----------  -----------------------------------------------------------
16 // JLH  11/12/2005  Initial port to SDL
17 // JLH  11/18/2005  Wired up graphic soft switches
18 // JLH  12/02/2005  Setup timer subsystem for more accurate time keeping
19 // JLH  12/12/2005  Added preliminary state saving support
20 // JLH  09/24/2013  Added //e support
21 //
22
23 // STILL TO DO:
24 //
25 // - Port to SDL [DONE]
26 // - GUI goodies
27 // - Weed out unneeded functions [DONE]
28 // - Disk I/O [DONE]
29 // - 128K IIe related stuff [DONE]
30 // - State loading/saving
31 //
32
33 #include "apple2.h"
34
35 #include <SDL2/SDL.h>
36 #include <fstream>
37 #include <string>
38 #include <iomanip>
39 #include <iostream>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <time.h>
43 #include "log.h"
44 #include "video.h"
45 #include "sound.h"
46 #include "settings.h"
47 #include "v65c02.h"
48 #include "applevideo.h"
49 #include "timing.h"
50 #include "floppy.h"
51 #include "firmware.h"
52 #include "mmu.h"
53
54 #include "gui/gui.h"
55
56 // Debug and misc. defines
57
58 #define THREADED_65C02
59 #define CPU_THREAD_OVERFLOW_COMPENSATION
60 #define DEBUG_LC
61 //#define CPU_CLOCK_CHECKING
62 //#define THREAD_DEBUGGING
63 #define SOFT_SWITCH_DEBUGGING
64
65 // Global variables
66
67 uint8_t ram[0x10000], rom[0x10000];                     // RAM & ROM spaces
68 uint8_t ram2[0x10000];                                          // Auxillary RAM
69 V65C02REGS mainCPU;                                                     // v65C02 execution context
70 uint8_t appleType = APPLE_TYPE_IIE;
71 FloppyDrive floppyDrive;
72 bool powerStateChangeRequested = false;
73
74 // Local variables (actually, they're global since they're not static)
75
76 uint8_t lastKeyPressed = 0;
77 bool keyDown = false;
78 bool openAppleDown = false;
79 bool closedAppleDown = false;
80 bool store80Mode = false;
81 bool vbl = false;
82 bool slotCXROM = false;
83 bool slotC3ROM = false;
84 bool ramrd = false;
85 bool ramwrt = false;
86 bool altzp = false;
87 bool ioudis = true;
88 bool dhires = false;
89 // Language card state (ROM read, no write)
90 uint8_t lcState = 0x02;
91
92 static bool running = true;                                     // Machine running state flag...
93 static uint32_t startTicks;
94 static bool pauseMode = false;
95 static bool fullscreenDebounce = false;
96 static bool capsLock = false;
97 static bool capsLockDebounce = false;
98
99 // Local functions
100
101 static void SaveApple2State(const char * filename);
102 static bool LoadApple2State(const char * filename);
103 static void ResetApple2State(void);
104
105 // Local timer callback functions
106
107 static void FrameCallback(void);
108 static void BlinkTimer(void);
109
110 #ifdef THREADED_65C02
111 // Test of threaded execution of 6502
112 static SDL_Thread * cpuThread = NULL;
113 //static SDL_mutex * cpuMutex = NULL;
114 static SDL_cond * cpuCond = NULL;
115 static SDL_sem * mainSem = NULL;
116 static bool cpuFinished = false;
117 //static bool cpuSleep = false;
118
119 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
120
121 // Let's try a thread...
122 //
123 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
124 // it up
125 //
126 int CPUThreadFunc(void * data)
127 {
128         // Mutex must be locked for conditional to work...
129         // Also, must be created in the thread that uses it...
130         SDL_mutex * cpuMutex = SDL_CreateMutex();
131
132 // decrement mainSem...
133 //SDL_SemWait(mainSem);
134 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
135         float overflow = 0.0;
136 #endif
137
138         do
139         {
140 // This is never set to true anywhere...
141 //              if (cpuSleep)
142 //                      SDL_CondWait(cpuCond, cpuMutex);
143
144 // decrement mainSem...
145 #ifdef THREAD_DEBUGGING
146 WriteLog("CPU: SDL_SemWait(mainSem);\n");
147 #endif
148                 SDL_SemWait(mainSem);
149
150 // There are exactly 800 slices of 21.333 cycles per frame, so it works out
151 // evenly.
152 #ifdef THREAD_DEBUGGING
153 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
154 #endif
155                 for(int i=0; i<800; i++)
156                 {
157                         uint32_t cycles = 21;
158                         overflow += 0.333333334;
159
160                         if (overflow > 1.0)
161                         {
162                                 cycles++;
163                                 overflow -= 1.0;
164                         }
165
166                         Execute65C02(&mainCPU, cycles);
167                         WriteSampleToBuffer();
168
169                         // Dunno if this is correct (seems to be close enough)...
170                         vbl = (i < 670 ? true : false);
171                 }
172
173 #ifdef THREAD_DEBUGGING
174 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
175 #endif
176                 SDL_mutexP(cpuMutex);
177 // increment mainSem...
178 #ifdef THREAD_DEBUGGING
179 WriteLog("CPU: SDL_SemPost(mainSem);\n");
180 #endif
181                 SDL_SemPost(mainSem);
182 #ifdef THREAD_DEBUGGING
183 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
184 #endif
185                 SDL_CondWait(cpuCond, cpuMutex);
186
187 #ifdef THREAD_DEBUGGING
188 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
189 #endif
190                 SDL_mutexV(cpuMutex);
191         }
192         while (!cpuFinished);
193
194         SDL_DestroyMutex(cpuMutex);
195
196         return 0;
197 }
198 #endif
199
200
201 //
202 // Request a change in the power state of the emulated Apple
203 //
204 void SetPowerState(void)
205 {
206         powerStateChangeRequested = true;
207 }
208
209
210 //
211 // Load a file into RAM/ROM image space
212 //
213 bool LoadImg(char * filename, uint8_t * ram, int size)
214 {
215         FILE * fp = fopen(filename, "rb");
216
217         if (fp == NULL)
218                 return false;
219
220         fread(ram, 1, size, fp);
221         fclose(fp);
222
223         return true;
224 }
225
226
227 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.0";
228 static void SaveApple2State(const char * filename)
229 {
230         WriteLog("Main: Saving Apple2 state...\n");
231         FILE * file = fopen(filename, "wb");
232
233         if (!file)
234         {
235                 WriteLog("Could not open file \"%s\" for writing!\n", filename);
236                 return;
237         }
238
239         // Write out header
240         fwrite(stateHeader, 1, 18, file);
241
242         // Write out CPU state
243         fwrite(&mainCPU, 1, sizeof(mainCPU), file);
244
245         // Write out main memory
246         fwrite(ram, 1, 0x10000, file);
247         fwrite(ram2, 1, 0x10000, file);
248
249         // Write out state variables
250         fputc((uint8_t)keyDown, file);
251         fputc((uint8_t)openAppleDown, file);
252         fputc((uint8_t)closedAppleDown, file);
253         fputc((uint8_t)store80Mode, file);
254         fputc((uint8_t)vbl, file);
255         fputc((uint8_t)slotCXROM, file);
256         fputc((uint8_t)slotC3ROM, file);
257         fputc((uint8_t)ramrd, file);
258         fputc((uint8_t)ramwrt, file);
259         fputc((uint8_t)altzp, file);
260         fputc((uint8_t)ioudis, file);
261         fputc((uint8_t)dhires, file);
262         fputc((uint8_t)flash, file);
263         fputc((uint8_t)textMode, file);
264         fputc((uint8_t)mixedMode, file);
265         fputc((uint8_t)displayPage2, file);
266         fputc((uint8_t)hiRes, file);
267         fputc((uint8_t)alternateCharset, file);
268         fputc((uint8_t)col80Mode, file);
269         fputc(lcState, file);
270
271         // Write out floppy state
272         floppyDrive.SaveState(file);
273         fclose(file);
274 }
275
276
277 static bool LoadApple2State(const char * filename)
278 {
279         WriteLog("Main: Loading Apple2 state...\n");
280         FILE * file = fopen(filename, "rb");
281
282         if (!file)
283         {
284                 WriteLog("Could not open file \"%s\" for reading!\n", filename);
285                 return false;
286         }
287
288         uint8_t buffer[18];
289         fread(buffer, 1, 18, file);
290
291         // Sanity check...
292         if (memcmp(buffer, stateHeader, 18) != 0)
293         {
294                 fclose(file);
295                 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
296                 return false;
297         }
298
299         // Read CPU state
300         fread(&mainCPU, 1, sizeof(mainCPU), file);
301
302         // Read main memory
303         fread(ram, 1, 0x10000, file);
304         fread(ram2, 1, 0x10000, file);
305
306         // Read in state variables
307         keyDown = (bool)fgetc(file);
308         openAppleDown = (bool)fgetc(file);
309         closedAppleDown = (bool)fgetc(file);
310         store80Mode = (bool)fgetc(file);
311         vbl = (bool)fgetc(file);
312         slotCXROM = (bool)fgetc(file);
313         slotC3ROM = (bool)fgetc(file);
314         ramrd = (bool)fgetc(file);
315         ramwrt = (bool)fgetc(file);
316         altzp = (bool)fgetc(file);
317         ioudis = (bool)fgetc(file);
318         dhires = (bool)fgetc(file);
319         flash = (bool)fgetc(file);
320         textMode = (bool)fgetc(file);
321         mixedMode = (bool)fgetc(file);
322         displayPage2 = (bool)fgetc(file);
323         hiRes = (bool)fgetc(file);
324         alternateCharset = (bool)fgetc(file);
325         col80Mode = (bool)fgetc(file);
326         lcState = fgetc(file);
327
328         // Read in floppy state
329         floppyDrive.LoadState(file);
330
331         fclose(file);
332
333         // Make sure things are in a sane state before execution :-P
334         mainCPU.RdMem = AppleReadMem;
335         mainCPU.WrMem = AppleWriteMem;
336         ResetMMUPointers();
337
338         return true;
339 }
340
341
342 static void ResetApple2State(void)
343 {
344         keyDown = false;
345         openAppleDown = false;
346         closedAppleDown = false;
347         store80Mode = false;
348         vbl = false;
349         slotCXROM = false;
350         slotC3ROM = false;
351         ramrd = false;
352         ramwrt = false;
353         altzp = false;
354         ioudis = true;
355         dhires = false;
356         lcState = 0x02;
357         ResetMMUPointers();
358
359         // Without this, you can wedge the system :-/
360         memset(ram, 0, 0x10000);
361         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
362 }
363
364
365 #ifdef CPU_CLOCK_CHECKING
366 uint8_t counter = 0;
367 uint32_t totalCPU = 0;
368 uint64_t lastClock = 0;
369 #endif
370 //
371 // Main loop
372 //
373 int main(int /*argc*/, char * /*argv*/[])
374 {
375         InitLog("./apple2.log");
376         LoadSettings();
377         srand(time(NULL));                                                                      // Initialize RNG
378
379         // Zero out memory
380         memset(ram, 0, 0x10000);
381         memset(rom, 0, 0x10000);
382         memset(ram2, 0, 0x10000);
383
384         // Set up MMU
385         SetupAddressMap();
386         ResetMMUPointers();
387
388         // Set up V65C02 execution context
389         memset(&mainCPU, 0, sizeof(V65C02REGS));
390         mainCPU.RdMem = AppleReadMem;
391         mainCPU.WrMem = AppleWriteMem;
392         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
393
394         if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
395         {
396                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
397                 return -1;
398         }
399
400         WriteLog("About to initialize video...\n");
401
402         if (!InitVideo())
403         {
404                 std::cout << "Aborting!" << std::endl;
405                 return -1;
406         }
407
408         GUI::Init(sdlRenderer);
409
410         // Have to do this *after* video init but *before* sound init...!
411 //Shouldn't be necessary since we're not doing emulation in the ISR...
412         if (settings.autoStateSaving)
413         {
414                 // Load last state from file...
415                 if (!LoadApple2State(settings.autoStatePath))
416                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
417         }
418
419         WriteLog("About to initialize audio...\n");
420         SoundInit();
421         SetupBlurTable();                                                       // Set up the color TV emulation blur table
422         running = true;                                                         // Set running status...
423         InitializeEventList();                                          // Clear the event list before we use it...
424         SetCallbackTime(FrameCallback, 16666.66666667); // Set frame to fire at 1/60 s interval
425         SetCallbackTime(BlinkTimer, 250000);            // Set up blinking at 1/4 s intervals
426         startTicks = SDL_GetTicks();
427
428 #ifdef THREADED_65C02
429         cpuCond = SDL_CreateCond();
430         mainSem = SDL_CreateSemaphore(1);
431         cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
432 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
433 //      SDL_sem * mainMutex = SDL_CreateMutex();
434 #endif
435
436         WriteLog("Entering main loop...\n");
437
438         while (running)
439         {
440                 double timeToNextEvent = GetTimeToNextEvent();
441 #ifndef THREADED_65C02
442                 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
443
444         #ifdef CPU_CLOCK_CHECKING
445 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
446         #endif
447 #endif
448                 HandleNextEvent();
449         }
450
451 #ifdef THREADED_65C02
452 WriteLog("Main: cpuFinished = true;\n");
453         cpuFinished = true;
454
455 WriteLog("Main: SDL_SemWait(mainSem);\n");
456         // Only do this if NOT in power off/emulation paused mode!
457         if (!pauseMode)
458                 // Should lock until CPU thread is waiting...
459                 SDL_SemWait(mainSem);
460 #endif
461
462 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
463         SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
464 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
465         SDL_WaitThread(cpuThread, NULL);
466 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
467         SDL_DestroyCond(cpuCond);
468         SDL_DestroySemaphore(mainSem);
469
470         // Autosave state here, if requested...
471         if (settings.autoStateSaving)
472                 SaveApple2State(settings.autoStatePath);
473
474         floppyDrive.SaveImage(0);
475         floppyDrive.SaveImage(1);
476
477         SoundDone();
478         VideoDone();
479         SaveSettings();
480         LogDone();
481
482         return 0;
483 }
484
485
486 /*
487 Apple II keycodes
488 -----------------
489
490 Key     Aln CTL SHF BTH
491 -----------------------
492 space   $A0     $A0     $A0 $A0         No xlation
493 RETURN  $8D     $8D     $8D     $8D             No xlation
494 0               $B0     $B0     $B0     $B0             Need to screen shift+0 (?)
495 1!              $B1 $B1 $A1 $A1         No xlation
496 2"              $B2     $B2     $A2     $A2             No xlation
497 3#              $B3     $B3     $A3     $A3             No xlation
498 4$              $B4     $B4     $A4     $A4             No xlation
499 5%              $B5     $B5     $A5     $A5             No xlation
500 6&              $B6     $B6     $A6     $A6             No xlation
501 7'              $B7     $B7     $A7     $A7             No xlation
502 8(              $B8     $B8     $A8     $A8             No xlation
503 9)              $B9     $B9     $A9     $A9             No xlation
504 :*              $BA     $BA     $AA     $AA             No xlation
505 ;+              $BB     $BB     $AB     $AB             No xlation
506 ,<              $AC     $AC     $BC     $BC             No xlation
507 -=              $AD     $AD     $BD     $BD             No xlation
508 .>              $AE     $AE     $BE     $BE             No xlation
509 /?              $AF     $AF     $BF     $BF             No xlation
510 A               $C1     $81     $C1     $81
511 B               $C2     $82     $C2     $82
512 C               $C3     $83     $C3     $83
513 D               $C4     $84     $C4     $84
514 E               $C5     $85     $C5     $85
515 F               $C6     $86     $C6     $86
516 G               $C7     $87     $C7     $87
517 H               $C8     $88     $C8     $88
518 I               $C9     $89     $C9     $89
519 J               $CA     $8A     $CA     $8A
520 K               $CB     $8B     $CB     $8B
521 L               $CC     $8C     $CC     $8C
522 M               $CD     $8D     $DD     $9D             -> ODD
523 N^              $CE     $8E     $DE     $9E             -> ODD
524 O               $CF     $8F     $CF     $8F
525 P@              $D0     $90     $C0     $80             Need to xlate CTL+SHFT+P & SHFT+P (?)
526 Q               $D1     $91     $D1     $91
527 R               $D2     $92     $D2     $92
528 S               $D3     $93     $D3     $93
529 T               $D4     $94     $D4     $94
530 U               $D5     $95     $D5     $95
531 V               $D6     $96     $D6     $96
532 W               $D7     $97     $D7     $97
533 X               $D8     $98     $D8     $98
534 Y               $D9     $99     $D9     $99
535 Z               $DA     $9A     $DA     $9A
536 <-              $88     $88     $88     $88
537 ->              $95     $95     $95     $95
538 ESC             $9B     $9B     $9B     $9B             No xlation
539 */
540
541 static uint32_t frameCount = 0;
542 static void FrameCallback(void)
543 {
544         SDL_Event event;
545
546         while (SDL_PollEvent(&event))
547         {
548                 switch (event.type)
549                 {
550 // Problem with using SDL_TEXTINPUT is that it causes key delay. :-/
551 #if 0
552                 case SDL_TEXTINPUT:
553 //Need to do some key translation here, and screen out non-apple keys as well...
554 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
555 // everything else done separately. this is slightly easier. :-P)
556 //                      if (event.key.keysym.sym == SDLK_TAB)   // Prelim key screening...
557                         if (event.edit.text[0] == '\t') // Prelim key screening...
558                                 break;
559
560                         lastKeyPressed = event.edit.text[0];
561                         keyDown = true;
562
563                         //kludge: should have a caps lock thingy here...
564                         //or all uppercase for ][+...
565 //                      if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
566 //                              lastKeyPressed &= 0xDF;         // Convert to upper case...
567
568                         break;
569 #endif
570                 case SDL_KEYDOWN:
571                         // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
572                         // method
573                         if ((event.key.keysym.mod & KMOD_CTRL)
574                                 && (event.key.keysym.mod & KMOD_SHIFT)
575                                 && (event.key.keysym.sym == SDLK_q))
576                         {
577                                 running = false;
578                                 // We return here, because we don't want to pick up any
579                                 // spurious keypresses with our exit sequence.
580                                 return;
581                         }
582
583                         // CTRL+RESET key emulation (mapped to CTRL+`)
584 // This doesn't work...
585 //                      if (event.key.keysym.sym == SDLK_BREAK && (event.key.keysym.mod & KMOD_CTRL))
586 //                      if (event.key.keysym.sym == SDLK_PAUSE && (event.key.keysym.mod & KMOD_CTRL))
587                         if ((event.key.keysym.mod & KMOD_CTRL)
588                                 && (event.key.keysym.sym == SDLK_BACKQUOTE))
589                         {
590 //NOTE that this shouldn't take place until the key is lifted... !!! FIX !!!
591 //ALSO it seems to leave the machine in an inconsistent state vis-a-vis the language card...
592                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
593                                 break;
594                         }
595
596                         if (event.key.keysym.sym == SDLK_RIGHT)
597                                 lastKeyPressed = 0x15, keyDown = true;
598                         else if (event.key.keysym.sym == SDLK_LEFT)
599                                 lastKeyPressed = 0x08, keyDown = true;
600                         else if (event.key.keysym.sym == SDLK_UP)
601                                 lastKeyPressed = 0x0B, keyDown = true;
602                         else if (event.key.keysym.sym == SDLK_DOWN)
603                                 lastKeyPressed = 0x0A, keyDown = true;
604                         else if (event.key.keysym.sym == SDLK_RETURN)
605                                 lastKeyPressed = 0x0D, keyDown = true;
606                         else if (event.key.keysym.sym == SDLK_ESCAPE)
607                                 lastKeyPressed = 0x1B, keyDown = true;
608                         else if (event.key.keysym.sym == SDLK_BACKSPACE)
609                                 lastKeyPressed = 0x7F, keyDown = true;
610
611                         // Fix CTRL+key combo...
612                         if (event.key.keysym.mod & KMOD_CTRL)
613                         {
614                                 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
615                                 {
616                                         lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 1;
617                                         keyDown = true;
618 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
619                                         break;
620                                 }
621                         }
622
623 #if 1
624                         // Fix SHIFT+key combo...
625                         if (event.key.keysym.mod & KMOD_SHIFT)
626                         {
627                                 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z)
628                                 {
629                                         lastKeyPressed = (event.key.keysym.sym - SDLK_a) + 0x41;
630                                         keyDown = true;
631 //printf("Key combo pressed: CTRL+%c\n", lastKeyPressed + 0x40);
632                                         break;
633                                 }
634                                 else if (event.key.keysym.sym == SDLK_1)
635                                 {
636                                         lastKeyPressed = '!';
637                                         keyDown = true;
638                                         break;
639                                 }
640                                 else if (event.key.keysym.sym == SDLK_2)
641                                 {
642                                         lastKeyPressed = '@';
643                                         keyDown = true;
644                                         break;
645                                 }
646                                 else if (event.key.keysym.sym == SDLK_3)
647                                 {
648                                         lastKeyPressed = '#';
649                                         keyDown = true;
650                                         break;
651                                 }
652                                 else if (event.key.keysym.sym == SDLK_4)
653                                 {
654                                         lastKeyPressed = '$';
655                                         keyDown = true;
656                                         break;
657                                 }
658                                 else if (event.key.keysym.sym == SDLK_5)
659                                 {
660                                         lastKeyPressed = '%';
661                                         keyDown = true;
662                                         break;
663                                 }
664                                 else if (event.key.keysym.sym == SDLK_6)
665                                 {
666                                         lastKeyPressed = '^';
667                                         keyDown = true;
668                                         break;
669                                 }
670                                 else if (event.key.keysym.sym == SDLK_7)
671                                 {
672                                         lastKeyPressed = '&';
673                                         keyDown = true;
674                                         break;
675                                 }
676                                 else if (event.key.keysym.sym == SDLK_8)
677                                 {
678                                         lastKeyPressed = '*';
679                                         keyDown = true;
680                                         break;
681                                 }
682                                 else if (event.key.keysym.sym == SDLK_9)
683                                 {
684                                         lastKeyPressed = '(';
685                                         keyDown = true;
686                                         break;
687                                 }
688                                 else if (event.key.keysym.sym == SDLK_0)
689                                 {
690                                         lastKeyPressed = ')';
691                                         keyDown = true;
692                                         break;
693                                 }
694                                 else if (event.key.keysym.sym == SDLK_MINUS)
695                                 {
696                                         lastKeyPressed = '_';
697                                         keyDown = true;
698                                         break;
699                                 }
700                                 else if (event.key.keysym.sym == SDLK_EQUALS)
701                                 {
702                                         lastKeyPressed = '+';
703                                         keyDown = true;
704                                         break;
705                                 }
706                                 else if (event.key.keysym.sym == SDLK_LEFTBRACKET)
707                                 {
708                                         lastKeyPressed = '{';
709                                         keyDown = true;
710                                         break;
711                                 }
712                                 else if (event.key.keysym.sym == SDLK_RIGHTBRACKET)
713                                 {
714                                         lastKeyPressed = '}';
715                                         keyDown = true;
716                                         break;
717                                 }
718                                 else if (event.key.keysym.sym == SDLK_BACKSLASH)
719                                 {
720                                         lastKeyPressed = '|';
721                                         keyDown = true;
722                                         break;
723                                 }
724                                 else if (event.key.keysym.sym == SDLK_SEMICOLON)
725                                 {
726                                         lastKeyPressed = ':';
727                                         keyDown = true;
728                                         break;
729                                 }
730                                 else if (event.key.keysym.sym == SDLK_QUOTE)
731                                 {
732                                         lastKeyPressed = '"';
733                                         keyDown = true;
734                                         break;
735                                 }
736                                 else if (event.key.keysym.sym == SDLK_COMMA)
737                                 {
738                                         lastKeyPressed = '<';
739                                         keyDown = true;
740                                         break;
741                                 }
742                                 else if (event.key.keysym.sym == SDLK_PERIOD)
743                                 {
744                                         lastKeyPressed = '>';
745                                         keyDown = true;
746                                         break;
747                                 }
748                                 else if (event.key.keysym.sym == SDLK_SLASH)
749                                 {
750                                         lastKeyPressed = '?';
751                                         keyDown = true;
752                                         break;
753                                 }
754                                 else if (event.key.keysym.sym == SDLK_BACKQUOTE)
755                                 {
756                                         lastKeyPressed = '~';
757                                         keyDown = true;
758                                         break;
759                                 }
760                         }
761 #endif
762
763                         // General keys...
764                         if (event.key.keysym.sym >= SDLK_SPACE && event.key.keysym.sym <= SDLK_z)
765                         {
766                                 lastKeyPressed = event.key.keysym.sym;
767                                 keyDown = true;
768
769                                 // Check for Caps Lock key...
770                                 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z && capsLock)
771                                         lastKeyPressed -= 0x20;
772
773                                 break;
774                         }
775
776                         if (event.key.keysym.sym == SDLK_PAUSE)
777                         {
778                                 pauseMode = !pauseMode;
779
780                                 if (pauseMode)
781                                 {
782                                         SoundPause();
783                                         SpawnMessage("*** PAUSED ***");
784                                 }
785                                 else
786                                 {
787                                         SoundResume();
788                                         SpawnMessage("*** RESUME ***");
789                                 }
790                         }
791
792                         // Paddle buttons 0 & 1
793                         if (event.key.keysym.sym == SDLK_LALT)
794                                 openAppleDown = true;
795                         if (event.key.keysym.sym == SDLK_RALT)
796                                 closedAppleDown = true;
797
798                         if (event.key.keysym.sym == SDLK_F11)
799                                 dumpDis = !dumpDis;                             // Toggle the disassembly process
800
801 /*else if (event.key.keysym.sym == SDLK_F9)
802 {
803         floppyDrive.CreateBlankImage(0);
804 //      SpawnMessage("Image cleared...");
805 }//*/
806 /*else if (event.key.keysym.sym == SDLK_F10)
807 {
808         floppyDrive.SwapImages();
809 //      SpawnMessage("Image swapped...");
810 }//*/
811
812                         if (event.key.keysym.sym == SDLK_F2)
813                                 TogglePalette();
814                         else if (event.key.keysym.sym == SDLK_F3)
815                                 CycleScreenTypes();
816                         else if (event.key.keysym.sym == SDLK_F5)
817                         {
818                                 VolumeDown();
819                                 char volStr[19] = "[****************]";
820
821                                 for(int i=GetVolume(); i<16; i++)
822                                         volStr[1 + i] = '-';
823
824                                 SpawnMessage("Volume: %s", volStr);
825                         }
826                         else if (event.key.keysym.sym == SDLK_F6)
827                         {
828                                 VolumeUp();
829                                 char volStr[19] = "[****************]";
830
831                                 for(int i=GetVolume(); i<16; i++)
832                                         volStr[1 + i] = '-';
833
834                                 SpawnMessage("Volume: %s", volStr);
835                         }
836                         else if (event.key.keysym.sym == SDLK_F12)
837                         {
838                                 if (!fullscreenDebounce)
839                                 {
840                                         ToggleFullScreen();
841                                         fullscreenDebounce = true;
842                                 }
843                         }
844
845                         if (event.key.keysym.sym == SDLK_CAPSLOCK)
846                         {
847                                 if (!capsLockDebounce)
848                                 {
849                                         capsLock = !capsLock;
850                                         capsLockDebounce = true;
851                                 }
852                         }
853
854                         break;
855                 case SDL_KEYUP:
856                         if (event.key.keysym.sym == SDLK_F12)
857                                 fullscreenDebounce = false;
858                         if (event.key.keysym.sym == SDLK_CAPSLOCK)
859                                 capsLockDebounce = false;
860
861                         // Paddle buttons 0 & 1
862                         if (event.key.keysym.sym == SDLK_LALT)
863                                 openAppleDown = false;
864                         if (event.key.keysym.sym == SDLK_RALT)
865                                 closedAppleDown = false;
866
867                         break;
868                 case SDL_MOUSEBUTTONDOWN:
869                         GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
870                         break;
871                 case SDL_MOUSEBUTTONUP:
872                         GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
873                         break;
874                 case SDL_MOUSEMOTION:
875                         GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
876                         break;
877                 case SDL_WINDOWEVENT:
878                         if (event.window.event == SDL_WINDOWEVENT_LEAVE)
879                                 GUI::MouseMove(0, 0, 0);
880
881                         break;
882                 case SDL_QUIT:
883                         running = false;
884                 }
885         }
886
887         // Handle power request from the GUI
888         if (powerStateChangeRequested)
889         {
890                 if (GUI::powerOnState)
891                 {
892                         pauseMode = false;
893                         // Unlock the CPU thread...
894                         SDL_SemPost(mainSem);
895                 }
896                 else
897                 {
898                         pauseMode = true;
899                         // Should lock until CPU thread is waiting...
900                         SDL_SemWait(mainSem);
901                         ResetApple2State();
902                 }
903
904                 powerStateChangeRequested = false;
905         }
906
907         RenderVideoFrame();                             // Render Apple screen to buffer
908         RenderScreenBuffer();                   // Render buffer to host screen
909         GUI::Render(sdlRenderer);
910         SDL_RenderPresent(sdlRenderer);
911         SetCallbackTime(FrameCallback, 16666.66666667);
912
913 #ifdef CPU_CLOCK_CHECKING
914 //We know it's stopped, so we can get away with this...
915 counter++;
916 if (counter == 60)
917 {
918         uint64_t clock = GetCurrentV65C02Clock();
919 //totalCPU += (uint32_t)(clock - lastClock);
920
921         printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
922         lastClock = clock;
923 //      totalCPU = 0;
924         counter = 0;
925 }
926 #endif
927
928 // This is the problem: If you set the interval to 16, it runs faster than
929 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
930 // have it do 16 for one frame, then 17 for two others. Then it should average
931 // out to 1/60s per frame every 3 frames.
932         frameCount = (frameCount + 1) % 3;
933         uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
934
935         // Wait for next frame...
936         while (SDL_GetTicks() - startTicks < waitFrameTime)
937                 SDL_Delay(1);
938
939         startTicks = SDL_GetTicks();
940 #if 0
941         uint64_t cpuCycles = GetCurrentV65C02Clock();
942         uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
943         WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
944         lastCPUCycles = cpuCycles
945 #endif
946
947 //let's wait, then signal...
948 //works longer, but then still falls behind... [FIXED, see above]
949 #ifdef THREADED_65C02
950         if (!pauseMode)
951                 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
952 #endif
953 }
954
955
956 static void BlinkTimer(void)
957 {
958         // Set up blinking at 1/4 sec intervals
959         flash = !flash;
960         SetCallbackTime(BlinkTimer, 250000);
961 }
962
963
964 /*
965 Next problem is this: How to have events occur and synchronize with the rest
966 of the threads?
967
968   o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
969     remainder CPU cycles over...)
970
971 One way would be to use a fractional accumulator, then subtract 1 every
972 time it overflows. Like so:
973
974 double overflow = 0;
975 uint32_t time = 20;
976 while (!done)
977 {
978         Execute6808(&soundCPU, time);
979         overflow += 0.289115646;
980         if (overflow > 1.0)
981         {
982                 overflow -= 1.0;
983                 time = 21;
984         }
985         else
986                 time = 20;
987 }
988 */
989