Skip to content
Merged
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ The project includes E2E boot tests (`e2e_boot_test.go`, `e2e_woz_test.go`) that
- Main emulator code is in the root package `izapple2`
- Subpackages include: `storage`, `screen`, `fujinet`, `component`
- Frontend implementations are in `frontend/` directory
- `frontend/shared` has the code more than one frontend needs and the emulator library has no place for, like the drop targets screen

### Naming Conventions

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Each one has a page with how to build it, how to use it and what it can not do:
- [**a2sdl3**](doc/frontend_sdl3.md): the same, on SDL3. It needs neither cgo, nor a C compiler, nor SDL developer files, and it cross compiles to every platform from any of them. Experimental, not in the releases.
- [**console**](doc/frontend_console.md): text mode right on the terminal with ANSI escape codes, without the SDL2 dependency. Input goes in a line at a time.
- [**a2libretro**](doc/frontend_libretro.md): a libretro core, to run inside RetroArch, Lakka, Batocera, RetroPie and the rest. On a Mac, [doc/frontend_libretro_macos.md](doc/frontend_libretro_macos.md) walks through the whole thing from installing RetroArch.
- [**a2ebiten**](doc/frontend_ebiten.md): a window with [Ebitengine](https://ebitengine.org/). The same keys as a2sdl, no disks to drop and no joysticks. It is the desktop half of the WebAssembly frontend.
- [**a2ebiten**](doc/frontend_ebiten.md): a window with [Ebitengine](https://ebitengine.org/). The same keys as a2sdl, the same disks dropped on the window and no joysticks. It is the desktop half of the WebAssembly frontend.
- [**a2wasm**](doc/frontend_wasm.md): the emulator in the browser, compiled to WebAssembly with a React interface around it.
- [**a2fyne**](doc/frontend_fyne.md): a window with [Fyne](https://fyne.io/), a toolbar and a panel listing the cards in the slots. No sound. Unfinished.
- [**headless**](doc/frontend_headless.md): no window and no screen, a command prompt to drive the machine and take snapshots. For scripting and for tests.
Expand Down
24 changes: 10 additions & 14 deletions apple2.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import (

// Apple2 represents all the components and state of the emulated machine
type Apple2 struct {
Name string
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
video screen.VideoSource
cg *CharacterGenerator
Name string
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
video screen.VideoSource
cg *CharacterGenerator

cards [8]Card
tracers []executionTracer
tickerCards []cardTicker
Expand All @@ -42,7 +43,7 @@ type Apple2 struct {
paused atomic.Bool
cpuTrace bool
forceCaps bool
removableMediaDrives []drive
removableMediaDrives []removableMediaDrive

currentFreqMHz float64
}
Expand Down Expand Up @@ -78,7 +79,6 @@ func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
a.io.setKeyboardProvider(kb)
}


// SetJoysticksProvider attaches an external joysticks provider
func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) {
a.io.setJoysticksProvider(j)
Expand Down Expand Up @@ -157,20 +157,16 @@ func (a *Apple2) IsFastModeRequested() bool {
return atomic.LoadInt32(&a.fastRequestsCounter) > 0
}

func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, d)
}

// InsertDiskette inserts a diskette into a drive (for WASM/in-memory loading)
func (a *Apple2) InsertDiskette(unit int, diskette storage.Diskette, name string) error {
if unit < 0 || unit >= len(a.removableMediaDrives) {
return fmt.Errorf("unit %v not defined", unit)
}

// Type assert to access the diskette field
switch d := a.removableMediaDrives[unit].(type) {
switch d := a.removableMediaDrives[unit].drive.(type) {
case *cardDisk2Drive:
d.name = name
d.name.set(name)
d.diskette = diskette
return nil
default:
Expand Down
18 changes: 12 additions & 6 deletions cardDisk2.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type CardDisk2 struct {

type drive interface {
insertDiskette(path string) error
getMediaName() string
}

type cardDisk2Drive struct {
Expand All @@ -49,7 +50,7 @@ type cardDisk2Drive struct {
// card and is kept here for the diskettes inserted later on.
saveDirectory string

name string
name mediaName
diskette storage.Diskette
phases uint8 // q3, q2, q1 and q0 with q0 on the LSB. Magnets that are active on the stepper motor
trackStep int // Stepmotor for tracks position. 4 steps per track
Expand Down Expand Up @@ -124,10 +125,10 @@ func (c *CardDisk2) GetInfo() map[string]string {
}
info["power"] = strconv.FormatBool(c.power)

info["D1 name"] = c.drive[0].name
info["D1 name"] = c.drive[0].name.get()
info["D1 track"] = strconv.FormatFloat(float64(c.drive[0].trackStep)/4, 'f', 2, 64)

info["D2 name"] = c.drive[1].name
info["D2 name"] = c.drive[1].name.get()
info["D2 track"] = strconv.FormatFloat(float64(c.drive[1].trackStep)/4, 'f', 2, 64)
return info
}
Expand All @@ -147,8 +148,9 @@ func (c *CardDisk2) setTrackTracer(tt trackTracer) {
}

func (c *CardDisk2) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1])
for i := range c.drive {
a.registerRemovableMediaDrive(&c.drive[i], fmt.Sprintf("S%vD%v", slot, i+1))
}

// Q1, Q2, Q3 and Q4 phase control soft switches,
for i := range uint8(4) {
Expand Down Expand Up @@ -307,7 +309,11 @@ func (d *cardDisk2Drive) insertDiskette(name string) error {
return err
}

d.name = name
d.name.set(name)
d.diskette = diskette
return nil
}

func (d *cardDisk2Drive) getMediaName() string {
return d.name.get()
}
9 changes: 6 additions & 3 deletions cardDisk2Sequencer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package izapple2

import (
"fmt"

"github.com/ivanizag/izapple2/component"
)

Expand Down Expand Up @@ -46,7 +48,7 @@ type cardDisk2Shared interface {

const (
disk2MotorOffDelay = uint64(2 * 1000 * 1000) // 2 Mhz cycles. Total 1 second.
disk2PulseCycles = uint8(8) // 8 cycles = 4ms * 2Mhz
disk2PulseCycles = uint8(8) // 8 cycles = 4ms * 2Mhz

/*
We skip register calculations for long periods with the motor
Expand Down Expand Up @@ -135,8 +137,9 @@ func (c *CardDisk2Sequencer) setTrackTracer(tt trackTracer) {
}

func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1])
for i := range c.drive {
a.registerRemovableMediaDrive(&c.drive[i], fmt.Sprintf("S%vD%v", slot, i+1))
}

c.addCardSoftSwitches(func(address uint8, data uint8, _ bool) uint8 {
/*
Expand Down
6 changes: 6 additions & 0 deletions cardDisk2SequencerDrive.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
)

type cardDisk2SequencerDrive struct {
name mediaName
data *storage.FileWoz
enabled bool
writeProtected bool
Expand All @@ -35,12 +36,17 @@ func (d *cardDisk2SequencerDrive) insertDiskette(filename string) error {
return errors.New("only 5.25 disks are supported")
}

d.name.set(filename)
d.data = f
d.writeProtected = !writeable

return nil
}

func (d *cardDisk2SequencerDrive) getMediaName() string {
return d.name.get()
}

func (d *cardDisk2SequencerDrive) enable(enabled bool) {
d.enabled = enabled
}
Expand Down
4 changes: 2 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ func (a *Apple2) executeCommand(command command) {
}

func (a *Apple2) changeDisk(unit int, path string) error {
if unit < len(a.removableMediaDrives) {
return a.removableMediaDrives[unit].insertDiskette(path)
if unit >= 0 && unit < len(a.removableMediaDrives) {
return a.removableMediaDrives[unit].drive.insertDiskette(path)
}
return fmt.Errorf("unit %v not defined", unit)
}
35 changes: 26 additions & 9 deletions doc/frontend_ebiten.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# The Ebitengine frontend

`a2ebiten` opens a window with [Ebitengine](https://ebitengine.org/). It has
the screen with all its modes, the sound and the same function keys as
[a2sdl](frontend_sdl2.md), and it is missing the things around them: no
diskettes dropped on the window, no joysticks and no mouse.
the screen with all its modes, the sound, the diskettes dropped on the window
and the same function keys as [a2sdl](frontend_sdl2.md), and it is missing the
things around them: no joysticks and no mouse.

## Building

Expand All @@ -24,9 +24,8 @@ go build .
casa@servidor:~$ ./a2ebiten
```

The [command line options](command_line.md) are the same as everywhere else,
and the diskettes have to go in the command line: this frontend has no way to
insert one afterwards. Press F1 for the help.
The [command line options](command_line.md) are the same as everywhere else.
Press F1 for the help.

## Keys

Expand All @@ -37,12 +36,30 @@ The same as [a2sdl](frontend_sdl2.md#keys), with two differences:
terminal.
- There is no paste from the clipboard.

The help screen that F1 shows is the one of a2sdl and mentions dropping a file
on the window. That does not work here.
## Diskettes

Drop a file on the window to insert it. The window is divided in as many
vertical areas as removable media drives the machine has, and the file goes to
the drive of the area it is dropped on. F8 shows a screen with the areas, each
one with the name of its drive and the image it has inserted, or `EMPTY`, and
it is shown again for a moment after a drop with the drive that got the file
marked.

Ebitengine hands over the files dropped as a file system that hides their
paths, but it opens the real files and the handle of a file tells its path
back. The image is loaded from there like on any other frontend, compressed
files included, and whatever the emulated software writes goes back to the file
or to the `-saveDir` directory. In the browser the files dropped have no path
and cannot be loaded.

Ebitengine does not report where a file was dropped either, and the pointer
position is not updated while another application drags a file over the window,
so the area used is the one the pointer was last seen on. Check with F8 before
dragging, or look at the areas shown after the drop to see where the file
landed.

## What is missing

- **Diskettes dropped on the window.** They go in the command line.
- **Joysticks and paddles**, and the mouse as a joystick.
- **The mouse**, so the models that use it, like `desktop`, are not much use.
- **Pasting** from the clipboard.
Expand Down
20 changes: 17 additions & 3 deletions doc/frontend_sdl2.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Escape mapped to what the Apple II understands. The rest:
| Ctrl-F5 | Print the current speed on the terminal |
| F6 | Next screen mode: NTSC colour, plain, green, with or without scan lines |
| F7 | Show or hide the four panels with the actual screen, page 1, page 2 and the extra info of the video mode |
| F8 | Show or hide the areas where a diskette can be dropped |
| F9 | Dump the state of the machine on the terminal |
| F10 | Next character set |
| Ctrl-F10 | Show or hide the character map |
Expand All @@ -110,9 +111,22 @@ being displayed instead.

## Diskettes

Drop a file on the window to insert it: on the left half it goes to drive 1, on
the right half to drive 2. It works with everything that goes on the command
line, including compressed images.
Drop a file on the window to insert it. The window is divided in as many
vertical areas as removable media drives the machine has, and the file goes to
the drive of the area it is dropped on. It works with everything that goes on
the command line, including compressed images.

F8 shows a screen with the areas, each one with the name of its drive and the
image it has inserted, or `EMPTY`, and the one under the pointer marked. It
takes over the picture the same way the help does, on 80 columns. The same
screen is shown for a moment after a drop, with the drive that got the file
marked.

SDL2 does not report a file being dragged over the window, so the areas cannot
be shown while the file moves; the position of the pointer is read when the
file is dropped. The [SDL3 frontend](frontend_sdl3.md) does show them during
the drag. The areas are the same on every frontend, they are built in
`frontend/shared`.

Whatever the emulated software writes goes back to the file it came from. To
keep the images untouched use `-saveDir`, which puts the writes in an overlay
Expand Down
7 changes: 4 additions & 3 deletions doc/frontend_sdl3.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ and the Closed-Apple would never be pressed if it were looked up by keycode.

Nothing that you use, and a few things underneath:

- SDL3 reports where a file was dropped, so the drive it goes to is the one
under the pointer. In SDL2 it is worked out from the last known mouse
position.
- SDL3 reports a file being dragged over the window, so the screen with the
areas of the drives it can be dropped on is shown while it moves, with the
one under the pointer marked. SDL2 only knows about the file once it is
dropped, and has to show the areas with F8 or after the drop.
- Losing the window focus releases the joystick keys, so an Open-Apple held
while switching windows does not stay pressed.
- Ctrl-C on the terminal quits the same way as closing the window, releasing
Expand Down
Loading
Loading