diff --git a/Machines/Amiga/Chipset.cpp b/Machines/Amiga/Chipset.cpp
index a9dafbd82..38882cc86 100644
--- a/Machines/Amiga/Chipset.cpp
+++ b/Machines/Amiga/Chipset.cpp
@@ -1193,121 +1193,6 @@ void Chipset::set_component_prefers_clocking(ClockingHint::Source *, ClockingHin
 	disk_controller_is_sleeping_ = preference == ClockingHint::Preference::None;
 }
 
-// MARK: - Mouse.
-
-int Chipset::Mouse::get_number_of_buttons() {
-	return 2;
-}
-
-void Chipset::Mouse::set_button_pressed(int button, bool is_set) {
-	switch(button) {
-		case 0:
-			cia_state_ = (cia_state_ &~ 0x40) | (is_set ? 0 : 0x40);
-		break;
-		default:
-		break;
-	}
-}
-
-uint8_t Chipset::Mouse::get_cia_button() const {
-	return cia_state_;
-}
-
-void Chipset::Mouse::reset_all_buttons() {
-	cia_state_ = 0xff;
-}
-
-void Chipset::Mouse::move(int x, int y) {
-	position_[0] += x;
-	position_[1] += y;
-}
-
-Inputs::Mouse &Chipset::get_mouse() {
-	return mouse_;
-}
-
-uint16_t Chipset::Mouse::get_position() {
-	// The Amiga hardware retains only eight bits of position
-	// for the mouse; its software polls frequently and maps
-	// changes into a larger space.
-	//
-	// On modern computers with 5k+ displays and trackpads, it
-	// proved empirically possible to overflow the hardware
-	// counters more quickly than software would poll.
-	//
-	// Therefore the approach taken for mapping mouse motion
-	// into the Amiga is to do it in steps of no greater than
-	// [-128, +127], as per the below.
-	const int pending[] = {
-		position_[0], position_[1]
-	};
-
-	const int8_t change[] = {
-		int8_t(std::clamp(pending[0], -128, 127)),
-		int8_t(std::clamp(pending[1], -128, 127))
-	};
-
-	position_[0] -= change[0];
-	position_[1] -= change[1];
-	declared_position_[0] += change[0];
-	declared_position_[1] += change[1];
-
-	return uint16_t(
-		(declared_position_[1] << 8) |
-		declared_position_[0]
-	);
-}
-
-// MARK: - Joystick.
-
-// TODO: add second fire button.
-
-Chipset::Joystick::Joystick() :
-	ConcreteJoystick({
-						Input(Input::Up),
-						Input(Input::Down),
-						Input(Input::Left),
-						Input(Input::Right),
-						Input(Input::Fire, 0),
-					}) {}
-
-void Chipset::Joystick::did_set_input(const Input &input, bool is_active) {
-	// Accumulate state.
-	inputs_[input.type] = is_active;
-
-	// Determine what that does to the two position bits.
-	const auto low =
-		(inputs_[Input::Type::Down] ^ inputs_[Input::Type::Right]) |
-		(inputs_[Input::Type::Right] << 1);
-	const auto high =
-		(inputs_[Input::Type::Up] ^ inputs_[Input::Type::Left]) |
-		(inputs_[Input::Type::Left] << 1);
-
-	// Ripple upwards if that affects the mouse position counters.
-	const uint8_t previous_low = position_ & 3;
-	uint8_t low_upper = (position_ >> 2) & 0x3f;
-	const uint8_t previous_high = (position_ >> 8) & 3;
-	uint8_t high_upper = (position_ >> 10) & 0x3f;
-
-	if(!low && previous_low == 3) ++low_upper;
-	if(!previous_low && low == 3) --low_upper;
-	if(!high && previous_high == 3) ++high_upper;
-	if(!previous_high && high == 3) --high_upper;
-
-	position_ = uint16_t(
-		low | ((low_upper & 0x3f) << 2) |
-		(high << 8) | ((high_upper & 0x3f) << 10)
-	);
-}
-
-uint16_t Chipset::Joystick::get_position() const {
-	return position_;
-}
-
-uint8_t Chipset::Joystick::get_cia_button() const {
-	return inputs_[Input::Type::Fire] ? 0xbf : 0xff;
-}
-
 // MARK: - Synchronisation.
 
 void Chipset::flush() {
diff --git a/Machines/Amiga/Chipset.hpp b/Machines/Amiga/Chipset.hpp
index 51d20fa68..307311256 100644
--- a/Machines/Amiga/Chipset.hpp
+++ b/Machines/Amiga/Chipset.hpp
@@ -19,8 +19,6 @@
 #include "../../ClockReceiver/ClockingHintSource.hpp"
 #include "../../ClockReceiver/JustInTime.hpp"
 #include "../../Components/6526/6526.hpp"
-#include "../../Inputs/Joystick.hpp"
-#include "../../Inputs/Mouse.hpp"
 #include "../../Outputs/CRT/CRT.hpp"
 #include "../../Processors/68000/68000.hpp"
 #include "../../Storage/Disk/Controller/DiskController.hpp"
@@ -32,6 +30,7 @@
 #include "DMADevice.hpp"
 #include "Flags.hpp"
 #include "Keyboard.hpp"
+#include "MouseJoystick.hpp"
 #include "MemoryMap.hpp"
 #include "Sprites.hpp"
 
@@ -284,41 +283,15 @@ class Chipset: private ClockingHint::Observer {
 
 		// MARK: - Mouse.
 	private:
-		class Mouse: public Inputs::Mouse {
-			public:
-				uint16_t get_position();
-				uint8_t get_cia_button() const;
-
-			private:
-				int get_number_of_buttons() final;
-				void set_button_pressed(int, bool) final;
-				void reset_all_buttons() final;
-				void move(int, int) final;
-
-				uint8_t declared_position_[2]{};
-				uint8_t cia_state_ = 0xff;
-				std::array<std::atomic<int>, 2> position_{};
-		} mouse_;
+		Mouse mouse_;
 
 	public:
-		Inputs::Mouse &get_mouse();
+		Inputs::Mouse &get_mouse() {
+			return mouse_;
+		}
 
 		// MARK: - Joystick.
 	private:
-		class Joystick: public Inputs::ConcreteJoystick {
-			public:
-				Joystick();
-
-				uint16_t get_position() const;
-				uint8_t get_cia_button() const;
-
-			private:
-				void did_set_input(const Input &input, bool is_active) final;
-
-				bool inputs_[Joystick::Input::Type::Max]{};
-				uint16_t position_ = 0;
-		};
-
 		std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
 		Joystick &joystick(size_t index) const {
 			return *static_cast<Joystick *>(joysticks_[index].get());
diff --git a/Machines/Amiga/MouseJoystick.cpp b/Machines/Amiga/MouseJoystick.cpp
new file mode 100644
index 000000000..b7e1519a2
--- /dev/null
+++ b/Machines/Amiga/MouseJoystick.cpp
@@ -0,0 +1,122 @@
+//
+//  MouseJoystick.cpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 26/11/2021.
+//  Copyright © 2021 Thomas Harte. All rights reserved.
+//
+
+#include "MouseJoystick.hpp"
+
+using namespace Amiga;
+
+// MARK: - Mouse.
+
+int Mouse::get_number_of_buttons() {
+	return 2;
+}
+
+void Mouse::set_button_pressed(int button, bool is_set) {
+	switch(button) {
+		case 0:
+			cia_state_ = (cia_state_ &~ 0x40) | (is_set ? 0 : 0x40);
+		break;
+		default:
+		break;
+	}
+}
+
+uint8_t Mouse::get_cia_button() const {
+	return cia_state_;
+}
+
+void Mouse::reset_all_buttons() {
+	cia_state_ = 0xff;
+}
+
+void Mouse::move(int x, int y) {
+	position_[0] += x;
+	position_[1] += y;
+}
+
+uint16_t Mouse::get_position() {
+	// The Amiga hardware retains only eight bits of position
+	// for the mouse; its software polls frequently and maps
+	// changes into a larger space.
+	//
+	// On modern computers with 5k+ displays and trackpads, it
+	// proved empirically possible to overflow the hardware
+	// counters more quickly than software would poll.
+	//
+	// Therefore the approach taken for mapping mouse motion
+	// into the Amiga is to do it in steps of no greater than
+	// [-128, +127], as per the below.
+	const int pending[] = {
+		position_[0], position_[1]
+	};
+
+	const int8_t change[] = {
+		int8_t(std::clamp(pending[0], -128, 127)),
+		int8_t(std::clamp(pending[1], -128, 127))
+	};
+
+	position_[0] -= change[0];
+	position_[1] -= change[1];
+	declared_position_[0] += change[0];
+	declared_position_[1] += change[1];
+
+	return uint16_t(
+		(declared_position_[1] << 8) |
+		declared_position_[0]
+	);
+}
+
+// MARK: - Joystick.
+
+// TODO: add second fire button.
+
+Joystick::Joystick() :
+	ConcreteJoystick({
+						Input(Input::Up),
+						Input(Input::Down),
+						Input(Input::Left),
+						Input(Input::Right),
+						Input(Input::Fire, 0),
+					}) {}
+
+void Joystick::did_set_input(const Input &input, bool is_active) {
+	// Accumulate state.
+	inputs_[input.type] = is_active;
+
+	// Determine what that does to the two position bits.
+	const auto low =
+		(inputs_[Input::Type::Down] ^ inputs_[Input::Type::Right]) |
+		(inputs_[Input::Type::Right] << 1);
+	const auto high =
+		(inputs_[Input::Type::Up] ^ inputs_[Input::Type::Left]) |
+		(inputs_[Input::Type::Left] << 1);
+
+	// Ripple upwards if that affects the mouse position counters.
+	const uint8_t previous_low = position_ & 3;
+	uint8_t low_upper = (position_ >> 2) & 0x3f;
+	const uint8_t previous_high = (position_ >> 8) & 3;
+	uint8_t high_upper = (position_ >> 10) & 0x3f;
+
+	if(!low && previous_low == 3) ++low_upper;
+	if(!previous_low && low == 3) --low_upper;
+	if(!high && previous_high == 3) ++high_upper;
+	if(!previous_high && high == 3) --high_upper;
+
+	position_ = uint16_t(
+		low | ((low_upper & 0x3f) << 2) |
+		(high << 8) | ((high_upper & 0x3f) << 10)
+	);
+}
+
+uint16_t Joystick::get_position() {
+	return position_;
+}
+
+uint8_t Joystick::get_cia_button() const {
+	return inputs_[Input::Type::Fire] ? 0xbf : 0xff;
+}
diff --git a/Machines/Amiga/MouseJoystick.hpp b/Machines/Amiga/MouseJoystick.hpp
new file mode 100644
index 000000000..32037fc9c
--- /dev/null
+++ b/Machines/Amiga/MouseJoystick.hpp
@@ -0,0 +1,57 @@
+//
+//  MouseJoystick.hpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 26/11/2021.
+//  Copyright © 2021 Thomas Harte. All rights reserved.
+//
+
+#ifndef MouseJoystick_hpp
+#define MouseJoystick_hpp
+
+#include <array>
+#include <atomic>
+
+#include "../../Inputs/Joystick.hpp"
+#include "../../Inputs/Mouse.hpp"
+
+namespace Amiga {
+
+struct MouseJoystickInput {
+	virtual uint16_t get_position() = 0;
+	virtual uint8_t get_cia_button() const = 0;
+};
+
+class Mouse: public Inputs::Mouse, public MouseJoystickInput {
+	public:
+		uint16_t get_position() final;
+		uint8_t get_cia_button() const final;
+
+	private:
+		int get_number_of_buttons() final;
+		void set_button_pressed(int, bool) final;
+		void reset_all_buttons() final;
+		void move(int, int) final;
+
+		uint8_t declared_position_[2]{};
+		uint8_t cia_state_ = 0xff;
+		std::array<std::atomic<int>, 2> position_{};
+};
+
+class Joystick: public Inputs::ConcreteJoystick, public MouseJoystickInput {
+	public:
+		Joystick();
+
+		uint16_t get_position() final;
+		uint8_t get_cia_button() const final;
+
+	private:
+		void did_set_input(const Input &input, bool is_active) final;
+
+		bool inputs_[Joystick::Input::Type::Max]{};
+		uint16_t position_ = 0;
+};
+
+}
+
+#endif /* MouseJoystick_hpp */
diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj
index 1ca3d5e8d..7df4cb0c8 100644
--- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
+++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
@@ -435,6 +435,8 @@
 		4B7BC7F51F58F27800D1B1B4 /* 6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */; };
 		4B7C681627517A59001671EC /* Sprites.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681427517A59001671EC /* Sprites.cpp */; };
 		4B7C681727517A59001671EC /* Sprites.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C681427517A59001671EC /* Sprites.cpp */; };
