OXYGENHUB
◆ main openmsx / src / input / Touchpad.cc
7.1 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
// This implementation is based on the reverse-engineering effort done
2
// by 'SD-snatcher'. See here:
3
//   http://www.msx.org/news/en/msx-touchpad-protocol-reverse-engineered
4
//   http://www.msx.org/wiki/Touchpad
5
// Also thanks to Manuel Bilderbeek for donating a Philips NMS-1150 that
6
// made this effort possible.
7
8
#include "Touchpad.hh"
9
10
#include "MSXEventDistributor.hh"
11
#include "StateChange.hh"
12
#include "StateChangeDistributor.hh"
13
14
#include "Clock.hh"
15
#include "CommandController.hh"
16
#include "CommandException.hh"
17
#include "Display.hh"
18
#include "Event.hh"
19
#include "OutputSurface.hh"
20
#include "serialize.hh"
21
#include "serialize_meta.hh"
22
23
#include "xrange.hh"
24
25
#include <iostream>
26
27
using namespace gl;
28
29
namespace openmsx {
30
31
Touchpad::Touchpad(MSXEventDistributor& eventDistributor_,
32
                   StateChangeDistributor& stateChangeDistributor_,
33
                   Display& display_,
34
                   CommandController& commandController)
35
	: eventDistributor(eventDistributor_)
36
	, stateChangeDistributor(stateChangeDistributor_)
37
	, display(display_)
38
	, transformSetting(commandController,
39
		"touchpad_transform_matrix",
40
		"2x3 matrix to transform host mouse coordinates to "
41
		"MSX touchpad coordinates, see manual for details",
42
		"{ 256 0 0 } { 0 256 0 }")
43
{
44
	auto& interp = commandController.getInterpreter();
45
	transformSetting.setChecker([this, &interp](const TclObject& newValue) {
46
		try {
47
			parseTransformMatrix(interp, newValue);
48
		} catch (CommandException& e) {
49
			throw CommandException(
50
				"Invalid transformation matrix: ", e.getMessage());
51
		}
52
	});
53
	try {
54
		parseTransformMatrix(interp, transformSetting.getValue());
55
	} catch (CommandException& e) {
56
		// should only happen when settings.xml was manually edited
57
		std::cerr << e.getMessage() << '\n';
58
		// fill in safe default values
59
		m[0][0] = 256.0f; m[1][0] =   0.0f; m[2][0] = 0.0f;
60
		m[0][1] =   0.0f; m[1][1] = 256.0f; m[2][1] = 0.0f;
61
	}
62
}
63
64
Touchpad::~Touchpad()
65
{
66
	if (isPluggedIn()) {
67
		Touchpad::unplugHelper(EmuTime::dummy());
68
	}
69
}
70
71
void Touchpad::parseTransformMatrix(Interpreter& interp, const TclObject& value)
72
{
73
	if (value.getListLength(interp) != 2) {
74
		throw CommandException("must have 2 rows");
75
	}
76
	for (auto i : xrange(2)) {
77
		TclObject row = value.getListIndex(interp, i);
78
		if (row.getListLength(interp) != 3) {
79
			throw CommandException("each row must have 3 elements");
80
		}
81
		for (auto j : xrange(3)) {
82
			m[j][i] = row.getListIndex(interp, j).getFloat(interp);
83
		}
84
	}
85
}
86
87
// Pluggable
88
zstring_view Touchpad::getName() const
89
{
90
	return "touchpad";
91
}
92
93
zstring_view Touchpad::getDescription() const
94
{
95
	return "MSX Touchpad";
96
}
97
98
void Touchpad::plugHelper(Connector& /*connector*/, EmuTime /*time*/)
99
{
100
	eventDistributor.registerEventListener(*this);
101
	stateChangeDistributor.registerListener(*this);
102
}
103
104
void Touchpad::unplugHelper(EmuTime /*time*/)
105
{
106
	stateChangeDistributor.unregisterListener(*this);
107
	eventDistributor.unregisterEventListener(*this);
108
}
109
110
// JoystickDevice
111
static constexpr uint8_t SENSE  = JoystickDevice::RD_PIN1;
112
static constexpr uint8_t EOC    = JoystickDevice::RD_PIN2;
113
static constexpr uint8_t SO     = JoystickDevice::RD_PIN3;
114
static constexpr uint8_t BUTTON = JoystickDevice::RD_PIN4;
115
static constexpr uint8_t SCK    = JoystickDevice::WR_PIN6;
116
static constexpr uint8_t SI     = JoystickDevice::WR_PIN7;
117
static constexpr uint8_t CS     = JoystickDevice::WR_PIN8;
118
119
uint8_t Touchpad::read(EmuTime time)
120
{
121
	uint8_t result = SENSE | BUTTON; // 1-bit means not pressed
122
	if (touch)  result &= ~SENSE;
123
	if (button) result &= ~BUTTON;
124
125
	// EOC remains zero for 56 cycles after CS 0->1
126
	// TODO at what clock frequency does the UPD7001 run?
127
	//    400kHz is only the recommended value from the UPD7001 datasheet.
128
	constexpr EmuDuration delta = Clock<400000>::duration(56);
129
	if ((time - start) > delta) {
130
		result |= EOC;
131
	}
132
133
	if (shift & 0x80) result |= SO;
134
	if (last & CS)    result |= SO;
135
136
	return result | 0x30;
137
}
138
139
void Touchpad::write(uint8_t value, EmuTime time)
140
{
141
	uint8_t diff = last ^ value;
142
	last = value;
143
	if (diff & CS) {
144
		if (value & CS) {
145
			// CS 0->1
146
			channel = shift & 3;
147
			start = time; // to keep EOC=0 for 56 cycles
148
		} else {
149
			// CS 1->0
150
			// Tested by SD-Snatcher (see RFE #252):
151
			//  When not touched X is always 0, and Y floats
152
			//  between 147 and 149 (mostly 148).
153
			shift = (channel == 0) ? (touch ? x :   0)
154
			      : (channel == 3) ? (touch ? y : 148)
155
			      : 0; // channel 1 and 2 return 0
156
		}
157
	}
158
	if (((value & (CS | SCK)) == SCK) && (diff & SCK)) {
159
		// SC=0 & SCK 0->1
160
		shift <<= 1;
161
		shift |= (value & SI) != 0;
162
	}
163
}
164
165
ivec2 Touchpad::transformCoords(ivec2 xy)
166
{
167
	if (const auto* output = display.getOutputSurface()) {
168
		vec2 uv = vec2(xy) / vec2(output->getLogicalSize());
169
		xy = ivec2(m * vec3(uv, 1.0f));
170
	}
171
	return clamp(xy, 0, 255);
172
}
173
174
// MSXEventListener
175
void Touchpad::signalMSXEvent(const Event& event,
176
                              EmuTime time) noexcept
177
{
178
	ivec2 pos = hostPos;
179
	auto b = hostButtons;
180
181
	std::visit(overloaded{
182
		[&](const MouseMotionEvent& e) {
183
			pos = transformCoords(ivec2(e.getAbsX(), e.getAbsY()));
184
		},
185
		[&](const MouseButtonDownEvent& e) {
186
			switch (e.getButton()) {
187
			case SDL_BUTTON_LEFT:
188
				b |= 1;
189
				break;
190
			case SDL_BUTTON_RIGHT:
191
				b |= 2;
192
				break;
193
			default:
194
				// ignore other buttons
195
				break;
196
			}
197
		},
198
		[&](const MouseButtonUpEvent& e) {
199
			switch (e.getButton()) {
200
			case SDL_BUTTON_LEFT:
201
				b &= ~1;
202
				break;
203
			case SDL_BUTTON_RIGHT:
204
				b &= ~2;
205
				break;
206
			default:
207
				// ignore other buttons
208
				break;
209
			}
210
		},
211
		[](const EventBase&) { /*ignore*/ }
212
	}, event);
213
214
	if ((pos != hostPos) || (b != hostButtons)) {
215
		hostPos     = pos;
216
		hostButtons = b;
217
		createTouchpadStateChange(
218
			time,
219
			narrow_cast<uint8_t>(pos.x),
220
			narrow_cast<uint8_t>(pos.y),
221
			(hostButtons & 1) != 0,
222
			(hostButtons & 2) != 0);
223
	}
224
}
225
226
void Touchpad::createTouchpadStateChange(
227
	EmuTime time, uint8_t x_, uint8_t y_, bool touch_, bool button_)
228
{
229
	stateChangeDistributor.distributeNew<TouchpadState>(
230
		time, x_, y_, touch_, button_);
231
}
232
233
// StateChangeListener
234
void Touchpad::signalStateChange(const StateChange& event)
235
{
236
	if (const auto* ts = std::get_if<TouchpadState>(&event)) {
237
		x      = ts->getX();
238
		y      = ts->getY();
239
		touch  = ts->getTouch();
240
		button = ts->getButton();
241
	}
242
}
243
244
void Touchpad::stopReplay(EmuTime time) noexcept
245
{
246
	// TODO Get actual mouse state. Is it worth the trouble?
247
	if (x || y || touch || button) {
248
		stateChangeDistributor.distributeNew<TouchpadState>(
249
			time, uint8_t(0), uint8_t(0), false, false);
250
	}
251
}
252
253
254
template<typename Archive>
255
void Touchpad::serialize(Archive& ar, unsigned /*version*/)
256
{
257
	// no need to serialize hostX, hostY, hostButtons,
258
	//                      transformSetting, m[][]
259
	ar.serialize("start",   start,
260
	             "x",       x,
261
	             "y",       y,
262
	             "touch",   touch,
263
	             "button",  button,
264
	             "shift",   shift,
265
	             "channel", channel,
266
	             "last",    last);
267
268
	if constexpr (Archive::IS_LOADER) {
269
		if (isPluggedIn()) {
270
			plugHelper(*getConnector(), EmuTime::dummy());
271
		}
272
	}
273
}
274
INSTANTIATE_SERIALIZE_METHODS(Touchpad);
275
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, Touchpad, "Touchpad");
276
277
} // namespace openmsx