]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
1ef3ff6251c3599f20b03880af41dbecadc93227
[apple2] / src / apple2.cpp
1 //
2 // Apple 2 SDL Portable Apple Emulator
3 //
4 // by James Hammons
5 // © 2017 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 <iomanip>
38 #include <iostream>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string>
42 #include <time.h>
43 #include "firmware.h"
44 #include "floppy.h"
45 #include "log.h"
46 #include "mmu.h"
47 #include "settings.h"
48 #include "sound.h"
49 #include "timing.h"
50 #include "v65c02.h"
51 #include "video.h"
52 #include "gui/gui.h"
53
54 // Debug and misc. defines
55
56 #define THREADED_65C02
57 #define CPU_THREAD_OVERFLOW_COMPENSATION
58 #define DEBUG_LC
59 //#define CPU_CLOCK_CHECKING
60 //#define THREAD_DEBUGGING
61 #define SOFT_SWITCH_DEBUGGING
62
63 // Global variables
64
65 uint8_t ram[0x10000], rom[0x10000];                     // RAM & ROM spaces
66 uint8_t ram2[0x10000];                                          // Auxillary RAM
67 V65C02REGS mainCPU;                                                     // v65C02 execution context
68 uint8_t appleType = APPLE_TYPE_IIE;
69 FloppyDrive floppyDrive;
70 bool powerStateChangeRequested = false;
71
72 // Local variables (actually, they're global since they're not static)
73
74 uint8_t lastKeyPressed = 0;
75 bool keyDown = false;
76 bool openAppleDown = false;
77 bool closedAppleDown = false;
78 bool store80Mode = false;
79 bool vbl = false;
80 bool slotCXROM = false;
81 bool slotC3ROM = false;
82 bool ramrd = false;
83 bool ramwrt = false;
84 bool altzp = false;
85 bool ioudis = true;
86 bool dhires = false;
87 // Language card state (ROM read, no write)
88 uint8_t lcState = 0x02;
89
90 static bool running = true;                                     // Machine running state flag...
91 static uint32_t startTicks;
92 static bool pauseMode = false;
93 static bool fullscreenDebounce = false;
94 static bool capsLock = false;
95 static bool capsLockDebounce = false;
96 static bool resetKeyDown = false;
97 static int8_t hideMouseTimeout = 60;
98
99 // Vars to handle the //e's 2-key rollover
100 static SDL_Keycode keysHeld[2];
101 static uint8_t keysHeldAppleCode[2];
102 static uint8_t keyDownCount = 0;
103 static uint8_t keyDelay = 0;
104
105 // Local functions
106
107 static void SaveApple2State(const char * filename);
108 static bool LoadApple2State(const char * filename);
109 static void ResetApple2State(void);
110
111 // Local timer callback functions
112
113 static void FrameCallback(void);
114 static void BlinkTimer(void);
115
116 #ifdef THREADED_65C02
117 // Test of threaded execution of 6502
118 static SDL_Thread * cpuThread = NULL;
119 static SDL_cond * cpuCond = NULL;
120 static SDL_sem * mainSem = NULL;
121 static bool cpuFinished = false;
122
123 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
124
125 // Let's try a thread...
126 //
127 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
128 // it up.
129 //
130 int CPUThreadFunc(void * data)
131 {
132         // Mutex must be locked for conditional to work...
133         // Also, must be created in the thread that uses it...
134         SDL_mutex * cpuMutex = SDL_CreateMutex();
135
136 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
137         float overflow = 0.0;
138 #endif
139
140         do
141         {
142 // decrement mainSem...
143 #ifdef THREAD_DEBUGGING
144 WriteLog("CPU: SDL_SemWait(mainSem);\n");
145 #endif
146                 SDL_SemWait(mainSem);
147
148                 // There are exactly 800 slices of 21.333 cycles per frame, so it works
149                 // out evenly.
150 #ifdef THREAD_DEBUGGING
151 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
152 #endif
153                 for(int i=0; i<800; i++)
154                 {
155                         uint32_t cycles = 21;
156                         overflow += 0.333333334;
157
158                         if (overflow > 1.0)
159                         {
160                                 cycles++;
161                                 overflow -= 1.0;
162                         }
163
164                         // If the CTRL+Reset key combo is being held, make sure the RESET
165                         // line stays asserted:
166                         if (resetKeyDown)
167                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
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 //
205 // Request a change in the power state of the emulated Apple
206 //
207 void SetPowerState(void)
208 {
209         powerStateChangeRequested = true;
210 }
211
212
213 //
214 // Load a file into RAM/ROM image space
215 //
216 bool LoadImg(char * filename, uint8_t * ram, int size)
217 {
218         FILE * fp = fopen(filename, "rb");
219
220         if (fp == NULL)
221                 return false;
222
223         fread(ram, 1, size, fp);
224         fclose(fp);
225
226         return true;
227 }
228
229
230 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.0";
231 static void SaveApple2State(const char * filename)
232 {
233         WriteLog("Main: Saving Apple2 state...\n");
234         FILE * file = fopen(filename, "wb");
235
236         if (!file)
237         {
238                 WriteLog("Could not open file \"%s\" for writing!\n", filename);
239                 return;
240         }
241
242         // Write out header
243         fwrite(stateHeader, 1, 18, file);
244
245         // Write out CPU state
246         fwrite(&mainCPU, 1, sizeof(mainCPU), file);
247
248         // Write out main memory
249         fwrite(ram, 1, 0x10000, file);
250         fwrite(ram2, 1, 0x10000, file);
251
252         // Write out state variables
253         fputc((uint8_t)keyDown, file);
254         fputc((uint8_t)openAppleDown, file);
255         fputc((uint8_t)closedAppleDown, file);
256         fputc((uint8_t)store80Mode, file);
257         fputc((uint8_t)vbl, file);
258         fputc((uint8_t)slotCXROM, file);
259         fputc((uint8_t)slotC3ROM, file);
260         fputc((uint8_t)ramrd, file);
261         fputc((uint8_t)ramwrt, file);
262         fputc((uint8_t)altzp, file);
263         fputc((uint8_t)ioudis, file);
264         fputc((uint8_t)dhires, file);
265         fputc((uint8_t)flash, file);
266         fputc((uint8_t)textMode, file);
267         fputc((uint8_t)mixedMode, file);
268         fputc((uint8_t)displayPage2, file);
269         fputc((uint8_t)hiRes, file);
270         fputc((uint8_t)alternateCharset, file);
271         fputc((uint8_t)col80Mode, file);
272         fputc(lcState, file);
273
274         // Write out floppy state
275         floppyDrive.SaveState(file);
276         fclose(file);
277 }
278
279
280 static bool LoadApple2State(const char * filename)
281 {
282         WriteLog("Main: Loading Apple2 state...\n");
283         FILE * file = fopen(filename, "rb");
284
285         if (!file)
286         {
287                 WriteLog("Could not open file \"%s\" for reading!\n", filename);
288                 return false;
289         }
290
291         uint8_t buffer[18];
292         fread(buffer, 1, 18, file);
293
294         // Sanity check...
295         if (memcmp(buffer, stateHeader, 18) != 0)
296         {
297                 fclose(file);
298                 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
299                 return false;
300         }
301
302         // Read CPU state
303         fread(&mainCPU, 1, sizeof(mainCPU), file);
304
305         // Read main memory
306         fread(ram, 1, 0x10000, file);
307         fread(ram2, 1, 0x10000, file);
308
309         // Read in state variables
310         keyDown = (bool)fgetc(file);
311         openAppleDown = (bool)fgetc(file);
312         closedAppleDown = (bool)fgetc(file);
313         store80Mode = (bool)fgetc(file);
314         vbl = (bool)fgetc(file);
315         slotCXROM = (bool)fgetc(file);
316         slotC3ROM = (bool)fgetc(file);
317         ramrd = (bool)fgetc(file);
318         ramwrt = (bool)fgetc(file);
319         altzp = (bool)fgetc(file);
320         ioudis = (bool)fgetc(file);
321         dhires = (bool)fgetc(file);
322         flash = (bool)fgetc(file);
323         textMode = (bool)fgetc(file);
324         mixedMode = (bool)fgetc(file);
325         displayPage2 = (bool)fgetc(file);
326         hiRes = (bool)fgetc(file);
327         alternateCharset = (bool)fgetc(file);
328         col80Mode = (bool)fgetc(file);
329         lcState = fgetc(file);
330
331         // Read in floppy state
332         floppyDrive.LoadState(file);
333
334         fclose(file);
335
336         // Make sure things are in a sane state before execution :-P
337         mainCPU.RdMem = AppleReadMem;
338         mainCPU.WrMem = AppleWriteMem;
339         ResetMMUPointers();
340
341         return true;
342 }
343
344
345 static void ResetApple2State(void)
346 {
347         keyDown = false;
348         openAppleDown = false;
349         closedAppleDown = false;
350         store80Mode = false;
351         vbl = false;
352         slotCXROM = false;
353         slotC3ROM = false;
354         ramrd = false;
355         ramwrt = false;
356         altzp = false;
357         ioudis = true;
358         dhires = false;
359         lcState = 0x02;
360         ResetMMUPointers();
361
362         // Without this, you can wedge the system :-/
363         memset(ram, 0, 0x10000);
364         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
365 }
366
367
368 #ifdef CPU_CLOCK_CHECKING
369 uint8_t counter = 0;
370 uint32_t totalCPU = 0;
371 uint64_t lastClock = 0;
372 #endif
373 //
374 // Main loop
375 //
376 int main(int /*argc*/, char * /*argv*/[])
377 {
378         InitLog("./apple2.log");
379         LoadSettings();
380         srand(time(NULL));                      // Initialize RNG
381
382         // Zero out memory
383         memset(ram, 0, 0x10000);
384         memset(rom, 0, 0x10000);
385         memset(ram2, 0, 0x10000);
386
387         // Set up MMU
388         SetupAddressMap();
389         ResetMMUPointers();
390
391         // Set up V65C02 execution context
392         memset(&mainCPU, 0, sizeof(V65C02REGS));
393         mainCPU.RdMem = AppleReadMem;
394         mainCPU.WrMem = AppleWriteMem;
395         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
396
397         if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
398         {
399                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
400                 return -1;
401         }
402
403         WriteLog("About to initialize video...\n");
404
405         if (!InitVideo())
406         {
407                 printf("Could not init screen: aborting!\n");
408                 return -1;
409         }
410
411         GUI::Init(sdlRenderer);
412         WriteLog("About to initialize audio...\n");
413         SoundInit();
414
415         if (settings.autoStateSaving)
416         {
417                 // Load last state from file...
418                 if (!LoadApple2State(settings.autoStatePath))
419                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
420         }
421
422         running = true;
423         InitializeEventList();
424         // Set frame to fire at 1/60 s interval
425         SetCallbackTime(FrameCallback, 16666.66666667);
426         // Set up blinking at 1/4 s intervals
427         SetCallbackTime(BlinkTimer, 250000);
428         startTicks = SDL_GetTicks();
429
430 #ifdef THREADED_65C02
431         // Kick off the CPU...
432         cpuCond = SDL_CreateCond();
433         mainSem = SDL_CreateSemaphore(1);
434         cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
435 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
436 //      SDL_sem * mainMutex = SDL_CreateMutex();
437 #endif
438
439         WriteLog("Entering main loop...\n");
440
441         while (running)
442         {
443                 double timeToNextEvent = GetTimeToNextEvent();
444 #ifndef THREADED_65C02
445                 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
446
447         #ifdef CPU_CLOCK_CHECKING
448 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
449         #endif
450 #endif
451                 HandleNextEvent();
452         }
453
454 #ifdef THREADED_65C02
455 WriteLog("Main: cpuFinished = true;\n");
456         cpuFinished = true;
457
458 WriteLog("Main: SDL_SemWait(mainSem);\n");
459         // Only do this if NOT in power off/emulation paused mode!
460         if (!pauseMode)
461                 // Should lock until CPU thread is waiting...
462                 SDL_SemWait(mainSem);
463 #endif
464
465 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
466         SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
467 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
468         SDL_WaitThread(cpuThread, NULL);
469 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
470         SDL_DestroyCond(cpuCond);
471         SDL_DestroySemaphore(mainSem);
472
473         // Autosave state here, if requested...
474         if (settings.autoStateSaving)
475                 SaveApple2State(settings.autoStatePath);
476
477         floppyDrive.SaveImage(0);
478         floppyDrive.SaveImage(1);
479
480         SoundDone();
481         VideoDone();
482         SaveSettings();
483         LogDone();
484
485         return 0;
486 }
487
488
489 //
490 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
491 // +CTRL+SHIFT (3). Order of keys is:
492 // Delete, left, tab, down, up, return, right, escape
493 // Space, single quote, comma, minus, period, slash
494 // Numbers 0-9
495 // Semicolon, equals, left bracket, backslash, right bracket, backquote
496 // Letters a-z (lowercase)
497 //
498 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
499 //       keyboards, so this table should suffice for the shifted keys just fine.
500 //
501 uint8_t apple2e_keycode[4][56] = {
502         {       // Normal
503                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
504                 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
505                 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
506                 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
507                 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
508                 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
509                 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
510         },
511         {       // With CTRL held
512                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
513                 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
514                 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
515                 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
516                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
517                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
518                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
519         },
520         {       // With Shift held
521                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
522                 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
523                 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
524                 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
525                 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
526                 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
527                 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
528         },
529         {       // With CTRL+Shift held
530                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
531                 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
532                 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
533                 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
534                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
535                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
536                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
537         }
538 };
539
540 static uint32_t frameCount = 0;
541 static void FrameCallback(void)
542 {
543         SDL_Event event;
544         uint8_t keyIndex;
545
546         while (SDL_PollEvent(&event))
547         {
548                 switch (event.type)
549                 {
550                 case SDL_KEYDOWN:
551                         // We do our own repeat handling thank you very much! :-)
552                         if (event.key.repeat != 0)
553                                 break;
554
555                         // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
556                         // method
557                         if ((event.key.keysym.mod & KMOD_CTRL)
558                                 && (event.key.keysym.mod & KMOD_SHIFT)
559                                 && (event.key.keysym.sym == SDLK_q))
560                         {
561                                 running = false;
562                                 // We return here, because we don't want to pick up any
563                                 // spurious keypresses with our exit sequence.
564                                 return;
565                         }
566
567                         // CTRL+RESET key emulation (mapped to CTRL+HOME)
568                         if ((event.key.keysym.mod & KMOD_CTRL)
569                                 && (event.key.keysym.sym == SDLK_HOME))
570                         {
571 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
572                                 resetKeyDown = true;
573                                 break;
574                         }
575
576                         // There has GOT to be a better way off mapping SDLKs to our
577                         // keyindex. But for now, this should suffice.
578                         keyIndex = 0xFF;
579
580                         switch (event.key.keysym.sym)
581                         {
582                         case SDLK_BACKSPACE:    keyIndex =  0; break;
583                         case SDLK_LEFT:         keyIndex =  1; break;
584                         case SDLK_TAB:          keyIndex =  2; break;
585                         case SDLK_DOWN:         keyIndex =  3; break;
586                         case SDLK_UP:           keyIndex =  4; break;
587                         case SDLK_RETURN:       keyIndex =  5; break;
588                         case SDLK_RIGHT:        keyIndex =  6; break;
589                         case SDLK_ESCAPE:       keyIndex =  7; break;
590                         case SDLK_SPACE:        keyIndex =  8; break;
591                         case SDLK_QUOTE:        keyIndex =  9; break;
592                         case SDLK_COMMA:        keyIndex = 10; break;
593                         case SDLK_MINUS:        keyIndex = 11; break;
594                         case SDLK_PERIOD:       keyIndex = 12; break;
595                         case SDLK_SLASH:        keyIndex = 13; break;
596                         case SDLK_0:            keyIndex = 14; break;
597                         case SDLK_1:            keyIndex = 15; break;
598                         case SDLK_2:            keyIndex = 16; break;
599                         case SDLK_3:            keyIndex = 17; break;
600                         case SDLK_4:            keyIndex = 18; break;
601                         case SDLK_5:            keyIndex = 19; break;
602                         case SDLK_6:            keyIndex = 20; break;
603                         case SDLK_7:            keyIndex = 21; break;
604                         case SDLK_8:            keyIndex = 22; break;
605                         case SDLK_9:            keyIndex = 23; break;
606                         case SDLK_SEMICOLON:    keyIndex = 24; break;
607                         case SDLK_EQUALS:       keyIndex = 25; break;
608                         case SDLK_LEFTBRACKET:  keyIndex = 26; break;
609                         case SDLK_BACKSLASH:    keyIndex = 27; break;
610                         case SDLK_RIGHTBRACKET: keyIndex = 28; break;
611                         case SDLK_BACKQUOTE:    keyIndex = 29; break;
612                         case SDLK_a:            keyIndex = 30; break;
613                         case SDLK_b:            keyIndex = 31; break;
614                         case SDLK_c:            keyIndex = 32; break;
615                         case SDLK_d:            keyIndex = 33; break;
616                         case SDLK_e:            keyIndex = 34; break;
617                         case SDLK_f:            keyIndex = 35; break;
618                         case SDLK_g:            keyIndex = 36; break;
619                         case SDLK_h:            keyIndex = 37; break;
620                         case SDLK_i:            keyIndex = 38; break;
621                         case SDLK_j:            keyIndex = 39; break;
622                         case SDLK_k:            keyIndex = 40; break;
623                         case SDLK_l:            keyIndex = 41; break;
624                         case SDLK_m:            keyIndex = 42; break;
625                         case SDLK_n:            keyIndex = 43; break;
626                         case SDLK_o:            keyIndex = 44; break;
627                         case SDLK_p:            keyIndex = 45; break;
628                         case SDLK_q:            keyIndex = 46; break;
629                         case SDLK_r:            keyIndex = 47; break;
630                         case SDLK_s:            keyIndex = 48; break;
631                         case SDLK_t:            keyIndex = 49; break;
632                         case SDLK_u:            keyIndex = 50; break;
633                         case SDLK_v:            keyIndex = 51; break;
634                         case SDLK_w:            keyIndex = 52; break;
635                         case SDLK_x:            keyIndex = 53; break;
636                         case SDLK_y:            keyIndex = 54; break;
637                         case SDLK_z:            keyIndex = 55; break;
638                         }
639
640                         // Stuff the key in if we have a valid one...
641                         if (keyIndex != 0xFF)
642                         {
643                                 // Handle Shift, CTRL, & Shift+CTRL combos
644                                 uint8_t table = 0;
645
646                                 if (event.key.keysym.mod & KMOD_CTRL)
647                                         table |= 1;
648
649                                 if (event.key.keysym.mod & KMOD_SHIFT)
650                                         table |= 2;
651
652                                 lastKeyPressed = apple2e_keycode[table][keyIndex];
653                                 keyDown = true;
654
655                                 // Handle Caps Lock
656                                 if (capsLock
657                                         && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
658                                         lastKeyPressed -= 0x20;
659
660                                 // Handle key repeat if the key hasn't been held
661 //                              if (keyDelay == 0)
662                                         keyDelay = 15;
663
664                                 keyDownCount++;
665
666                                 // Buffer the key held. Note that the last key is always
667                                 // stuffed into keysHeld[0].
668                                 if (keyDownCount >= 2)
669                                 {
670                                         keysHeld[1] = keysHeld[0];
671                                         keysHeldAppleCode[1] = keysHeldAppleCode[0];
672
673                                         if (keyDownCount > 2)
674                                                 keyDownCount = 2;
675                                 }
676
677                                 keysHeld[0] = event.key.keysym.sym;
678                                 keysHeldAppleCode[0] = lastKeyPressed;
679                                 break;
680                         }
681
682                         if (event.key.keysym.sym == SDLK_PAUSE)
683                         {
684                                 pauseMode = !pauseMode;
685
686                                 if (pauseMode)
687                                 {
688                                         SoundPause();
689                                         SpawnMessage("*** PAUSED ***");
690                                 }
691                                 else
692                                 {
693                                         SoundResume();
694                                         SpawnMessage("*** RESUME ***");
695                                 }
696                         }
697                         // Buttons 0 & 1
698                         else if (event.key.keysym.sym == SDLK_LALT)
699                                 openAppleDown = true;
700                         else if (event.key.keysym.sym == SDLK_RALT)
701                                 closedAppleDown = true;
702                         // Toggle the disassembly process
703                         else if (event.key.keysym.sym == SDLK_F11)
704                                 dumpDis = !dumpDis;
705
706 /*else if (event.key.keysym.sym == SDLK_F9)
707 {
708         floppyDrive.CreateBlankImage(0);
709 //      SpawnMessage("Image cleared...");
710 }//*/
711 /*else if (event.key.keysym.sym == SDLK_F10)
712 {
713         floppyDrive.SwapImages();
714 //      SpawnMessage("Image swapped...");
715 }//*/
716
717                         else if (event.key.keysym.sym == SDLK_F2)
718                                 TogglePalette();
719                         else if (event.key.keysym.sym == SDLK_F3)
720                                 CycleScreenTypes();
721                         else if (event.key.keysym.sym == SDLK_F5)
722                         {
723                                 VolumeDown();
724                                 char volStr[19] = "[****************]";
725
726                                 for(int i=GetVolume(); i<16; i++)
727                                         volStr[1 + i] = '-';
728
729                                 SpawnMessage("Volume: %s", volStr);
730                         }
731                         else if (event.key.keysym.sym == SDLK_F6)
732                         {
733                                 VolumeUp();
734                                 char volStr[19] = "[****************]";
735
736                                 for(int i=GetVolume(); i<16; i++)
737                                         volStr[1 + i] = '-';
738
739                                 SpawnMessage("Volume: %s", volStr);
740                         }
741                         else if (event.key.keysym.sym == SDLK_F12)
742                         {
743                                 if (!fullscreenDebounce)
744                                 {
745                                         ToggleFullScreen();
746                                         fullscreenDebounce = true;
747                                 }
748                         }
749                         else if (event.key.keysym.sym == SDLK_CAPSLOCK)
750                         {
751                                 if (!capsLockDebounce)
752                                 {
753                                         capsLock = !capsLock;
754                                         capsLockDebounce = true;
755                                 }
756                         }
757
758                         break;
759
760                 case SDL_KEYUP:
761                         if (event.key.keysym.sym == SDLK_F12)
762                                 fullscreenDebounce = false;
763                         else if (event.key.keysym.sym == SDLK_CAPSLOCK)
764                                 capsLockDebounce = false;
765                         // Paddle buttons 0 & 1
766                         else if (event.key.keysym.sym == SDLK_LALT)
767                                 openAppleDown = false;
768                         else if (event.key.keysym.sym == SDLK_RALT)
769                                 closedAppleDown = false;
770                         else if ((event.key.keysym.mod & KMOD_CTRL)
771                                 && (event.key.keysym.sym == SDLK_HOME))
772                                 resetKeyDown = false;
773                         else
774                         {
775                                 // Handle key buffering 'key up' event (2 key rollover)
776                                 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
777                                 {
778                                         keyDownCount--;
779                                         keyDelay = 0;   // Reset key delay
780                                 }
781                                 else if (keyDownCount == 2)
782                                 {
783                                         if (event.key.keysym.sym == keysHeld[0])
784                                         {
785                                                 keyDownCount--;
786                                                 keysHeld[0] = keysHeld[1];
787                                                 keysHeldAppleCode[0] = keysHeldAppleCode[1];
788                                         }
789                                         else if (event.key.keysym.sym == keysHeld[1])
790                                         {
791                                                 keyDownCount--;
792                                         }
793                                 }
794                         }
795
796                         break;
797
798                 case SDL_MOUSEBUTTONDOWN:
799                         GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
800                         break;
801
802                 case SDL_MOUSEBUTTONUP:
803                         GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
804                         break;
805
806                 case SDL_MOUSEMOTION:
807                         GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
808
809                         // Handle mouse showing when the mouse is hidden...
810                         if (hideMouseTimeout == -1)
811                                 SDL_ShowCursor(1);
812
813                         hideMouseTimeout = 60;
814                         break;
815
816                 case SDL_WINDOWEVENT:
817                         if (event.window.event == SDL_WINDOWEVENT_LEAVE)
818                                 GUI::MouseMove(0, 0, 0);
819
820                         break;
821
822                 case SDL_QUIT:
823                         running = false;
824                 }
825         }
826
827         // Hide the mouse if it's been 1s since the last time it was moved
828         // N.B.: Should disable mouse hiding if it's over the GUI...
829         if (hideMouseTimeout > 0)
830                 hideMouseTimeout--;
831         else if (hideMouseTimeout == 0)
832         {
833                 hideMouseTimeout--;
834                 SDL_ShowCursor(0);
835         }
836
837         // Stuff the Apple keyboard buffer, if any keys are pending
838         // N.B.: May have to simulate the key repeat delay too [yup, sure do]
839         if (keyDownCount > 0)
840         {
841                 keyDelay--;
842
843                 if (keyDelay == 0)
844                 {
845                         keyDelay = 3;
846                         lastKeyPressed = keysHeldAppleCode[0];
847                         keyDown = true;
848                 }
849         }
850
851         // Handle power request from the GUI
852         if (powerStateChangeRequested)
853         {
854                 if (GUI::powerOnState)
855                 {
856                         pauseMode = false;
857                         // Unlock the CPU thread...
858                         SDL_SemPost(mainSem);
859                 }
860                 else
861                 {
862                         pauseMode = true;
863                         // Should lock until CPU thread is waiting...
864                         SDL_SemWait(mainSem);
865                         ResetApple2State();
866                 }
867
868                 powerStateChangeRequested = false;
869         }
870
871         // Render the Apple screen + GUI overlay
872         RenderAppleScreen(sdlRenderer);
873         GUI::Render(sdlRenderer);
874         SDL_RenderPresent(sdlRenderer);
875         SetCallbackTime(FrameCallback, 16666.66666667);
876
877 #ifdef CPU_CLOCK_CHECKING
878 //We know it's stopped, so we can get away with this...
879 counter++;
880 if (counter == 60)
881 {
882         uint64_t clock = GetCurrentV65C02Clock();
883 //totalCPU += (uint32_t)(clock - lastClock);
884
885         printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
886         lastClock = clock;
887 //      totalCPU = 0;
888         counter = 0;
889 }
890 #endif
891
892 // This is the problem: If you set the interval to 16, it runs faster than
893 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
894 // have it do 16 for one frame, then 17 for two others. Then it should average
895 // out to 1/60s per frame every 3 frames. [And now it does!]
896         frameCount = (frameCount + 1) % 3;
897         uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
898
899         // Wait for next frame...
900         while (SDL_GetTicks() - startTicks < waitFrameTime)
901                 SDL_Delay(1);
902
903         startTicks = SDL_GetTicks();
904 #if 0
905         uint64_t cpuCycles = GetCurrentV65C02Clock();
906         uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
907         WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
908         lastCPUCycles = cpuCycles
909 #endif
910
911 //let's wait, then signal...
912 //works longer, but then still falls behind... [FIXED, see above]
913 #ifdef THREADED_65C02
914         if (!pauseMode)
915                 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
916 #endif
917 }
918
919
920 static void BlinkTimer(void)
921 {
922         // Set up blinking at 1/4 sec intervals
923         flash = !flash;
924         SetCallbackTime(BlinkTimer, 250000);
925 }
926
927
928 /*
929 Next problem is this: How to have events occur and synchronize with the rest
930 of the threads?
931
932   o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
933     remainder CPU cycles over...)
934
935 One way would be to use a fractional accumulator, then subtract 1 every
936 time it overflows. Like so:
937
938 double overflow = 0;
939 uint32_t time = 20;
940 while (!done)
941 {
942         Execute6808(&soundCPU, time);
943         overflow += 0.289115646;
944         if (overflow > 1.0)
945         {
946                 overflow -= 1.0;
947                 time = 21;
948         }
949         else
950                 time = 20;
951 }
952 */
953