+		4B7C681A275196E8001671EC /* MouseJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C6818275196E8001671EC /* MouseJoystick.cpp */; };
+		4B7C681B275196E8001671EC /* MouseJoystick.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7C6818275196E8001671EC /* MouseJoystick.cpp */; };
 		4B7F188E2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
 		4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F188C2154825D00388727 /* MasterSystem.cpp */; };
 		4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7F1896215486A100388727 /* StaticAnalyser.cpp */; };
@@ -1429,6 +1431,8 @@
 		4B7BA03F23D55E7900B98D9E /* LFSR.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LFSR.hpp; sourceTree = "<group>"; };
 		4B7C681427517A59001671EC /* Sprites.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Sprites.cpp; sourceTree = "<group>"; };
 		4B7C681527517A59001671EC /* Sprites.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Sprites.hpp; sourceTree = "<group>"; };
+		4B7C6818275196E8001671EC /* MouseJoystick.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MouseJoystick.cpp; sourceTree = "<group>"; };
+		4B7C6819275196E8001671EC /* MouseJoystick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseJoystick.hpp; sourceTree = "<group>"; };
 		4B7F188C2154825D00388727 /* MasterSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MasterSystem.cpp; sourceTree = "<group>"; };
 		4B7F188D2154825D00388727 /* MasterSystem.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MasterSystem.hpp; sourceTree = "<group>"; };
 		4B7F1895215486A100388727 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
@@ -4310,6 +4314,7 @@
 				4BC6236C26F4235400F83DFE /* Copper.cpp */,
 				4B1A1B1C27320FBB00119335 /* Disk.cpp */,
 				4B9EC0E826B384080060A31F /* Keyboard.cpp */,
