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