[stella] 01/03: New upstream version 5.0.2

Stephen Kitt skitt at moszumanska.debian.org
Mon Aug 21 07:19:40 UTC 2017


This is an automated email from the git hooks/post-receive script.

skitt pushed a commit to branch master
in repository stella.

commit 74ed136003850afa167a7cf5c18444edc3c63a1b
Author: Stephen Kitt <steve at sk2.org>
Date:   Mon Aug 21 09:14:00 2017 +0200

    New upstream version 5.0.2
---
 Announce.txt                                       |  22 +-
 Changes.txt                                        |  42 +++-
 Makefile                                           |   2 +-
 debian/changelog                                   |   7 +
 docs/graphics/cheat.png                            | Bin 4048 -> 4140 bytes
 docs/graphics/debugger_tiatab.png                  | Bin 15637 -> 14655 bytes
 docs/graphics/eventmapping.png                     | Bin 7823 -> 8495 bytes
 docs/graphics/eventmapping_combo.png               | Bin 4129 -> 3445 bytes
 docs/graphics/eventmapping_devsports.png           | Bin 9253 -> 8996 bytes
 docs/graphics/eventmapping_remap.png               | Bin 6803 -> 7995 bytes
 docs/graphics/launcher_filter.png                  | Bin 2881 -> 2029 bytes
 docs/graphics/launcher_options_files.png           | Bin 5630 -> 5921 bytes
 docs/graphics/launcher_options_snapshots.png       | Bin 6454 -> 5988 bytes
 docs/graphics/launcher_override.png                | Bin 9058 -> 8152 bytes
 docs/graphics/logs.png                             | Bin 12114 -> 10758 bytes
 docs/graphics/options_audio.png                    | Bin 3076 -> 3163 bytes
 docs/graphics/options_debugger.png                 | Bin 5369 -> 4853 bytes
 docs/graphics/options_gameinfo.png                 | Bin 7429 -> 6561 bytes
 docs/graphics/options_input.png                    | Bin 6807 -> 0 bytes
 docs/graphics/options_misc.png                     | Bin 3976 -> 3992 bytes
 docs/graphics/options_ui.png                       | Bin 4413 -> 4743 bytes
 docs/graphics/options_video.png                    | Bin 7699 -> 8226 bytes
 docs/graphics/options_video_dbgcolors.png          | Bin 5744 -> 5796 bytes
 docs/graphics/options_video_tv.png                 | Bin 9534 -> 9238 bytes
 docs/graphics/rom_browser.png                      | Bin 10200 -> 8692 bytes
 docs/graphics/romaudit.png                         | Bin 3317 -> 3465 bytes
 docs/index.html                                    |  35 ++-
 src/common/SoundSDL2.cxx                           |  13 +-
 src/common/SoundSDL2.hxx                           |   5 +-
 src/common/Version.hxx                             |   4 +-
 src/common/bspf.hxx                                |   8 +-
 src/common/tv_filters/AtariNTSC.cxx                | 257 ++++++++++++++++++---
 src/common/tv_filters/AtariNTSC.hxx                |  42 +++-
 src/common/tv_filters/NTSCFilter.hxx               |  15 ++
 src/debugger/DebuggerParser.cxx                    |  52 ++---
 src/debugger/DebuggerParser.hxx                    |   8 +-
 src/debugger/DiStella.cxx                          |  12 +-
 src/debugger/DiStella.hxx                          |   2 +-
 src/debugger/gui/CartCDFWidget.cxx                 |   2 +-
 src/emucore/AmigaMouse.cxx                         | 122 ----------
 src/emucore/AmigaMouse.hxx                         |  88 ++-----
 src/emucore/AtariMouse.cxx                         | 122 ----------
 src/emucore/AtariMouse.hxx                         |  90 ++------
 src/emucore/CartBUS.cxx                            |   6 +-
 src/emucore/CartCDF.cxx                            |  99 +++++---
 src/emucore/CartCDF.hxx                            |  24 +-
 src/emucore/Console.cxx                            |  23 +-
 src/emucore/EventHandler.cxx                       |  34 ++-
 src/emucore/EventHandler.hxx                       |  13 ++
 src/emucore/M6502.cxx                              |   2 +-
 src/emucore/Paddles.hxx                            |   8 +-
 src/emucore/PointingDevice.cxx                     | 156 +++++++++++++
 src/emucore/{AtariMouse.hxx => PointingDevice.hxx} |  93 +++++---
 src/emucore/Random.hxx                             |  56 ++++-
 src/emucore/Settings.cxx                           |  26 ++-
 src/emucore/Settings.hxx                           |   3 +
 src/emucore/StateManager.cxx                       |   2 +-
 src/emucore/System.cxx                             |   4 +
 src/emucore/TIASurface.cxx                         |  90 +++-----
 src/emucore/TIASurface.hxx                         |  14 +-
 src/emucore/Thumbulator.cxx                        |  63 +++++
 src/emucore/Thumbulator.hxx                        |   1 +
 src/emucore/TrakBall.cxx                           | 125 ----------
 src/emucore/TrakBall.hxx                           |  91 ++------
 src/emucore/module.mk                              |   4 +-
 src/emucore/tia/Ball.hxx                           |   5 +-
 src/emucore/tia/Missile.hxx                        |   5 +-
 src/emucore/tia/Player.hxx                         |   5 +-
 src/emucore/tia/Playfield.hxx                      |   6 +-
 src/emucore/tia/TIA.cxx                            | 104 +++++----
 src/gui/InputDialog.cxx                            |  38 ++-
 src/gui/InputDialog.hxx                            |   3 +
 src/gui/VideoDialog.cxx                            |  14 ++
 src/gui/VideoDialog.hxx                            |   1 +
 src/macosx/English.lproj/InfoPlist.strings         |   4 +-
 src/macosx/Info-Stella.plist                       |   2 +-
 src/macosx/stella.xcodeproj/project.pbxproj        |  20 +-
 src/unix/stella.spec                               |   5 +-
 src/windows/OSystemWINDOWS.hxx                     |   2 -
 src/windows/SerialPortWINDOWS.hxx                  |   2 -
 src/windows/Stella.vcxproj                         |  21 +-
 src/windows/Stella.vcxproj.filters                 |  15 +-
 src/windows/stella.rc                              |   8 +-
 83 files changed, 1161 insertions(+), 986 deletions(-)

diff --git a/Announce.txt b/Announce.txt
index 51c7f6b..76fbbdd 100644
--- a/Announce.txt
+++ b/Announce.txt
@@ -9,7 +9,7 @@
      SSSS     ttt  eeeee llll llll  aaaaa
 
 ===========================================================================
-                Release 5.0.1 for Linux, MacOSX and Windows
+                Release 5.0.2 for Linux, MacOSX and Windows
 ===========================================================================
 
 The Atari 2600 Video Computer System (VCS), introduced in 1977, was the
@@ -21,30 +21,30 @@ all of your favourite Atari 2600 games again!  Stella was originally
 developed for Linux by Bradford W. Mott, however, it has been ported to a
 number of other platforms and is currently maintained by Stephen Anthony.
 
-This is the 5.0.1 release of Stella for Linux, Mac OSX and Windows.  The
+This is the 5.0.2 release of Stella for Linux, Mac OSX and Windows.  The
 distributions currently available are:
 
   * Binaries for Windows XP_SP3(*)/Vista/7/8/10 :
-      Stella-5.0.1-win32.exe   (32-bit EXE installer)
-      Stella-5.0.1-x64.exe     (64-bit EXE installer)
-      Stella-5.0.1-windows.zip (32/64 bit versions)
+      Stella-5.0.2-win32.exe   (32-bit EXE installer)
+      Stella-5.0.2-x64.exe     (64-bit EXE installer)
+      Stella-5.0.2-windows.zip (32/64 bit versions)
 
       (*) Note: Support for Windows XP is problematic on some systems,
           and will probably be discontinued in a future release.
 
   * Binary distribution for MacOS X 10.7 and above :
-      Stella-5.0.1-macosx.dmg (64-bit Intel)
+      Stella-5.0.2-macosx.dmg (64-bit Intel)
 
   * Binary distribution in 32-bit & 64-bit Ubuntu DEB format :
-      stella_5.0.1-1_i386.deb
-      stella_5.0.1-1_amd64.deb
+      stella_5.0.2-1_i386.deb
+      stella_5.0.2-1_amd64.deb
 
   * Binary distribution in 32-bit & 64-bit RPM format :
-      stella-5.0.1-2.i386.rpm
-      stella-5.0.1-2.x86_64.rpm
+      stella-5.0.2-2.i386.rpm
+      stella-5.0.2-2.x86_64.rpm
 
   * Source code distribution for all platforms :
-      stella-5.0.1-src.tar.xz
+      stella-5.0.2-src.tar.xz
 
 
 Distribution Site
diff --git a/Changes.txt b/Changes.txt
index 6500823..50a949e 100644
--- a/Changes.txt
+++ b/Changes.txt
@@ -12,6 +12,46 @@
                                Release History
 ===========================================================================
 
+5.0.1 to 5.0.2: (August 20, 2017)
+
+  * Improved emulation of Trakball controller, eliminating bias in left/
+    right directions.  Thanks to Thomas Jentzsch for the idea and code.
+    Related to this, added 'tsense' commandline argument and associated
+    UI item, to allow changing sensitivity of mouse trackball emulation.
+
+  * Added preliminary support for multi-threading in the Blargg TV effects
+    code.  This is still a WIP; more improvements are coming.  Related to
+    this, further optimized the TIA rendering code.  Also added 'threads'
+    commandline argument and associated UI item to enable/disable
+    multi-threading.  Thanks to Thomas Jentzsch for the bulk of the work
+    in this area.
+
+  * Blargg TV effects now no longer cut off the right side of the image
+    (by several pixels) in certain cases.
+
+  * Updated CDF scheme to latest version from Spiceware.  In addition,
+    this scheme now supports versioning, so older and newer ROMs will
+    continue to work.
+
+  * Fixed an annoying bug in Linux, where Alt-Tab'ing out of a window and
+    then back again would pass a 'Tab' key event to the app, which in
+    most cases would navigate to the next UI element.
+
+  * Fixed potential issue with state file saving and the debugger; under
+    certain circumstances a rewind would give a different state than
+    before (note that the state file format has changed because of this).
+
+  * Fixed lockups when entering the debugger under certain circumstances.
+
+  * The debugger 'listtraps' command now shows all traps set, not just
+    the first one(s).
+
+  * Reverted joystick changes for Decathlon ROMs from last release, as
+    it was added by mistake.
+
+-Have fun!
+
+
 5.0 to 5.0.1: (July 23, 2017)
 
   * Fixed issues in keypad, Genesis and various other controllers that use
@@ -25,8 +65,6 @@
 
   * Codebase now uses C++14 features.
 
--Have fun!
-
 
 4.7.3 to 5.0: (July 16, 2017)
 
diff --git a/Makefile b/Makefile
index 346640e..3bf0fc1 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,7 @@
 srcdir      ?= .
 
 DEFINES     := -D_GLIBCXX_USE_CXX11_ABI=1
-LDFLAGS     :=
+LDFLAGS     := -pthread
 INCLUDES    :=
 LIBS	    :=
 OBJS	    :=
diff --git a/debian/changelog b/debian/changelog
index f69c26c..aba8443 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,5 +1,12 @@
 stella (5.0.1-1) stable; urgency=high
 
+  * Version 5.0.2 release
+
+ -- Stephen Anthony <stephena at users.sf.net>  Sun, 20 Aug 2017 17:09:59 -0230
+
+
+stella (5.0.1-1) stable; urgency=high
+
   * Version 5.0.1 release
 
  -- Stephen Anthony <stephena at users.sf.net>  Sun, 23 Jul 2017 17:09:59 -0230
diff --git a/docs/graphics/cheat.png b/docs/graphics/cheat.png
index bdac1ab..f4df513 100644
Binary files a/docs/graphics/cheat.png and b/docs/graphics/cheat.png differ
diff --git a/docs/graphics/debugger_tiatab.png b/docs/graphics/debugger_tiatab.png
index 0f1834e..f9b1795 100644
Binary files a/docs/graphics/debugger_tiatab.png and b/docs/graphics/debugger_tiatab.png differ
diff --git a/docs/graphics/eventmapping.png b/docs/graphics/eventmapping.png
index da13d04..5eb1c91 100644
Binary files a/docs/graphics/eventmapping.png and b/docs/graphics/eventmapping.png differ
diff --git a/docs/graphics/eventmapping_combo.png b/docs/graphics/eventmapping_combo.png
index e87112e..d3e99fa 100644
Binary files a/docs/graphics/eventmapping_combo.png and b/docs/graphics/eventmapping_combo.png differ
diff --git a/docs/graphics/eventmapping_devsports.png b/docs/graphics/eventmapping_devsports.png
index 2fa98cc..5cd391c 100644
Binary files a/docs/graphics/eventmapping_devsports.png and b/docs/graphics/eventmapping_devsports.png differ
diff --git a/docs/graphics/eventmapping_remap.png b/docs/graphics/eventmapping_remap.png
index ca0d9ad..04b77a8 100644
Binary files a/docs/graphics/eventmapping_remap.png and b/docs/graphics/eventmapping_remap.png differ
diff --git a/docs/graphics/launcher_filter.png b/docs/graphics/launcher_filter.png
index acf1037..35af291 100644
Binary files a/docs/graphics/launcher_filter.png and b/docs/graphics/launcher_filter.png differ
diff --git a/docs/graphics/launcher_options_files.png b/docs/graphics/launcher_options_files.png
index 6db5ffa..85b0c1d 100644
Binary files a/docs/graphics/launcher_options_files.png and b/docs/graphics/launcher_options_files.png differ
diff --git a/docs/graphics/launcher_options_snapshots.png b/docs/graphics/launcher_options_snapshots.png
index 2de1695..989ece1 100644
Binary files a/docs/graphics/launcher_options_snapshots.png and b/docs/graphics/launcher_options_snapshots.png differ
diff --git a/docs/graphics/launcher_override.png b/docs/graphics/launcher_override.png
index af1d49b..9bbc16e 100644
Binary files a/docs/graphics/launcher_override.png and b/docs/graphics/launcher_override.png differ
diff --git a/docs/graphics/logs.png b/docs/graphics/logs.png
index ae6373b..2516e9e 100644
Binary files a/docs/graphics/logs.png and b/docs/graphics/logs.png differ
diff --git a/docs/graphics/options_audio.png b/docs/graphics/options_audio.png
index 3b624a9..19f3f8f 100644
Binary files a/docs/graphics/options_audio.png and b/docs/graphics/options_audio.png differ
diff --git a/docs/graphics/options_debugger.png b/docs/graphics/options_debugger.png
index ee6dd4e..1482e5b 100644
Binary files a/docs/graphics/options_debugger.png and b/docs/graphics/options_debugger.png differ
diff --git a/docs/graphics/options_gameinfo.png b/docs/graphics/options_gameinfo.png
index 83cf0e8..76a5159 100644
Binary files a/docs/graphics/options_gameinfo.png and b/docs/graphics/options_gameinfo.png differ
diff --git a/docs/graphics/options_input.png b/docs/graphics/options_input.png
deleted file mode 100644
index fb9423b..0000000
Binary files a/docs/graphics/options_input.png and /dev/null differ
diff --git a/docs/graphics/options_misc.png b/docs/graphics/options_misc.png
index 493402e..87b6047 100644
Binary files a/docs/graphics/options_misc.png and b/docs/graphics/options_misc.png differ
diff --git a/docs/graphics/options_ui.png b/docs/graphics/options_ui.png
index 1114cfe..bf7a61c 100644
Binary files a/docs/graphics/options_ui.png and b/docs/graphics/options_ui.png differ
diff --git a/docs/graphics/options_video.png b/docs/graphics/options_video.png
index 6bb857b..495b833 100644
Binary files a/docs/graphics/options_video.png and b/docs/graphics/options_video.png differ
diff --git a/docs/graphics/options_video_dbgcolors.png b/docs/graphics/options_video_dbgcolors.png
index 107d83b..dd7361f 100644
Binary files a/docs/graphics/options_video_dbgcolors.png and b/docs/graphics/options_video_dbgcolors.png differ
diff --git a/docs/graphics/options_video_tv.png b/docs/graphics/options_video_tv.png
index 878caca..ccbf27c 100644
Binary files a/docs/graphics/options_video_tv.png and b/docs/graphics/options_video_tv.png differ
diff --git a/docs/graphics/rom_browser.png b/docs/graphics/rom_browser.png
index 76c0cef..a0ac1f1 100644
Binary files a/docs/graphics/rom_browser.png and b/docs/graphics/rom_browser.png differ
diff --git a/docs/graphics/romaudit.png b/docs/graphics/romaudit.png
index 86d867e..8e8fa40 100644
Binary files a/docs/graphics/romaudit.png and b/docs/graphics/romaudit.png differ
diff --git a/docs/index.html b/docs/index.html
index 8223c2b..62dd440 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -10,7 +10,7 @@
   <br><br>
   <center><h2><b>A multi-platform Atari 2600 VCS emulator</b></h2></center>
 
-  <center><h4><b>Release 5.0.1</b></h4></center>
+  <center><h4><b>Release 5.0.2</b></h4></center>
   <br><br>
 
   <center><h2><b>User's Guide</b></h2></center>
@@ -1353,8 +1353,8 @@
 
     <tr>
       <td>Toggle frame stats (scanline count/fps/bs type/etc)</td>
-      <td>Alt + l</td>
-      <td>Cmd + l</td>
+      <td>Alt + L</td>
+      <td>Cmd + L</td>
     </tr>
 
     <tr>
@@ -1895,6 +1895,11 @@
       <td>Enable default phosphor blending level; 0 implies no mixing, and 100
         is full mixing (not recommended).  Note that this doesn't actually
         enable phosphor mode; that is done for each ROM in the ROM properties.
+        Higher blend values will intensify the phosphor effect. Depending on your
+        display and personal preferences, the optimal default for you may vary.
+        Slow LCDs (especially for office use) may only need a low blend of
+        around 30, while fast switching gamer LCDs may need about 70 to look
+        similar to a CRT.
       </td>
     </tr>
 
@@ -2034,14 +2039,21 @@
       <td><pre>-dsense <number></pre></td>
       <td>Sensitivity for emulation of paddles when using a digital device
       (ie, joystick digital axis or button, keyboard key, etc).
-      Valid range of values is from 1 to 10, with larger numbers causing
+      Valid range of values is from 1 to 20, with larger numbers causing
       faster movement.</td>
     </tr>
 
     <tr>
       <td><pre>-msense <number></pre></td>
       <td>Sensitivity for emulation of paddles when using a mouse.
-      Valid range of values is from 1 to 15, with larger numbers causing
+      Valid range of values is from 1 to 20, with larger numbers causing
+      faster movement.</td>
+    </tr>
+
+    <tr>
+      <td><pre>-tsense <number></pre></td>
+      <td>Sensitivity for emulation of trackball controllers when using a mouse.
+      Valid range of values is from 1 to 20, with larger numbers causing
       faster movement.</td>
     </tr>
 
@@ -2078,6 +2090,11 @@
     </tr>
 
     <tr>
+      <td><pre>-threads <1|0></pre></td>
+      <td>Enable multi-threaded video rendering (may not improve performance on all systems).</td>
+    </tr>
+
+    <tr>
       <td><pre>-snapsavedir <path></pre></td>
       <td>The directory to save snapshot files to.</td>
     </tr>
@@ -2403,7 +2420,7 @@
     <tr>
       <td><pre>-ppblend <number></pre></td>
       <td>Set "Display.PPBlend" property, used for phosphor effect (0-100).
-        Default is 77.</td>
+        Default is whatever is specified for tv.phosblend.</td>
     </tr>
 
     <tr>
@@ -2451,6 +2468,7 @@
           <tr><td>Fast SC/AR BIOS</td><td>skip progress loading bars for SuperCharger ROMs</td><td>-fastscbios</td></tr>
           <tr><td>Show UI messages</td><td>overlay UI messages onscreen</td><td>-uimessages</td></tr>
           <tr><td>Center window</td><td>attempt to center application window</td><td>-center</td></tr>
+          <tr><td>Use multi-threading</td><td>enable multi-threaded rendering</td><td>-threads</td></tr>
         </table>
       </td>
     </tr>
@@ -2522,7 +2540,7 @@
   <p><b>Input Settings</b> dialog:</p>
   <table border="5" cellpadding="2" frame="box" rules="none">
     <tr>
-      <td><img src="graphics/options_input.png"></td>
+      <td><img src="graphics/eventmapping.png"></td>
       <td>    </td>
       <td valign="top"><br>This dialog is described in further detail in
       <b>Advanced Configuration - <a href="#Remapping">Event Remapping</a></b>.</td>
@@ -2691,6 +2709,7 @@
           <tr><td>Joy deadzone size</td><td>Deadzone area for axes on joysticks/gamepads</td><td>-joydeadzone</td></tr>
           <tr><td>Digital paddle sensitivity</td><td>Sensitivity used when emulating a paddle using a digital device</td><td>-dsense</td></tr>
           <tr><td>Mouse paddle sensitivity</td><td>Sensitivity used when emulating a paddle using a mouse</td><td>-msense</td></tr>
