Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ To use the MSR605, make sure your user has access to the serial ports
(`dialout` group for Debian-based, `uucp` for Arch).

## Limitations

- Writing currently has some issues. I'll fix it soon!
- Write seems to be working only in Hi-Co. Lo-Co still has some issues
- The GUI looks different depending on your system theme; see [andlabs/ui](https://github.com/andlabs/ui)
- Refreshing the list of devices just adds onto it.
- Saving / opening files currently doesn't work, as I want to make it compatible with Deftun's MSRX software.

## Disclaimer

This fork's bug fixes and improvements were developed with the assistance of AI (Claude Code Max by Anthropic).
107 changes: 79 additions & 28 deletions internal/gui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type App struct {
infoTypeCB *ui.Combobox
showInfoButton *ui.Button
tracks [3]*Track
lastRawTracks [3][]byte
resetButton,
readButton,
writeButton,
Expand All @@ -59,7 +60,10 @@ func (a *App) throwErr(err error) {
if err == usb.ErrDeviceClosed {

} else {
ui.MsgBoxError(a.win, "Error", err.Error())
msg := err.Error()
ui.QueueMain(func() {
ui.MsgBoxError(a.win, "Error", msg)
})
}
}

Expand Down Expand Up @@ -113,7 +117,8 @@ func (a *App) selectDevice(cb *ui.Combobox) {
if cb.Selected() == 0 {
return // already disconnected
}
d, err := a.availableDevs[cb.Selected()-1].Open()
devInfo := a.availableDevs[cb.Selected()-1]
d, err := devInfo.Open()
if err != nil {
cb.SetSelected(0)
a.throwErr(err)
Expand Down Expand Up @@ -160,13 +165,17 @@ func (a *App) setFrozen(f bool) {

func (a *App) freeze() {
a.mu.Lock()
a.progBar.Show()
a.setFrozen(true)
ui.QueueMain(func() {
a.progBar.Show()
a.setFrozen(true)
})
}

func (a *App) unfreeze() {
a.setFrozen(false)
a.progBar.Hide()
ui.QueueMain(func() {
a.setFrozen(false)
a.progBar.Hide()
})
a.mu.Unlock()
}

Expand Down Expand Up @@ -218,21 +227,47 @@ func (a *App) read() {
a.throwErr(err)
return
}
for i, t := range a.tracks {
chars, _, _ := t.decode(rawTracks[i])
t.edit.SetText(string(chars))
a.lastRawTracks = rawTracks
ui.QueueMain(func() {
for i, t := range a.tracks {
chars, _, _ := t.decode(rawTracks[i])
t.edit.SetText(string(chars))
}
})
}

// stripSentinels removes start sentinel (%/;/+) and end sentinel (?) from ISO track data.
// DecodeRaw includes sentinels, but the MSR605 ISO write adds them automatically.
func stripSentinels(data []byte) []byte {
if len(data) == 0 {
return data
}
start := 0
if data[0] == '%' || data[0] == ';' || data[0] == '+' {
start = 1
}
end := len(data)
if end > start && data[end-1] == '?' {
end--
}
return data[start:end]
}

func (a *App) write() {
a.freeze()
defer a.unfreeze()
// Coercivity is set by the radio button handler.
var tracks [3][]byte
for i, track := range a.tracks {
if !track.disabled {
tracks[i] = []byte(track.edit.Text())
done := make(chan struct{})
ui.QueueMain(func() {
for i, track := range a.tracks {
if !track.disabled {
tracks[i] = stripSentinels([]byte(track.edit.Text()))
}
}
}
close(done)
})
<-done
err := a.device.WriteISOTracks(tracks[0], tracks[1], tracks[2])
if err != nil {
a.throwErr(err)
Expand All @@ -242,15 +277,14 @@ func (a *App) write() {
func (a *App) writeRaw() {
a.freeze()
defer a.unfreeze()
var rawTracks [3][]byte
for i, track := range a.tracks {
track.edit.SetReadOnly(true)
if !track.disabled {
chars := []byte(track.edit.Text())
rawTracks[i] = track.encode(chars)
}
// Coercivity is already set by the radio button handler (setCoercivity).
// Sending it again right before raw write causes immediate rejection on MSR605X.
// Restore standard leading zeros (may have been changed by previous operations).
if err := a.device.SetLeadingZeros(61, 22); err != nil {
a.throwErr(err)
return
}
err := a.device.WriteRawTracks(rawTracks[0], rawTracks[1], rawTracks[2])
err := a.device.WriteRawTracks(a.lastRawTracks[0], a.lastRawTracks[1], a.lastRawTracks[2])
if err != nil {
a.throwErr(err)
}
Expand All @@ -259,13 +293,29 @@ func (a *App) writeRaw() {
func (a *App) erase() {
a.freeze()
defer a.unfreeze()
var tracks [3]bool
for i, t := range a.tracks {
if !t.disabled {
tracks[i] = true
t.edit.SetText("")
if a.coRadio.Selected() == hiCo {
if err := a.device.SetHiCo(); err != nil {
a.throwErr(err)
return
}
} else {
if err := a.device.SetLoCo(); err != nil {
a.throwErr(err)
return
}
}
var tracks [3]bool
done := make(chan struct{})
ui.QueueMain(func() {
for i, t := range a.tracks {
if !t.disabled {
tracks[i] = true
t.edit.SetText("")
}
}
close(done)
})
<-done
err := a.device.Erase(tracks[0], tracks[1], tracks[2])
if err != nil {
a.throwErr(err)
Expand Down Expand Up @@ -388,7 +438,9 @@ func MakeMainUI(win *ui.Window) ui.Control {
a.readButton = ui.NewButton("Read")
a.readButton.OnClicked(func(*ui.Button) { go a.read() })
a.writeButton = ui.NewButton("Write")
a.writeButton.OnClicked(func(*ui.Button) { go a.write() })
a.writeButton.OnClicked(func(*ui.Button) {
go a.write()
})
a.eraseButton = ui.NewButton("Erase")
a.eraseButton.OnClicked(func(*ui.Button) { go a.erase() })
a.openButton = ui.NewButton("Open file")
Expand Down Expand Up @@ -420,7 +472,6 @@ func MakeMainUI(win *ui.Window) ui.Control {
if len(a.availableDevs) > 0 {
d, err := a.availableDevs[0].Open()
if err == nil {
a.device = libmsr.NewDevice(d)
a.deviceCB.SetSelected(1)
a.connect(d)
}
Expand Down
Loading