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