+          <tr><td>Trackball sensitivity</td><td>Sensitivity used when emulating a trackball device using a mouse</td><td>-tsense</td></tr>
           <tr><td>Allow all 4 ...</td><td>Allow all 4 joystick directions to be pressed simultaneously</td><td>-joyallow4</td></tr>
           <tr><td>Grab mouse ...</td><td>Keep mouse in window in emulation mode</td><td>-grabmouse</td></tr>
           <tr><td>Use Control key combos</td><td>Enable using Control key in keyboard actions</td><td>-ctrlcombo</td></tr>
@@ -3446,7 +3465,7 @@ Ms Pac-Man (Stella extended codes):
       <td VALIGN="TOP"><i>Display.PPBlend:</i></td>
       <td>Indicates the amount of blending which will occur while using the
       phosphor effect.  The value must be <i>n</i> such that 0 <= <i>n</i>
-      <= 100.  The default value is 77.</td>
+      <= 100.  The default value is whatever is specified for tv.phosblend.</td>
     </tr>
   </table>
 <!--
diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx
index 5451b61..04e37ca 100644
--- a/src/common/SoundSDL2.cxx
+++ b/src/common/SoundSDL2.cxx
@@ -261,11 +261,7 @@ void SoundSDL2::set(uInt16 addr, uInt8 value, Int32 cycle)
   // the sound to "scale" correctly, we have to know the games real frame
   // rate (e.g., 50 or 60) and the currently emulated frame rate. We use these
   // values to "scale" the time before the register change occurs.
-  RegWrite info;
-  info.addr = addr;
-  info.value = value;
-  info.delta = delta;
-  myRegWriteQueue.enqueue(info);
+  myRegWriteQueue.enqueue(addr, value, delta);
 
   // Update last cycle counter to the current cycle
   myLastRegisterSetCycle = cycle;
@@ -476,14 +472,17 @@ double SoundSDL2::RegWriteQueue::duration() const
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-void SoundSDL2::RegWriteQueue::enqueue(const RegWrite& info)
+void SoundSDL2::RegWriteQueue::enqueue(uInt16 addr, uInt8 value, double delta)
 {
   // If an attempt is made to enqueue more than the queue can hold then
   // we'll enlarge the queue's capacity.
   if(mySize == myCapacity)
     grow();
 
-  myBuffer[myTail] = info;
+  RegWrite& reg = myBuffer[myTail];
+  reg.addr  = addr;
+  reg.value = value;
+  reg.delta = delta;
   myTail = (myTail + 1) % myCapacity;
   ++mySize;
 }
diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx
index 5d186a5..914a715 100644
--- a/src/common/SoundSDL2.hxx
+++ b/src/common/SoundSDL2.hxx
@@ -174,6 +174,9 @@ class SoundSDL2 : public Sound
       uInt16 addr;
       uInt8 value;
       double delta;
+
+      RegWrite(uInt16 a = 0, uInt8 v = 0, double d = 0.0)
+        : addr(a), value(v), delta(d) { }
     };
 
     /**
@@ -209,7 +212,7 @@ class SoundSDL2 : public Sound
         /**
           Enqueue the specified object.
         */
-        void enqueue(const RegWrite& info);
+        void enqueue(uInt16 addr, uInt8 value, double delta);
 
         /**
           Return the item at the front on the queue.
diff --git a/src/common/Version.hxx b/src/common/Version.hxx
index 313e78d..d3011fa 100644
--- a/src/common/Version.hxx
+++ b/src/common/Version.hxx
@@ -18,7 +18,7 @@
 #ifndef VERSION_HXX
 #define VERSION_HXX
 
-#define STELLA_VERSION "5.0.1"
-#define STELLA_BUILD "3487"
+#define STELLA_VERSION "5.0.2"
+#define STELLA_BUILD "3535"
 
 #endif
diff --git a/src/common/bspf.hxx b/src/common/bspf.hxx
index 13975f0..607c452 100644
--- a/src/common/bspf.hxx
+++ b/src/common/bspf.hxx
@@ -111,9 +111,13 @@ namespace BSPF
 
   // Combines 'max' and 'min', and clamps value to the upper/lower value
   // if it is outside the specified range
-  template<typename T> inline T clamp(T a, T l, T u)
+  template<class T> inline T clamp(T val, T lower, T upper)
   {
-    return (a<l) ? l : (a>u) ? u : a;
+    return (val < lower) ? lower : (val > upper) ? upper : val;
+  }
+  template<class T> inline void clamp(T& val, T lower, T upper, T setVal)
+  {
+    if(val < lower || val > upper)  val = setVal;
   }
 
   // Compare two strings, ignoring case
diff --git a/src/common/tv_filters/AtariNTSC.cxx b/src/common/tv_filters/AtariNTSC.cxx
index 155a3b7..a16a364 100644
--- a/src/common/tv_filters/AtariNTSC.cxx
+++ b/src/common/tv_filters/AtariNTSC.cxx
@@ -15,6 +15,7 @@
 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
 //============================================================================
 
+#include <thread>
 #include "AtariNTSC.hxx"
 
 // blitter related
@@ -66,46 +67,229 @@ void AtariNTSC::initializePalette(const uInt8* palette)
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-void AtariNTSC::render(const uInt8* atari_in, uInt32 in_width,
-                       uInt32 in_height, void* rgb_out, uInt32 out_pitch)
+void AtariNTSC::enableThreading(bool enable)
 {
+  uInt32 systemThreads = enable ? std::thread::hardware_concurrency() : 0;
+  if(systemThreads <= 1)
+  {
+    myWorkerThreads = 0;
+    myTotalThreads  = 1;
+  }
+  else
+  {
+    systemThreads = std::min(4u, systemThreads);
+
+    myWorkerThreads = systemThreads - 1;
+    myTotalThreads  = systemThreads;
+
+    myThreads = make_unique<std::thread[]>(myWorkerThreads);
+  }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void AtariNTSC::render(const uInt8* atari_in, const uInt32 in_width, const uInt32 in_height,
+  void* rgb_out, const uInt32 out_pitch, uInt32* rgb_in)
+{
+  // Spawn the threads...
+  for(uInt32 i = 0; i < myWorkerThreads; ++i)
+  {
+    myThreads[i] = std::thread([=] {
+    rgb_in == nullptr ?
+      renderThread(atari_in, in_width, in_height, myTotalThreads, i+1, rgb_out, out_pitch) :
+      renderWithPhosphorThread(atari_in, in_width, in_height, myTotalThreads, i+1, rgb_in, rgb_out, out_pitch);
+    });
+  }
+  // Make the main thread busy too
+  rgb_in == nullptr ?
+    renderThread(atari_in, in_width, in_height, myTotalThreads, 0, rgb_out, out_pitch) :
+    renderWithPhosphorThread(atari_in, in_width, in_height, myTotalThreads, 0, rgb_in, rgb_out, out_pitch);
+  // ...and make them join again
+  for(uInt32 i = 0; i < myWorkerThreads; ++i)
+    myThreads[i].join();
+
+  // Copy phosphor values into out buffer
+  if(rgb_in != nullptr)
+    memcpy(rgb_out, rgb_in, in_height * out_pitch);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void AtariNTSC::renderThread(const uInt8* atari_in, const uInt32 in_width,
+  const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum,
+  void* rgb_out, const uInt32 out_pitch)
+{
+  // Adapt parameters to thread number
+  const uInt32 yStart = in_height * threadNum / numThreads;
+  const uInt32 yEnd = in_height * (threadNum + 1) / numThreads;
+  atari_in += in_width * yStart;
+  rgb_out  = static_cast<char*>(rgb_out) + out_pitch * yStart;
+
+  uInt32 const chunk_count = (in_width - 1) / PIXEL_in_chunk;
+
+  for(uInt32 y = yStart; y < yEnd; ++y)
+  {
+    const uInt8* line_in = atari_in;
+    ATARI_NTSC_BEGIN_ROW(NTSC_black, line_in[0]);
+    uInt32* restrict line_out = static_cast<uInt32*>(rgb_out);
+    ++line_in;
+
+    for(uInt32 n = chunk_count; n; --n)
+    {
+      // order of input and output pixels must not be altered
+      ATARI_NTSC_COLOR_IN(0, line_in[0]);
+      ATARI_NTSC_RGB_OUT_8888(0, line_out[0]);
+      ATARI_NTSC_RGB_OUT_8888(1, line_out[1]);
+      ATARI_NTSC_RGB_OUT_8888(2, line_out[2]);
+      ATARI_NTSC_RGB_OUT_8888(3, line_out[3]);
+
+      ATARI_NTSC_COLOR_IN(1, line_in[1]);
+      ATARI_NTSC_RGB_OUT_8888(4, line_out[4]);
+      ATARI_NTSC_RGB_OUT_8888(5, line_out[5]);
+      ATARI_NTSC_RGB_OUT_8888(6, line_out[6]);
+
+      line_in += 2;
+      line_out += 7;
+    }
+
+    // finish final pixels
+    ATARI_NTSC_COLOR_IN(0, line_in[0]);
+    ATARI_NTSC_RGB_OUT_8888(0, line_out[0]);
+    ATARI_NTSC_RGB_OUT_8888(1, line_out[1]);
+    ATARI_NTSC_RGB_OUT_8888(2, line_out[2]);
+    ATARI_NTSC_RGB_OUT_8888(3, line_out[3]);
+
+    ATARI_NTSC_COLOR_IN(1, NTSC_black);
+    ATARI_NTSC_RGB_OUT_8888(4, line_out[4]);
+    ATARI_NTSC_RGB_OUT_8888(5, line_out[5]);
+    ATARI_NTSC_RGB_OUT_8888(6, line_out[6]);
+
+    line_in += 2;
+    line_out += 7;
+
+    ATARI_NTSC_COLOR_IN(0, NTSC_black);
+    ATARI_NTSC_RGB_OUT_8888(0, line_out[0]);
+    ATARI_NTSC_RGB_OUT_8888(1, line_out[1]);
+    ATARI_NTSC_RGB_OUT_8888(2, line_out[2]);
+    ATARI_NTSC_RGB_OUT_8888(3, line_out[3]);
+
+    ATARI_NTSC_COLOR_IN(1, NTSC_black);
+    ATARI_NTSC_RGB_OUT_8888(4, line_out[4]);
+#if 0
+    ATARI_NTSC_RGB_OUT_8888(5, line_out[5]);
+    ATARI_NTSC_RGB_OUT_8888(6, line_out[6]);
+#endif
+
+    atari_in += in_width;
+    rgb_out = static_cast<char*>(rgb_out) + out_pitch;
+  }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void AtariNTSC::renderWithPhosphorThread(const uInt8* atari_in, const uInt32 in_width,
+  const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum,
+  uInt32* rgb_in, void* rgb_out, const uInt32 out_pitch)
+{
+  // Adapt parameters to thread number
+  const uInt32 yStart = in_height * threadNum / numThreads;
+  const uInt32 yEnd = in_height * (threadNum + 1) / numThreads;
+  uInt32 bufofs = AtariNTSC::outWidth(in_width) * yStart;
+  uInt32* out = static_cast<uInt32*>(rgb_out);
+  atari_in += in_width * yStart;
+  rgb_out = static_cast<char*>(rgb_out) + out_pitch * yStart;
+
   uInt32 const chunk_count = (in_width - 1) / PIXEL_in_chunk;
-  while ( in_height-- )
+
+  for(uInt32 y = yStart; y < yEnd; ++y)
   {
     const uInt8* line_in = atari_in;
-    ATARI_NTSC_BEGIN_ROW( NTSC_black, line_in[0] );
+    ATARI_NTSC_BEGIN_ROW(NTSC_black, line_in[0]);
     uInt32* restrict line_out = static_cast<uInt32*>(rgb_out);
     ++line_in;
 
-    for ( uInt32 n = chunk_count; n; --n )
+    for(uInt32 n = chunk_count; n; --n)
     {
-      /* order of input and output pixels must not be altered */
-      ATARI_NTSC_COLOR_IN( 0, line_in[0] );
-      ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
-      ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
-      ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
-      ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
-
-      ATARI_NTSC_COLOR_IN( 1, line_in[1] );
-      ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
-      ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
-      ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
-
-      line_in  += 2;
+      // order of input and output pixels must not be altered
+      ATARI_NTSC_COLOR_IN(0, line_in[0]);
+      ATARI_NTSC_RGB_OUT_8888(0, line_out[0]);
+      ATARI_NTSC_RGB_OUT_8888(1, line_out[1]);
+      ATARI_NTSC_RGB_OUT_8888(2, line_out[2]);
+      ATARI_NTSC_RGB_OUT_8888(3, line_out[3]);
+
+      ATARI_NTSC_COLOR_IN(1, line_in[1]);
+      ATARI_NTSC_RGB_OUT_8888(4, line_out[4]);
+      ATARI_NTSC_RGB_OUT_8888(5, line_out[5]);
+      ATARI_NTSC_RGB_OUT_8888(6, line_out[6]);
+
+      line_in += 2;
       line_out += 7;
     }
 
-    /* finish final pixels */
-    ATARI_NTSC_COLOR_IN( 0, NTSC_black );
-    ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
-    ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
-    ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
-    ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
+    // finish final pixels
+    ATARI_NTSC_COLOR_IN(0, line_in[0]);
+    ATARI_NTSC_RGB_OUT_8888(0, line_out[0]);
+    ATARI_NTSC_RGB_OUT_8888(1, line_out[1]);
+    ATARI_NTSC_RGB_OUT_8888(2, line_out[2]);
+    ATARI_NTSC_RGB_OUT_8888(3, line_out[3]);
+
+    ATARI_NTSC_COLOR_IN(1, NTSC_black);
+    ATARI_NTSC_RGB_OUT_8888(4, line_out[4]);
+    ATARI_NTSC_RGB_OUT_8888(5, line_out[5]);
+    ATARI_NTSC_RGB_OUT_8888(6, line_out[6]);
+
+    line_in += 2;
+    line_out += 7;
+
+    ATARI_NTSC_COLOR_IN(0, NTSC_black);
+    ATARI_NTSC_RGB_OUT_8888(0, line_out[0]);
+    ATARI_NTSC_RGB_OUT_8888(1, line_out[1]);
+    ATARI_NTSC_RGB_OUT_8888(2, line_out[2]);
+    ATARI_NTSC_RGB_OUT_8888(3, line_out[3]);
+
+    ATARI_NTSC_COLOR_IN(1, NTSC_black);
+    ATARI_NTSC_RGB_OUT_8888(4, line_out[4]);
+#if 0
+    ATARI_NTSC_RGB_OUT_8888(5, line_out[5]);
+    ATARI_NTSC_RGB_OUT_8888(6, line_out[6]);
+#endif
 
-    ATARI_NTSC_COLOR_IN( 1, NTSC_black );
-    ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
-    ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
-    ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
+    // Do phosphor mode (blend the resulting frames)
+    // Note: The code assumes that AtariNTSC::outWidth(kTIAW) == outPitch == 565
+    for (uInt32 x = AtariNTSC::outWidth(in_width) / 8; x; --x)
+    {
+      // Store back into displayed frame buffer (for next frame)
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+      rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+      bufofs++;
+    }
+    // finish final pixels
+    rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+    bufofs++;
+    rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+    bufofs++;
+    rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+    bufofs++;
+    rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+    bufofs++;
+    rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+    bufofs++;
+#if 0
+    rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+    bufofs++;
+    rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]);
+    bufofs++;
+#endif
 
     atari_in += in_width;
     rgb_out = static_cast<char*>(rgb_out) + out_pitch;
