Add macOS support to the GUI#1010
Conversation
MPF found no drives at all on macOS. Environment.OSVersion.Platform reports PlatformID.Unix there, so GetDriveList took the Linux path, whose enumerators read /sys/block, /sys/class/scsi_generic and /dev/sr*. None of those exist on macOS, so each returned nothing without failing and the drive list came back empty, leaving the GUI showing "Found no drives". macOS now has its own enumerator built on `diskutil info`, mirroring the structure of Drive.Linux.cs. Optical drives are identified by the presence of diskutil's optical-only fields rather than by any value they carry: an emulated drive leaves "Optical Media Type" blank and reports its protocol as SATA, while a real USB drive fills the field in and reports USB, so neither value is a reliable marker on its own. Optical media is also left out of the DriveInfo query on macOS. macOS mounts every readable disc on insertion, and DriveInfo reports the mounted volume (e.g. "/Volumes/LABEL") as a CD-ROM drive. That is a mount point rather than a dumpable device, and diskutil already surfaces the drive holding the disc as a device node, so it would otherwise show up as a second, unusable entry for every disc that is inserted. Verified on macOS 26 against fixed disks, a USB floppy drive, and both an emulated and a real USB optical drive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The drive name reaches the dumping program verbatim, and on macOS a program takes a drive as its BSD name rather than as a device node path. Redumper resolves the name against the BSD name IOKit publishes for the drive and fails with "failed to find matching SCSI authoring device" when it is given "/dev/disk4", so every dump from an enumerated drive failed. This mirrors the existing platforms: the name is a drive letter on Windows and a device node on Linux, in both cases the form the program expects. Verified against a USB PLEXTOR PX-760A on macOS 26: redumper reads the drive and dumps a mixed-mode CD when given "disk4". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PrepareMacRedumperParameters stripped "--drive" from the Redumper parameters whenever the selected drive name began with "/Volumes/". That was a workaround for enumeration: the only optical entry macOS surfaced was the mounted volume rather than a device, and no dumping program takes a mount point as a drive. Dropping the argument made Redumper fall back to picking the first drive that holds a disc, which is only ever right when a single drive is loaded. With two loaded drives it silently dumped one the user had not selected. Enumeration now surfaces the device itself and passes it as its BSD name, so the workaround has nothing left to match and the guess is no longer needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dumping tool gets no console window of its own on macOS, the same as on Linux, so the GUI ran the tool with no visible output at all. The console window added for the Linux GUI (SabreTools#998) already fits: the window, its terminal buffer, and the output pump are platform-neutral. Only the gate that creates the console was limited to Linux, so macOS is added to it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mnadareski
left a comment
There was a problem hiding this comment.
There are definitely going to be merge conflicts given the amount of changes I did.
| /// <summary> | ||
| /// A macOS whole-disk device discovered via diskutil. | ||
| /// </summary> | ||
| internal sealed class MacOSBlockDevice |
There was a problem hiding this comment.
In changes that were queued up while this was being made, I removed the UnixBlockDevice type that this was modelled on. I would strongly prefer using Drive for all appropriate places instead of creating another new type that essentially does the same thing.
There was a problem hiding this comment.
Done — MacOSBlockDevice is gone. ParseMacOSDiskutilInfo now returns a Drive and EnumerateMacOSDevices a List<Drive>, built with an object initializer the same way EnumerateUnixFixedDevices does. The BSD name is the DevicePath, which is what DumpEnvironment hands to the dumping program verbatim.
Generated with Claude Opus 4.8.
| Directory.CreateDirectory(root); | ||
| try | ||
| { | ||
| File.WriteAllText(Path.Combine(root, "disk0"), string.Empty); |
There was a problem hiding this comment.
When I moved some of the previous methods to SabreTools.IO, I realized that each of these tests was constantly creating and deleting the exact same files every time. An easier and cheaper way of doing this is having the test files setup and just copied so the test runner can read them as they are without having to worry where they might end up. Look for instances of TestData in tests and project files to get an idea of what this would look like.
There was a problem hiding this comment.
Done — the seven fake device nodes now live in MPF.Frontend.Test/TestData/MacOSDev and are copied to the output directory, so the test just reads them where they are. I checked that it still fails without them (Assert.Equal() Failure: Collections differ) rather than passing vacuously.
The sysfs tests above still build their trees at runtime. Happy to convert those in this PR too if you want them done together.
Generated with Claude Opus 4.8.
| var desiredDriveTypes = new List<DriveType>(); | ||
| if (!isMacOS) | ||
| desiredDriveTypes.Add(DriveType.CDRom); |
There was a problem hiding this comment.
I feel like keeping the original logic here is pretty harmless even if it's not used in the MacOS path.
There was a problem hiding this comment.
Reverted — GetDriveList is back to new List<DriveType>() { DriveType.CDRom }.
It does have an effect on macOS though, so rather than dropping the handling I moved it into the macOS enumerator. macOS automounts every readable disc, and .NET reports that mount as a DriveType.CDRom drive, so the mount point ends up in the list next to the device that holds the disc. Measured just now on macOS 26.5, with the disc mounted (/dev/disk1s0 on /Volumes/MPFTEST):
Without the filter:
DevicePath=/Volumes/MPFTEST Type=Optical Display=MPFTEST
DevicePath=disk1 Type=Optical Display=disk1
DevicePath=disk4 Type=Optical Display=disk4
With it:
DevicePath=disk1 Type=Optical Display=disk1
DevicePath=disk4 Type=Optical Display=disk4
The /Volumes/... entry is not dumpable: DumpEnvironment hands DevicePath to the program verbatim, and Redumper wants the BSD name. So it is a second, failing entry for a disc that is already listed under disk1. AppendMacOSDrives now drops mount points, which is also what PrepareMacRedumperParameters was working around — that is why this branch removes it.
For what it is worth, Linux produces the same double entry on an auto-mounting desktop (/run/media/<user>/MPFTEST next to /dev/sr0). That is pre-existing and left alone here.
Generated with Claude Opus 4.8.
Two conflicts with the changes queued up upstream: - Drive.Name became Drive.DevicePath, and the helpers the macOS enumerator borrowed from the Linux one (the whole-disk node filter and the floppy media sizes) moved to SabreTools.IO as internals. Both are reproduced here, adjusted for the macOS naming convention (diskN, not srN/sdN). - PrepareMacRedumperParameters() is still removed. It strips "--drive" from the Redumper parameters when the drive name starts with "/Volumes/", which only happens because DriveInfo surfaces a mounted disc as a drive. This branch enumerates the device behind the disc instead, so the drive is named by its BSD name and the strip never applies.
- Drop MacOSBlockDevice and use Drive throughout, the way EnumerateUnixFixedDevices does since UnixBlockDevice was removed. ParseMacOSDiskutilInfo now returns a Drive and EnumerateMacOSDevices a List<Drive>, so the enumerator no longer carries a second type that holds the same fields. The BSD name becomes DevicePath, which is what a macOS dumping program takes and what DumpEnvironment passes on verbatim. - Read the device nodes for the node-filter test from a checked-in TestData directory instead of writing and deleting the same seven files on every run. - Leave the DriveInfo query in GetDriveList as it was. macOS still reports a mounted disc as a CD-ROM drive, so the mount point is dropped in the macOS enumerator, next to the code that surfaces the device behind it. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
MPF finds no drives at all on macOS: the GUI shows "Found no drives" and no dump can be started.
Environment.OSVersion.PlatformreportsPlatformID.Unixon macOS, soGetDriveListtakes the Linux path, whose enumerators read/sys/block,/sys/class/scsi_genericand/dev/sr*. None of those exist on macOS, so each returns nothing without failing and the drive list comes back empty.Four commits, each one thing.
Add macOS drive enumeration
A macOS enumerator built on
diskutil info, mirroring the structure ofDrive.Linux.cs.Optical drives are identified by the presence of diskutil's optical-only fields rather than by any value they carry: an emulated drive leaves "Optical Media Type" blank and reports its protocol as SATA, while a real USB drive fills the field in and reports USB, so neither value is a reliable marker on its own.
Optical media is left out of the
DriveInfoquery here. macOS mounts every readable disc on insertion, andDriveInforeports the mounted volume (for example/Volumes/LABEL) as a CD-ROM drive. A mount point is not a dumpable device, and diskutil already surfaces the drive holding the disc, so the volume would only add a second, unusable entry for the same disc.Name macOS drives by BSD name
The drive name reaches the dumping program verbatim. On macOS a program takes a drive as its BSD name rather than as a device node path: redumper resolves the name against the BSD name IOKit publishes for the drive and fails with
when it is given
/dev/disk4. Without this, every dump from an enumerated drive fails.This mirrors the existing platforms: the name is a drive letter on Windows and a device node on Linux, in both cases the form the program expects.
Remove the Redumper drive workaround on macOS
PrepareMacRedumperParametersstripped--drivefrom the Redumper parameters whenever the selected drive name began with/Volumes/. It existed because the mounted volume was the only optical entry macOS used to surface, and no dumping program takes a mount point as a drive.Dropping the argument made Redumper fall back to picking the first drive that holds a disc, which is only ever right when a single drive is loaded. With two loaded drives it silently dumped one the user had not selected.
With the device itself enumerated and passed as its BSD name, the workaround has nothing left to match and the guess is no longer needed.
Show the tool output console on macOS
A dumping tool gets no console window of its own on macOS, the same as on Linux, so the GUI ran the tool with no visible output at all.
The console window added for the Linux GUI (#998) already fits: the window, its terminal buffer, and the output pump are platform-neutral. Only the gate that creates the console was limited to Linux, so macOS is added to it. This is the whole change:
Verification
macOS 26 (x86_64) with a USB PLEXTOR PX-760A. The drive is enumerated, selected in the GUI, and dumped end to end with redumper: a mixed-mode CD, 0 SCSI errors, 0 C2 errors, and a
.scrambyte-for-byte identical in size to the same disc dumped on Linux. The tool output console shows the live output during the dump.316 lines of tests are added to
MPF.Frontend.Test/DriveTests.cs, covering the diskutil parsing, the optical-drive detection, and the BSD naming. The suite is green (582 tests).Prepared with AI assistance (Claude Opus 4.8) and reviewed before submission.