+				4B7C6818275196E8001671EC /* MouseJoystick.cpp */,
 				4B7C681427517A59001671EC /* Sprites.cpp */,
 				4BC080D726A25ADA00D03FD8 /* Amiga.hpp */,
 				4B2130E1273A7A0A008A77B4 /* Audio.hpp */,
@@ -4321,6 +4326,7 @@
 				4B9EC0E926B384080060A31F /* Keyboard.hpp */,
 				4BD1552E270B14AC00410C6E /* MemoryMap.hpp */,
 				4BC6237026F94A5B00F83DFE /* Minterms.hpp */,
+				4B7C6819275196E8001671EC /* MouseJoystick.hpp */,
 				4B7C681527517A59001671EC /* Sprites.hpp */,
 			);
 			path = Amiga;
@@ -5445,6 +5451,7 @@
 				4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
 				4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
 				4B25B5F925BD083C00362C84 /* DiskIIDrive.cpp in Sources */,
+				4B7C681B275196E8001671EC /* MouseJoystick.cpp in Sources */,
 				4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
 				4B2E86B825D7490E0024F1E9 /* ReactiveDevice.cpp in Sources */,
 				4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */,
@@ -5652,6 +5659,7 @@
 				4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
 				4B0F1C232605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */,
 				4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
+				4B7C681A275196E8001671EC /* MouseJoystick.cpp in Sources */,
 				4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
 				4B1B58FF246E19FD009C171E /* State.cpp in Sources */,
 				4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,