piscsi icon indicating copy to clipboard operation
piscsi copied to clipboard

[Idea] Mouse and Keyboard Emulation over SCSI

Open marciot opened this issue 2 years ago • 7 comments

I am thinking to implement mouse and keyboard control over SCSI. The reason is that I've heard that ADB peripherals are as hard to find, and sometimes as expensive, as the vintage Macs themselves. This would meet the need of someone who bought a Mac but didn't have an ADB mouse and keyboard and saves them the extra expense of buying a separate USB to ADB adapter.

The way this would work is that they would plug in a USB keyboard and mouse to the Raspberry Pi and then boot a custom boot image. This boot image would have a small system extension preinstalled that would read mouse and keyboard information via SCSI and use it to control the Mac.

The extension would more or less be what I already do with MiniVNC, only streamlined. Right now I can already boot a Mac without an ADB mouse and keyboard using a specially prepared boot image, but it's a bit of a Rube Goldberg situation since it uses TCP and you need a full PC running a VNC client to control the Mac.

On the Linux side of things, I found a blog with Python code that shows how to grab raw mouse coordinates and keyboard events from Linux, even if an X server is not running. I have already verified that it works on the RaSCSI and spits out raw data from a directly attached keyboard and mouse.

The only bit I don't know how to do is how to implement a custom SCSI device in RaSCSI. Any pointers on this would be appreciated.

marciot avatar Apr 09 '22 01:04 marciot

@marciot Regarding implementing custom devices there is some information on https://github.com/akuker/RASCSI/wiki/rules-for-adding-new-device-types. The SCSI command overhead is quite huge. I would expect a mouse driver to send a lot of commands in a short period of time. In such a case SCSI does not work very well, as we can also see with the Daynaport. If mouse events are sent during interrupts on the client side (not unusual with keyboard or mouse events) it might collide with other SCSI commands executed on behalf of the client at the same time. In addition, wouldn't you need the Pi to act as a SCSI initiator, because you want to send data to the Mac instead of from the Mac to the Pi? Or do you want to poll, which would probably not result in a good user experience.

uweseimet avatar Apr 09 '22 07:04 uweseimet

@uweseimet: I was thinking about polling. Since I've already gotten this working via the DaynaPort drivers, using MacTCP and my MiniVNC client, I already know it is usable, albeit a tad on the sluggish side. My hope is that by getting rid of the overhead of the DaynaPort drivers, MacTCP, and the VNC protocols, I might have something that is fairly useable. Although this is the sort of thing I won't know for sure until I try it out.

One effect I see with MiniVNC is that if I move the mouse slowly, it tracks alright. But if I move it fast, the Mac cannot keep up and takes a while to catch up. The mouse motion is smooth, because each mouse position is transmitted as a TCP/IP packet and is queued, but the cursor path on the screen lags the actual mouse movements on the client by a lot. By using polling without queueing, I'm hoping the effect can be minimized.

marciot avatar Apr 09 '22 15:04 marciot

@uweseimet: I am trying to use your print device as a template for my keyboard and mouse driver, but that code does not provide an example of how to read data from the RaSCSI. Basically, instead of:

SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP"), ScsiPrinterCommands() {
	dispatcher.AddCommand(eCmdWrite6, "Print", &SCSIPrinter::Print);
}

void SCSIPrinter::Print(SCSIDEV *controller)
{
	if (!CheckReservation(controller)) {
		return;
	}

	uint32_t length = ctrl->cmd[2];
	length <<= 8;
	length |= ctrl->cmd[3];
	length <<= 8;
	length |= ctrl->cmd[4];

	LOGTRACE("Receiving %d bytes to be printed", length);

	// TODO This device suffers from the statically allocated buffer size,
	// see https://github.com/akuker/RASCSI/issues/669
	if (length > (uint32_t)ctrl->bufsize) {
		LOGERROR("Transfer buffer overflow");

		controller->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
		return;
	}

	ctrl->length = length;
	controller->SetByteTransfer(true);

	controller->DataOut();
}

bool SCSIPrinter::WriteBytes(BYTE *buf, uint32_t length)
{
	if (fd == -1) {
		strcpy(filename, TMP_FILE_PATTERN);
		fd = mkstemp(filename);
		if (fd == -1) {
			LOGERROR("Can't create printer output file: %s", strerror(errno));
			return false;
		}

		LOGTRACE("Created printer output file '%s'", filename);
	}

	LOGTRACE("Appending %d byte(s) to printer output file", length);

	write(fd, buf, length);

	return true;
}

I need something that turns things around and provides data via a eCmdRead6. Here is my first attempt:

ScsiKvm::ScsiKvm() : PrimaryDevice("SCKM")
{
	dispatcher.AddCommand(eCmdRead6,    "ReadEvents",  &ScsiKvm::ReadEvents);
}

int ScsiKvm::ReadEvents(const DWORD *cdb, BYTE *buf)
{
	if (!CheckReservation(controller)) {
		return;
	}

	uint32_t length = ctrl->cmd[2];
	length <<= 8;
	length |= ctrl->cmd[3];
	length <<= 8;
	length |= ctrl->cmd[4];

	LOGTRACE("Sending %d bytes of events", length);

	// TODO This device suffers from the statically allocated buffer size,
	// see https://github.com/akuker/RASCSI/issues/669
	if (length > (uint32_t)ctrl->bufsize) {
		LOGERROR("Transfer buffer overflow");

		controller->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
		return;
	}

	ctrl->length = length;
	controller->SetByteTransfer(true);

	controller->DataIn();
}

// This function does not actually exist.
bool ScsiKvm::ReadBytes(BYTE *buf, uint32_t length)
{
	return true;
}

It looks like it isn't as simple as this, because there is no "ReadBytes" command in the base class "PrimaryDevice" to override. Any hints on how to proceed?

marciot avatar Apr 12 '22 17:04 marciot

@marciot I recommend studying other parts of the RaSCSI code for how data can be read. Note that most likely some refactoring is required in order to have a clean solution for reading data where the amount of bytes read is not a multiple of a sector. A lot of the current code can only deal with sectors, not with data of any length. Also see https://github.com/akuker/RASCSI/issues/479, which is related becasue READ/WRITE LONG also deals with transferring an arbitrary number of bytes.

uweseimet avatar Apr 12 '22 17:04 uweseimet

@uweseimet: Okay, it looks like I may have gotten more than I bargained for!

marciot avatar Apr 13 '22 20:04 marciot

@marciot - You may be interested in another side project I've been (very slowly) working on.... https://github.com/akuker/adbuino

akuker avatar Apr 19 '22 01:04 akuker

@akuker: I’m sure there is a market for this, but my thought was to have the RaSCSI itself act as a keyboard/mouse interface so you didn’t need to buy two pieces of hardware.

Anyhow, making a custom SCSI device seems to be beyond my abilities at this point, but it occurred to me that I could just as well write a headless VNC client that would run on Raspberry Pi and connect to MiniVNC running via the existing DaynaPort driver. I think that would accomplish what I want without cluttering RaSCSI with yet another custom SCSI device.

If this worked well, I would think there could simply be a button on the RaSCSI web interface that would start the headless VNC client to allow a connected mouse and keyboard to control the Mac.

marciot avatar Apr 19 '22 02:04 marciot

Based on the discussion, I'm closing this as WontFix. HID support is beyond the scope of RaSCSI.

rdmark avatar Aug 19 '22 16:08 rdmark