@@ -113,6 +297,23 @@ void AtariNTSC::render(const uInt8* atari_in, uInt32 in_width,
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+inline uInt32 AtariNTSC::getRGBPhosphor(const uInt32 c, const uInt32 p) const
+{
+#define TO_RGB(color, red, green, blue) \
+    const uInt8 red = color >> 16; const uInt8 green = color >> 8; const uInt8 blue = color;
+
+  TO_RGB(c, rc, gc, bc);
+  TO_RGB(p, rp, gp, bp);
+
+  // Mix current calculated frame with previous displayed frame
+  const uInt8 rn = myPhosphorPalette[rc][rp];
+  const uInt8 gn = myPhosphorPalette[gc][gp];
+  const uInt8 bn = myPhosphorPalette[bc][bp];
+
+  return (rn << 16) | (gn << 8) | bn;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void AtariNTSC::init(init_t& impl, const Setup& setup)
 {
   impl.brightness = float(setup.brightness) * (0.5f * rgb_unit) + rgb_offset;
diff --git a/src/common/tv_filters/AtariNTSC.hxx b/src/common/tv_filters/AtariNTSC.hxx
index f9c4269..d5dbecd 100644
--- a/src/common/tv_filters/AtariNTSC.hxx
+++ b/src/common/tv_filters/AtariNTSC.hxx
@@ -51,6 +51,9 @@ class AtariNTSC
       entry_size = 2 * 14,
     };
 
+    // By default, threading is turned off
+    AtariNTSC() { enableThreading(false); }
+
     // Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
     // in parenthesis and should remain fairly stable in future versions.
     struct Setup
@@ -80,28 +83,53 @@ class AtariNTSC
     void initialize(const Setup& setup, const uInt8* palette);
     void initializePalette(const uInt8* palette);
 
+    // Set up threading
+    void enableThreading(bool enable);
+
+    // Set phosphor palette, for use in Blargg + phosphor mode
+    void setPhosphorPalette(uInt8 palette[256][256]) {
+      memcpy(myPhosphorPalette, palette, 256 * 256);
+    }
+
     // Filters one or more rows of pixels. Input pixels are 8-bit Atari
     // palette colors.
     //  In_row_width is the number of pixels to get to the next input row.
     //  Out_pitch is the number of *bytes* to get to the next output row.
-    void render(const uInt8* atari_in, uInt32 in_width, uInt32 in_height,
-                void* rgb_out, uInt32 out_pitch);
+    void render(const uInt8* atari_in, const uInt32 in_width, const uInt32 in_height,
+                void* rgb_out, const uInt32 out_pitch, uInt32* rgb_in = nullptr);
 
     // Number of input pixels that will fit within given output width.
     // Might be rounded down slightly; use outWidth() on result to find
     // rounded value.
     static constexpr uInt32 inWidth( uInt32 out_width ) {
-      return (((out_width) / PIXEL_out_chunk - 1) * PIXEL_in_chunk + 1);
+      return (((out_width-5) / PIXEL_out_chunk - 1) * PIXEL_in_chunk + 1);
     }
 
     // Number of output pixels written by blitter for given input width.
     // Width might be rounded down slightly; use inWidth() on result to
     // find rounded value. Guaranteed not to round 160 down at all.
     static constexpr uInt32 outWidth(uInt32 in_width) {
-      return ((((in_width) - 1) / PIXEL_in_chunk + 1)* PIXEL_out_chunk);
+      return ((((in_width) - 1) / PIXEL_in_chunk + 1)* PIXEL_out_chunk) + 5;
     }
 
   private:
+    // Threaded rendering
+    void renderThread(const uInt8* atari_in, const uInt32 in_width,
+      const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum, void* rgb_out, const uInt32 out_pitch);
+    void renderWithPhosphorThread(const uInt8* atari_in, const uInt32 in_width,
+      const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum, uInt32* rgb_in, void* rgb_out, const uInt32 out_pitch);
+
+    /**
+      Used to calculate an averaged color for the 'phosphor' effect.
+
+      @param c  RGB Color 1 (current frame)
+      @param p  RGB Color 2 (previous frame)
+
+      @return  Averaged value of the two RGB colors
+    */
+    uInt32 getRGBPhosphor(const uInt32 c, const uInt32 cp) const;
+
+  private:
     enum {
       PIXEL_in_chunk  = 2,   // number of input pixels read per chunk
       PIXEL_out_chunk = 7,   // number of output pixels generated per chunk
@@ -138,6 +166,12 @@ class AtariNTSC
     #define LUMA_CUTOFF 0.20
 
     uInt32 myColorTable[palette_size][entry_size];
+    uInt8 myPhosphorPalette[256][256];
+
+    // Rendering threads
+    unique_ptr<std::thread[]> myThreads;
+    // Number of rendering and total threads
+    uInt32 myWorkerThreads, myTotalThreads;
 
     struct init_t
     {
diff --git a/src/common/tv_filters/NTSCFilter.hxx b/src/common/tv_filters/NTSCFilter.hxx
index cb18433..987687f 100644
--- a/src/common/tv_filters/NTSCFilter.hxx
+++ b/src/common/tv_filters/NTSCFilter.hxx
@@ -72,6 +72,10 @@ class NTSCFilter
       myNTSC.initializePalette(myTIAPalette);
     }
 
+    inline void setPhosphorPalette(uInt8 palette[256][256]) {
+      myNTSC.setPhosphorPalette(palette);
+    }
+
     // The following are meant to be used strictly for toggling from the GUI
     string setPreset(Preset preset);
 
@@ -110,6 +114,17 @@ class NTSCFilter
     {
       myNTSC.render(src_buf, src_width, src_height, dest_buf, dest_pitch);
     }
+    inline void render(uInt8* src_buf, uInt32 src_width, uInt32 src_height,
+                       uInt32* dest_buf, uInt32 dest_pitch, uInt32* prev_buf)
+    {
+      myNTSC.render(src_buf, src_width, src_height, dest_buf, dest_pitch, prev_buf);
+    }
+
+    // Enable threading for the NTSC rendering
+    inline void enableThreading(bool enable)
+    {
+      myNTSC.enableThreading(enable);
+    }
 
   private:
     // Convert from atari_ntsc_setup_t values to equivalent adjustables
diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx
index f71bdf0..134a154 100644
--- a/src/debugger/DebuggerParser.cxx
+++ b/src/debugger/DebuggerParser.cxx
@@ -284,16 +284,16 @@ int DebuggerParser::decipher_arg(const string& str)
 string DebuggerParser::showWatches()
 {
   ostringstream buf;
-  for(uInt32 i = 0; i < watches.size(); i++)
+  for(uInt32 i = 0; i < myWatches.size(); ++i)
   {
-    if(watches[i] != "")
+    if(myWatches[i] != "")
     {
       // Clear the args, since we're going to pass them to eval()
       argStrings.clear();
       args.clear();
 
       argCount = 1;
-      argStrings.push_back(watches[i]);
+      argStrings.push_back(myWatches[i]);
       args.push_back(decipher_arg(argStrings[0]));
       if(args[0] < 0)
         buf << "BAD WATCH " << (i+1) << ": " << argStrings[0] << endl;
@@ -534,13 +534,14 @@ string DebuggerParser::eval()
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-string DebuggerParser::trapStatus(int addr)
+string DebuggerParser::trapStatus(uInt32 addr, bool& enabled)
 {
   string result;
   result += Base::toString(addr);
   result += ": ";
   bool r = debugger.readTrap(addr);
   bool w = debugger.writeTrap(addr);
+  enabled = r || w;
   if(r && w)
     result += "read|write";
   else if(r)
@@ -550,8 +551,7 @@ string DebuggerParser::trapStatus(int addr)
   else
     result += "none";
 
-  // TODO - technically, we should determine if the label is read or write
-  const string& l = debugger.cartDebug().getLabel(addr, true);
+  const string& l = debugger.cartDebug().getLabel(addr, !w);
   if(l != "") {
     result += "  (";
     result += l;
@@ -570,11 +570,11 @@ bool DebuggerParser::saveScriptFile(string file)
   ofstream out(file);
 
   FunctionDefMap funcs = debugger.getFunctionDefMap();
-  for(const auto& i: funcs)
-    out << "function " << i.first << " { " << i.second << " }" << endl;
+  for(const auto& f: funcs)
+    out << "function " << f.first << " { " << f.second << " }" << endl;
 
-  for(const auto& i: watches)
-    out << "watch " << i << endl;
+  for(const auto& w: myWatches)
+    out << "watch " << w << endl;
 
   for(uInt32 i = 0; i < 0x10000; ++i)
     if(debugger.breakPoint(i))
@@ -735,6 +735,7 @@ void DebuggerParser::executeClearconfig()
 // "cleartraps"
 void DebuggerParser::executeCleartraps()
 {
+  myTraps.clear();
   debugger.clearAllTraps();
   commandResult << "all traps cleared";
 }
@@ -743,7 +744,7 @@ void DebuggerParser::executeCleartraps()
 // "clearwatches"
 void DebuggerParser::executeClearwatches()
 {
-  watches.clear();
+  myWatches.clear();
   commandResult << "all watches cleared";
 }
 
@@ -856,9 +857,9 @@ void DebuggerParser::executeDelfunction()
 void DebuggerParser::executeDelwatch()
 {
   int which = args[0] - 1;
-  if(which >= 0 && which < int(watches.size()))
+  if(which >= 0 && which < int(myWatches.size()))
   {
-    Vec::removeAt(watches, which);
+    Vec::removeAt(myWatches, which);
     commandResult << "removed watch";
   }
   else
@@ -1096,19 +1097,13 @@ void DebuggerParser::executeListfunctions()
 // "listtraps"
 void DebuggerParser::executeListtraps()
 {
-  int count = 0;
-
-  for(uInt32 i = 0; i <= 0xffff; ++i)
+  if(myTraps.size() > 0)
   {
-    if(debugger.readTrap(i) || debugger.writeTrap(i))
-    {
-      commandResult << trapStatus(i) << " + mirrors" << endl;
-      count++;
-      break;
-    }
+    bool enabled = true;
+    for(const auto& trap: myTraps)
+      commandResult << trapStatus(trap, enabled) << " + mirrors" << endl;
   }
-
-  if(!count)
+  else
     commandResult << "no traps set";
 }
 
@@ -1570,7 +1565,12 @@ void DebuggerParser::executeTrapRW(uInt32 addr, bool read, bool write)
     }
   }
 
-  commandResult << trapStatus(addr) << " + mirrors" << endl;
+  bool trapEnabled = false;
+  const string& result = trapStatus(addr, trapEnabled);
+  if(trapEnabled) myTraps.insert(addr);
+  else            myTraps.erase(addr);
+
+  commandResult << result << " + mirrors" << endl;
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1629,7 +1629,7 @@ void DebuggerParser::executeV()
 // "watch"
 void DebuggerParser::executeWatch()
 {
-  watches.push_back(argStrings[0]);
+  myWatches.push_back(argStrings[0]);
   commandResult << "added watch \"" << argStrings[0] << "\"";
 }
 
diff --git a/src/debugger/DebuggerParser.hxx b/src/debugger/DebuggerParser.hxx
index d0bcb50..f473748 100644
--- a/src/debugger/DebuggerParser.hxx
+++ b/src/debugger/DebuggerParser.hxx
@@ -20,6 +20,7 @@
 
 #include <functional>
 #include <sstream>
+#include <set>
 
 class Debugger;
 class FilesystemNode;
@@ -64,7 +65,6 @@ class DebuggerParser
     bool getArgs(const string& command, string& verb);
     bool validateArgs(int cmd);
     string eval();
-    string trapStatus(int addr);
     bool saveScriptFile(string file);
 
   private:
@@ -114,7 +114,11 @@ class DebuggerParser
     StringList argStrings;
     uInt32 argCount;
 
-    StringList watches;
+    StringList myWatches;
+
+    // Keep track of traps (read and/or write)
+    std::set<uInt32> myTraps;
+    string trapStatus(uInt32 addr, bool& enabled);
 
     // List of available command methods
     void executeA();
diff --git a/src/debugger/DiStella.cxx b/src/debugger/DiStella.cxx
index a96fef2..3e5dd44 100644
--- a/src/debugger/DiStella.cxx
+++ b/src/debugger/DiStella.cxx
@@ -103,9 +103,16 @@ DiStella::DiStella(const CartDebug& dbg, CartDebug::DisassemblyList& list,
     // use all access points determined by Stella during emulation
     int codeAccessPoint = 0;
 
-    while(!myAddressQueue.empty())
+    // Sometimes we get a circular reference, in that processing a certain
+    // PC address leads us to a sequence of addresses that end up trying
+    // to process the same address again.  We detect such consecutive PC
+    // addresses and only process the first one
+    uInt16 lastPC = 0;
+    bool duplicateFound = false;
+    while(!(myAddressQueue.empty() || duplicateFound))
     {
-      myPC = myAddressQueue.front();
+      myPC = lastPC = myAddressQueue.front();
+
       uInt16 pcBeg = myPC;
       myAddressQueue.pop();
       disasm(myPC, 1);
@@ -174,6 +181,7 @@ DiStella::DiStella(const CartDebug& dbg, CartDebug::DisassemblyList& list,
           ++codeAccessPoint;
         }
       }
+      duplicateFound = (myAddressQueue.front() == lastPC);
     }
     for (int k = 0; k <= myAppData.end; k++)
     {
diff --git a/src/debugger/DiStella.hxx b/src/debugger/DiStella.hxx
index 43cf5fa..f42885b 100644
--- a/src/debugger/DiStella.hxx
+++ b/src/debugger/DiStella.hxx
@@ -41,7 +41,7 @@ class DiStella
     // A list of options that can be applied to the disassembly
     // This will eventually grow to include all options supported by
     // standalone Distella
-    struct Settings{
+    struct Settings {
       Common::Base::Format gfx_format;
       bool resolve_code;    // Attempt to detect code vs. data sections
       bool show_addresses;  // Show PC addresses (always off for external output)
diff --git a/src/debugger/gui/CartCDFWidget.cxx b/src/debugger/gui/CartCDFWidget.cxx
index 04e308c..a34899b 100644
--- a/src/debugger/gui/CartCDFWidget.cxx
+++ b/src/debugger/gui/CartCDFWidget.cxx
@@ -30,7 +30,7 @@ CartridgeCDFWidget::CartridgeCDFWidget(
   uInt16 size = 8 * 4096;
 
   ostringstream info;
-  info << "CDF cartridge\n"
+  info << "CDF cartridge (version " << cart.myVersion << ")\n"
   << "32K ROM, seven 4K banks are accessible to 2600\n"
   << "8K CDF RAM\n"
   << "CDF registers accessible @ $FFF0 - $FFF3\n"
diff --git a/src/emucore/AmigaMouse.cxx b/src/emucore/AmigaMouse.cxx
deleted file mode 100644
index 4c0eaf5..0000000
--- a/src/emucore/AmigaMouse.cxx
+++ /dev/null
@@ -1,122 +0,0 @@
-//============================================================================
-//
-//   SSSS    tt          lll  lll
-//  SS  SS   tt           ll   ll
-//  SS     tttttt  eeee   ll   ll   aaaa
-//   SSSS    tt   ee  ee  ll   ll      aa
-//      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
-//  SS  SS   tt   ee      ll   ll  aa  aa
-//   SSSS     ttt  eeeee llll llll  aaaaa
-//
-// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
-// and the Stella Team
-//
-// See the file "License.txt" for information on usage and redistribution of
-// this file, and for a DISCLAIMER OF ALL WARRANTIES.
-//============================================================================
-
-#include "Event.hxx"
-#include "System.hxx"
-#include "TIA.hxx"
-#include "AmigaMouse.hxx"
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-AmigaMouse::AmigaMouse(Jack jack, const Event& event, const System& system)
-  : Controller(jack, event, system, Controller::AmigaMouse),
-    myHCounter(0),
-    myVCounter(0),
-    myMouseEnabled(false)
-{
-  // This code in ::read() is set up to always return IOPortA values in
-  // the lower 4 bits data value
-  // As such, the jack type (left or right) isn't necessary here
-
-  myTrakBallCountH = myTrakBallCountV = 0;
-  myTrakBallLinesH = myTrakBallLinesV = 1;
-
-  myTrakBallLeft = myTrakBallDown = myScanCountV = myScanCountH =
-    myCountV = myCountH = 0;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-uInt8 AmigaMouse::read()
-{
-  int scanline = mySystem.tia().scanlines();
-
-  if(myScanCountV > scanline) myScanCountV = 0;
-  if(myScanCountH > scanline) myScanCountH = 0;
-  while((myScanCountV + myTrakBallLinesV) < scanline)
-  {
-    if(myTrakBallCountV)
-    {
-      if(myTrakBallDown) myCountV--;
-      else               myCountV++;
-      myTrakBallCountV--;
-    }
-    myScanCountV += myTrakBallLinesV;
-  }
-
-  while((myScanCountH + myTrakBallLinesH) < scanline)
-  {
-    if(myTrakBallCountH)
-    {
-      if(myTrakBallLeft) myCountH--;
-      else               myCountH++;
-      myTrakBallCountH--;
-    }
-    myScanCountH += myTrakBallLinesH;
-  }
-
-  myCountV &= 0x03;
-  myCountH &= 0x03;
-
-  static constexpr uInt32 ourTableH[4] = { 0x00, 0x10, 0x50, 0x40 };
-  static constexpr uInt32 ourTableV[4] = { 0x00, 0x80, 0xa0, 0x20 };
-  uInt8 IOPortA = ourTableV[myCountV] | ourTableH[myCountH];
-
-  myDigitalPinState[One]   = IOPortA & 0x10;
-  myDigitalPinState[Two]   = IOPortA & 0x20;
-  myDigitalPinState[Three] = IOPortA & 0x40;
-  myDigitalPinState[Four]  = IOPortA & 0x80;
-
-  return (IOPortA >> 4);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-void AmigaMouse::update()
-{
-  if(!myMouseEnabled)
-    return;
-
-  // Get the current mouse position
-  myHCounter = myEvent.get(Event::MouseAxisXValue);
-  myVCounter = myEvent.get(Event::MouseAxisYValue);
-
-  if(myVCounter < 0) myTrakBallLeft = 1;
-  else               myTrakBallLeft = 0;
-  if(myHCounter < 0) myTrakBallDown = 0;
-  else               myTrakBallDown = 1;
-  myTrakBallCountH = abs(myVCounter >> 1);
-  myTrakBallCountV = abs(myHCounter >> 1);
-  myTrakBallLinesH = mySystem.tia().height() / (myTrakBallCountH + 1);
-  if(myTrakBallLinesH == 0) myTrakBallLinesH = 1;
-  myTrakBallLinesV = mySystem.tia().height() / (myTrakBallCountV + 1);
-  if(myTrakBallLinesV == 0) myTrakBallLinesV = 1;
-
-  // Get mouse button state
-  myDigitalPinState[Six] = (myEvent.get(Event::MouseButtonLeftValue) == 0) &&
-                           (myEvent.get(Event::MouseButtonRightValue) == 0);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-bool AmigaMouse::setMouseControl(
-    Controller::Type xtype, int xid, Controller::Type ytype, int yid)
-{
-  // Currently, the various trakball controllers take full control of the
-  // mouse, and use both mouse buttons for the single fire button
-  // As well, there's no separate setting for x and y axis, so any
-  // combination of Controller and id is valid
-  myMouseEnabled = (xtype == myType || ytype == myType) &&
-                   (xid != -1 || yid != -1);
-  return true;
-}
diff --git a/src/emucore/AmigaMouse.hxx b/src/emucore/AmigaMouse.hxx
index 872b95d..7ed9d1a 100644
--- a/src/emucore/AmigaMouse.hxx
+++ b/src/emucore/AmigaMouse.hxx
@@ -18,17 +18,9 @@
 #ifndef AMIGAMOUSE_HXX
 #define AMIGAMOUSE_HXX
 
-#include "bspf.hxx"
-#include "Control.hxx"
-#include "Event.hxx"
+#include "PointingDevice.hxx"
 
-/**
-  Trakball-like controller emulating the original Amiga mouse.
-  This code was heavily borrowed from z26.
-
-  @author  Stephen Anthony & z26 team
-*/
-class AmigaMouse : public Controller
+class AmigaMouse : public PointingDevice
 {
   public:
     /**
@@ -38,73 +30,21 @@ class AmigaMouse : public Controller
       @param event  The event object to use for events
       @param system The system using this controller
     */
-    AmigaMouse(Jack jack, const Event& event, const System& system);
+    AmigaMouse(Jack jack, const Event& event, const System& system)
+      : PointingDevice(jack, event, system, Controller::AmigaMouse,
+        trackballSensitivity) { }
     virtual ~AmigaMouse() = default;
 
-  public:
-    using Controller::read;
-
-    /**
-      Read the entire state of all digital pins for this controller.
-      Note that this method must use the lower 4 bits, and zero the upper bits.
-
-      @return The state of all digital pins
-    */
-    uInt8 read() override;
-
-    /**
-      Update the entire digital and analog pin state according to the
-      events currently set.
-    */
-    void update() override;
-
-    /**
-      Determines how this controller will treat values received from the
-      X/Y axis and left/right buttons of the mouse.  Since not all controllers
-      use the mouse the same way (or at all), it's up to the specific class to
-      decide how to use this data.
-
-      In the current implementation, the left button is tied to the X axis,
-      and the right one tied to the Y axis.
-
-      @param xtype  The controller to use for x-axis data
-      @param xid    The controller ID to use for x-axis data (-1 for no id)
-      @param ytype  The controller to use for y-axis data
-      @param yid    The controller ID to use for y-axis data (-1 for no id)
-
-      @return  Whether the controller supports using the mouse
-    */
-    bool setMouseControl(Controller::Type xtype, int xid,
-                         Controller::Type ytype, int yid) override;
-
-  private:
-    // Counter to iterate through the gray codes
-    int myHCounter, myVCounter;
-
-    // How many new horizontal and vertical values this frame
-    int myTrakBallCountH, myTrakBallCountV;
-
-    // How many lines to wait before sending new horz and vert val
-    int myTrakBallLinesH, myTrakBallLinesV;
-
-    // Was TrakBall moved left or moved right instead
-    int myTrakBallLeft;
-
-    // Was TrakBall moved down or moved up instead
-    int myTrakBallDown;
-
-    int myScanCountH, myScanCountV, myCountH, myCountV;
+  protected:
+    uInt8 ioPortA(uInt8 countH, uInt8 countV, uInt8, uInt8) override
+    {
+      static constexpr uInt32 ourTableH[4] = { 0x00, 0x80, 0xa0, 0x20 };
+      static constexpr uInt32 ourTableV[4] = { 0x00, 0x10, 0x50, 0x40 };
 
-    // Whether to use the mouse to emulate this controller
-    int myMouseEnabled;
+      return ourTableH[countH] | ourTableV[countV];
+    }
 
-  private:
-    // Following constructors and assignment operators not supported
-    AmigaMouse() = delete;
-    AmigaMouse(const AmigaMouse&) = delete;
-    AmigaMouse(AmigaMouse&&) = delete;
-    AmigaMouse& operator=(const AmigaMouse&) = delete;
-    AmigaMouse& operator=(AmigaMouse&&) = delete;
+    static constexpr float trackballSensitivity = 0.8f;
 };
 
-#endif
+#endif // AMIGAMOUSE_HXX
diff --git a/src/emucore/AtariMouse.cxx b/src/emucore/AtariMouse.cxx
deleted file mode 100644
index f495a7b..0000000
--- a/src/emucore/AtariMouse.cxx
+++ /dev/null
@@ -1,122 +0,0 @@
-//============================================================================
-//
-//   SSSS    tt          lll  lll
-//  SS  SS   tt           ll   ll
-//  SS     tttttt  eeee   ll   ll   aaaa
-//   SSSS    tt   ee  ee  ll   ll      aa
-//      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
-//  SS  SS   tt   ee      ll   ll  aa  aa
-//   SSSS     ttt  eeeee llll llll  aaaaa
-//
-// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
-// and the Stella Team
-//
-// See the file "License.txt" for information on usage and redistribution of
-// this file, and for a DISCLAIMER OF ALL WARRANTIES.
-//============================================================================
-
-#include "Event.hxx"
-#include "System.hxx"
-#include "TIA.hxx"
-#include "AtariMouse.hxx"
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-AtariMouse::AtariMouse(Jack jack, const Event& event, const System& system)
-  : Controller(jack, event, system, Controller::AtariMouse),
-    myHCounter(0),
-    myVCounter(0),
-    myMouseEnabled(false)
-{
-  // This code in ::read() is set up to always return IOPortA values in
-  // the lower 4 bits data value
-  // As such, the jack type (left or right) isn't necessary here
-
-  myTrakBallCountH = myTrakBallCountV = 0;
-  myTrakBallLinesH = myTrakBallLinesV = 1;
-
-  myTrakBallLeft = myTrakBallDown = myScanCountV = myScanCountH =
-    myCountV = myCountH = 0;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-uInt8 AtariMouse::read()
-{
-  int scanline = mySystem.tia().scanlines();
-
-  if(myScanCountV > scanline) myScanCountV = 0;
-  if(myScanCountH > scanline) myScanCountH = 0;
-  while((myScanCountV + myTrakBallLinesV) < scanline)
-  {
-    if(myTrakBallCountV)
-    {
-      if(myTrakBallDown) myCountV--;
-      else               myCountV++;
-      myTrakBallCountV--;
-    }
-    myScanCountV += myTrakBallLinesV;
-  }
-
-  while((myScanCountH + myTrakBallLinesH) < scanline)
-  {
-    if(myTrakBallCountH)
-    {
-      if(myTrakBallLeft) myCountH--;
-      else               myCountH++;
-      myTrakBallCountH--;
-    }
-    myScanCountH += myTrakBallLinesH;
-  }
-
-  myCountV &= 0x03;
-  myCountH &= 0x03;
-
-  static constexpr uInt32 ourTableH[4] = { 0x00, 0x80, 0xc0, 0x40 };
-  static constexpr uInt32 ourTableV[4] = { 0x00, 0x10, 0x30, 0x20 };
-  uInt8 IOPortA = ourTableV[myCountV] | ourTableH[myCountH];
-
-  myDigitalPinState[One]   = IOPortA & 0x10;
-  myDigitalPinState[Two]   = IOPortA & 0x20;
-  myDigitalPinState[Three] = IOPortA & 0x40;
-  myDigitalPinState[Four]  = IOPortA & 0x80;
-
-  return (IOPortA >> 4);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-void AtariMouse::update()
-{
-  if(!myMouseEnabled)
-    return;
-
-  // Get the current mouse position
-  myHCounter = myEvent.get(Event::MouseAxisXValue);
-  myVCounter = myEvent.get(Event::MouseAxisYValue);
-
-  if(myVCounter < 0) myTrakBallLeft = 1;
-  else               myTrakBallLeft = 0;
-  if(myHCounter < 0) myTrakBallDown = 0;
-  else               myTrakBallDown = 1;
-  myTrakBallCountH = abs(myVCounter >> 1);
-  myTrakBallCountV = abs(myHCounter >> 1);
-  myTrakBallLinesH = mySystem.tia().height() / (myTrakBallCountH + 1);
-  if(myTrakBallLinesH == 0) myTrakBallLinesH = 1;
-  myTrakBallLinesV = mySystem.tia().height() / (myTrakBallCountV + 1);
-  if(myTrakBallLinesV == 0) myTrakBallLinesV = 1;
-
-  // Get mouse button state
-  myDigitalPinState[Six] = (myEvent.get(Event::MouseButtonLeftValue) == 0) &&
-                           (myEvent.get(Event::MouseButtonRightValue) == 0);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-bool AtariMouse::setMouseControl(
-    Controller::Type xtype, int xid, Controller::Type ytype, int yid)
-{
-  // Currently, the various trakball controllers take full control of the
-  // mouse, and use both mouse buttons for the single fire button
-  // As well, there's no separate setting for x and y axis, so any
-  // combination of Controller and id is valid
-  myMouseEnabled = (xtype == myType || ytype == myType) &&
-                   (xid != -1 || yid != -1);
-  return true;
-}
diff --git a/src/emucore/AtariMouse.hxx b/src/emucore/AtariMouse.hxx
index 46637d9..255ecbb 100644
--- a/src/emucore/AtariMouse.hxx
+++ b/src/emucore/AtariMouse.hxx
@@ -18,93 +18,33 @@
 #ifndef ATARIMOUSE_HXX
 #define ATARIMOUSE_HXX
 
-#include "bspf.hxx"
-#include "Control.hxx"
-#include "Event.hxx"
+#include "PointingDevice.hxx"
 
-/**
-  Trakball-like controller emulating the Atari ST mouse.
-  This code was heavily borrowed from z26.
-
-  @author  Stephen Anthony & z26 team
-*/
-class AtariMouse : public Controller
+class AtariMouse : public PointingDevice
 {
   public:
     /**
-      Create a new AtariMouse controller plugged into the specified jack
+      Create a new Atari Mouse controller plugged into the specified jack
 
       @param jack   The jack the controller is plugged into
       @param event  The event object to use for events
       @param system The system using this controller
     */
-    AtariMouse(Jack jack, const Event& event, const System& system);
+    AtariMouse(Jack jack, const Event& event, const System& system)
+      : PointingDevice(jack, event, system, Controller::AtariMouse,
+        trackballSensitivity) { }
     virtual ~AtariMouse() = default;
 
-  public:
-    using Controller::read;
-
-    /**
-      Read the entire state of all digital pins for this controller.
-      Note that this method must use the lower 4 bits, and zero the upper bits.
-
-      @return The state of all digital pins
-    */
-    uInt8 read() override;
-
-    /**
-      Update the entire digital and analog pin state according to the
-      events currently set.
-    */
-    void update() override;
-
-    /**
-      Determines how this controller will treat values received from the
-      X/Y axis and left/right buttons of the mouse.  Since not all controllers
-      use the mouse the same way (or at all), it's up to the specific class to
-      decide how to use this data.
-
-      In the current implementation, the left button is tied to the X axis,
-      and the right one tied to the Y axis.
-
-      @param xtype  The controller to use for x-axis data
-      @param xid    The controller ID to use for x-axis data (-1 for no id)
-      @param ytype  The controller to use for y-axis data
-      @param yid    The controller ID to use for y-axis data (-1 for no id)
-
-      @return  Whether the controller supports using the mouse
-    */
-    bool setMouseControl(Controller::Type xtype, int xid,
-                         Controller::Type ytype, int yid) override;
-
-  private:
-    // Counter to iterate through the gray codes
-    int myHCounter, myVCounter;
-
-    // How many new horizontal and vertical values this frame
-    int myTrakBallCountH, myTrakBallCountV;
-
-    // How many lines to wait before sending new horz and vert val
-    int myTrakBallLinesH, myTrakBallLinesV;
-
-    // Was TrakBall moved left or moved right instead
-    int myTrakBallLeft;
-
-    // Was TrakBall moved down or moved up instead
-    int myTrakBallDown;
-
-    int myScanCountH, myScanCountV, myCountH, myCountV;
+  protected:
+    uInt8 ioPortA(uInt8 countH, uInt8 countV, uInt8, uInt8) override
+    {
+      static constexpr uInt32 ourTableH[4] = { 0x00, 0x10, 0x30, 0x20 };
+      static constexpr uInt32 ourTableV[4] = { 0x00, 0x80, 0xc0, 0x40 };
 
-    // Whether to use the mouse to emulate this controller
-    int myMouseEnabled;
+      return ourTableH[countH] | ourTableV[countV];
+    }
 
-  private:
-    // Following constructors and assignment operators not supported
-    AtariMouse() = delete;
-    AtariMouse(const AtariMouse&) = delete;
-    AtariMouse(AtariMouse&&) = delete;
-    AtariMouse& operator=(const AtariMouse&) = delete;
-    AtariMouse& operator=(AtariMouse&&) = delete;
+    static constexpr float trackballSensitivity = 0.8f;
 };
 
-#endif
+#endif // ATARIMOUSE_HXX
diff --git a/src/emucore/CartBUS.cxx b/src/emucore/CartBUS.cxx
index 2769ada..56da91f 100644
--- a/src/emucore/CartBUS.cxx
+++ b/src/emucore/CartBUS.cxx
@@ -74,11 +74,7 @@ CartridgeBUS::CartridgeBUS(const BytePtr& image, uInt32 size,
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void CartridgeBUS::reset()
 {
-  // Initialize RAM
-  if(mySettings.getBool("ramrandom"))
-    initializeRAM(myBUSRAM+2048, 8192-2048);
-  else
-    memset(myBUSRAM+2048, 0, 8192-2048);
+  initializeRAM(myBUSRAM+2048, 8192-2048);
 
   // Update cycles to the current system cycles
   mySystemCycles = mySystem->cycles();
diff --git a/src/emucore/CartCDF.cxx b/src/emucore/CartCDF.cxx
index dbd5d28..f0eec20 100644
--- a/src/emucore/CartCDF.cxx
+++ b/src/emucore/CartCDF.cxx
@@ -27,9 +27,10 @@
 #include "TIA.hxx"
 
 // Location of data within the RAM copy of the CDF Driver.
-#define DSxPTR        0x06E0
-#define DSxINC        0x0768
-#define WAVEFORM      0x07F0
+//  Version                   0       1
+const uInt16 DSxPTR[]   = {0x06E0, 0x00A0};
+const uInt16 DSxINC[]   = {0x0768, 0x0128};
+const uInt16 WAVEFORM[] = {0x07F0, 0x01B0};
 #define DSRAM         0x0800
 
 #define COMMSTREAM    0x20
@@ -62,10 +63,15 @@ CartridgeCDF::CartridgeCDF(const BytePtr& image, uInt32 size,
 
   // Pointer to the display RAM
   myDisplayImage = myCDFRAM + DSRAM;
+
+  setVersion();
+
 #ifdef THUMB_SUPPORT
   // Create Thumbulator ARM emulator
-  myThumbEmulator = make_unique<Thumbulator>((uInt16*)myImage, (uInt16*)myCDFRAM,
-      settings.getBool("thumb.trapfatal"), Thumbulator::ConfigureFor::CDF, this);
+  myThumbEmulator = make_unique<Thumbulator>(
+      (uInt16*)myImage, (uInt16*)myCDFRAM, settings.getBool("thumb.trapfatal"),
+      myVersion ? Thumbulator::ConfigureFor::CDF1 : Thumbulator::ConfigureFor::CDF,
+      this);
 #endif
   setInitialState();
 }
@@ -73,11 +79,7 @@ CartridgeCDF::CartridgeCDF(const BytePtr& image, uInt32 size,
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void CartridgeCDF::reset()
 {
-  // Initialize RAM
-  if(mySettings.getBool("ramrandom"))
-    initializeRAM(myCDFRAM+2048, 8192-2048);
-  else
-    memset(myCDFRAM+2048, 0, 8192-2048);
+  initializeRAM(myCDFRAM+2048, 8192-2048);
 
   // Update cycles to the current system cycles
   myAudioCycles = mySystem->cycles();
@@ -580,51 +582,57 @@ bool CartridgeCDF::load(Serializer& in)
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 uInt32 CartridgeCDF::getDatastreamPointer(uInt8 index) const
 {
-  //  index &= 0x0f;
+  uInt16 address = DSxPTR[myVersion] + index * 4;
 
-  return myCDFRAM[DSxPTR + index*4 + 0]        +  // low byte
-        (myCDFRAM[DSxPTR + index*4 + 1] << 8)  +
-        (myCDFRAM[DSxPTR + index*4 + 2] << 16) +
-        (myCDFRAM[DSxPTR + index*4 + 3] << 24) ;  // high byte
+  return myCDFRAM[address + 0]        +  // low byte
+        (myCDFRAM[address + 1] << 8)  +
+        (myCDFRAM[address + 2] << 16) +
+        (myCDFRAM[address + 3] << 24) ;  // high byte
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void CartridgeCDF::setDatastreamPointer(uInt8 index, uInt32 value)
 {
-  //  index &= 0x1f;
-  myCDFRAM[DSxPTR + index*4 + 0] = value & 0xff;          // low byte
-  myCDFRAM[DSxPTR + index*4 + 1] = (value >> 8) & 0xff;
-  myCDFRAM[DSxPTR + index*4 + 2] = (value >> 16) & 0xff;
-  myCDFRAM[DSxPTR + index*4 + 3] = (value >> 24) & 0xff;  // high byte
+  uInt16 address = DSxPTR[myVersion] + index * 4;
+
+  myCDFRAM[address + 0] = value & 0xff;          // low byte
+  myCDFRAM[address + 1] = (value >> 8) & 0xff;
+  myCDFRAM[address + 2] = (value >> 16) & 0xff;
+  myCDFRAM[address + 3] = (value >> 24) & 0xff;  // high byte
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 uInt32 CartridgeCDF::getDatastreamIncrement(uInt8 index) const
 {
-  return myCDFRAM[DSxINC + index*4 + 0]        +   // low byte
-        (myCDFRAM[DSxINC + index*4 + 1] << 8)  +
-        (myCDFRAM[DSxINC + index*4 + 2] << 16) +
-        (myCDFRAM[DSxINC + index*4 + 3] << 24) ;   // high byte
+  uInt16 address = DSxINC[myVersion] + index * 4;
+
+  return myCDFRAM[address + 0]        +   // low byte
+        (myCDFRAM[address + 1] << 8)  +
+        (myCDFRAM[address + 2] << 16) +
+        (myCDFRAM[address + 3] << 24) ;   // high byte
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void CartridgeCDF::setDatastreamIncrement(uInt8 index, uInt32 value)
 {
-  myCDFRAM[DSxINC + index*4 + 0] = value & 0xff;          // low byte
-  myCDFRAM[DSxINC + index*4 + 1] = (value >> 8) & 0xff;
-  myCDFRAM[DSxINC + index*4 + 2] = (value >> 16) & 0xff;
-  myCDFRAM[DSxINC + index*4 + 3] = (value >> 24) & 0xff;  // high byte
+  uInt16 address = DSxINC[myVersion] + index * 4;
+
+  myCDFRAM[address + 0] = value & 0xff;          // low byte
+  myCDFRAM[address + 1] = (value >> 8) & 0xff;
+  myCDFRAM[address + 2] = (value >> 16) & 0xff;
+  myCDFRAM[address + 3] = (value >> 24) & 0xff;  // high byte
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 uInt32 CartridgeCDF::getWaveform(uInt8 index) const
 {
   uInt32 result;
+  uInt16 address = WAVEFORM[myVersion] + index * 4;
 
-  result = myCDFRAM[WAVEFORM + index*4 + 0]        +  // low byte
-          (myCDFRAM[WAVEFORM + index*4 + 1] << 8)  +
-          (myCDFRAM[WAVEFORM + index*4 + 2] << 16) +
-          (myCDFRAM[WAVEFORM + index*4 + 3] << 24);   // high byte
+  result = myCDFRAM[address + 0]        +  // low byte
+          (myCDFRAM[address + 1] << 8)  +
+          (myCDFRAM[address + 2] << 16) +
+          (myCDFRAM[address + 3] << 24);   // high byte
 
   result -= (0x40000000 + DSRAM);
 
@@ -638,11 +646,12 @@ uInt32 CartridgeCDF::getWaveform(uInt8 index) const
 uInt32 CartridgeCDF::getSample()
 {
   uInt32 result;
+  uInt16 address = WAVEFORM[myVersion];
 
-  result = myCDFRAM[WAVEFORM + 0]        +  // low byte
-          (myCDFRAM[WAVEFORM + 1] << 8)  +
-          (myCDFRAM[WAVEFORM + 2] << 16) +
-          (myCDFRAM[WAVEFORM + 3] << 24);   // high byte
+  result = myCDFRAM[address + 0]        +  // low byte
+          (myCDFRAM[address + 1] << 8)  +
+          (myCDFRAM[address + 2] << 16) +
+          (myCDFRAM[address + 3] << 24);   // high byte
 
   return result;
 }
@@ -673,3 +682,21 @@ uInt8 CartridgeCDF::readFromDatastream(uInt8 index)
   setDatastreamPointer(index, pointer);
   return value;
 }
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void CartridgeCDF::setVersion()
+{
+  myVersion = 0;
+
+  for(uInt32 i = 0; i < 2048; i += 4)
+  {
+    // CDF signature occurs 3 times in a row, i+3 (+7 or +11) is version
+    if (    myImage[i+0] == 0x43 && myImage[i + 4] == 0x43 && myImage[i + 8] == 0x43) // C
+      if (  myImage[i+1] == 0x44 && myImage[i + 5] == 0x44 && myImage[i + 9] == 0x44) // D
+        if (myImage[i+2] == 0x46 && myImage[i + 6] == 0x46 && myImage[i +10] == 0x46) // F
+        {
+          myVersion = myImage[i+3];
+          break;
+        }
+  }
+}
diff --git a/src/emucore/CartCDF.hxx b/src/emucore/CartCDF.hxx
index 0e08b2e..f5ce5cb 100644
--- a/src/emucore/CartCDF.hxx
+++ b/src/emucore/CartCDF.hxx
@@ -36,7 +36,7 @@ class System;
 
 
   There are seven 4K program banks, a 4K Display Data RAM,
-  1K C Varaible and Stack, and the CDF chip.
+  1K C Variable and Stack, and the CDF chip.
   CDF chip access is mapped to $1000 - $103F.
 
   @authors: Darrell Spice Jr, Chris Walton, Fred Quimby,
@@ -209,6 +209,7 @@ class CartridgeCDF : public Cartridge
     uInt32 getWaveform(uInt8 index) const;
     uInt32 getWaveformSize(uInt8 index) const;
     uInt32 getSample();
+    void setVersion();
 
   private:
     // The 32K ROM image of the cartridge
@@ -250,16 +251,16 @@ class CartridgeCDF : public Cartridge
     // Thumbulator will trap these calls and pass the appropriate information to
     // the Cartridge Class via callFunction() so it can emulate the 32 bit audio routines.
 
-  /* Register usage for audio:
-   r8  = channel0 accumulator
-   r9  = channel1 accumulator
-   r10 = channel2 accumulator
-   r11 = channel0 frequency
-   r12 = channel1 frequency
-   r13 = channel2 frequency
-   r14 = timer base */
+    /* Register usage for audio:
+      r8  = channel0 accumulator
+      r9  = channel1 accumulator
+      r10 = channel2 accumulator
+      r11 = channel0 frequency
+      r12 = channel1 frequency
+      r13 = channel2 frequency
+      r14 = timer base  */
 
-  // The music counters, ARM FIQ shadow registers r8, r9, r10
+    // The music counters, ARM FIQ shadow registers r8, r9, r10
     uInt32 myMusicCounters[3];
 
     // The music frequency, ARM FIQ shadow registers r11, r12, r13
@@ -289,6 +290,9 @@ class CartridgeCDF : public Cartridge
 
     uInt8 myFastJumpActive;
 
+    // version of CDF
+    uInt16 myVersion;
+
   private:
     // Following constructors and assignment operators not supported
     CartridgeCDF() = delete;
diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx
index d3e964e..de4baaf 100644
--- a/src/emucore/Console.cxx
+++ b/src/emucore/Console.cxx
@@ -183,18 +183,13 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
     myConsoleTiming = ConsoleTiming::secam;
   }
 
-  // Bumper Bash and Decathlon always require all 4 directions
+  // Bumper Bash always require all 4 directions
   // Other ROMs can use it if the setting is enabled
   // Hopefully this list should stay short
   // If it starts to get too long, we should add a ROM properties entry
   bool joyallow4 = md5 == "aa1c41f86ec44c0a44eb64c332ce08af" || // Bumper Bash
                    md5 == "16ee443c990215f61f7dd1e55a0d2256" || // Bumper Bash (PAL)
                    md5 == "1bf503c724001b09be79c515ecfcbd03" || // Bumper Bash (Unknown)
-                   md5 == "ac7c2260378975614192ca2bc3d20e0b" || // Decathlon
-                   md5 == "883258dcd68cefc6cd4d40b1185116dc" || // Decathlon (PAL)
-                   md5 == "ede4ab11ca346bd023b2c21d941e0c50" || // Decathlon (SECAM)
-                   md5 == "525f2dfc8b21b0186cff2568e0509bfc" || // Decathlon [fixed]
-                   md5 == "bf52327c2197d9d2c4544be053caded1" || // Decathlon (HES)
                    myOSystem.settings().getBool("joyallow4");
   myOSystem.eventHandler().allowAllDirections(joyallow4);
 
@@ -429,32 +424,26 @@ void Console::setPalette(const string& type)
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void Console::togglePhosphor()
 {
-  const string& phosphor = myProperties.get(Display_Phosphor);
-  int blend = atoi(myProperties.get(Display_PPBlend).c_str());
-  bool enable;
-  if(phosphor == "YES")
+  if(myOSystem.frameBuffer().tiaSurface().phosphorEnabled())
   {
     myProperties.set(Display_Phosphor, "No");
-    enable = false;
+    myOSystem.frameBuffer().tiaSurface().enablePhosphor(false);
     myOSystem.frameBuffer().showMessage("Phosphor effect disabled");
   }
   else
   {
     myProperties.set(Display_Phosphor, "Yes");
-    enable = true;
+    myOSystem.frameBuffer().tiaSurface().enablePhosphor(true);
     myOSystem.frameBuffer().showMessage("Phosphor effect enabled");
   }
-
-  myOSystem.frameBuffer().tiaSurface().enablePhosphor(enable, blend);
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void Console::changePhosphor(int direction)
 {
-  bool enable = myProperties.get(Display_Phosphor) == "YES";
   int blend = atoi(myProperties.get(Display_PPBlend).c_str());
 
-  if(enable)
+  if(myOSystem.frameBuffer().tiaSurface().phosphorEnabled())
   {
     if(direction == +1)       // increase blend
     {
@@ -483,7 +472,7 @@ void Console::changePhosphor(int direction)
     val << blend;
     myProperties.set(Display_PPBlend, val.str());
     myOSystem.frameBuffer().showMessage("Phosphor blend " + val.str());
-    myOSystem.frameBuffer().tiaSurface().enablePhosphor(enable, blend);
+    myOSystem.frameBuffer().tiaSurface().enablePhosphor(true, blend);
   }
   else
     myOSystem.frameBuffer().showMessage("Phosphor effect disabled");
diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx
index e41f326..cb043aa 100644
--- a/src/emucore/EventHandler.cxx
+++ b/src/emucore/EventHandler.cxx
@@ -32,6 +32,7 @@
 #include "OSystem.hxx"
 #include "Joystick.hxx"
 #include "Paddles.hxx"
+#include "PointingDevice.hxx"
 #include "PropsSet.hxx"
 #include "ListWidget.hxx"
 #include "ScrollBarWidget.hxx"
@@ -62,6 +63,7 @@ EventHandler::EventHandler(OSystem& osystem)
     myFryingFlag(false),
     myUseCtrlKeyFlag(true),
     mySkipMouseMotion(true),
+    myAltKeyCounter(0),
     myContSnapshotInterval(0),
     myContSnapshotCounter(0)
 {
@@ -94,6 +96,7 @@ void EventHandler::initialize()
   Joystick::setDeadZone(myOSystem.settings().getInt("joydeadzone"));
   Paddles::setDigitalSensitivity(myOSystem.settings().getInt("dsense"));
   Paddles::setMouseSensitivity(myOSystem.settings().getInt("msense"));
+  PointingDevice::setSensitivity(myOSystem.settings().getInt("tsense"));
 
   // Set quick select delay when typing characters in listwidgets
   ListWidget::setQuickSelectDelay(myOSystem.settings().getInt("listdelay"));
@@ -246,6 +249,16 @@ void EventHandler::handleTextEvent(char text)
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 void EventHandler::handleKeyEvent(StellaKey key, StellaMod mod, bool state)
 {
+  // Swallow KBDK_TAB under certain conditions
+  // See commments on 'myAltKeyCounter' for more information
+#ifdef BSPF_UNIX
+  if(myAltKeyCounter > 1 && key == KBDK_TAB)
+  {
+    myAltKeyCounter = false;
+    return;
+  }
+#endif
+
   bool handled = true;
 
   // Immediately store the key state
@@ -263,7 +276,13 @@ void EventHandler::handleKeyEvent(StellaKey key, StellaMod mod, bool state)
     }
     else
 #endif
-    if(key == KBDK_RETURN)
+    if(key == KBDK_TAB)
+    {
+      // Swallow Alt-Tab, but remember that it happened
+      myAltKeyCounter = 1;
+      return;
+    }
+    else if(key == KBDK_RETURN)
     {
       myOSystem.frameBuffer().toggleFullscreen();
     }
@@ -823,11 +842,18 @@ void EventHandler::handleSystemEvent(SystemEvent e, int, int)
   switch(e)
   {
     case EVENT_WINDOW_EXPOSED:
-        myOSystem.frameBuffer().update();
-        break;
+      myOSystem.frameBuffer().update();
+      break;
+
+    case EVENT_WINDOW_FOCUS_GAINED:
+      // Used to handle Alt-x key combos; sometimes the key associated with
+      // Alt gets 'stuck'  and is passed to the core for processing
+      if(myAltKeyCounter > 0)
+        myAltKeyCounter = 2;
+      break;
 #if 0
     case EVENT_WINDOW_MINIMIZED:
-        if(myState == S_EMULATE) enterMenuMode(S_MENU);
+      if(myState == S_EMULATE) enterMenuMode(S_MENU);
         break;
 #endif
     default:  // handle other events as testing requires
diff --git a/src/emucore/EventHandler.hxx b/src/emucore/EventHandler.hxx
index d6dc58b..74dfb69 100644
--- a/src/emucore/EventHandler.hxx
+++ b/src/emucore/EventHandler.hxx
@@ -583,6 +583,19 @@ class EventHandler
     // state change; we detect when this happens and discard the event
     bool mySkipMouseMotion;
 
+    // Sometimes key combos with the Alt key become 'stuck' after the
+    // window changes state, and we want to ignore that event
+    // For example, press Alt-Tab and then upon re-entering the window,
+    // the app receives 'tab'; obviously the 'tab' shouldn't be happening
+    // So we keep track of the cases that matter (for now, Alt-Tab)
+    // and swallow the event afterwards
+    // Basically, the initial event sets the variable to 1, and upon
+    // returning to the app (ie, receiving EVENT_WINDOW_FOCUS_GAINED),
+    // the count is updated to 2, but only if it was already updated to 1
+    // TODO - This may be a bug in SDL, and might be removed in the future
+    //        It only seems to be an issue in Linux
+    uInt8 myAltKeyCounter;
+
     // Used for continuous snapshot mode
     uInt32 myContSnapshotInterval;
     uInt32 myContSnapshotCounter;
diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx
index 20bacfa..7f35b85 100644
--- a/src/emucore/M6502.cxx
+++ b/src/emucore/M6502.cxx
@@ -414,7 +414,7 @@ void M6502::attach(Debugger& debugger)
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 uInt32 M6502::addCondBreak(Expression* e, const string& name)
 {
-  myBreakConds.emplace_back(unique_ptr<Expression>(e));
+  myBreakConds.emplace_back(e);
   myBreakCondNames.push_back(name);
   return uInt32(myBreakConds.size() - 1);
 }
diff --git a/src/emucore/Paddles.hxx b/src/emucore/Paddles.hxx
index 3d12638..0f7c977 100644
--- a/src/emucore/Paddles.hxx
+++ b/src/emucore/Paddles.hxx
@@ -127,12 +127,12 @@ class Paddles : public Controller
 
     // Range of values over which digital and mouse movement is scaled
     // to paddle resistance
-    static const int TRIGMIN = 1;
-    static const int TRIGMAX = 4096;
+    static constexpr int TRIGMIN = 1;
+    static constexpr int TRIGMAX = 4096;
     static int TRIGRANGE;  // This one is variable for the upper range
 
-    static const int MAX_DIGITAL_SENSE = 20;
-    static const int MAX_MOUSE_SENSE = 20;
+    static constexpr int MAX_DIGITAL_SENSE = 20;
+    static constexpr int MAX_MOUSE_SENSE = 20;
     static int DIGITAL_SENSITIVITY, DIGITAL_DISTANCE;
     static int MOUSE_SENSITIVITY;
 
diff --git a/src/emucore/PointingDevice.cxx b/src/emucore/PointingDevice.cxx
new file mode 100644
index 0000000..be7c6fa
--- /dev/null
+++ b/src/emucore/PointingDevice.cxx
@@ -0,0 +1,156 @@
+//============================================================================
+//
+//   SSSS    tt          lll  lll
+//  SS  SS   tt           ll   ll
+//  SS     tttttt  eeee   ll   ll   aaaa
+//   SSSS    tt   ee  ee  ll   ll      aa
+//      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
+//  SS  SS   tt   ee      ll   ll  aa  aa
+//   SSSS     ttt  eeeee llll llll  aaaaa
+//
+// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
+// and the Stella Team
+//
+// See the file "License.txt" for information on usage and redistribution of
+// this file, and for a DISCLAIMER OF ALL WARRANTIES.
+//============================================================================
+
+#include "Control.hxx"
+#include "Event.hxx"
+#include "System.hxx"
+#include "TIA.hxx"
+
+#include "PointingDevice.hxx"
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+PointingDevice::PointingDevice(Jack jack, const Event& event,
+                               const System& system, Controller::Type type,
+                               float sensitivity)
+  : Controller(jack, event, system, type),
+    mySensitivity(sensitivity),
+    myHCounterRemainder(0.0), myVCounterRemainder(0.0),
+    myTrackBallLinesH(1), myTrackBallLinesV(1),
+    myTrackBallLeft(false), myTrackBallDown(false),
+    myCountH(0), myCountV(0),
+    myScanCountH(0), myScanCountV(0),
+    myFirstScanOffsetH(0), myFirstScanOffsetV(0),
+    myMouseEnabled(false)
+{
+  // The code in ::read() is set up to always return IOPortA values in
+  // the lower 4 bits data value
+  // As such, the jack type (left or right) isn't necessary here
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+uInt8 PointingDevice::read()
+{
+  int scanline = mySystem.tia().scanlines();
+
+  // Loop over all missed changes
+  while(myScanCountH < scanline)
+  {
+    if(myTrackBallLeft) myCountH--;
+    else                myCountH++;
+
+    // Define scanline of next change
+    myScanCountH += myTrackBallLinesH;
+  }
+
+  // Loop over all missed changes
+  while(myScanCountV < scanline)
+  {
+    if(myTrackBallDown) myCountV--;
+    else                myCountV++;
+
+    // Define scanline of next change
+    myScanCountV += myTrackBallLinesV;
+  }
+
+  myCountH &= 0x03;
+  myCountV &= 0x03;
+
+  uInt8 portA = ioPortA(myCountH, myCountV, myTrackBallLeft, myTrackBallDown);
+
+  myDigitalPinState[One]   = portA & 0x10;
+  myDigitalPinState[Two]   = portA & 0x20;
+  myDigitalPinState[Three] = portA & 0x40;
+  myDigitalPinState[Four]  = portA & 0x80;
+
+  return (portA >> 4);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void PointingDevice::update()
+{
+  if(!myMouseEnabled)
+    return;
+
+  // Update horizontal direction
+  updateDirection( myEvent.get(Event::MouseAxisXValue), myHCounterRemainder,
+      myTrackBallLeft, myTrackBallLinesH, myScanCountH, myFirstScanOffsetH);
+
+  // Update vertical direction
+  updateDirection(-myEvent.get(Event::MouseAxisYValue), myVCounterRemainder,
+      myTrackBallDown, myTrackBallLinesV, myScanCountV, myFirstScanOffsetV);
+
+  // Get mouse button state
+  myDigitalPinState[Six] = (myEvent.get(Event::MouseButtonLeftValue)  == 0) &&
+                           (myEvent.get(Event::MouseButtonRightValue) == 0);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool PointingDevice::setMouseControl(
+    Controller::Type xtype, int xid, Controller::Type ytype, int yid)
+{
+  // Currently, the various trakball controllers take full control of the
+  // mouse, and use both mouse buttons for the single fire button
+  // As well, there's no separate setting for x and y axis, so any
+  // combination of Controller and id is valid
+  myMouseEnabled = (xtype == myType || ytype == myType) &&
+                   (xid != -1 || yid != -1);
+  return true;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void PointingDevice::setSensitivity(int sensitivity)
+{
+  BSPF::clamp(sensitivity, 1, 20, 10);
+  TB_SENSITIVITY = sensitivity / 10.0;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void PointingDevice::updateDirection(int counter, float& counterRemainder,
+    bool& trackBallDir, int& trackBallLines, int& scanCount, int& firstScanOffset)
+{
+  // Apply sensitivity and calculate remainder
+  float fTrackBallCount = counter * mySensitivity * TB_SENSITIVITY + counterRemainder;
+  int trackBallCount = int(std::lround(fTrackBallCount));
+  counterRemainder = fTrackBallCount - trackBallCount;
+
+  if(trackBallCount)
+  {
+    trackBallDir = (trackBallCount > 0);
+    trackBallCount = abs(trackBallCount);
+
+    // Calculate lines to wait between sending new horz/vert values
+    trackBallLines = mySystem.tia().scanlinesLastFrame() / trackBallCount;
+
+    // Set lower limit in case of (unrealistic) ultra fast mouse movements
+    if (trackBallLines == 0) trackBallLines = 1;
+
+    // Define scanline of first change
+    scanCount = (trackBallLines * firstScanOffset) >> 12;
+  }
+  else
+  {
+    // Prevent any change
+    scanCount = INT_MAX;
+
+    // Define offset factor for first change, move randomly forward by up to 1/8th
+    firstScanOffset = (((firstScanOffset << 3) + rand() %
+                      (1 << 12)) >> 3) & ((1 << 12) - 1);
+  }
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+float PointingDevice::TB_SENSITIVITY = 1.0;
diff --git a/src/emucore/AtariMouse.hxx b/src/emucore/PointingDevice.hxx
similarity index 51%
copy from src/emucore/AtariMouse.hxx
copy to src/emucore/PointingDevice.hxx
index 46637d9..6a6f588 100644
--- a/src/emucore/AtariMouse.hxx
+++ b/src/emucore/PointingDevice.hxx
@@ -15,31 +15,27 @@
 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
 //============================================================================
 
-#ifndef ATARIMOUSE_HXX
-#define ATARIMOUSE_HXX
+#ifndef POINTING_DEVICE_HXX
+#define POINTING_DEVICE_HXX
+
+class Controller;
+class Event;
 
 #include "bspf.hxx"
-#include "Control.hxx"
-#include "Event.hxx"
 
 /**
-  Trakball-like controller emulating the Atari ST mouse.
+  Common controller class for pointing devices (Atari Mouse, Amiga Mouse, Trak-Ball)
   This code was heavily borrowed from z26.
 
-  @author  Stephen Anthony & z26 team
+  @author  Stephen Anthony, Thomas Jentzsch & z26 team
 */
-class AtariMouse : public Controller
+class PointingDevice : public Controller
 {
   public:
-    /**
-      Create a new AtariMouse controller plugged into the specified jack
-
-      @param jack   The jack the controller is plugged into
-      @param event  The event object to use for events
-      @param system The system using this controller
-    */
-    AtariMouse(Jack jack, const Event& event, const System& system);
-    virtual ~AtariMouse() = default;
+    PointingDevice(Jack jack, const Event& event,
+                   const System& system, Controller::Type type,
+                   float sensitivity);
+    virtual ~PointingDevice() = default;
 
   public:
     using Controller::read;
@@ -77,34 +73,61 @@ class AtariMouse : public Controller
     bool setMouseControl(Controller::Type xtype, int xid,
                          Controller::Type ytype, int yid) override;
 
+    /**
+      Sets the sensitivity for analog emulation of trackball movement
+      using a mouse.
+
+      @param sensitivity  Value from 1 to 20, with larger values causing
+                          more movement (10 represents the baseline)
+    */
+    static void setSensitivity(int sensitivity);
+
+  protected:
+    // Each derived class must implement this, to determine how its
+    // IOPortA values are calculated
+    virtual uInt8 ioPortA(uInt8 countH, uInt8 countV, uInt8 left, uInt8 down) = 0;
+
   private:
-    // Counter to iterate through the gray codes
-    int myHCounter, myVCounter;
+    void updateDirection(int counter, float& counterRemainder,
+                         bool& trackBallDir, int& trackBallLines,
+                         int& scanCount, int& firstScanOffset);
+
+  private:
+    // Mouse input to sensitivity emulation
+    float mySensitivity, myHCounterRemainder, myVCounterRemainder;
 
-    // How many new horizontal and vertical values this frame
-    int myTrakBallCountH, myTrakBallCountV;
+    // How many lines to wait between sending new horz and vert values
+    int myTrackBallLinesH, myTrackBallLinesV;
 
-    // How many lines to wait before sending new horz and vert val
-    int myTrakBallLinesH, myTrakBallLinesV;
+    // Was TrackBall moved left or moved right instead
+    bool myTrackBallLeft;
 
-    // Was TrakBall moved left or moved right instead
-    int myTrakBallLeft;
+    // Was TrackBall moved down or moved up instead
+    bool myTrackBallDown;
 
-    // Was TrakBall moved down or moved up instead
-    int myTrakBallDown;
+    // Counter to iterate through the gray codes
+    uInt8 myCountH, myCountV;
+
+    // Next scanline for change
+    int myScanCountH, myScanCountV;
 
-    int myScanCountH, myScanCountV, myCountH, myCountV;
+    // Offset factor for first scanline, 0..(1 << 12 - 1)
+    int myFirstScanOffsetH, myFirstScanOffsetV;
 
     // Whether to use the mouse to emulate this controller
-    int myMouseEnabled;
+    bool myMouseEnabled;
 
-  private:
+    // User-defined sensitivity; adjustable since end-users may have different
+    // mouse speeds
+    static float TB_SENSITIVITY;
+
+private:
     // Following constructors and assignment operators not supported
-    AtariMouse() = delete;
-    AtariMouse(const AtariMouse&) = delete;
-    AtariMouse(AtariMouse&&) = delete;
-    AtariMouse& operator=(const AtariMouse&) = delete;
-    AtariMouse& operator=(AtariMouse&&) = delete;
+    PointingDevice() = delete;
+    PointingDevice(const PointingDevice&) = delete;
+    PointingDevice(PointingDevice&&) = delete;
+    PointingDevice& operator=(const PointingDevice&) = delete;
+    PointingDevice& operator=(PointingDevice&&) = delete;
 };
 
-#endif
+#endif // POINTING_DEVICE_HXX
diff --git a/src/emucore/Random.hxx b/src/emucore/Random.hxx
index 2170a59..940e3fa 100644
--- a/src/emucore/Random.hxx
+++ b/src/emucore/Random.hxx
@@ -22,6 +22,7 @@
 
 #include "bspf.hxx"
 #include "OSystem.hxx"
+#include "Serializable.hxx"
 
 /**
   This is a quick-and-dirty random number generator.  It is based on
@@ -30,7 +31,7 @@
 
   @author  Bradford W. Mott
 */
-class Random
+class Random : public Serializable
 {
   public:
     /**
@@ -57,6 +58,59 @@ class Random
       return (myValue = (myValue * 2416 + 374441) % 1771875);
     }
 
+    /**
+      Save the current state of this device to the given Serializer.
+
+      @param out  The Serializer object to use
+      @return  False on any errors, else true
+    */
+    bool save(Serializer& out) const override
+    {
+        try
+        {
+          out.putString(name());
+          out.putInt(myValue);
+        }
+        catch(...)
+        {
+          cerr << "ERROR: Random::save" << endl;
+          return false;
+        }
+
+        return true;
+    }
+
+    /**
+      Load the current state of this device from the given Serializer.
+
+      @param in  The Serializer object to use
+      @return  False on any errors, else true
+    */
+    bool load(Serializer& in) override
+    {
+      try
+      {
+        if(in.getString() != name())
+          return false;
+
+        myValue = in.getInt();
+      }
+      catch(...)
+      {
+        cerr << "ERROR: Random::load" << endl;
+        return false;
+      }
+
+      return true;
+    }
+
+    /**
+      Get a descriptor for the device name (used in error checking).
+
+      @return The name of the object
+    */
+    string name() const override { return "Random"; }
+
   private:
     // Set the OSystem we're using
     const OSystem& myOSystem;
diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx
index 79fecf1..9b97f45 100644
--- a/src/emucore/Settings.cxx
+++ b/src/emucore/Settings.cxx
@@ -90,6 +90,7 @@ Settings::Settings(OSystem& osystem)
   setInternal("cursor", "2");
   setInternal("dsense", "10");
   setInternal("msense", "10");
+  setInternal("tsense", "10");
   setInternal("saport", "lr");
   setInternal("ctrlcombo", "true");
 
@@ -138,6 +139,7 @@ Settings::Settings(OSystem& osystem)
   setInternal("avoxport", "");
   setInternal("stats", "false");
   setInternal("fastscbios", "true");
+  setInternal("threads", "false");
   setExternal("romloadcount", "0");
   setExternal("maxres", "");
 
@@ -294,7 +296,7 @@ void Settings::validate()
 
 #ifdef SOUND_SUPPORT
   i = getInt("volume");
-  if(i < 0 || i > 100)    setInternal("volume", "100");
+  if(i < 0 || i > 100)  setInternal("volume", "100");
   i = getInt("freq");
   if(!(i == 11025 || i == 22050 || i == 31400 || i == 44100 || i == 48000))
     setInternal("freq", "31400");
@@ -309,12 +311,16 @@ void Settings::validate()
     setInternal("cursor", "2");
 
   i = getInt("dsense");
-  if(i < 1)        setInternal("dsense", "1");
-  else if(i > 20)  setInternal("dsense", "10");
+  if(i < 1 || i > 20)
+    setInternal("dsense", "10");
 
   i = getInt("msense");
-  if(i < 1)        setInternal("msense", "1");
-  else if(i > 20)  setInternal("msense", "15");
+  if(i < 1 || i > 20)
+    setInternal("msense", "10");
+
+  i = getInt("tsense");
+  if(i < 1 || i > 20)
+    setInternal("tsense", "10");
 
   i = getInt("ssinterval");
   if(i < 1)        setInternal("ssinterval", "2");
@@ -413,11 +419,13 @@ void Settings::usage() const
     << "  -cursor       <0,1,2,3>      Set cursor state in UI/emulation modes\n"
     << "  -dsense       <number>       Sensitivity of digital emulated paddle movement (1-20)\n"
     << "  -msense       <number>       Sensitivity of mouse emulated paddle movement (1-20)\n"
+    << "  -tsense       <number>       Sensitivity of mouse emulated trackball movement (1-20)\n"
     << "  -saport       <lr|rl>        How to assign virtual ports to multiple Stelladaptor/2600-daptors\n"
     << "  -ctrlcombo    <1|0>          Use key combos involving the Control key (Control-Q for quit may be disabled!)\n"
     << "  -autoslot     <1|0>          Automatically switch to next save slot when state saving\n"
     << "  -stats        <1|0>          Overlay console info during emulation\n"
     << "  -fastscbios   <1|0>          Disable Supercharger BIOS progress loading bars\n"
+    << "  -threads      <1|0>          Whether to using multi-threading during emulation\n"
     << "  -snapsavedir  <path>         The directory to save snapshot files to\n"
     << "  -snaploaddir  <path>         The directory to load snapshot files from\n"
     << "  -snapname     <int|rom>      Name snapshots according to internal database or ROM\n"
@@ -616,9 +624,7 @@ int Settings::setInternal(const string& key, const Variant& value,
   }
   else
   {
-    Setting setting;
-    setting.key   = key;
-    setting.value = value;
+    Setting setting(key, value);
     if(useAsInitial) setting.initialValue = value;
 
     myInternalSettings.push_back(setting);
@@ -670,9 +676,7 @@ int Settings::setExternal(const string& key, const Variant& value,
   }
   else
   {
-    Setting setting;
-    setting.key   = key;
-    setting.value = value;
+    Setting setting(key, value);
     if(useAsInitial) setting.initialValue = value;
 
     myExternalSettings.push_back(setting);
diff --git a/src/emucore/Settings.hxx b/src/emucore/Settings.hxx
index f635155..0020512 100644
--- a/src/emucore/Settings.hxx
+++ b/src/emucore/Settings.hxx
@@ -115,6 +115,9 @@ class Settings
       string key;
       Variant value;
       Variant initialValue;
+
+      Setting(const string& k, const Variant& v, const Variant& i = EmptyVariant)
+        : key(k), value(v), initialValue(i) { }
     };
     using SettingsArray = vector<Setting>;
 
diff --git a/src/emucore/StateManager.cxx b/src/emucore/StateManager.cxx
index 8e3101f..3a7b5ba 100644
--- a/src/emucore/StateManager.cxx
+++ b/src/emucore/StateManager.cxx
@@ -28,7 +28,7 @@
 
 #include "StateManager.hxx"
 
-#define STATE_HEADER "05000000state"
+#define STATE_HEADER "05000200state"
 #define MOVIE_HEADER "03030000movie"
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/emucore/System.cxx b/src/emucore/System.cxx
index dfcf2f8..688ba4a 100644
--- a/src/emucore/System.cxx
+++ b/src/emucore/System.cxx
@@ -221,6 +221,8 @@ bool System::save(Serializer& out) const
       return false;
     if(!myCart.save(out))
       return false;
+    if(!randGenerator().save(out))
+      return false;
   }
   catch(...)
   {
@@ -251,6 +253,8 @@ bool System::load(Serializer& in)
       return false;
     if(!myCart.load(in))
       return false;
+    if(!randGenerator().load(in))
+      return false;
   }
   catch(...)
   {
diff --git a/src/emucore/TIASurface.cxx b/src/emucore/TIASurface.cxx
index 5c84874..76d1045 100644
--- a/src/emucore/TIASurface.cxx
+++ b/src/emucore/TIASurface.cxx
@@ -16,6 +16,7 @@
 //============================================================================
 
 #include <cmath>
+#include <algorithm>
 
 #include "FrameBuffer.hxx"
 #include "Settings.hxx"
@@ -55,6 +56,9 @@ TIASurface::TIASurface(OSystem& system)
   myBaseTiaSurface = myFB.allocateSurface(kTIAW*2, kTIAH);
 
   memset(myRGBFramebuffer, 0, AtariNTSC::outWidth(kTIAW) * kTIAH);
+
+  // Enable/disable threading in the NTSC TV effects renderer
+  myNTSCFilter.enableThreading(myOSystem.settings().getBool("threads"));
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -139,7 +143,7 @@ uInt32 TIASurface::pixel(uInt32 idx, uInt8 shift)
     const uInt32 p = myRGBFramebuffer[idx];
 
     // Mix current calculated frame with previous displayed frame
-    const uInt32 retVal = getRGBPhosphor(myPalette[c], p, shift);
+    const uInt32 retVal = getRGBPhosphor(myPalette[c], p);
 
     // Store back into displayed frame buffer (for next frame)
     myRGBFramebuffer[idx] = retVal;
@@ -228,35 +232,38 @@ void TIASurface::enableScanlineInterpolation(bool enable)
 void TIASurface::enablePhosphor(bool enable, int blend)
 {
   myUsePhosphor = enable;
-  myPhosphorPercent = blend / 100.0;
+  if(blend >= 0)
+    myPhosphorPercent = blend / 100.0;
   myFilter = Filter(enable ? uInt8(myFilter) | 0x01 : uInt8(myFilter) & 0x10);
 
   myTiaSurface->setDirty();
   mySLineSurface->setDirty();
   memset(myRGBFramebuffer, 0, AtariNTSC::outWidth(kTIAW) * kTIAH * 4);
+
+  // Precalculate the average colors for the 'phosphor' effect
+  if(myUsePhosphor)
+  {
+    for(Int16 c = 255; c >= 0; c--)
+      for(Int16 p = 255; p >= 0; p--)
+        myPhosphorPalette[c][p] = getPhosphor(c, p);
+
+    myNTSCFilter.setPhosphorPalette(myPhosphorPalette);
+  }
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-inline uInt32 TIASurface::getRGBPhosphor(uInt32 c, uInt32 p, uInt8 shift) const
+inline uInt32 TIASurface::getRGBPhosphor(const uInt32 c, const uInt32 p) const
 {
   #define TO_RGB(color, red, green, blue) \
-    red = color >> 16;  green = color >> 8;  blue = color;
-
-  uInt8 rc, gc, bc, rp, gp, bp;
+    const uInt8 red = color >> 16; const uInt8 green = color >> 8; const uInt8 blue = color;
 
   TO_RGB(c, rc, gc, bc);
   TO_RGB(p, rp, gp, bp);
 
   // Mix current calculated frame with previous displayed frame
-  uInt8 rn = getPhosphor(rc, rp);
-  uInt8 gn = getPhosphor(gc, gp);
-  uInt8 bn = getPhosphor(bc, bp);
-
-  if(shift)
-  {
-    // Convert RGB to grayscale
-    rn = gn = bn = uInt8(0.2126*rn + 0.7152*gn + 0.0722*bn);
-  }
+  const uInt8 rn = myPhosphorPalette[rc][rp];
+  const uInt8 gn = myPhosphorPalette[gc][gp];
+  const uInt8 bn = myPhosphorPalette[bc][bp];
 
   return (rn << 16) | (gn << 8) | bn;
 }
@@ -325,16 +332,17 @@ void TIASurface::render()
   {
     case Filter::Normal:
     {
-      uInt8* in = myTIA->frameBuffer();
+      uInt8* tiaIn = myTIA->frameBuffer();
 
-      uInt32 bufofsY = 0, screenofsY = 0, pos = 0;
+      uInt32 bufofs = 0, screenofsY = 0, pos;
       for(uInt32 y = 0; y < height; ++y)
       {
         pos = screenofsY;
-        for(uInt32 x = 0; x < width; ++x)
-          out[pos++] = myPalette[in[bufofsY + x]];
-
-        bufofsY    += width;
+        for (uInt32 x = width / 2; x; --x)
+        {
+          out[pos++] = myPalette[tiaIn[bufofs++]];
+          out[pos++] = myPalette[tiaIn[bufofs++]];
+        }
         screenofsY += outPitch;
       }
       break;
@@ -345,21 +353,18 @@ void TIASurface::render()
       uInt8*  tiaIn = myTIA->frameBuffer();
       uInt32* rgbIn = myRGBFramebuffer;
 
-      uInt32 bufofsY = 0, screenofsY = 0, pos = 0;
-      for(uInt32 y = 0; y < height; ++y)
+      uInt32 bufofs = 0, screenofsY = 0, pos;
+      for(uInt32 y = height; y ; --y)
       {
         pos = screenofsY;
-        for(uInt32 x = 0; x < width; ++x)
+        for(uInt32 x = width / 2; x ; --x)
         {
-          const uInt32 bufofs = bufofsY + x;
-          const uInt8 c = tiaIn[bufofs];
-          const uInt32 retVal = getRGBPhosphor(myPalette[c], rgbIn[bufofs]);
-
           // Store back into displayed frame buffer (for next frame)
-          rgbIn[bufofs] = retVal;
-          out[pos++] = retVal;
+          rgbIn[bufofs] = out[pos++] = getRGBPhosphor(myPalette[tiaIn[bufofs]], rgbIn[bufofs]);
+          bufofs++;
+          rgbIn[bufofs] = out[pos++] = getRGBPhosphor(myPalette[tiaIn[bufofs]], rgbIn[bufofs]);
+          bufofs++;
         }
-        bufofsY    += width;
         screenofsY += outPitch;
       }
       break;
@@ -373,28 +378,7 @@ void TIASurface::render()
 
     case Filter::BlarggPhosphor:
     {
-      // First do Blargg filtering
-      myNTSCFilter.render(myTIA->frameBuffer(), width, height, out, outPitch << 2);
-
-      // Then do phosphor mode (blend the resulting frames)
-      uInt32* rgbIn = myRGBFramebuffer;
-
-      uInt32 bufofsY = 0, screenofsY = 0, pos = 0;
-      for(uInt32 y = 0; y < height; ++y)
-      {
-        pos = screenofsY;
-        for(uInt32 x = 0; x < AtariNTSC::outWidth(kTIAW); ++x)
-        {
-          const uInt32 bufofs = bufofsY + x;
-          const uInt32 retVal = getRGBPhosphor(out[bufofs], rgbIn[bufofs]);
-
-          // Store back into displayed frame buffer (for next frame)
-          rgbIn[bufofs] = retVal;
-          out[pos++] = retVal;
-        }
-        bufofsY    += AtariNTSC::outWidth(kTIAW);
-        screenofsY += outPitch;
-      }
+      myNTSCFilter.render(myTIA->frameBuffer(), width, height, out, outPitch << 2, myRGBFramebuffer);
       break;
     }
   }
diff --git a/src/emucore/TIASurface.hxx b/src/emucore/TIASurface.hxx
index 549c1df..f8f35db 100644
--- a/src/emucore/TIASurface.hxx
+++ b/src/emucore/TIASurface.hxx
@@ -25,6 +25,8 @@ class FrameBuffer;
 class FBSurface;
 class VideoMode;
 
+#include <thread>
+
 #include "FrameManager.hxx"
 #include "Rect.hxx"
 #include "NTSCFilter.hxx"
@@ -107,9 +109,10 @@ class TIASurface
     void enableScanlineInterpolation(bool enable);
 
     /**
-      Enable/disable phosphor effect.
+      Enable/disable/query phosphor effect.
     */
-    void enablePhosphor(bool enable, int blend);
+    void enablePhosphor(bool enable, int blend = -1);
+    bool phosphorEnabled() const { return myUsePhosphor; }
 
     /**
       Used to calculate an averaged color for the 'phosphor' effect.
@@ -119,7 +122,7 @@ class TIASurface
 
       @return  Averaged value of the two colors
     */
-    inline uInt8 getPhosphor(uInt8 c1, uInt8 c2) const {
+    inline uInt8 getPhosphor(const uInt8 c1, uInt8 c2) const {
       // Use maximum of current and decayed previous values
       c2 = uInt8(c2 * myPhosphorPercent);
       if(c1 > c2)  return c1; // raise (assumed immediate)
@@ -134,7 +137,7 @@ class TIASurface
 
       @return  Averaged value of the two RGB colors
     */
-    uInt32 getRGBPhosphor(uInt32 c, uInt32 cp, uInt8 shift = 0) const;
+    uInt32 getRGBPhosphor(const uInt32 c, const uInt32 cp) const;
 
     /**
       Enable/disable/query NTSC filtering effects.
@@ -184,6 +187,9 @@ class TIASurface
 
     // Amount to blend when using phosphor effect
     float myPhosphorPercent;
+
+    // Precalculated averaged phosphor colors
+    uInt8 myPhosphorPalette[256][256];
     /////////////////////////////////////////////////////////////
 
     // Use scanlines in TIA rendering mode
diff --git a/src/emucore/Thumbulator.cxx b/src/emucore/Thumbulator.cxx
index 9363b64..84c2e6a 100644
--- a/src/emucore/Thumbulator.cxx
+++ b/src/emucore/Thumbulator.cxx
@@ -234,6 +234,7 @@ void Thumbulator::write16(uInt32 addr, uInt32 data)
     // as additional RAM
     case ConfigureFor::BUS:
     case ConfigureFor::CDF:
+    case ConfigureFor::CDF1:
       if((addr > 0x40000028) && (addr < 0x40000800))
         fatalError("write16", addr, "to bankswitch code area");
       break;
@@ -1198,6 +1199,67 @@ int Thumbulator::execute()
 
           break;
 
+        case ConfigureFor::CDF1:
+          // this subroutine interface is used in the CDF driver,
+          // it starts at address 0x00000750
+          // _SetNote:
+          //   ldr     r4, =NoteStore
+          //   bx      r4   // bx instruction at 0x000006e2
+          // _ResetWave:
+          //   ldr     r4, =ResetWaveStore
+          //   bx      r4   // bx instruction at 0x000006e6
+          // _GetWavePtr:
+          //   ldr     r4, =WavePtrFetch
+          //   bx      r4   // bx instruction at 0x000006ea
+          // _SetWaveSize:
+          //   ldr     r4, =WaveSizeStore
+          //   bx      r4   // bx instruction at 0x000006ee
+
+          // address to test for is + 4 due to pipelining
+
+#define CDF1_SetNote     (0x00000752 + 4)
+#define CDF1_ResetWave   (0x00000756 + 4)
+#define CDF1_GetWavePtr  (0x0000075a + 4)
+#define CDF1_SetWaveSize (0x0000075e + 4)
+
+          if      (pc == CDF1_SetNote)
+          {
+            myCartridge->thumbCallback(0, read_register(2), read_register(3));
+            handled = true;
+          }
+          else if (pc == CDF1_ResetWave)
+          {
+            myCartridge->thumbCallback(1, read_register(2), 0);
+            handled = true;
+          }
+          else if (pc == CDF1_GetWavePtr)
+          {
+            write_register(2, myCartridge->thumbCallback(2, read_register(2), 0));
+            handled = true;
+          }
+          else if (pc == CDF1_SetWaveSize)
+          {
+            myCartridge->thumbCallback(3, read_register(2), read_register(3));
+            handled = true;
+          }
+          else if (pc == 0x0000083a)
+          {
+            // exiting Custom ARM code, returning to BUS Driver control
+          }
+          else
+          {
+#if 0  // uncomment this for testing
+            uInt32 r0 = read_register(0);
+            uInt32 r1 = read_register(1);
+            uInt32 r2 = read_register(2);
+            uInt32 r3 = read_register(3);
+            uInt32 r4 = read_register(4);
+#endif
+            myCartridge->thumbCallback(255, 0, 0);
+          }
+
+          break;
+
         case ConfigureFor::DPCplus:
           // no 32-bit subroutines in DPC+
           break;
@@ -2266,6 +2328,7 @@ int Thumbulator::reset()
     // future 2K Harmony/Melody drivers will most likely use these settings
     case ConfigureFor::BUS:
     case ConfigureFor::CDF:
+    case ConfigureFor::CDF1:
       reg_norm[14] = 0x00000800; // Link Register
       reg_norm[15] = 0x0000080B; // Program Counter
       break;
diff --git a/src/emucore/Thumbulator.hxx b/src/emucore/Thumbulator.hxx
index 1051c37..4adf328 100644
--- a/src/emucore/Thumbulator.hxx
+++ b/src/emucore/Thumbulator.hxx
@@ -50,6 +50,7 @@ class Thumbulator
     enum ConfigureFor {
       BUS,      // cartridges of type BUS
       CDF,      // cartridges of type CDF
+      CDF1,     // cartridges of type CDF version 1
       DPCplus   // cartridges of type DPC+
     };
 
diff --git a/src/emucore/TrakBall.cxx b/src/emucore/TrakBall.cxx
deleted file mode 100644
index fb28e73..0000000
--- a/src/emucore/TrakBall.cxx
+++ /dev/null
@@ -1,125 +0,0 @@
-//============================================================================
-//
-//   SSSS    tt          lll  lll
-//  SS  SS   tt           ll   ll
-//  SS     tttttt  eeee   ll   ll   aaaa
-//   SSSS    tt   ee  ee  ll   ll      aa
-//      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
-//  SS  SS   tt   ee      ll   ll  aa  aa
-//   SSSS     ttt  eeeee llll llll  aaaaa
-//
-// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
-// and the Stella Team
-//
-// See the file "License.txt" for information on usage and redistribution of
-// this file, and for a DISCLAIMER OF ALL WARRANTIES.
-//============================================================================
-
-#include <cstdlib>
-
-#include "Event.hxx"
-#include "System.hxx"
-#include "TIA.hxx"
-#include "TrakBall.hxx"
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-TrakBall::TrakBall(Jack jack, const Event& event, const System& system)
-  : Controller(jack, event, system, Controller::TrakBall),
-    myHCounter(0),
-    myVCounter(0),
-    myMouseEnabled(false)
-{
-  // This code in ::read() is set up to always return IOPortA values in
-  // the lower 4 bits data value
-  // As such, the jack type (left or right) isn't necessary here
-
-  myTrakBallCountH = myTrakBallCountV = 0;
-  myTrakBallLinesH = myTrakBallLinesV = 1;
-
-  myTrakBallLeft = myTrakBallDown = myScanCountV = myScanCountH =
-    myCountV = myCountH = 0;
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-uInt8 TrakBall::read()
-{
-  int scanline = mySystem.tia().scanlines();
-
-  if(myScanCountV > scanline) myScanCountV = 0;
-  if(myScanCountH > scanline) myScanCountH = 0;
-  while((myScanCountV + myTrakBallLinesV) < scanline)
-  {
-    if(myTrakBallCountV)
-    {
-      if(myTrakBallDown) myCountV--;
-      else               myCountV++;
-      myTrakBallCountV--;
-    }
-    myScanCountV += myTrakBallLinesV;
-  }
-
-  while((myScanCountH + myTrakBallLinesH) < scanline)
-  {
-    if(myTrakBallCountH)
-    {
-      if(myTrakBallLeft) myCountH--;
-      else               myCountH++;
-      myTrakBallCountH--;
-    }
-    myScanCountH += myTrakBallLinesH;
-  }
-
-  myCountV &= 0x03;
-  myCountH &= 0x03;
-
-  static constexpr uInt32 ourTableH[2][2] = {{ 0x40, 0x00 }, { 0xc0, 0x80 }};
-  static constexpr uInt32 ourTableV[2][2] = {{ 0x00, 0x10 }, { 0x20, 0x30 }};
-  uInt8 IOPortA = ourTableV[myCountV & 0x01][myTrakBallDown] |
-                  ourTableH[myCountH & 0x01][myTrakBallLeft];
-
-  myDigitalPinState[One]   = IOPortA & 0x10;
-  myDigitalPinState[Two]   = IOPortA & 0x20;
-  myDigitalPinState[Three] = IOPortA & 0x40;
-  myDigitalPinState[Four]  = IOPortA & 0x80;
-
-  return (IOPortA >> 4);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-void TrakBall::update()
-{
-  if(!myMouseEnabled)
-    return;
-
-  // Get the current mouse position
-  myHCounter = myEvent.get(Event::MouseAxisXValue);
-  myVCounter = myEvent.get(Event::MouseAxisYValue);
-
-  if(myVCounter < 0) myTrakBallLeft = 1;
-  else               myTrakBallLeft = 0;
-  if(myHCounter < 0) myTrakBallDown = 0;
-  else               myTrakBallDown = 1;
-  myTrakBallCountH = abs(myVCounter >> 2); // Extra div by 2, since trakball has
-  myTrakBallCountV = abs(myHCounter >> 2); // half spatial resolution as ST/Amiga mouse
-  myTrakBallLinesH = mySystem.tia().height() / (myTrakBallCountH + 1);
-  if(myTrakBallLinesH == 0) myTrakBallLinesH = 1;
-  myTrakBallLinesV = mySystem.tia().height() / (myTrakBallCountV + 1);
-  if(myTrakBallLinesV == 0) myTrakBallLinesV = 1;
-
-  // Get mouse button state
-  myDigitalPinState[Six] = (myEvent.get(Event::MouseButtonLeftValue) == 0) &&
-                           (myEvent.get(Event::MouseButtonRightValue) == 0);
-}
-
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-bool TrakBall::setMouseControl(
-    Controller::Type xtype, int xid, Controller::Type ytype, int yid)
-{
-  // Currently, the various trakball controllers take full control of the
-  // mouse, and use both mouse buttons for the single fire button
-  // As well, there's no separate setting for x and y axis, so any
-  // combination of Controller and id is valid
-  myMouseEnabled = (xtype == myType || ytype == myType) &&
-                   (xid != -1 || yid != -1);
-  return true;
-}
diff --git a/src/emucore/TrakBall.hxx b/src/emucore/TrakBall.hxx
index 45a85c8..ef51b23 100644
--- a/src/emucore/TrakBall.hxx
+++ b/src/emucore/TrakBall.hxx
@@ -18,93 +18,34 @@
 #ifndef TRAKBALL_HXX
 #define TRAKBALL_HXX
 
-#include "bspf.hxx"
-#include "Control.hxx"
-#include "Event.hxx"
+#include "PointingDevice.hxx"
 
-/**
-  Emulates the standard trakball controller, sometimes known as the 'CX22'
-  controller. This code was heavily borrowed from z26.
-
-  @author  Stephen Anthony & z26 team
-*/
-class TrakBall : public Controller
+class TrakBall : public PointingDevice
 {
   public:
     /**
-      Create a new TrakBall controller plugged into the specified jack
+      Create a new trakball controller plugged into the specified jack
 
       @param jack   The jack the controller is plugged into
       @param event  The event object to use for events
       @param system The system using this controller
     */
-    TrakBall(Jack jack, const Event& event, const System& system);
+    TrakBall(Jack jack, const Event& event, const System& system)
+      : PointingDevice(jack, event, system, Controller::TrakBall,
+        trackballSensitivity) { }
     virtual ~TrakBall() = default;
 
-  public:
-    using Controller::read;
-
-    /**
-      Read the entire state of all digital pins for this controller.
-      Note that this method must use the lower 4 bits, and zero the upper bits.
-
-      @return The state of all digital pins
-    */
-    uInt8 read() override;
-
-    /**
-      Update the entire digital and analog pin state according to the
-      events currently set.
-    */
-    void update() override;
-
-    /**
-      Determines how this controller will treat values received from the
-      X/Y axis and left/right buttons of the mouse.  Since not all controllers
-      use the mouse the same way (or at all), it's up to the specific class to
-      decide how to use this data.
-
-      In the current implementation, the left button is tied to the X axis,
-      and the right one tied to the Y axis.
-
-      @param xtype  The controller to use for x-axis data
-      @param xid    The controller ID to use for x-axis data (-1 for no id)
-      @param ytype  The controller to use for y-axis data
-      @param yid    The controller ID to use for y-axis data (-1 for no id)
-
-      @return  Whether the controller supports using the mouse
-    */
-    bool setMouseControl(Controller::Type xtype, int xid,
-                         Controller::Type ytype, int yid) override;
-
-  private:
-    // Counter to iterate through the gray codes
-    int myHCounter, myVCounter;
-
-    // How many new horizontal and vertical values this frame
-    int myTrakBallCountH, myTrakBallCountV;
-
-    // How many lines to wait before sending new horz and vert val
-    int myTrakBallLinesH, myTrakBallLinesV;
-
-    // Was TrakBall moved left or moved right instead
-    int myTrakBallLeft;
-
-    // Was TrakBall moved down or moved up instead
-    int myTrakBallDown;
-
-    int myScanCountH, myScanCountV, myCountH, myCountV;
+  protected:
+    uInt8 ioPortA(uInt8 countH, uInt8 countV, uInt8 left, uInt8 down) override
+    {
+      static constexpr uInt32 ourTableH[2][2] = {{ 0x00, 0x10 }, { 0x20, 0x30 }};
+      static constexpr uInt32 ourTableV[2][2] = {{ 0x40, 0x00 }, { 0xc0, 0x80 }};
 
-    // Whether to use the mouse to emulate this controller
-    int myMouseEnabled;
+      return ourTableH[countH & 0x01][left] | ourTableV[countV & 0x01][down];
+    }
 
-  private:
-    // Following constructors and assignment operators not supported
-    TrakBall() = delete;
-    TrakBall(const TrakBall&) = delete;
-    TrakBall(TrakBall&&) = delete;
-    TrakBall& operator=(const TrakBall&) = delete;
-    TrakBall& operator=(TrakBall&&) = delete;
+    // 50% of Atari and Amiga mouse
+    static constexpr float trackballSensitivity = 0.4f;
 };
 
-#endif
+#endif // TRAKBALL_HXX
diff --git a/src/emucore/module.mk b/src/emucore/module.mk
index 26ce32c..8469d1a 100644
--- a/src/emucore/module.mk
+++ b/src/emucore/module.mk
@@ -67,6 +67,7 @@ MODULE_OBJS := \
 	src/emucore/MD5.o \
 	src/emucore/OSystem.o \
 	src/emucore/Paddles.o \
+	src/emucore/PointingDevice.o \
 	src/emucore/Props.o \
 	src/emucore/PropsSet.o \
 	src/emucore/SaveKey.o \
@@ -77,9 +78,6 @@ MODULE_OBJS := \
 	src/emucore/System.o \
 	src/emucore/TIASnd.o \
 	src/emucore/TIASurface.o \
-	src/emucore/AmigaMouse.o \
-	src/emucore/AtariMouse.o \
-	src/emucore/TrakBall.o \
 	src/emucore/Thumbulator.o
 
 MODULE_DIRS += \
diff --git a/src/emucore/tia/Ball.hxx b/src/emucore/tia/Ball.hxx
index 1f6a8d5..9602334 100644
--- a/src/emucore/tia/Ball.hxx
+++ b/src/emucore/tia/Ball.hxx
@@ -62,9 +62,8 @@ class Ball : public Serializable
 
     void tick(bool isReceivingMclock = true);
 
-    uInt8 getPixel(uInt8 colorIn) const {
-      return (collision & 0x8000) ? myColor : colorIn;
-    }
+    bool isOn() const { return (collision & 0x8000); }
+    uInt8 getColor() const { return myColor; }
 
     void shuffleStatus();
 
diff --git a/src/emucore/tia/Missile.hxx b/src/emucore/tia/Missile.hxx
index 12bef66..9d34336 100644
--- a/src/emucore/tia/Missile.hxx
+++ b/src/emucore/tia/Missile.hxx
@@ -63,9 +63,8 @@ class Missile : public Serializable
 
     void toggleEnabled(bool enabled);
 
-    uInt8 getPixel(uInt8 colorIn) const {
-      return (collision & 0x8000) ? myColor : colorIn;
-    }
+    bool isOn() const { return (collision & 0x8000); }
+    uInt8 getColor() const { return myColor; }
 
     uInt8 getPosition() const;
     void setPosition(uInt8 newPosition);
diff --git a/src/emucore/tia/Player.hxx b/src/emucore/tia/Player.hxx
index e2cca4c..8addd44 100644
--- a/src/emucore/tia/Player.hxx
+++ b/src/emucore/tia/Player.hxx
@@ -64,9 +64,8 @@ class Player : public Serializable
     void tick();
     uInt8 getClock() const { return myCounter; }
 
-    uInt8 getPixel(uInt8 colorIn) const {
-      return (collision & 0x8000) ? myColor : colorIn;
-    }
+    bool isOn() const { return (collision & 0x8000); }
+    uInt8 getColor() const { return myColor; }
 
     void shufflePatterns();
 
diff --git a/src/emucore/tia/Playfield.hxx b/src/emucore/tia/Playfield.hxx
index 59aaa55..1d987e2 100644
--- a/src/emucore/tia/Playfield.hxx
+++ b/src/emucore/tia/Playfield.hxx
@@ -59,10 +59,8 @@ class Playfield : public Serializable
 
     void tick(uInt32 x);
 
-    uInt8 getPixel(uInt8 colorIn) const {
-      if (collision & 0x8000) return myX < 80 ? myColorLeft : myColorRight;
-      return colorIn;
-    }
+    bool isOn() const { return (collision & 0x8000); }
+    uInt8 getColor() const { return myX < 80 ? myColorLeft : myColorRight; }
 
     /**
       Serializable methods (see that class for more information).
diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx
index badc4ca..8de4665 100644
--- a/src/emucore/tia/TIA.cxx
+++ b/src/emucore/tia/TIA.cxx
@@ -417,7 +417,7 @@ uInt8 TIA::peek(uInt16 address)
       break;
 
     case CXBLPF:
-      result = collCXBLPF() | (lastDataBusValue & 0x40);
+      result = collCXBLPF();
       break;
 
     case INPT0:
@@ -1019,8 +1019,8 @@ TIA& TIA::updateScanline()
 {
   // Update frame by one scanline at a time
   uInt32 line = scanlines();
-  while (line == scanlines())
-    updateScanlineByStep();
+  while (line == scanlines() && mySystem->m6502().execute(1))
+    updateEmulation();
 
   return *this;
 }
@@ -1029,8 +1029,8 @@ TIA& TIA::updateScanline()
 TIA& TIA::updateScanlineByStep()
 {
   // Update frame by one CPU instruction/color clock
-  mySystem->m6502().execute(1);
-  updateEmulation();
+  if (mySystem->m6502().execute(1))
+    updateEmulation();
 
   return *this;
 }
@@ -1038,8 +1038,10 @@ TIA& TIA::updateScanlineByStep()
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 TIA& TIA::updateScanlineByTrace(int target)
 {
-  while (mySystem->m6502().getPC() != target)
-    updateScanlineByStep();
+  uInt32 count = 100;  // only try up to 100 steps
+  while (mySystem->m6502().getPC() != target && count-- &&
+         mySystem->m6502().execute(1))
+    updateEmulation();
 
   return *this;
 }
@@ -1276,51 +1278,57 @@ void TIA::renderPixel(uInt32 x, uInt32 y)
 {
   if (x >= 160) return;
 
-  uInt8 color = myBackground.getColor();
+  uInt8 color = 0;
 
-  switch (myPriority)
+  if (!myFrameManager.vblank())
   {
-    // Playfield has priority so ScoreBit isn't used
-    // Priority from highest to lowest:
-    //   BL/PF => P0/M0 => P1/M1 => BK
-    case Priority::pfp:  // CTRLPF D2=1, D1=ignored
-      color = myMissile1.getPixel(color);
-      color = myPlayer1.getPixel(color);
-      color = myMissile0.getPixel(color);
-      color = myPlayer0.getPixel(color);
-      color = myPlayfield.getPixel(color);
-      color = myBall.getPixel(color);
-      break;
-
-    case Priority::score:  // CTRLPF D2=0, D1=1
-      // Formally we have (priority from highest to lowest)
-      //   PF/P0/M0 => P1/M1 => BL => BK
-      // for the first half and
-      //   P0/M0 => PF/P1/M1 => BL => BK
-      // for the second half. However, the first ordering is equivalent
-      // to the second (PF has the same color as P0/M0), so we can just
-      // write
-      color = myBall.getPixel(color);
-      color = myMissile1.getPixel(color);
-      color = myPlayer1.getPixel(color);
-      color = myPlayfield.getPixel(color);
-      color = myMissile0.getPixel(color);
-      color = myPlayer0.getPixel(color);
-      break;
-
-    // Priority from highest to lowest:
-    //   P0/M0 => P1/M1 => BL/PF => BK
-    case Priority::normal:  // CTRLPF D2=0, D1=0
-      color = myPlayfield.getPixel(color);
-      color = myBall.getPixel(color);
-      color = myMissile1.getPixel(color);
-      color = myPlayer1.getPixel(color);
-      color = myMissile0.getPixel(color);
-      color = myPlayer0.getPixel(color);
-      break;
+    switch (myPriority)
+    {
+      case Priority::pfp:  // CTRLPF D2=1, D1=ignored
+        // Playfield has priority so ScoreBit isn't used
+        // Priority from highest to lowest:
+        //   BL/PF => P0/M0 => P1/M1 => BK
+        if (myPlayfield.isOn())       color = myPlayfield.getColor();
+        else if (myBall.isOn())       color = myBall.getColor();
+        else if (myPlayer0.isOn())    color = myPlayer0.getColor();
+        else if (myMissile0.isOn())   color = myMissile0.getColor();
+        else if (myPlayer1.isOn())    color = myPlayer1.getColor();
+        else if (myMissile1.isOn())   color = myMissile1.getColor();
+        else                          color = myBackground.getColor();
+        break;
+
+      case Priority::score:  // CTRLPF D2=0, D1=1
+        // Formally we have (priority from highest to lowest)
+        //   PF/P0/M0 => P1/M1 => BL => BK
+        // for the first half and
+        //   P0/M0 => PF/P1/M1 => BL => BK
+        // for the second half. However, the first ordering is equivalent
+        // to the second (PF has the same color as P0/M0), so we can just
+        // write
+        if (myPlayer0.isOn())         color = myPlayer0.getColor();
+        else if (myMissile0.isOn())   color = myMissile0.getColor();
+        else if (myPlayfield.isOn())  color = myPlayfield.getColor();
+        else if (myPlayer1.isOn())    color = myPlayer1.getColor();
+        else if (myMissile1.isOn())   color = myMissile1.getColor();
+        else if (myBall.isOn())       color = myBall.getColor();
+        else                          color = myBackground.getColor();
+        break;
+
+      case Priority::normal:  // CTRLPF D2=0, D1=0
+        // Priority from highest to lowest:
+        //   P0/M0 => P1/M1 => BL/PF => BK
+        if (myPlayer0.isOn())         color = myPlayer0.getColor();
+        else if (myMissile0.isOn())   color = myMissile0.getColor();
+        else if (myPlayer1.isOn())    color = myPlayer1.getColor();
+        else if (myMissile1.isOn())   color = myMissile1.getColor();
+        else if (myPlayfield.isOn())  color = myPlayfield.getColor();
+        else if (myBall.isOn())       color = myBall.getColor();
+        else                          color = myBackground.getColor();
+        break;
+    }
   }
 
-  myFramebuffer[y * 160 + x] = myFrameManager.vblank() ? 0 : color;
+  myFramebuffer[y * 160 + x] = color;
 }
 
 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/gui/InputDialog.cxx b/src/gui/InputDialog.cxx
index c0bc7ad..2e3bfc8 100644
--- a/src/gui/InputDialog.cxx
+++ b/src/gui/InputDialog.cxx
@@ -20,6 +20,7 @@
 #include "OSystem.hxx"
 #include "Joystick.hxx"
 #include "Paddles.hxx"
+#include "PointingDevice.hxx"
 #include "Settings.hxx"
 #include "EventMappingWidget.hxx"
 #include "EditTextWidget.hxx"
@@ -45,7 +46,7 @@ InputDialog::InputDialog(OSystem& osystem, DialogContainer& parent,
 
   // Set real dimensions
   _w = std::min(50 * fontWidth + 10, max_w);
-  _h = std::min(15 * (lineHeight + 4) + 14, max_h);
+  _h = std::min(16 * (lineHeight + 4) + 14, max_h);
 
   // The tab widget
   xpos = 2; ypos = vBorder;
@@ -164,8 +165,8 @@ void InputDialog::addDevicePortTab(const GUI::Font& font)
   // Add paddle speed (digital emulation)
   xpos = 5;  ypos += lineHeight + 4;
   myDPaddleSpeed = new SliderWidget(myTab, font, xpos, ypos, pwidth, lineHeight,
-                                   "Digital paddle sensitivity ",
-                                   lwidth, kDPSpeedChanged);
+                                    "Digital paddle sensitivity ",
+                                    lwidth, kDPSpeedChanged);
   myDPaddleSpeed->setMinValue(1); myDPaddleSpeed->setMaxValue(20);
   xpos += myDPaddleSpeed->getWidth() + 5;
   myDPaddleLabel = new StaticTextWidget(myTab, font, xpos, ypos+1, 24, lineHeight,
@@ -176,8 +177,8 @@ void InputDialog::addDevicePortTab(const GUI::Font& font)
   // Add paddle speed (mouse emulation)
   xpos = 5;  ypos += lineHeight + 4;
   myMPaddleSpeed = new SliderWidget(myTab, font, xpos, ypos, pwidth, lineHeight,
-                                   "Mouse paddle sensitivity ",
-                                   lwidth, kMPSpeedChanged);
+                                    "Mouse paddle sensitivity ",
+                                    lwidth, kMPSpeedChanged);
   myMPaddleSpeed->setMinValue(1); myMPaddleSpeed->setMaxValue(20);
   xpos += myMPaddleSpeed->getWidth() + 5;
   myMPaddleLabel = new StaticTextWidget(myTab, font, xpos, ypos+1, 24, lineHeight,
@@ -185,6 +186,18 @@ void InputDialog::addDevicePortTab(const GUI::Font& font)
   myMPaddleSpeed->setFlags(WIDGET_CLEARBG);
   wid.push_back(myMPaddleSpeed);
 
+  // Add trackball speed
+  xpos = 5;  ypos += lineHeight + 4;
+  myTrackBallSpeed = new SliderWidget(myTab, font, xpos, ypos, pwidth, lineHeight,
+                                      "Trackball sensitivity ",
+                                      lwidth, kTBSpeedChanged);
+  myTrackBallSpeed->setMinValue(1); myTrackBallSpeed->setMaxValue(20);
+  xpos += myTrackBallSpeed->getWidth() + 5;
+  myTrackBallLabel = new StaticTextWidget(myTab, font, xpos, ypos+1, 24, lineHeight,
+                                          "", kTextAlignLeft);
+  myTrackBallSpeed->setFlags(WIDGET_CLEARBG);
+  wid.push_back(myTrackBallSpeed);
+
   // Add 'allow all 4 directions' for joystick
   xpos = 10;  ypos += lineHeight + 12;
   myAllowAll4 = new CheckboxWidget(myTab, font, xpos, ypos,
@@ -240,6 +253,10 @@ void InputDialog::loadConfig()
   myMPaddleSpeed->setValue(instance().settings().getInt("msense"));
   myMPaddleLabel->setLabel(instance().settings().getString("msense"));
 
+  // Trackball speed
+  myTrackBallSpeed->setValue(instance().settings().getInt("tsense"));
+  myTrackBallLabel->setLabel(instance().settings().getString("tsense"));
+
   // AtariVox serial port
   myAVoxPort->setText(instance().settings().getString("avoxport"));
 
@@ -279,6 +296,11 @@ void InputDialog::saveConfig()
   instance().settings().setValue("msense", sensitivity);
   Paddles::setMouseSensitivity(sensitivity);
 
+  // Trackball speed
+  sensitivity = myTrackBallSpeed->getValue();
+  instance().settings().setValue("tsense", sensitivity);
+  PointingDevice::setSensitivity(sensitivity);
+
   // AtariVox serial port
   instance().settings().setValue("avoxport", myAVoxPort->getText());
 
@@ -330,6 +352,8 @@ void InputDialog::setDefaults()
       myDPaddleLabel->setLabel("10");
       myMPaddleSpeed->setValue(10);
       myMPaddleLabel->setLabel("10");
+      myTrackBallSpeed->setValue(10);
+      myTrackBallLabel->setLabel("10");
 
       // AtariVox serial port
       myAVoxPort->setText("");
@@ -433,6 +457,10 @@ void InputDialog::handleCommand(CommandSender* sender, int cmd,
       myMPaddleLabel->setValue(myMPaddleSpeed->getValue());
       break;
 
+    case kTBSpeedChanged:
+      myTrackBallLabel->setValue(myTrackBallSpeed->getValue());
+      break;
+
     case kDBButtonPressed:
       if(!myJoyDialog)
         myJoyDialog = make_unique<JoystickDialog>
diff --git a/src/gui/InputDialog.hxx b/src/gui/InputDialog.hxx
index d471c8e..d2b56ef 100644
--- a/src/gui/InputDialog.hxx
+++ b/src/gui/InputDialog.hxx
@@ -57,6 +57,7 @@ class InputDialog : public Dialog
       kDeadzoneChanged = 'DZch',
       kDPSpeedChanged  = 'PDch',
       kMPSpeedChanged  = 'PMch',
+      kTBSpeedChanged  = 'TBch',
       kDBButtonPressed = 'DBbp'
     };
 
@@ -75,8 +76,10 @@ class InputDialog : public Dialog
     StaticTextWidget* myDeadzoneLabel;
     SliderWidget*     myDPaddleSpeed;
     SliderWidget*     myMPaddleSpeed;
+    SliderWidget*     myTrackBallSpeed;
     StaticTextWidget* myDPaddleLabel;
     StaticTextWidget* myMPaddleLabel;
+    StaticTextWidget* myTrackBallLabel;
     CheckboxWidget*   myAllowAll4;
     CheckboxWidget*   myGrabMouse;
     CheckboxWidget*   myCtrlCombo;
diff --git a/src/gui/VideoDialog.cxx b/src/gui/VideoDialog.cxx
index 1a20c18..9a311b7 100644
--- a/src/gui/VideoDialog.cxx
+++ b/src/gui/VideoDialog.cxx
@@ -188,6 +188,11 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent,
   // Center window (in windowed mode)
   myCenter = new CheckboxWidget(myTab, font, xpos, ypos, "Center window");
   wid.push_back(myCenter);
+  ypos += lineHeight + 4;
+
+  // Use multi-threading
+  myUseThreads = new CheckboxWidget(myTab, font, xpos, ypos, "Use multi-threading");
+  wid.push_back(myUseThreads);
 
   // Add items for tab 0
   addToFocusList(wid, myTab, tabID);
@@ -437,6 +442,9 @@ void VideoDialog::loadConfig()
   // Fast loading of Supercharger BIOS
   myFastSCBios->setState(instance().settings().getBool("fastscbios"));
 
+  // Multi-threaded rendering
+  myUseThreads->setState(instance().settings().getBool("threads"));
+
   // TV Mode
   myTVMode->setSelected(
     instance().settings().getString("tv.filter"), "0");
@@ -530,6 +538,11 @@ void VideoDialog::saveConfig()
   // Fast loading of Supercharger BIOS
   instance().settings().setValue("fastscbios", myFastSCBios->getState());
 
+  // Multi-threaded rendering
+  instance().settings().setValue("threads", myUseThreads->getState());
+  if(instance().hasConsole())
+    instance().frameBuffer().tiaSurface().ntsc().enableThreading(myUseThreads->getState());
+
   // TV Mode
   instance().settings().setValue("tv.filter",
     myTVMode->getSelectedTag().toString());
@@ -607,6 +620,7 @@ void VideoDialog::setDefaults()
       myUIMessages->setState(true);
       myCenter->setState(false);
       myFastSCBios->setState(true);
+      myUseThreads->setState(false);
       break;
     }
 
diff --git a/src/gui/VideoDialog.hxx b/src/gui/VideoDialog.hxx
index b15bcd1..71222d8 100644
--- a/src/gui/VideoDialog.hxx
+++ b/src/gui/VideoDialog.hxx
@@ -74,6 +74,7 @@ class VideoDialog : public Dialog
     CheckboxWidget*   myUIMessages;
     CheckboxWidget*   myCenter;
     CheckboxWidget*   myFastSCBios;
+    CheckboxWidget*   myUseThreads;
 
     // TV effects adjustables (custom mode)
     PopUpWidget*      myTVMode;
diff --git a/src/macosx/English.lproj/InfoPlist.strings b/src/macosx/English.lproj/InfoPlist.strings
index aa4dc64..9d601a2 100755
--- a/src/macosx/English.lproj/InfoPlist.strings
+++ b/src/macosx/English.lproj/InfoPlist.strings
@@ -1,8 +1,8 @@
 /* Localized versions of Info.plist keys */
 
 CFBundleName = "Stella";
-CFBundleShortVersionString = "Stella version 5.0.1";
-CFBundleGetInfoString = "Stella version 5.0.1";
+CFBundleShortVersionString = "Stella version 5.0.2";
+CFBundleGetInfoString = "Stella version 5.0.2";
 NSHumanReadableCopyright = "Stella MacOS X version by Stephen Anthony and Mark Grebe.";
 
 "Atari 2600 Cartridge File" = "Atari 2600 Cartridge File";
diff --git a/src/macosx/Info-Stella.plist b/src/macosx/Info-Stella.plist
index 36c1a43..f7725ed 100644
--- a/src/macosx/Info-Stella.plist
+++ b/src/macosx/Info-Stella.plist
@@ -53,7 +53,7 @@
 	<key>CFBundleSignature</key>
 	<string>StLa</string>
 	<key>CFBundleVersion</key>
-	<string>5.0.1</string>
+	<string>5.0.2</string>
 	<key>LSApplicationCategoryType</key>
 	<string>public.app-category.games</string>
 	<key>LSMinimumSystemVersionByArchitecture</key>
diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj
index 922004d..cbfac9d 100644
--- a/src/macosx/stella.xcodeproj/project.pbxproj
+++ b/src/macosx/stella.xcodeproj/project.pbxproj
@@ -236,11 +236,8 @@
 		DC13B540176FF2F500B8B4BB /* RomListSettings.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC13B53E176FF2F500B8B4BB /* RomListSettings.hxx */; };
 		DC173F760E2CAC1E00320F94 /* ContextMenu.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC173F740E2CAC1E00320F94 /* ContextMenu.cxx */; };
 		DC173F770E2CAC1E00320F94 /* ContextMenu.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC173F750E2CAC1E00320F94 /* ContextMenu.hxx */; };
-		DC1B2EC31E50036100F62837 /* AmigaMouse.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC1B2EBD1E50036100F62837 /* AmigaMouse.cxx */; };
 		DC1B2EC41E50036100F62837 /* AmigaMouse.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC1B2EBE1E50036100F62837 /* AmigaMouse.hxx */; };
-		DC1B2EC51E50036100F62837 /* AtariMouse.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC1B2EBF1E50036100F62837 /* AtariMouse.cxx */; };
 		DC1B2EC61E50036100F62837 /* AtariMouse.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC1B2EC01E50036100F62837 /* AtariMouse.hxx */; };
-		DC1B2EC71E50036100F62837 /* TrakBall.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC1B2EC11E50036100F62837 /* TrakBall.cxx */; };
 		DC1B2EC81E50036100F62837 /* TrakBall.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC1B2EC21E50036100F62837 /* TrakBall.hxx */; };
 		DC1FC18A0DB3B2C7009B3DF7 /* SerialPortMACOSX.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC1FC1880DB3B2C7009B3DF7 /* SerialPortMACOSX.cxx */; };
 		DC1FC18B0DB3B2C7009B3DF7 /* SerialPortMACOSX.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC1FC1890DB3B2C7009B3DF7 /* SerialPortMACOSX.hxx */; };
@@ -258,6 +255,7 @@
 		DC368F5918A2FB710084199C /* SoundSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC368F5318A2FB710084199C /* SoundSDL2.hxx */; };
 		DC36D2C814CAFAB0007DC821 /* CartFA2.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC36D2C614CAFAB0007DC821 /* CartFA2.cxx */; };
 		DC36D2C914CAFAB0007DC821 /* CartFA2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC36D2C714CAFAB0007DC821 /* CartFA2.hxx */; };
+		DC3DAFAC1F2E233B00A64410 /* PointingDevice.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC3DAFAB1F2E233B00A64410 /* PointingDevice.hxx */; };
 		DC3EE8561E2C0E6D00905161 /* adler32.c in Sources */ = {isa = PBXBuildFile; fileRef = DC3EE83C1E2C0E6D00905161 /* adler32.c */; };
 		DC3EE8571E2C0E6D00905161 /* compress.c in Sources */ = {isa = PBXBuildFile; fileRef = DC3EE83D1E2C0E6D00905161 /* compress.c */; };
 		DC3EE8581E2C0E6D00905161 /* crc32.c in Sources */ = {isa = PBXBuildFile; fileRef = DC3EE83E1E2C0E6D00905161 /* crc32.c */; };
@@ -305,6 +303,7 @@
 		DC4AC6F00DC8DACB00CD3AD2 /* RiotWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC4AC6EE0DC8DACB00CD3AD2 /* RiotWidget.hxx */; };
 		DC4AC6F30DC8DAEF00CD3AD2 /* SaveKey.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC4AC6F10DC8DAEF00CD3AD2 /* SaveKey.cxx */; };
 		DC4AC6F40DC8DAEF00CD3AD2 /* SaveKey.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC4AC6F20DC8DAEF00CD3AD2 /* SaveKey.hxx */; };
+		DC53B6AE1F3622DA00AA6BFB /* PointingDevice.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC53B6AD1F3622DA00AA6BFB /* PointingDevice.cxx */; };
 		DC56FCDE14CCCC4900A31CC3 /* MouseControl.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC56FCDC14CCCC4900A31CC3 /* MouseControl.cxx */; };
 		DC56FCDF14CCCC4900A31CC3 /* MouseControl.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC56FCDD14CCCC4900A31CC3 /* MouseControl.hxx */; };
 		DC5BE4B317C913AC0091FD64 /* ConsoleBFont.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC5BE4B117C913AC0091FD64 /* ConsoleBFont.hxx */; };
@@ -863,11 +862,8 @@
 		DC13B53E176FF2F500B8B4BB /* RomListSettings.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RomListSettings.hxx; sourceTree = "<group>"; };
 		DC173F740E2CAC1E00320F94 /* ContextMenu.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = ContextMenu.cxx; sourceTree = "<group>"; };
 		DC173F750E2CAC1E00320F94 /* ContextMenu.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = ContextMenu.hxx; sourceTree = "<group>"; };
-		DC1B2EBD1E50036100F62837 /* AmigaMouse.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AmigaMouse.cxx; sourceTree = "<group>"; };
 		DC1B2EBE1E50036100F62837 /* AmigaMouse.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AmigaMouse.hxx; sourceTree = "<group>"; };
-		DC1B2EBF1E50036100F62837 /* AtariMouse.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AtariMouse.cxx; sourceTree = "<group>"; };
 		DC1B2EC01E50036100F62837 /* AtariMouse.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AtariMouse.hxx; sourceTree = "<group>"; };
-		DC1B2EC11E50036100F62837 /* TrakBall.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrakBall.cxx; sourceTree = "<group>"; };
 		DC1B2EC21E50036100F62837 /* TrakBall.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TrakBall.hxx; sourceTree = "<group>"; };
 		DC1FC1880DB3B2C7009B3DF7 /* SerialPortMACOSX.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SerialPortMACOSX.cxx; sourceTree = "<group>"; };
 		DC1FC1890DB3B2C7009B3DF7 /* SerialPortMACOSX.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = SerialPortMACOSX.hxx; sourceTree = "<group>"; };
@@ -885,6 +881,7 @@
 		DC368F5318A2FB710084199C /* SoundSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundSDL2.hxx; sourceTree = "<group>"; };
 		DC36D2C614CAFAB0007DC821 /* CartFA2.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartFA2.cxx; sourceTree = "<group>"; };
 		DC36D2C714CAFAB0007DC821 /* CartFA2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartFA2.hxx; sourceTree = "<group>"; };
+		DC3DAFAB1F2E233B00A64410 /* PointingDevice.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = PointingDevice.hxx; sourceTree = "<group>"; };
 		DC3EE83C1E2C0E6D00905161 /* adler32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = adler32.c; sourceTree = "<group>"; };
 		DC3EE83D1E2C0E6D00905161 /* compress.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = compress.c; sourceTree = "<group>"; };
 		DC3EE83E1E2C0E6D00905161 /* crc32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crc32.c; sourceTree = "<group>"; };
@@ -932,6 +929,7 @@
 		DC4AC6EE0DC8DACB00CD3AD2 /* RiotWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = RiotWidget.hxx; sourceTree = "<group>"; };
 		DC4AC6F10DC8DAEF00CD3AD2 /* SaveKey.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SaveKey.cxx; sourceTree = "<group>"; };
 		DC4AC6F20DC8DAEF00CD3AD2 /* SaveKey.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = SaveKey.hxx; sourceTree = "<group>"; };
+		DC53B6AD1F3622DA00AA6BFB /* PointingDevice.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PointingDevice.cxx; sourceTree = "<group>"; };
 		DC56FCDC14CCCC4900A31CC3 /* MouseControl.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MouseControl.cxx; sourceTree = "<group>"; };
 		DC56FCDD14CCCC4900A31CC3 /* MouseControl.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MouseControl.hxx; sourceTree = "<group>"; };
 		DC5BE4B117C913AC0091FD64 /* ConsoleBFont.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConsoleBFont.hxx; sourceTree = "<group>"; };
@@ -1533,9 +1531,7 @@
 		2D6050CC0898776500C6DE89 /* emucore */ = {
 			isa = PBXGroup;
 			children = (
-				DC1B2EBD1E50036100F62837 /* AmigaMouse.cxx */,
 				DC1B2EBE1E50036100F62837 /* AmigaMouse.hxx */,
-				DC1B2EBF1E50036100F62837 /* AtariMouse.cxx */,
 				DC1B2EC01E50036100F62837 /* AtariMouse.hxx */,
 				DC487FB40DA5350900E12499 /* AtariVox.cxx */,
 				DC487FB50DA5350900E12499 /* AtariVox.hxx */,
@@ -1674,6 +1670,8 @@
 				2DDBEB7508457B7D00812C11 /* OSystem.hxx */,
 				2DE2DF820627AE34006BEC99 /* Paddles.cxx */,
 				2DE2DF830627AE34006BEC99 /* Paddles.hxx */,
+				DC53B6AD1F3622DA00AA6BFB /* PointingDevice.cxx */,
+				DC3DAFAB1F2E233B00A64410 /* PointingDevice.hxx */,
 				2DE2DF840627AE34006BEC99 /* Props.cxx */,
 				2DE2DF850627AE34006BEC99 /* Props.hxx */,
 				2DE2DF860627AE34006BEC99 /* PropsSet.cxx */,
@@ -1702,7 +1700,6 @@
 				DC2AADAC194F389C0026C7A4 /* TIASurface.cxx */,
 				DC2AADAD194F389C0026C7A4 /* TIASurface.hxx */,
 				DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */,
-				DC1B2EC11E50036100F62837 /* TrakBall.cxx */,
 				DC1B2EC21E50036100F62837 /* TrakBall.hxx */,
 			);
 			path = emucore;
@@ -2246,6 +2243,7 @@
 				DC676A581729A0B000E4E73D /* CartMCWidget.hxx in Headers */,
 				DC676A5A1729A0B000E4E73D /* CartSBWidget.hxx in Headers */,
 				DC676A5C1729A0B000E4E73D /* CartX07Widget.hxx in Headers */,
+				DC3DAFAC1F2E233B00A64410 /* PointingDevice.hxx in Headers */,
 				DC7A24D5173B1CF600B20FE9 /* Variant.hxx in Headers */,
 				DC7A24E0173B1DBC00B20FE9 /* FileListWidget.hxx in Headers */,
 				DC13B540176FF2F500B8B4BB /* RomListSettings.hxx in Headers */,
@@ -2429,7 +2427,6 @@
 				2D9174B609BA90380026E9FF /* Menu.cxx in Sources */,
 				DC5E473B19EC9A14000E45DF /* EventJoyHandler.cxx in Sources */,
 				CFE3F60D1E84A9A200A8204E /* CartCDFWidget.cxx in Sources */,
-				DC1B2EC71E50036100F62837 /* TrakBall.cxx in Sources */,
 				2D9174B709BA90380026E9FF /* OptionsDialog.cxx in Sources */,
 				2D9174B809BA90380026E9FF /* PopUpWidget.cxx in Sources */,
 				DCBDDE9A1D6A5F0E009DF1E9 /* Cart3EPlusWidget.cxx in Sources */,
@@ -2443,12 +2440,12 @@
 				2D9174BE09BA90380026E9FF /* CartUA.cxx in Sources */,
 				DC3EE86E1E2C0E6D00905161 /* zutil.c in Sources */,
 				CFE3F60B1E84A9A200A8204E /* CartBUSWidget.cxx in Sources */,
-				DC1B2EC51E50036100F62837 /* AtariMouse.cxx in Sources */,
 				DCEC58581E945125002F0246 /* DelayQueueWidget.cxx in Sources */,
 				2D9174BF09BA90380026E9FF /* FSNode.cxx in Sources */,
 				DCF3A6FE1DFC75E3008A8AF3 /* TIA.cxx in Sources */,
 				2D9174C009BA90380026E9FF /* OSystem.cxx in Sources */,
 				2D9174C209BA90380026E9FF /* Preferences.m in Sources */,
+				DC53B6AE1F3622DA00AA6BFB /* PointingDevice.cxx in Sources */,
 				2D9174C409BA90380026E9FF /* AboutBox.m in Sources */,
 				2D9174C509BA90380026E9FF /* Font.cxx in Sources */,
 				2D9174C609BA90380026E9FF /* Debugger.cxx in Sources */,
@@ -2479,7 +2476,6 @@
 				2D9174FD09BA90380026E9FF /* RomListWidget.cxx in Sources */,
 				DCF3A6F81DFC75E3008A8AF3 /* PaddleReader.cxx in Sources */,
 				2D9174FE09BA90380026E9FF /* RomWidget.cxx in Sources */,
-				DC1B2EC31E50036100F62837 /* AmigaMouse.cxx in Sources */,
 				2D9174FF09BA90380026E9FF /* TiaInfoWidget.cxx in Sources */,
 				2D91750009BA90380026E9FF /* TiaOutputWidget.cxx in Sources */,
 				2D91750109BA90380026E9FF /* TiaWidget.cxx in Sources */,
diff --git a/src/unix/stella.spec b/src/unix/stella.spec
index 872053f..67b6ffa 100644
--- a/src/unix/stella.spec
+++ b/src/unix/stella.spec
@@ -1,5 +1,5 @@
 %define name    stella
-%define version 5.0.1
+%define version 5.0.2
 %define rel     1
 
 %define enable_sound 1
@@ -101,6 +101,9 @@ rm -rf $RPM_BUILD_DIR/%{name}-%{version}
 %_datadir/icons/large/%{name}.png
 
 %changelog
+* Sun Aug 20 2017 Stephen Anthony <stephena at users.sf.net> 5.0.2-1
+- Version 5.0.2 release
+
 * Sun Jul 23 2017 Stephen Anthony <stephena at users.sf.net> 5.0.1-1
 - Version 5.0.1 release
 
diff --git a/src/windows/OSystemWINDOWS.hxx b/src/windows/OSystemWINDOWS.hxx
index 87ee39f..16bfe37 100644
--- a/src/windows/OSystemWINDOWS.hxx
+++ b/src/windows/OSystemWINDOWS.hxx
@@ -22,8 +22,6 @@
 
 /**
   This class defines Windows system specific settings.
-
-  @author  Stephen Anthony
 */
 class OSystemWINDOWS : public OSystem
 {
diff --git a/src/windows/SerialPortWINDOWS.hxx b/src/windows/SerialPortWINDOWS.hxx
index ea0694f..5d0c2b0 100644
--- a/src/windows/SerialPortWINDOWS.hxx
+++ b/src/windows/SerialPortWINDOWS.hxx
@@ -24,8 +24,6 @@
 
 /**
   Implement reading and writing from a serial port under Windows systems.
-
-  @author  Stephen Anthony
 */
 class SerialPortWINDOWS : public SerialPort
 {
diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj
index 7cf7f41..62db4cf 100644
--- a/src/windows/Stella.vcxproj
+++ b/src/windows/Stella.vcxproj
@@ -89,22 +89,22 @@
     <CodeAnalysisRuleSet Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AllRules.ruleset</CodeAnalysisRuleSet>
     <CodeAnalysisRules Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
     <CodeAnalysisRuleAssemblies Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
-    <IncludePath Condition="'$(Configuration)|$(Platform)'=='Release|x64'">C:\Users\stephen\Source\sdl\include;$(IncludePath)</IncludePath>
-    <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">C:\Users\stephen\Source\sdl\include;$(IncludePath)</IncludePath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
-    <IncludePath>C:\Users\stephen\Source\sdl\include;$(IncludePath)</IncludePath>
-    <LibraryPath>C:\Users\stephen\Source\sdl\lib\x86;$(LibraryPath)</LibraryPath>
+    <LibraryPath>$(ProjectDir)\SDL\lib\x86;$(LibraryPath)</LibraryPath>
+    <IncludePath>$(ProjectDir)\SDL\include;$(IncludePath)</IncludePath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
-    <IncludePath>C:\Users\stephen\Source\sdl\include;$(IncludePath)</IncludePath>
-    <LibraryPath>C:\Users\stephen\Source\sdl\lib\x86;$(LibraryPath)</LibraryPath>
+    <LibraryPath>$(ProjectDir)\SDL\lib\x86;$(LibraryPath)</LibraryPath>
+    <IncludePath>$(ProjectDir)\SDL\include;$(IncludePath)</IncludePath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
-    <LibraryPath>C:\Users\stephen\Source\sdl\lib\x64;$(LibraryPath)</LibraryPath>
+    <LibraryPath>$(ProjectDir)\SDL\lib\x64;$(LibraryPath)</LibraryPath>
+    <IncludePath>$(ProjectDir)\SDL\include;$(IncludePath)</IncludePath>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
-    <LibraryPath>C:\Users\stephen\Source\sdl\lib\x64;$(LibraryPath)</LibraryPath>
+    <LibraryPath>$(ProjectDir)\SDL\lib\x64;$(LibraryPath)</LibraryPath>
+    <IncludePath>$(ProjectDir)\SDL\include;$(IncludePath)</IncludePath>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
@@ -292,8 +292,6 @@
     <ClCompile Include="..\debugger\gui\RiotRamWidget.cxx" />
     <ClCompile Include="..\debugger\gui\RomListSettings.cxx" />
     <ClCompile Include="..\debugger\gui\SaveKeyWidget.cxx" />
-    <ClCompile Include="..\emucore\AmigaMouse.cxx" />
-    <ClCompile Include="..\emucore\AtariMouse.cxx" />
     <ClCompile Include="..\emucore\Cart3EPlus.cxx" />
     <ClCompile Include="..\emucore\Cart4KSC.cxx" />
     <ClCompile Include="..\emucore\CartBF.cxx" />
@@ -314,6 +312,7 @@
     <ClCompile Include="..\emucore\EventJoyHandler.cxx" />
     <ClCompile Include="..\emucore\FBSurface.cxx" />
     <ClCompile Include="..\emucore\MindLink.cxx" />
+    <ClCompile Include="..\emucore\PointingDevice.cxx" />
     <ClCompile Include="..\emucore\TIASurface.cxx" />
     <ClCompile Include="..\emucore\tia\Background.cxx" />
     <ClCompile Include="..\emucore\tia\Ball.cxx" />
@@ -326,7 +325,6 @@
     <ClCompile Include="..\emucore\tia\Playfield.cxx" />
     <ClCompile Include="..\emucore\tia\TIA.cxx" />
     <ClCompile Include="..\emucore\tia\VblankManager.cxx" />
-    <ClCompile Include="..\emucore\TrakBall.cxx" />
     <ClCompile Include="..\gui\ColorWidget.cxx" />
     <ClCompile Include="..\gui\FileListWidget.cxx" />
     <ClCompile Include="..\gui\JoystickDialog.cxx" />
@@ -590,6 +588,7 @@
     <ClInclude Include="..\emucore\CompuMate.hxx" />
     <ClInclude Include="..\emucore\FBSurface.hxx" />
     <ClInclude Include="..\emucore\MindLink.hxx" />
+    <ClInclude Include="..\emucore\PointingDevice.hxx" />
     <ClInclude Include="..\emucore\TIASurface.hxx" />
     <ClInclude Include="..\emucore\tia\Background.hxx" />
     <ClInclude Include="..\emucore\tia\Ball.hxx" />
diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters
index b1b815c..90f1d69 100644
--- a/src/windows/Stella.vcxproj.filters
+++ b/src/windows/Stella.vcxproj.filters
@@ -816,15 +816,6 @@
     <ClCompile Include="..\emucore\tia\VblankManager.cxx">
       <Filter>Source Files\emucore\tia</Filter>
     </ClCompile>
-    <ClCompile Include="..\emucore\AmigaMouse.cxx">
-      <Filter>Source Files\emucore</Filter>
-    </ClCompile>
-    <ClCompile Include="..\emucore\AtariMouse.cxx">
-      <Filter>Source Files\emucore</Filter>
-    </ClCompile>
-    <ClCompile Include="..\emucore\TrakBall.cxx">
-      <Filter>Source Files\emucore</Filter>
-    </ClCompile>
     <ClCompile Include="..\debugger\gui\CartBUSWidget.cxx">
       <Filter>Source Files\debugger</Filter>
     </ClCompile>
@@ -849,6 +840,9 @@
     <ClCompile Include="..\gui\ColorWidget.cxx">
       <Filter>Source Files\gui</Filter>
     </ClCompile>
+    <ClCompile Include="..\emucore\PointingDevice.cxx">
+      <Filter>Source Files\emucore</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\common\bspf.hxx">
@@ -1724,6 +1718,9 @@
     <ClInclude Include="..\gui\ColorWidget.hxx">
       <Filter>Header Files\gui</Filter>
     </ClInclude>
+    <ClInclude Include="..\emucore\PointingDevice.hxx">
+      <Filter>Header Files\emucore</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="stella.ico">
diff --git a/src/windows/stella.rc b/src/windows/stella.rc
index b1549d3..01bb8af 100755
--- a/src/windows/stella.rc
+++ b/src/windows/stella.rc
@@ -36,8 +36,8 @@ IDI_ICON                ICON                    "stella.ico"
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 5,0,1,0
- PRODUCTVERSION 5,0,1,0
+ FILEVERSION 5,0,2,0
+ PRODUCTVERSION 5,0,2,0
  FILEFLAGSMASK 0x3fL
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -55,12 +55,12 @@ BEGIN
             VALUE "Comments", "The multi-platform Atari 2600 emulator.  Stella is released under the GPLv2."
             VALUE "CompanyName", "The Stella Team (https://stella-emu.github.io)"
             VALUE "FileDescription", "Stella"
-            VALUE "FileVersion", "5.0.1"
+            VALUE "FileVersion", "5.0.2"
             VALUE "InternalName", "Stella"
             VALUE "LegalCopyright", "Copyright (C) 1995-2017 The Stella Team"
             VALUE "OriginalFilename", "Stella.exe"
             VALUE "ProductName", "Stella"
-            VALUE "ProductVersion", "5.0.1"
+            VALUE "ProductVersion", "5.0.2"
         END
     END
     BLOCK "VarFileInfo"

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/stella.git



More information about the Pkg-games-commits mailing list