1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-02-28 01:16:11 +00:00
Files
livingcomputermuseum.ContrAlto/Contralto/IO/MouseAndKeyset.cs
Josh Dersch d96d232fd3 Initial commit of changes for 1.2.3. This includes:
- Scripting support: Allows for recording and playback of mouse/keyboard input and various system control actions.  Simple (i.e. basic) scripting format.

- Fix for stale packets left in ethernet input queue; packets received by pcap while Alto's receiver is off are discarded.

- Mouse input made more accurate, and tweaked to avoid Alto microcode bug that causes erroneous mouse inputs under very rare circumstances on real hardware, but much more frequently under emulation.

- Small code cleanup here and there.  Moved many UI strings to resources, many more to go.
2018-03-20 14:16:07 -07:00

334 lines
10 KiB
C#

/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.CPU;
using Contralto.Memory;
using Contralto.Scripting;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Contralto.IO
{
[Flags]
public enum AltoMouseButton
{
None = 0x0,
Middle = 0x1,
Right = 0x2,
Left = 0x4,
}
[Flags]
public enum AltoKeysetKey
{
None = 0x00,
Keyset0 = 0x80, // left-most (bit 8)
Keyset1 = 0x40,
Keyset2 = 0x20,
Keyset3 = 0x10,
Keyset4 = 0x08, // right-most (bit 12)
}
/// <summary>
/// Implements the hardware for the standard Alto mouse
/// and the Keyset, because both share the same memory-mapped
/// address. When the Diablo printer is finally emulated,
/// I'll have to revisit this scheme because it ALSO shares
/// the same address and that's just silly.
/// </summary>
public class MouseAndKeyset : IMemoryMappedDevice
{
public MouseAndKeyset(AltoSystem system)
{
_system = system;
_lock = new ReaderWriterLockSlim();
Reset();
}
public void Reset()
{
_keyset = 0;
_buttons = AltoMouseButton.None;
_moves = new Queue<MouseMovement>();
_currentMove = null;
_pollCounter = 0;
}
public ushort Read(int address, TaskType task, bool extendedMemoryReference)
{
return (ushort)~((int)_buttons | (int)_keyset);
}
public void Load(int address, ushort data, TaskType task, bool extendedMemoryReference)
{
// nothing
}
public void MouseMove(int dx, int dy)
{
// Calculate number of steps in x and y to be decremented every call to PollMouseBits
MouseMovement nextMove = new MouseMovement(Math.Abs(dx), Math.Abs(dy), Math.Sign(dx), Math.Sign(dy));
_lock.EnterWriteLock();
_moves.Enqueue(nextMove);
if (ScriptManager.IsRecording)
{
ScriptManager.Recorder.MouseMoveRelative(dx, dy);
}
_lock.ExitWriteLock();
}
public void MouseDown(AltoMouseButton button)
{
_buttons |= button;
if (ScriptManager.IsRecording)
{
//
// Record the absolute position of the mouse (as held in MOUSELOC in system memory).
// All other mouse movements in the script will be recorded relative to this point.
//
//int x = _system.Memory.Read(0x114, CPU.TaskType.Ethernet, false);
//int y = _system.Memory.Read(0x115, CPU.TaskType.Ethernet, false);
//ScriptManager.Recorder.MouseMoveAbsolute(x, y);
ScriptManager.Recorder.MouseDown(button);
}
}
public void MouseUp(AltoMouseButton button)
{
_buttons ^= button;
if (ScriptManager.IsRecording)
{
ScriptManager.Recorder.MouseUp(button);
}
}
public void KeysetDown(AltoKeysetKey key)
{
_keyset |= key;
}
public void KeysetUp(AltoKeysetKey key)
{
_keyset ^= key;
}
/// <summary>
/// Gets the bits read by the "<-MOUSE" special function, and moves
/// the pointer one step closer to its final destination (if it has moved at all).
/// </summary>
/// <returns></returns>
public ushort PollMouseBits()
{
//
// The bits returned correspond to the delta incurred by mouse movement in the X and Y direction
// and map to:
// 0 : no change in X or Y
// 1 : dy = -1
// 2 : dy = 1
// 3 : dx = -1
// 4 : dy = -1, dx = -1
// 5 : dy = 1, dx = -1
// 6 : dx = 1
// 7 : dy = -1, dx = 1
// 8 : dy = 1, dx =1
ushort bits = 0;
_lock.EnterReadLock();
if (_currentMove == null && _moves.Count > 0)
{
_currentMove = _moves.Dequeue();
}
//
// <-MOUSE is invoked by the Memory Refresh Task once per scanline (including during vblank) which
// works out to about 13,000 times a second. To more realistically simulate the movement of a mouse
// across a desk, we return actual mouse movement data only periodically.
//
if (_currentMove != null && (_pollCounter % _currentMove.PollRate) == 0)
{
//
// Choose a direction. We do not provide movements in both X and Y at the same time;
// this is solely to avoid a microcode bug that causes erroneous movements in such cases
// (which then plays havoc with scripting and absolute coordinates.)
// (It is also the case that on the real hardware, such movements are extremely rare due to
// the nature of the hardware involved).
//
int dx = _currentMove.DX;
int dy = _currentMove.DY;
if (dx != 0 && dy != 0)
{
// Choose just one of the two directions to move in.
if (_currentDirection)
{
dx = 0;
}
else
{
dy = 0;
}
_currentDirection = !_currentDirection;
}
if (dy == -1 && dx == 0)
{
bits = 1;
}
else if (dy == 1 && dx == 0)
{
bits = 2;
}
else if (dy == 0 && dx == -1)
{
bits = 3;
}
else if (dy == -1 && dx == -1)
{
bits = 4;
}
else if (dy == 1 && dx == -1)
{
bits = 5;
}
else if (dy == 0 && dx == 1)
{
bits = 6;
}
else if (dy == -1 && dx == 1)
{
bits = 7;
}
else if (dy == 1 && dx == 1)
{
bits = 8;
}
//
// Move the mouse closer to its destination in either X or Y
// (but not both)
if (_currentMove.XSteps > 0 && dx != 0)
{
_currentMove.XSteps--;
if (_currentMove.XSteps == 0)
{
_currentMove.DX = 0;
}
}
if (_currentMove.YSteps > 0 && dy != 0)
{
_currentMove.YSteps--;
if (_currentMove.YSteps == 0)
{
_currentMove.DY = 0;
}
}
if (_currentMove.XSteps == 0 && _currentMove.YSteps == 0)
{
_currentMove = null;
}
}
_lock.ExitReadLock();
_pollCounter++;
return bits;
}
public MemoryRange[] Addresses
{
get { return _addresses; }
}
private readonly MemoryRange[] _addresses =
{
new MemoryRange(0xfe18, 0xfe1b), // UTILIN: 177030-177033
};
AltoSystem _system;
// Mouse buttons:
AltoMouseButton _buttons;
// Keyset switches:
AltoKeysetKey _keyset;
private ReaderWriterLockSlim _lock;
// Used to control the rate of mouse movement data
//
public int _pollCounter;
/// <summary>
/// Where the mouse is moving to every time PollMouseBits is called.
/// </summary>
private Queue<MouseMovement> _moves;
private MouseMovement _currentMove;
private bool _currentDirection;
private class MouseMovement
{
public MouseMovement(int xsteps, int ysteps, int dx, int dy)
{
XSteps = xsteps;
YSteps = ysteps;
DX = dx;
DY = dy;
//
// Calculate the rate at which mouse data should be returned in PollMouseBits,
// this is a function of the distance moved in this movement. We assume that the
// movement occurred in 1/60th of a second; PollMouseBits is invoked (via <-MOUSE)
// by the MRT approximately every 1/13000th of a second.
// This is all approximate and not expected to be completely accurate.
//
double distance = Math.Sqrt(Math.Pow(xsteps, 2) + Math.Pow(ysteps, 2));
PollRate = (int)((13000.0 / 120.0) / (distance + 1));
if (PollRate == 0)
{
PollRate = 1;
}
}
public int XSteps;
public int YSteps;
public int DX;
public int DY;
public int PollRate;
}
}
}