Version 1.8, see changelog for details

Tons of changes, see changelog

Signed-off-by: Michel Pollet <buserror@gmail.com>
This commit is contained in:
Michel Pollet 2024-03-13 08:45:11 +00:00
parent 2e12a37bce
commit e231ecb762
No known key found for this signature in database
80 changed files with 6982 additions and 1228 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ cachegrind.out.*
callgrind.out.*
.cache
*.miov
hezner
*_scrot.png

97
CHANGELOG.md Normal file
View File

@ -0,0 +1,97 @@
<p align="center">
<img src="contrib/mii-icon-64.png" alt="MII Logo">
</p>
# MII Version Changelog
## 1.8
* Changed the floppy disk view. *It now rotates*, and the heat map is now a
'trail' of the head, showing where it's been. It's a bit more useful, and
looks cooler.
<div align="center">
<img src="docs/screen/v18new_display.gif" alt="New Floppy display">
</div>
<center><i>Poor quality gif, It is a LOT smoother at 60fps in the program!</i>
</center>
* Added support for a *Ramworks III card*, with 1MB of RAM. It could have more,
but I thought and extra whole friggin MEGABYTE was enough for anyone.
* Added support for flashing text in text mode. I know, it was a bit of a
glaring omission, but it's there now.
* Internal changes to the UI, I've split the whole codebase into a few more files,
split the 'XORG/GLX' code from the 'Pure GL' code from the 'MII UI' code, so it should be a lot easier to port to other platforms.
* Redid the *DHRES rendering*, it's now a lot more accurate, has the correct
artifacts. It's not as optimized as the previous version, but it looks better.
* Now remap the *joystick* coodinates to a 'square' -- my current 8bitdo joystick
has a circular deadzone, and it was a bit annoying to use. I might make that
a setting, but for now, it's hardwired.
* *Emulator now passes a2audit*. There is only one kludge to do it, regarding the
'phantom read' of the indirect addressing.
* *Working Super Serial Card Driver*, it can bridge to a Linux /dev device for the moment, or to
a 'fake' loopback device. IN#x and PR#x works, and I can bootstrap using
ADTPro. It's all in there so I can run Mastodon!
<div align="center">
<img src="docs/screen/v18ssc_dialog.png" alt="SSC Config">
</div>
<center><i>Super Serial Card config dialog</i></center>
### libmui
* Standard file picker now shows *floppy icons*.
* Added a *Color Apple Menu*, in pure Macintosh II style.
<div align="center">
<img src="docs/screen/v18colorapple.png" alt="Color Apple">
</div>
* Fixed a few minor memory leaks.
* Tons more stuff in libmui, inc new font styles (bold, underline, condensed). There is also a text edit control now (still prototype).
### Internals
* Made an architecture document, see [Compiling](docs/Compiling.md) for a top-down view.
* Ported the support for VCD (Value Change Dump) from simavr, so I can now
record and playback the whole simulation of the floppy driver. No real use for the user.
* Split the video rendering into bits with a 'main' line rendering function pointer that is set only when video mode changes. This is a bit faster, and that gets rid of the Giant Function.
## 1.7
* New animated about box, because, everyone loves a good about box.
* Added support for Write Protect of floppy disks; disk can be write protected manually, or if the file lacks write permissions, OR if the file format (NIB, DSK) doesn't support writes.
* New fancypants 'bit view' of the floppy are they are read/written, with a
heat map to show where the head was last. Drive 1 appears left of the screen,
drive 2 on the right. It fades out after the drive motor stops.
<div align="center">
<img src="docs/screen/v17heatmap.png" alt="Heat map disk view">
</div>
<center><i>DOS 3.3 Disk 'bitstream view' on the left, the green trace shows what's just be read.</i></center>
## 1.6
* Another big update, yanked the old DiskII driver, and replaced it with a
homebrew one, low level, with native support for WOZ (1 & 2) files (*read AND write!*) as well as NIB and DSK (read only).
* This is big news, and MII can now boot all kind of copy protected disks, in
fact I tried a few hundreds, and they all worked so far!
* There is currently no way to create a new disk, but you can use a tool like
[CiderPress](https://a2ciderpress.com/) to create them, and then use them in MII. Or just copy your DOS 3.3.woz file and reformat it!
* There were a few other minor changes, mostly added some timing measurement
tooling, and a couple of (necessary for disk to work) tweaks to the emulator
itself as it was not counting cycles correctly for a couple of instructions.
* The UI now has support for pure power-of-two textures, for really old OpenGL
implementations, it is turned off by default, but could work with some
old hardware. Also updated *libmui* to make it less linux-centric.
* Fixed some more color issues, mostly DHIRES.
* Added 'typeahead' for when you select files in the dialog, like on old Macs
## 1.5
* BIG update, loads of changes, fixes, improvements.
* New super UI, using home-made libmui, channeling both GS/OS and MacOS 7.x!
* New emulation fixes, way more accurate. Video redone, audio redone.
* New front-end program using XLib and OpenGL 'low level'.
* New Icon.
## 1.0
* Fixed a few graphics rendering bugs/color swapped
* Fixed a few Makefile issues involving pathnamed with 'spaces' in them.
* More tweaks to the emulation, added a few cycles here and there.
## 0.9
* Added a 'debugger' shell, accessible via telnet.
* Added a mini-assembler, used to compile the drivers and the CPU unit tests.
* Added a 'Titan Accelerator IIe' simulation, to turn on/off fast mode.
## 0.5
* Initial release

View File

@ -19,7 +19,10 @@ CFLAGS += -Wno-unused-parameter -Wno-unused-function
LDLIBS += -lX11 -lGL -lGLU
LDLIBS += -lpthread -lutil -lm
VERSION := ${shell git log -1 --date=short --pretty="%h %cd"}
VERSION := ${shell \
echo $$(git describe --tags --abbrev=0 2>/dev/null || \
echo "(dev)") \
$$(git log -1 --date=short --pretty="%h %cd")}
CPPFLAGS += -DMII_VERSION="\"$(VERSION)\""
HAS_ALSA := $(shell pkg-config --exists alsa && echo 1)

View File

@ -2,58 +2,18 @@
<img src="contrib/mii-icon-64.png" alt="MII Logo">
</p>
# MII Version Changelog
## 1.7
* New animated about box, because, everyone loves a good about box.
* Added support for Write Protect of floppy disks; disk can be write protected manually, or if the file lacks write permissions, OR if the file format (NIB, DSK) doesn't support writes.
* New fancypants 'bit view' of the floppy are they are read/written, with a
heat map to show where the head was last. Drive 1 appears left of the screen,
drive 2 on the right. It fades out after the drive motor stops.
![Heat map disk viewq](docs/heat_map.png)
*DOS 3.3 Disk 'bitstream view' on the left, the green trace shows what's just be read.*
## 1.6
* Another big update, yanked the old DiskII driver, and replaced it with a
homebrew one, low level, with native support for WOZ (1 & 2) files (*read AND write!*) as well as NIB and DSK (read only).
* This is big news, and MII can now boot all kind of copy protected disks, in
fact I tried a few hundreds, and they all worked so far!
* There is currently no way to create a new disk, but you can use a tool like
[CiderPress](https://a2ciderpress.com/) to create them, and then use them in MII. Or just copy your DOS 3.3.woz file and reformat it!
* There were a few other minor changes, mostly added some timing measurement
tooling, and a couple of (necessary for disk to work) tweaks to the emulator
itself as it was not counting cycles correctly for a couple of instructions.
* The UI now has support for pure power-of-two textures, for really old OpenGL
implementations, it is turned off by default, but could work with some
old hardware. Also updated *libmui* to make it less linux-centric.
* Fixed some more color issues, mostly DHIRES.
* Added 'typeahead' for when you select files in the dialog, like on old Macs
## 1.5
* BIG update, loads of changes, fixes, improvements.
* New super UI, using home-made libmui, channeling both GS/OS and MacOS 7.x!
* New emulation fixes, way more accurate. Video redone, audio redone.
* New front-end program using XLib and OpenGL 'low level'.
* New Icon.
## 1.0
* Fixed a few graphics rendering bugs/color swapped
* Fixed a few Makefile issues involving pathnamed with 'spaces' in them.
* More tweaks to the emulation, added a few cycles here and there.
## 0.9
* Added a 'debugger' shell, accessible via telnet.
* Added a mini-assembler, used to compile the drivers and the CPU unit tests.
* Added a 'Titan Accelerator IIe' simulation, to turn on/off fast mode.
## 0.5
* Initial release
# MII Apple //e Emulator
Note: Changelog has moves to [CHANGELOG.md](CHANGELOG.md)
I know there are many out there, but none of them were ticking my fancy, so I decide to write my own. To start with it was "How hard can it be really?" then it snowballed as more and more things were fixed & added. It's been shelved for a while because well, it lacked documentation, headers, licence and stuff, so I spent some time cleaning it up for release.
One primary reason for this project was that linapple (or -pie) codebase is really horrible. It dates back from 2000's or before, with loads of Windows crud leftover, some SDL crud added, the audio just doesn't really work, and overall if you want to hack around the codebase, it's pretty dreadful.
![Quick how to load and boot](docs/video_main.gif)
*Quick Howto Load & Boot*
<center>
<img src="docs/screen/video_main.gif" alt="Quick how to load and boot">
<i>Quick Howto Load & Boot</i>
</center>
I wanted something:
@ -64,9 +24,10 @@ I wanted something:
* No gigantic config file.
* I didn't need II+ or unenhanced IIe, just 65c02 //e.
![Glorious NTSC colors](docs/screen_color.png)
*Double hires in color*
<center>
<img src="docs/screen/screen_color.png" alt="Glorious NTSC colors">
<i>Double hires in color</i>
</center>
## What can it do?
* 65c02 //e with 128K of ram.
@ -81,15 +42,18 @@ I wanted something:
* No Slot Clock
* Joystick Support
* Smartport DMA 'hard drive' card
* RAMWorks III card, with 1MB of RAM
* "Titan Accelerator //e" simulation, to turn on/off fast mode.
* Terence's J Boldt [1MB ROM card](https://github.com/tjboldt/ProDOS-ROM-Drive), also because I own a couple!
* Floppy Drive [more on that later]
* Floppy Drive with WOZ 1/2 in read/write, NIB and DSK in read only.
* No dependencies (X11) OpenGL rendering
* Built-in debugger (using telnet access)
* Super cool looking UI!
![Phosphorescent Green](docs/screen_green.png)
*Good old green monitor style. Theres Amber too.*
<center>
<img src="docs/screen/screen_green.png" alt="Phosphorescent Green">
<i>Good old green monitor style. Theres Amber too.</i>
</center>
## How to I compile it and run it?
* You need a C compiler, make, and a few libraries:
@ -107,8 +71,10 @@ I wanted something:
If you run it with no options, and there are no config file, it will present
you with a dialog to select the ROMs and the drives.
![Config dialog](docs/screen_config.png)
*Main slot configuration dialog*
<center>
<img src="docs/screen/screen_config.png" alt="Config dialog">
<i>Main slot configuration dialog</i>
</center>
You can also use the command line to specify them, and other options.
@ -161,9 +127,10 @@ There are just a few keys that are mapped for anything useful. List is not exaus
* **Control-F6** 'steps' the emulator, ie one instruction at a time.
* **Control-F7** 'next' instruction, ie step over a JSR instruction.
![Telnet into mii_emu](docs/screen_mish.png)
*The built-in shell, telnet into the emulator!*
<center>
<img src="docs/screen/screen_mish.png" alt="Telnet into mii_emu">
<i>The built-in shell, telnet into the emulator!</i>
</center>
## Anything else?
* Well it has it's own command line shell, using my own [libmish](https://github.com/buserror/libmish) so there's loads you can do by... *telnet into* the emulator!
@ -195,9 +162,11 @@ There are just a few keys that are mapped for anything useful. List is not exaus
* Make a tool to 'flatten' overlay files back into the primary image.
* Make a UI for the debugger, instead of telnet.
<div align="center">
<img src="docs/screen/screen_total.png" alt="Total Replay">
</div>
<center><i>Obligatory View of Total Replay</i></center>
![Total Replay](docs/screen_total.png)
*Obligatory View of [Total Replay](https://github.com/a2-4am/4cade), from legend [4am](https://github.com/a2-4am)*
## Inspiration, Licence etc
* MIT Licence, I think this is the most permissive, and this work is a derivative and has a lot of inpsiration from too many projects to claim anything more restrictive.
* The CPU Emulation was inspired by a few other implementations:

View File

@ -1,3 +1,15 @@
## Top down view
<center>
<img src="mui_emulator.drawio.png" alt="Top down view">
<i>Here how it's supposed to work!</i>
</center>
The emulator was made to be portable on most things. It's plain C compiled with -Wextras, I run in regularly thru the static analyser, and I regulartly check it with valgrind. So it's pretty clean.
It evolved from the original mess, and I organized it into bits which would make porting it to other platform easier.
## How to I compile it and run it?
* You need a C compiler, make, and a few libraries:
* libasound2-dev [ optional, for audio ]
@ -26,9 +38,4 @@ I have pretty consistent code style across my projects.
## What needs doing?
* I'm sure there are bugs. I haven't tested it on a lot of hardware, and apart from quite a few games and a few known productivity app, it had had very little extensive testing. So testing!
* In the code there are a few bits which needs fixing
* The mouse card is not working properly. It works for some (most importantly A2 Desktop), but it's not working properly. I suspect the VBL Interrupt handling.
* In mii.c, the *mii_page_table_update()* does work, but it sub-optimal, it was handy to debug the countless combination of soft-switches, but it is not the ideal way to handle that problem; it needs a proper switch-case statement.
* The floppy drive emulation was borrowed from bobbin, and it works, and it
got it all working, but it's definitely not matching the style of the rest of the codebase. It needs to be replaced.
* Plenty of the most complicated piece of code (video, memory mapping) load a dozen of soft-switches, it probably should use a separately maintained bit field that is maintained by the on/off switches, and would make it a lot easier to test for bit combinations.
* The static array of memory 'banks' works, but it prevents me easily making a memory extension card. It should be refactored at some point.
* The floppy driver still has issues writing, most notably it fails to 'bit copy' stuff when using Copy II plus. It's a timing issue, but I haven't found it yet.

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 621 KiB

After

Width:  |  Height:  |  Size: 621 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 279 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

View File

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 790 KiB

After

Width:  |  Height:  |  Size: 790 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 KiB

View File

@ -60,6 +60,8 @@ $(LIB)/ui_tests.so : $(OBJ)/mii_mui_loadbin.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_1mb.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_2dsk.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_about.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_ssc.o
$(LIB)/ui_tests.so : $(OBJ)/mii_mui_prefs.o
$(OBJ)/mii_mui_about.o : CPPFLAGS+=-DMII_ICON64_DEFINE

View File

@ -67,9 +67,11 @@ Menubar, menus, checkmarks, keyboard shortcuts, all that stuff. Made to looks li
## Control Manager
Buttons, checkboxes, radio buttons, scrollbars (vertical), wrapping textboxes, all that stuff.
- It's missing bits like Edit Field (TODO), and a Slider.
- There IS a prototype version of a text edit control, but it's not quite right yet -- works fine for a one liner etc, but not for a multi line text box. Not far off tho.
## List Manager
More or less hard coded to display filenames so far, but plain lists are actually easier than this so. Handle arrow keys/page up/down, scroll wheel, etc.
- It's missing a way to 'compress' the font and/or use ellipsis abreviations (TODO) when the item text is too long.
+ You CAN use 'typeahead' to find the item you want, like the original.
## Alerts
It has the typical 'Cancel'+'OK' alert.
- Could do with more types of alerts (TODO).
@ -78,7 +80,7 @@ It has the classic 'Open' a file dialog. Haven't needed the other one. yet.
- Could do with a 'Save' dialog (TODO).
- Maybe a 'period correct' way to handle previously visited folders... Currently it can same the last folder you visited *per file type*.
- You can use arrow keys, page/up down, and you can even typehead to the file you want, like in the old days.
## Resource Manager
Nope! Not there; I'd need some sort of ResEdit and stuff -- and now that is *ONE* Feature Creep Too Far thank you very much.
I have a vague idea of making some sort of MessagePack format for resources, but that's for another day.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -190,7 +190,7 @@ c2_rect_contains_pt(
C2_DECL c2_rect_p
c2_rect_union(
c2_rect_p r,
c2_rect_p u )
const c2_rect_p u )
{
if (!r || !u) return r;
if (c2_rect_isempty(r)) {

View File

@ -124,9 +124,9 @@ C_ARRAY_DECL C_ARRAY_INLINE \
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_count_t __name##_insert(\
__name##_p a, __name##_count_t index, \
__name##_element_t * e, __name##_count_t count) \
const __name##_element_t * e, __name##_count_t count) \
{\
if (!a) return 0;\
if (!a || !e || !count) return 0;\
if (index > a->count) index = a->count;\
if (a->count + count >= a->size) \
__name##_realloc(a, (((a->count + count) / __name##_page_size)+1) * __name##_page_size);\
@ -137,6 +137,14 @@ C_ARRAY_DECL C_ARRAY_INLINE \
a->count += count;\
return a->count;\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_count_t __name##_append(\
__name##_p a, \
const __name##_element_t * e, __name##_count_t count) \
{\
if (!a) return 0;\
return __name##_insert(a, a->count, e, count);\
}\
C_ARRAY_DECL C_ARRAY_INLINE \
__name##_count_t __name##_delete(\
__name##_p a, __name##_count_t index, __name##_count_t count) \

View File

@ -22,8 +22,9 @@ mui_init(
//memset(ui, 0, sizeof(*ui));
ui->color.clear = MUI_COLOR(0xccccccff);
ui->color.highlight = MUI_COLOR(0xd6fcc0ff);
ui->timer.map = 0;
ui->carret_timer = 0xff;
TAILQ_INIT(&ui->windows);
TAILQ_INIT(&ui->zombies);
TAILQ_INIT(&ui->fonts);
mui_font_init(ui);
pixman_region32_init(&ui->redraw);
@ -37,6 +38,7 @@ mui_dispose(
{
pixman_region32_fini(&ui->inval);
pixman_region32_fini(&ui->redraw);
mui_utf8_free(&ui->clipboard);
mui_font_dispose(ui);
mui_window_t *w;
while ((w = TAILQ_FIRST(&ui->windows))) {
@ -121,7 +123,6 @@ mui_handle_event(
bool res = false;
if (!ev->when)
ev->when = mui_get_time();
ui->action_active++;
switch (ev->type) {
case MUI_EVENT_KEYUP:
case MUI_EVENT_KEYDOWN: {
@ -156,20 +157,44 @@ mui_handle_event(
printf("%s %d mouse %d %3dx%3d capture:%s\n", __func__,
ev->type, ev->mouse.button,
ev->mouse.where.x, ev->mouse.where.y,
ui->event_capture ?
ui->event_capture->title : "(none)");
if (ui->event_capture) {
res = mui_window_handle_mouse(ui->event_capture, ev);
ui->event_capture.window ?
ui->event_capture.window->title :
"(none)");
/* Handle double click detection */
if (ev->mouse.button < MUI_EVENT_BUTTON_MAX &&
ev->type == MUI_EVENT_BUTTONDOWN) {
int click_delta = ev->when - ui->last_click_stamp[ev->mouse.button];
if (ui->last_click_stamp[ev->mouse.button] &&
click_delta < (500 * MUI_TIME_MS)) {
ui->last_click_stamp[ev->mouse.button] = 0;
ev->mouse.count = 2;
} else {
ui->last_click_stamp[ev->mouse.button] = ev->when;
}
}
if (ui->event_capture.window) {
res = mui_window_handle_mouse(
ui->event_capture.window, ev);
break;
} else {
mui_window_t *w, *safe;
TAILQ_FOREACH_REVERSE_SAFE(w, &ui->windows, windows, self, safe) {
/* We can't use the REVERSE_SAFE macro here, as the window
* list can change quite a bit, especially when menus are involved*/
mui_window_t *w, *prev;
w = TAILQ_LAST(&ui->windows, windows);
while (w) {
mui_window_lock(w);
int done = 0;
if ((res = mui_window_handle_mouse(w, ev))) {
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
printf(" window:%s handled it\n",
w->title);
break;
done = 1;
}
prev = TAILQ_PREV(w, windows, self);
mui_window_unlock(w);
if (done)
break;
w = prev;
}
}
if (ev->modifiers & MUI_MODIFIER_EVENT_TRACE)
@ -177,7 +202,6 @@ mui_handle_event(
printf(" no window handled it\n");
} break;
}
ui->action_active--;
return res;
}
@ -206,7 +230,8 @@ mui_event_match_key(
return false;
if (toupper(ev->key.key) != toupper(key_equ.key))
return false;
if (_mui_simplify_mods(ev->modifiers) != _mui_simplify_mods(key_equ.mod))
if (_mui_simplify_mods(ev->modifiers) !=
_mui_simplify_mods(key_equ.mod))
return false;
return true;
}
@ -222,6 +247,7 @@ mui_timer_register(
fprintf(stderr, "%s ran out of timers\n", __func__);
return -1;
}
//printf("%s: delay %d\n", __func__, delay);
int ti = ffsl(~ui->timer.map) - 1;
ui->timer.map |= 1 << ti;
ui->timer.timers[ti].cb = cb;
@ -241,7 +267,7 @@ mui_timer_reset(
return 0;
if (!(ui->timer.map & (1L << id)) ||
ui->timer.timers[id].cb != cb) {
printf("%s: timer %d not active\n", __func__, id);
// printf("%s: timer %d not active\n", __func__, id);
return 0;
}
mui_time_t res = 0;
@ -251,9 +277,8 @@ mui_timer_reset(
ui->timer.timers[id].when = now + delay;
if (delay == 0) {
ui->timer.map &= ~(1L << id);
printf("%s: timer %d removed\n", __func__, id);
// printf("%s: timer %d removed\n", __func__, id);
}
return res;
}
@ -268,8 +293,7 @@ mui_timers_run(
map &= ~(1 << ti);
if (ui->timer.timers[ti].when > now)
continue;
mui_time_t r = ui->timer.timers[ti].cb(
ui, now,
mui_time_t r = ui->timer.timers[ti].cb(ui, now,
ui->timer.timers[ti].param);
if (r == 0)
ui->timer.map &= ~(1 << ti);
@ -278,27 +302,11 @@ mui_timers_run(
}
}
void
_mui_window_free(
mui_window_t *win);
void
mui_garbage_collect(
mui_t * ui)
{
mui_window_t *win, *safe;
TAILQ_FOREACH_SAFE(win, &ui->zombies, self, safe) {
TAILQ_REMOVE(&ui->zombies, win, self);
_mui_window_free(win);
}
}
void
mui_run(
mui_t *ui)
{
mui_timers_run(ui);
mui_garbage_collect(ui);
}
bool
@ -313,3 +321,165 @@ mui_has_active_windows(
}
return false;
}
void
mui_clipboard_set(
mui_t * ui,
const uint8_t * utf8,
uint len)
{
len = len ? len : strlen((char*)utf8);
mui_utf8_clear(&ui->clipboard);
mui_utf8_insert(&ui->clipboard, 0, utf8, len);
mui_utf8_add(&ui->clipboard, 0);
mui_window_action(
ui->menubar.window, MUI_CLIPBOARD_CHANGED, NULL);
}
const uint8_t *
mui_clipboard_get(
mui_t * ui,
uint * len)
{
mui_window_action(
ui->menubar.window, MUI_CLIPBOARD_REQUEST, NULL);
if (len)
*len = ui->clipboard.count > 1 ? ui->clipboard.count - 1 : 0;
return ui->clipboard.count ? ui->clipboard.e : NULL;
}
void
mui_refqueue_init(
mui_refqueue_t *queue)
{
TAILQ_INIT(&queue->head);
}
uint
mui_refqueue_dispose(
mui_refqueue_t *queue)
{
uint res = 0;
struct mui_ref_t *ref, *safe;
TAILQ_FOREACH_SAFE(ref, &queue->head, self, safe) {
if (ref->count) {
ref->count--;
if (ref->count) {
// printf("%s: ref %4.4s count %2d\n", __func__,
// (char*)&ref->kind, ref->count);
res++;
continue;
}
}
TAILQ_REMOVE(&queue->head, ref, self);
ref->queue = NULL;
if (ref->deref)
ref->deref(ref);
}
return res;
}
/* Remove reference 'ref' from it's reference queue */
void
mui_ref_deref(
struct mui_ref_t * ref)
{
if (!ref)
return;
if (ref->queue)
TAILQ_REMOVE(&ref->queue->head, ref, self);
if (ref->alloc) {
free(ref);
return;
}
ref->queue = NULL;
ref->deref = NULL;
ref->count = 0;
}
static void
mui_ref_deref_control(
struct mui_ref_t * _ref)
{
mui_control_ref_t * ref = (mui_control_ref_t*)_ref;
ref->control = NULL;
}
mui_control_ref_t *
mui_control_ref(
mui_control_ref_t * ref,
struct mui_control_t * control,
uint32_t kind)
{
if (!control)
return NULL;
if (ref && ref->ref.queue) {
printf("%s Warning: ref %p %4.4s already in queue\n",
__func__, ref, (char*)&kind);
if (ref->control != control) {
printf("%s ERROR: ref %p control %p != %p\n",
__func__, ref, ref->control, control);
}
return NULL;
}
struct mui_ref_t * res = ref ? ref : calloc(1, sizeof(*ref));
res->alloc = !ref;
res->queue = &control->refs;
res->kind = kind;
res->deref = mui_ref_deref_control;
res->count = 1;
ref = (mui_control_ref_t*)res;
ref->control = control;
TAILQ_INSERT_TAIL(&control->refs.head, res, self);
return ref;
}
void
mui_control_deref(
mui_control_ref_t * ref)
{
ref->control = NULL;
mui_ref_deref(&ref->ref);
}
static void
mui_ref_deref_window(
struct mui_ref_t * _ref)
{
mui_window_ref_t * ref = (mui_window_ref_t*)_ref;
ref->window = NULL;
}
mui_window_ref_t *
mui_window_ref(
mui_window_ref_t * ref,
struct mui_window_t * win,
uint32_t kind)
{
if (!win)
return NULL;
if (ref && ref->ref.queue) {
printf("%s Warning: ref %p %4.4s already in queue\n",
__func__, ref, (char*)&kind);
return NULL;
}
struct mui_ref_t * res = ref ? ref : calloc(1, sizeof(*ref));
res->alloc = !ref;
res->queue = &win->refs;
res->kind = kind;
res->deref = mui_ref_deref_window;
res->count = 1;
ref = (mui_window_ref_t*)res;
ref->window = win;
TAILQ_INSERT_TAIL(&win->refs.head, res, self);
return ref;
}
void
mui_window_deref(
mui_window_ref_t * ref)
{
ref->window = NULL;
mui_ref_deref(&ref->ref);
}

View File

@ -17,6 +17,19 @@
#include <stdbool.h>
#include <pixman.h>
#include "c2_arrays.h"
#if 0
#define _KERNEL
#define INVARIANTS
#define QUEUE_MACRO_DEBUG_TRACE
#define panic(...) \
do { \
fprintf(stderr, "PANIC: %s:%d\n", __func__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
*((int*)0) = 0xdead; \
} while(0)
#endif
#include "bsd_queue.h"
#include "stb_ttc.h"
@ -32,8 +45,10 @@
enum mui_event_e {
MUI_EVENT_KEYUP = 0,
MUI_EVENT_KEYDOWN,
MUI_EVENT_TEXT, // UTF8 sequence
MUI_EVENT_BUTTONUP,
MUI_EVENT_BUTTONDOWN,
MUI_EVENT_BUTTONDBL, // double click
MUI_EVENT_WHEEL,
MUI_EVENT_DRAG,
// the following ones aren't supported yet
@ -42,15 +57,22 @@ enum mui_event_e {
MUI_EVENT_RESIZE,
MUI_EVENT_CLOSE,
MUI_EVENT_COUNT,
// left, middle, right buttons for clicks
MUI_EVENT_BUTTON_MAX = 3,
};
enum mui_key_e {
MUI_KEY_ESCAPE = 0x1b,
MUI_KEY_SPACE = 0x20,
MUI_KEY_RETURN = 0x0d,
MUI_KEY_TAB = 0x09,
MUI_KEY_BACKSPACE = 0x08,
MUI_KEY_LEFT = 0x80,
MUI_KEY_UP,
MUI_KEY_RIGHT,
MUI_KEY_DOWN,
MUI_KEY_INSERT,
MUI_KEY_DELETE,
MUI_KEY_HOME,
MUI_KEY_END,
MUI_KEY_PAGEUP,
@ -62,8 +84,8 @@ enum mui_key_e {
MUI_KEY_RCTRL,
MUI_KEY_LALT,
MUI_KEY_RALT,
MUI_KEY_RSUPER,
MUI_KEY_LSUPER,
MUI_KEY_RSUPER,
MUI_KEY_CAPSLOCK,
MUI_KEY_MODIFIERS_LAST,
MUI_KEY_F1 = 0x100,
@ -111,6 +133,8 @@ enum mui_modifier_e {
#define MUI_ICON_HOME ""
#define MUI_ICON_SBAR_UP ""
#define MUI_ICON_SBAR_DOWN ""
#define MUI_ICON_FLOPPY5 ""
#define MUI_ICON_HARDDISK ""
/* These are specific to our custom version of the Charcoal System font */
#define MUI_GLYPH_APPLE "" // solid apple
@ -162,16 +186,25 @@ typedef struct mui_event_t {
bool up;
} key;
struct {
uint32_t button;
uint32_t button : 4,
count : 2; // click count
c2_pt_t where;
} mouse;
struct {
int32_t delta;
c2_pt_t where;
} wheel;
struct { // MUI_EVENT_TEXT is of variable size!
uint32_t size;
uint8_t text[0];
} text;
};
} mui_event_t;
/* Just a generic buffer for UTF8 text */
DECLARE_C_ARRAY(uint8_t, mui_utf8, 8);
IMPLEMENT_C_ARRAY(mui_utf8);
/*
* Key equivalent, used to match key events to menu items
* Might be extended to controls, right now only the 'key' is checked,
@ -179,10 +212,10 @@ typedef struct mui_event_t {
*/
typedef union mui_key_equ_t {
struct {
uint16_t mod;
uint16_t key;
uint16_t mod;
uint16_t key;
};
uint32_t value;
uint32_t value;
} mui_key_equ_t;
#define MUI_KEY_EQU(_mask, _key) \
@ -190,6 +223,69 @@ typedef union mui_key_equ_t {
struct mui_t;
/*
* References allows arbitrary code to keep a 'handle' on either
* a window or a control. This is used for example to keep track of
* the currently focused control.
* Client code Must Not keep random pointers on control and windows,
* as they could get deleted and they will end up with a dangling
* pointer.
* Instead, client code create a reference, and use that reference
* to keep track of the object. If an object is deleted, all it's
* current references are reset to NULL, so the client code can
* detect that the object is gone just by checking that its pointer
* is still around. Otherwise, it's just gone.
*/
struct mui_ref_t;
typedef struct mui_refqueue_t {
TAILQ_HEAD(head, mui_ref_t) head;
} mui_refqueue_t;
typedef void (*mui_deref_p)(
struct mui_ref_t * ref);
typedef struct mui_ref_t {
// in refqueue's 'head'
TAILQ_ENTRY(mui_ref_t) self;
mui_refqueue_t * queue;
// OPTIONAL arbitrary kind set when referencing an object.
uint32_t kind;
uint32_t alloc : 1, trace : 1, count : 8;
// OPTIONAL: called if the object win/control get disposed or
// otherwise dereferenced.
mui_deref_p deref;
} _mui_ref_t; // this is not a 'user' type.
typedef struct mui_window_ref_t {
_mui_ref_t ref;
struct mui_window_t * window;
} mui_window_ref_t;
typedef struct mui_control_ref_t {
_mui_ref_t ref;
struct mui_control_t * control;
} mui_control_ref_t;
// if 'ref' is NULL a new one is allocated, will be freed on deref()
mui_control_ref_t *
mui_control_ref(
mui_control_ref_t * ref,
struct mui_control_t * control,
uint32_t kind);
void
mui_control_deref(
mui_control_ref_t * ref);
// if 'ref' is NULL a new one is allocated, will be freed on deref()
mui_window_ref_t *
mui_window_ref(
mui_window_ref_t * ref,
struct mui_window_t * win,
uint32_t kind);
void
mui_window_deref(
mui_window_ref_t * ref);
typedef struct mui_listbox_elem_t {
uint32_t disabled : 1;
char icon[8];
@ -208,10 +304,12 @@ struct mui_listbox_elem_t;
* event handling.
*/
enum {
MUI_WDEF_INIT = 0,
MUI_WDEF_DISPOSE,
MUI_WDEF_DRAW,
MUI_WDEF_EVENT,
MUI_WDEF_INIT = 0, // param is NULL
MUI_WDEF_DISPOSE, // param is NULL
MUI_WDEF_DRAW, // param is mui_drawable_t*
MUI_WDEF_EVENT, // param is mui_event_t*
MUI_WDEF_SELECT, // param is NULL
MUI_WDEF_DESELECT, // param is NULL
};
typedef bool (*mui_wdef_p)(
struct mui_window_t * win,
@ -225,7 +323,12 @@ enum mui_cdef_e {
MUI_CDEF_SET_STATE,
MUI_CDEF_SET_VALUE,
MUI_CDEF_SET_TITLE,
// Used when hot-key is pressed, change control value
// to simulate a click
MUI_CDEF_SELECT,
// used when a window is selected, to set the focus to the
// first control that can accept it
MUI_CDEF_ACTIVATE, // param is int* with 0,1
};
typedef bool (*mui_cdef_p)(
struct mui_control_t * c,
@ -326,7 +429,7 @@ typedef struct mui_drawable_t {
struct cg_surface_t * cg_surface;
struct cg_ctx_t * cg;
union pixman_image * pixman; // (try) not to use these directly
unsigned int pixman_clip_dirty: 1,
uint pixman_clip_dirty: 1,
cg_clip_dirty : 1,
dispose_pixels : 1,
dispose_drawable : 1;
@ -334,7 +437,7 @@ typedef struct mui_drawable_t {
struct {
float opacity;
c2_pt_t size;
unsigned int id;
uint id, kind;
} texture;
// (default) position in destination when drawing
c2_pt_t origin;
@ -352,20 +455,20 @@ DECLARE_C_ARRAY(mui_drawable_t *, mui_drawable_array, 4);
* are not cleared. */
mui_drawable_t *
mui_drawable_new(
c2_pt_t size,
uint8_t bpp,
void * pixels, // if NULL, will allocate
uint32_t row_bytes);
c2_pt_t size,
uint8_t bpp,
void * pixels, // if NULL, will allocate
uint32_t row_bytes);
/* initialize a mui_drawable_t structure with the given parameters
* note it is not assumed 'd' contains anything valid, it will be
* overwritten */
mui_drawable_t *
mui_drawable_init(
mui_drawable_t * d,
c2_pt_t size,
uint8_t bpp,
void * pixels, // if NULL, will allocate
uint32_t row_bytes);
c2_pt_t size,
uint8_t bpp,
void * pixels, // if NULL, will allocate
uint32_t row_bytes);
void
mui_drawable_dispose(
mui_drawable_t * dr);
@ -440,11 +543,10 @@ typedef struct mui_control_color_t {
.alpha = (_c).a * 257, .red = (_c).r * (_c).a, \
.green = (_c).g * (_c).a, .blue = (_c).b * (_c).a }
typedef struct mui_font_t {
mui_drawable_t font; // points to ttc pixels!
char * name; // not filename, internal name, aka 'main'
unsigned int size; // in pixels
uint size; // in pixels
TAILQ_ENTRY(mui_font_t) self;
struct stb_ttc_info ttc;
} mui_font_t;
@ -466,67 +568,120 @@ mui_font_find(
mui_font_t *
mui_font_from_mem(
struct mui_t * ui,
const char *name,
unsigned int size,
const void *font_data,
unsigned int font_size );
const char * name,
uint size,
const void * font_data,
uint font_size );
/*
* Draw a text string at 'where' in the drawable 'dr' with the
* given color. This doesn't handle line wrapping, or anything,
* it just draws the text at the given position.
* If you want more fancy text drawing, use mui_font_textbox()
*/
void
mui_font_text_draw(
mui_font_t * font,
mui_drawable_t *dr,
c2_pt_t where,
const char * text,
unsigned int text_len,
uint text_len,
mui_color_t color);
/*
* This is a low level function to measure a text string, it returns
* the width of the string in pixels, and fills the 'm' structure with
* the position of each glyph in the string. Note that the returned
* values are all in FIXED POINT format.
*/
int
mui_font_text_measure(
mui_font_t * font,
const char * text,
struct stb_ttc_measure *m );
enum mui_text_align_e {
typedef enum mui_text_e {
// 2 bits for horizontal alignment, 2 bits for vertical alignment
MUI_TEXT_ALIGN_LEFT = 0,
MUI_TEXT_ALIGN_CENTER = (1 << 0),
MUI_TEXT_ALIGN_RIGHT = (1 << 1),
MUI_TEXT_ALIGN_TOP = 0,
MUI_TEXT_ALIGN_MIDDLE = (MUI_TEXT_ALIGN_CENTER << 2),
MUI_TEXT_ALIGN_BOTTOM = (MUI_TEXT_ALIGN_RIGHT << 2),
};
MUI_TEXT_ALIGN_COMPACT = (1 << 5), // compact line spacing
MUI_TEXT_DEBUG = (1 << 7),
MUI_TEXT_STYLE_BOLD = (1 << 8), // Synthetic (ugly) bold
MUI_TEXT_STYLE_ULINE = (1 << 9), // Underline
MUI_TEXT_STYLE_NARROW = (1 << 10),// Syntheric narrow
MUI_TEXT_FLAGS_COUNT = 11,
} mui_text_e;
/*
* Draw a text string in a bounding box, with the given color. The
* 'flags' parameter is a combination of MUI_TEXT_ALIGN_* flags.
* This function will handle line wrapping, and will draw as much text
* as it can in the given box.
* The 'text' parameter can be a UTF8 string, and the 'text_len' is
* the number of bytes in the string (or zero), not the number of
* glyphs.
* The 'text' string can contain '\n' to force a line break.
*/
void
mui_font_textbox(
mui_font_t * font,
mui_drawable_t *dr,
c2_rect_t bbox,
const char * text,
unsigned int text_len,
uint text_len,
mui_color_t color,
uint16_t flags );
mui_text_e flags );
DECLARE_C_ARRAY(unsigned int, mui_glyph_array, 8, int x, y, w; );
DECLARE_C_ARRAY(mui_glyph_array_t, mui_glyph_line_array, 8);
// this is what is returned by mui_font_measure()
typedef struct mui_glyph_t {
uint32_t pos; // position in text, in *bytes*
uint32_t w; // width of the glyph, in *pixels*
float x; // x position in *pixels*
uint32_t index; // cache index, for internal use, do not change
uint32_t glyph; // Unicode codepoint
} mui_glyph_t;
DECLARE_C_ARRAY(mui_glyph_t, mui_glyph_array, 8,
int x, y, t, b; float w;);
DECLARE_C_ARRAY(mui_glyph_array_t, mui_glyph_line_array, 8,
uint margin_left, margin_right, // minimum x, and max width
height; );
/*
* Measure a text string, return the number of lines, and each glyphs
* position already aligned to the MUI_TEXT_ALIGN_* flags.
* Note that the 'compact' and 'narrow' flags are used here,
* the 'compact' flag is used to reduce the line spacing, and the
* 'narrow' flag is used to reduce the advance between glyphs.
*/
void
mui_font_measure(
mui_font_t * font,
c2_rect_t bbox,
const char * text,
unsigned int text_len,
uint text_len,
mui_glyph_line_array_t *lines,
uint16_t flags);
// to be used exclusively with mui_font_measure
mui_text_e flags);
/*
* to be used exclusively with mui_font_measure.
* Draw the lines and glyphs returned by mui_font_measure, with the
* given color and flags.
* The significan flags here are no longer the text aligment, but
* how to render them:
* + MUI_TEXT_STYLE_BOLD will draw each glyphs twice, offset by 1 pixel
* + MUI_TEXT_STYLE_ULINE will draw a line under the text glyphs, unless
* they have a descent that is lower than the underline.
*/
void
mui_font_measure_draw(
mui_font_t *font,
mui_font_t * font,
mui_drawable_t *dr,
c2_rect_t bbox,
c2_rect_t bbox,
mui_glyph_line_array_t *lines,
mui_color_t color,
uint16_t flags);
mui_color_t color,
mui_text_e flags);
// clear all the lines, and glyph lists. Use it after mui_font_measure
void
mui_font_measure_clear(
@ -548,28 +703,59 @@ enum mui_window_action_e {
MUI_WINDOW_ACTION_CLOSE = FCC('w','c','l','s'),
};
/*
* A window is basically 2 rectangles in 'screen' coordinates. The
* 'frame' rectangle that encompasses the whole of the window, and the
* 'content' rectangle that is the area where the controls are drawn.
* * The 'content' rectangle is always fully included in the 'frame'
* rectangle.
* * The 'frame' rectangle is the one that is used to move the window
* around.
* * All controls coordinates are related to the 'content' rectangle.
*
* The window 'layer' is used to determine the order of the windows on the
* screen, the higher the layer, the more in front the window is.
* Windows can be 'selected' to bring them to the front -- that brings
* them to the front of their layer, not necessarily the topmost window.
*
* Windows contain an 'action' list that are called when the window
* wants to signal the application; for example when the window is closed,
* but it can be used for other things as application requires.
*
* Mouse clicks are handled by the window, and the window by first
* checking if the click is in a control, and if so, passing the event
* to the control.
* Any control that receives the 'mouse' down will ALSO receive the
* mouse DRAG and UP events, even if the mouse has moved outside the
* control. This is the meaning of the 'control_clicked' field.
*
* The 'control_focus' field is used to keep track of the control that
* has the keyboard focus. This is used to send key events to the
* control that has the focus. That control can still 'refuse' the event,
* in which case is it passed in turn to the others, and to the window.
*/
typedef struct mui_window_t {
TAILQ_ENTRY(mui_window_t) self;
struct mui_t * ui;
mui_wdef_p wdef;
uint32_t uid; // optional, pseudo unique id
struct {
unsigned long hidden: 1,
zombie: 1, // is in pre-delete ui->zombies
uint hidden: 1,
disposed : 1,
layer : 4,
hit_part : 8;
} flags;
c2_pt_t click_loc;
struct mui_drawable_t * dr;
// both these rectangles are in screen coordinates, even tho
// 'contents' is fully included in 'frame'
c2_rect_t frame, content;
char * title;
mui_action_queue_t actions;
TAILQ_HEAD(controls, mui_control_t) controls;
// anything deleted during an action goes in zombies
TAILQ_HEAD(zombies, mui_control_t) zombies;
struct mui_control_t * control_clicked;
mui_refqueue_t refs;
mui_window_ref_t lock;
mui_control_ref_t control_clicked;
mui_control_ref_t control_focus;
mui_region_t inval;
} mui_window_t;
@ -653,10 +839,14 @@ struct mui_menu_items_t;
* visible.
*/
typedef struct mui_menu_item_t {
uint32_t disabled : 1, hilited : 1;
uint32_t disabled : 1,
hilited : 1,
is_menutitle : 1;
uint32_t index: 9;
uint32_t uid;
char * title;
// curertnly only supported for menu titles
const uint32_t * color_icon; // optional, ARGB colors
char mark[8]; // UTF8 -- Charcoal
char icon[8]; // UTF8 -- Wider, icon font
char kcombo[16]; // UTF8 -- display only
@ -701,15 +891,23 @@ mui_menubar_get(
/*
* Add a menu to the menubar. 'items' is an array of mui_menu_item_t
* terminated by an element with a NULL title.
* Note: The array is NOT const, it will be tweaked for storing items position,
* it can also be tweaked to set/reset the disabled state, check marks etc
* Note: The array is NOT const, it will be tweaked for storing items
* position, it can also be tweaked to set/reset the disabled state,
* check marks etc
*/
struct mui_control_t *
mui_menubar_add_simple(
mui_window_t * win,
const char * title,
uint32_t menu_uid,
mui_menu_item_t * items );
mui_window_t * win,
const char * title,
uint32_t menu_uid,
mui_menu_item_t * items );
struct mui_control_t *
mui_menubar_add_menu(
mui_window_t * win,
uint32_t menu_uid,
mui_menu_item_t * items,
uint count );
/* Turn off any highlighted menu titles */
mui_window_t *
mui_menubar_highlight(
@ -747,13 +945,14 @@ enum mui_control_action_e {
typedef struct mui_control_t {
TAILQ_ENTRY(mui_control_t) self;
struct mui_window_t * win;
mui_refqueue_t refs;
mui_control_ref_t lock;
mui_cdef_p cdef;
uint32_t state;
uint32_t type;
uint32_t style;
struct {
unsigned int hidden : 1,
zombie : 1,
uint hidden : 1,
hit_part : 8;
} flags;
uint32_t value;
@ -861,7 +1060,8 @@ mui_button_new(
*/
enum mui_textbox_e {
// draw the frame around the text box
MUI_CONTROL_TEXTBOX_FRAME = (1 << 8),
MUI_CONTROL_TEXTBOX_FRAME = (1 << (MUI_TEXT_FLAGS_COUNT+1)),
MUI_CONTROL_TEXTBOX_FLAGS_COUNT = (MUI_TEXT_FLAGS_COUNT+1),
};
mui_control_t *
mui_textbox_new(
@ -869,13 +1069,36 @@ mui_textbox_new(
c2_rect_t frame,
const char * text,
const char * font,
uint16_t flags );
uint32_t flags );
mui_control_t *
mui_groupbox_new(
mui_window_t * win,
c2_rect_t frame,
const char * title,
uint16_t flags );
uint32_t flags );
/*
* Text editor control
*/
enum {
// do we handle multi-line text? If zero, we only handle one line
MUI_CONTROL_TEXTEDIT_VERTICAL = 1 << (MUI_CONTROL_TEXTBOX_FLAGS_COUNT+1),
MUI_CONTROL_TEXTEDIT_FLAGS_COUNT = (MUI_CONTROL_TEXTBOX_FLAGS_COUNT+1),
};
mui_control_t *
mui_textedit_control_new(
mui_window_t * win,
c2_rect_t frame,
uint32_t flags);
void
mui_textedit_set_text(
mui_control_t * c,
const char * text);
void
mui_textedit_set_selection(
mui_control_t * c,
uint start,
uint end);
mui_control_t *
mui_scrollbar_new(
@ -914,7 +1137,7 @@ mui_popupmenu_new(
mui_window_t * win,
c2_rect_t frame,
const char * title,
uint32_t uid );
uint32_t uid);
mui_menu_items_t *
mui_popupmenu_get_items(
mui_control_t * c);
@ -1043,6 +1266,12 @@ mui_timer_reset(
mui_timer_p cb,
mui_time_t delay);
/*
* This is the head of the mui library, it contains the screen size,
* the color scheme, the list of windows, the list of fonts, and the
* clipboard.
* Basically this is the primary parameter that you keep around.
*/
typedef struct mui_t {
c2_pt_t screen_size;
struct {
@ -1050,7 +1279,9 @@ typedef struct mui_t {
mui_color_t highlight;
} color;
uint16_t modifier_keys;
mui_time_t last_click_stamp[MUI_EVENT_BUTTON_MAX];
int draw_debug;
int quit_request;
// this is the sum of all the window's dirty regions, inc moved windows etc
mui_region_t inval;
// once the pixels have been refreshed, 'inval' is copied to 'redraw'
@ -1059,14 +1290,12 @@ typedef struct mui_t {
TAILQ_HEAD(, mui_font_t) fonts;
TAILQ_HEAD(windows, mui_window_t) windows;
mui_window_t * menubar;
TAILQ_HEAD(, mui_window_t) zombies;
// this is used to track any active action callbacks to
// prevent recursion problem and track any 'delete' happening
// during an action callback
uint32_t action_active;
mui_window_t * event_capture;
mui_window_ref_t menubar;
mui_window_ref_t event_capture;
mui_utf8_t clipboard;
mui_timer_group_t timer;
// only used by the text editor, as we can only have one carret
uint8_t carret_timer;
char * pref_directory; /* optional */
} mui_t;
@ -1084,6 +1313,33 @@ mui_draw(
void
mui_run(
mui_t * ui);
/* If you want this notification, attach an action function to the
* menubar */
enum {
// note this will also be send if the application sets the
// clipboard with the system's clipboard, so watch out for
// recursion problems!
MUI_CLIPBOARD_CHANGED = FCC('c','l','p','b'),
// this is sent when the user type 'control-v', this gives
// a chance to the application to do a mui_clipboard_set()
// with the system's clipboard before it gets pasted.
// the default is of course to use our internal clipboard
MUI_CLIPBOARD_REQUEST = FCC('c','l','p','r'),
};
// This will send a notification that the clipboard was set,
// the notification is sent to the menubar, and the menubar will
// send it to the application.
void
mui_clipboard_set(
mui_t * ui,
const uint8_t * utf8,
uint len);
const uint8_t *
mui_clipboard_get(
mui_t * ui,
uint * len);
// return true if the event was handled by the ui
bool
mui_handle_event(
@ -1106,4 +1362,4 @@ mui_has_active_windows(
/* Return a hash value for string inString */
uint32_t
mui_hash(
const char * inString );
const char * inString );

View File

@ -72,11 +72,12 @@ mui_alert(
}
cf = C2_RECT_WH(0, 10, 540-140, 70);
c2_rect_left_of(&cf, c2_rect_width(&w->content), 20);
c = mui_textbox_new(w, cf, message, NULL, 0);
cf = C2_RECT_WH(10, 10, 80, 75);
c = mui_textbox_new(w, cf, message, NULL, MUI_TEXT_ALIGN_COMPACT);
cf = C2_RECT_WH(10, 10, 80, 85);
c = mui_textbox_new(w, cf,
"", "icon_large",
MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE);
MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE |
MUI_TEXT_ALIGN_COMPACT);
c = NULL;
TAILQ_FOREACH(c, &w->controls, self) {

View File

@ -22,7 +22,7 @@ enum {
typedef struct mui_textbox_control_t {
mui_control_t control;
mui_font_t * font;
uint16_t flags;
uint32_t flags;
} mui_textbox_control_t;
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
@ -155,7 +155,7 @@ mui_textbox_new(
c2_rect_t frame,
const char * text,
const char * font,
uint16_t flags )
uint32_t flags )
{
mui_control_t * c = mui_control_new(
win, MUI_CONTROL_TEXTBOX, mui_cdef_boxes,
@ -181,7 +181,7 @@ mui_groupbox_new(
mui_window_t * win,
c2_rect_t frame,
const char * title,
uint16_t flags )
uint32_t flags )
{
mui_control_t * c = mui_control_new(
win, MUI_CONTROL_GROUPBOX, mui_cdef_boxes,

View File

@ -46,6 +46,7 @@ mui_button_draw(
int title_width = m.x1 - m.x0;
c2_rect_t title = f;
title.t -= 1;
title.r = title.l + title_width + 1;
title.b = title.t + m.ascent - m.descent;
c2_rect_offset(&title, -m.x0 +
@ -83,14 +84,19 @@ mui_check_rad_draw(
c2_rect_t box = f;
box.r = box.l + (main->size * 0.95);
box.b = box.t + (main->size * 0.95);
c2_rect_offset(&box, 0, (c2_rect_height(&f) / 2) - (c2_rect_height(&box) / 2));
c2_rect_offset(&box, 1, (c2_rect_height(&f) / 2) - (c2_rect_height(&box) / 2));
c2_rect_t title = f;
title.l = box.r + 8;
// mui_drawable_clip_push(dr, &f);
// only do a a get_cg after the clip is set, as this is what converts
// the drawable clip rectangle list into a cg path
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_drawable_clip_push(dr, &f);
if (0) { // debug draw the text rectangle as a box
cg_rectangle(cg, title.l, title.t,
c2_rect_width(&title), c2_rect_height(&title));
cg_stroke(cg);
}
// draw the box/circle
if (c->style == MUI_BUTTON_STYLE_RADIO) {
cg_circle(cg, box.l + (c2_rect_width(&box) / 2),
@ -131,8 +137,8 @@ mui_check_rad_draw(
c->state == MUI_CONTROL_STATE_DISABLED ?
mui_control_color[c->state].text :
mui_control_color[0].text,
MUI_TEXT_ALIGN_MIDDLE);
mui_drawable_clip_pop(dr);
MUI_TEXT_ALIGN_MIDDLE|MUI_TEXT_ALIGN_COMPACT);
// mui_drawable_clip_pop(dr);
}

View File

@ -36,7 +36,7 @@ mui_drawable_draw(
mui_drawable_clip_push(dr, &f);
mui_drawable_control_t *dc = (mui_drawable_control_t *)c;
for (int i = 0; i < (int)dc->drawables.count; i++) {
for (uint i = 0; i < dc->drawables.count; i++) {
mui_drawable_t *d = dc->drawables.e[i];
if (!d->pix.pixels)
continue;
@ -75,7 +75,7 @@ mui_cdef_drawable(
switch (c->type) {
case MUI_CONTROL_DRAWABLE: {
mui_drawable_control_t *dc = (mui_drawable_control_t *)c;
for (int i = 0; i < (int)dc->drawables.count; i++) {
for (uint i = 0; i < dc->drawables.count; i++) {
mui_drawable_t *d = dc->drawables.e[i];
mui_drawable_dispose(d);
}

View File

@ -57,6 +57,7 @@ mui_listbox_draw(
c2_rect_inset(&clip, 1, 1);
mui_drawable_clip_push(dr, &clip);
}
cg = mui_drawable_get_cg(dr);
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
uint32_t top_element = lb->scroll / lb->elem_height;
uint32_t bottom_element = top_element + 1 +
@ -67,7 +68,7 @@ mui_listbox_draw(
mui_font_t * main = mui_font_find(win->ui, "main");
mui_color_t highlight = win->ui->color.highlight;
for (unsigned int ii = top_element;
for (uint ii = top_element;
ii < lb->elems.count && ii < bottom_element; ii++) {
c2_rect_t ef = f;
ef.b = ef.t + lb->elem_height;
@ -137,12 +138,12 @@ mui_listbox_typehead(
// that find something that matches, we're good. If not, try to match
// the prefix in a non-case sensitive way in case the user doesn't know
// what he wants...
for (unsigned int ii = 0; ii < lb->elems.count; ii++) {
for (uint ii = 0; ii < lb->elems.count; ii++) {
mui_listbox_elem_t *e = &lb->elems.e[ii];
if (strncmp(e->elem, lb->typehead.buf, lb->typehead.index) == 0)
return ii - lb->control.value;
}
for (unsigned int ii = 0; ii < lb->elems.count; ii++) {
for (uint ii = 0; ii < lb->elems.count; ii++) {
mui_listbox_elem_t *e = &lb->elems.e[ii];
if (strncasecmp(e->elem, lb->typehead.buf, lb->typehead.index) == 0)
return ii - lb->control.value;
@ -272,10 +273,13 @@ mui_cdef_listbox(
uint8_t what,
void * param)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
switch (what) {
case MUI_CDEF_INIT:
break;
case MUI_CDEF_DISPOSE:
// strings for the elements are not owned by the listbox
mui_listbox_elems_free(&lb->elems);
break;
case MUI_CDEF_DRAW: {
mui_drawable_t * dr = param;

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
#include <stdlib.h>
#include "mui.h"
#include "mui_priv.h"
const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT] = {
[MUI_CONTROL_STATE_NORMAL] = {
@ -35,6 +35,10 @@ const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT] = {
},
};
static void
mui_control_dispose_actions(
mui_control_t * c);
void
mui_control_draw(
mui_window_t * win,
@ -67,6 +71,7 @@ mui_control_new(
c->title = title ? strdup(title) : NULL;
c->win = win;
c->uid = uid;
mui_refqueue_init(&c->refs);
STAILQ_INIT(&c->actions);
TAILQ_INSERT_TAIL(&win->controls, c, self);
if (c->cdef)
@ -83,27 +88,29 @@ _mui_control_free(
if (c->title)
free(c->title);
c->title = NULL;
if (c->cdef)
c->cdef(c, MUI_CDEF_DISPOSE, NULL);
free(c);
}
void
mui_control_dispose(
mui_control_t * c )
{
if (!c)
return;
if (c->flags.zombie) {
printf("%s: DOUBLE delete %s\n", __func__, c->title);
if (c->win) {
TAILQ_REMOVE(&c->win->controls, c, self);
if (c->cdef)
c->cdef(c, MUI_CDEF_DISPOSE, NULL);
c->win = NULL;
mui_control_dispose_actions(c);
}
if (mui_refqueue_dispose(&c->refs) != 0) {
// fprintf(stderr, "%s Warning: control %s still has a lock\n",
// __func__, c->title);
return;
}
TAILQ_REMOVE(&c->win->controls, c, self);
if (c->win->flags.zombie || c->win->ui->action_active) {
c->flags.zombie = true;
TAILQ_INSERT_TAIL(&c->win->zombies, c, self);
} else
_mui_control_free(c);
_mui_control_free(c);
}
uint32_t
@ -146,13 +153,18 @@ _mui_control_highlight_timer_cb(
mui_time_t now,
void * param)
{
mui_control_t * c = param;
mui_control_ref_t *ref = param;
mui_control_t * c = ref->control;
if (!c) {
mui_control_deref(ref);
return 0;
}
// printf("%s: %s\n", __func__, c->title);
mui_control_set_state(c, MUI_CONTROL_STATE_NORMAL);
if (c->cdef)
c->cdef(c, MUI_CDEF_SELECT, NULL);
mui_control_action(c, MUI_CONTROL_ACTION_SELECT, NULL);
mui_control_deref(ref);
return 0;
}
@ -198,9 +210,12 @@ mui_control_event(
if (c->state != MUI_CONTROL_STATE_DISABLED &&
mui_event_match_key(ev, c->key_equ)) {
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
mui_control_ref_t * ref = mui_control_ref(NULL, c,
FCC('h', 'i', 'g', 'h'));
mui_timer_register(
c->win->ui, _mui_control_highlight_timer_cb,
c, MUI_TIME_SECOND / 10);
ref, MUI_TIME_SECOND / 10);
res = true;
}
break;
@ -267,6 +282,62 @@ mui_control_set_title(
mui_control_inval(c);
}
void
mui_control_lock(
mui_control_t *c)
{
if (!c)
return;
if (!c->lock.control) {
mui_control_ref(&c->lock, c, FCC('l', 'o', 'c', 'k'));
c->lock.ref.count = 10; // prevent it from being deleted
} else {
c->lock.ref.count += 10;
}
}
void
mui_control_unlock(
mui_control_t *c)
{
if (!c)
return;
if (c->lock.control) {
if (c->lock.ref.trace)
printf("%s: control %s was locked\n",
__func__, c->title);
if (c->lock.ref.count > 10) {
c->lock.ref.count -= 10;
} else { // control was disposed of
int delete = c->lock.ref.count < 10;
// we are the last one, remove the lock
if (c->lock.ref.trace)
printf("%s: control %s unlocked delete %d\n",
__func__, c->title, delete);
mui_control_deref(&c->lock);
if (delete)
mui_control_dispose(c);
}
} else {
// if (c->lock.ref.trace)
printf("%s: control %s was not locked\n", __func__, c->title);
}
}
static void
mui_control_dispose_actions(
mui_control_t * c)
{
if (!c)
return;
mui_action_t *a;
while ((a = STAILQ_FIRST(&c->actions)) != NULL) {
STAILQ_REMOVE_HEAD(&c->actions, self);
free(a);
}
}
void
mui_control_action(
mui_control_t * c,
@ -275,14 +346,17 @@ mui_control_action(
{
if (!c)
return;
c->win->ui->action_active++;
mui_action_t *a;
STAILQ_FOREACH(a, &c->actions, self) {
// this prevents the callbacks from disposing of the control
// the control is locked until the last callback is done
// then it's disposed of
mui_control_lock(c);
mui_action_t *a, *safe;
STAILQ_FOREACH_SAFE(a, &c->actions, self, safe) {
if (!a->control_cb)
continue;
a->control_cb(c, a->cb_param, what, param);
}
c->win->ui->action_active--;
mui_control_unlock(c);
}
void

View File

@ -68,7 +68,7 @@ mui_drawable_clear(
if (dr->pixman)
pixman_image_unref(dr->pixman);
dr->pixman = NULL;
for (int i = 0; i < (int)dr->clip.count; i++)
for (uint i = 0; i < dr->clip.count; i++)
pixman_region32_fini(&dr->clip.e[i]);
mui_clip_stack_clear(&dr->clip);
if (dr->pix.pixels && dr->dispose_pixels)
@ -209,7 +209,7 @@ mui_drawable_set_clip(
{
if (!dr)
return;
for (int i = 0; i < (int)dr->clip.count; i++)
for (uint i = 0; i < dr->clip.count; i++)
pixman_region32_fini(&dr->clip.e[i]);
mui_clip_stack_clear(&dr->clip);
if (clip && clip->count) {

View File

@ -54,9 +54,9 @@ mui_font_t *
mui_font_from_mem(
mui_t *ui,
const char *name,
unsigned int size,
uint size,
const void *font_data,
unsigned int font_size )
uint font_size )
{
mui_font_t *f = calloc(1, sizeof(*f));
f->name = strdup(name);
@ -89,6 +89,7 @@ mui_font_dispose(
while ((f = TAILQ_FIRST(&ui->fonts))) {
TAILQ_REMOVE(&ui->fonts, f, self);
stb_ttc_Free(&f->ttc);
mui_drawable_dispose(&f->font);
free(f->name);
free(f);
}
@ -113,15 +114,15 @@ mui_font_text_draw(
mui_drawable_t *dr,
c2_pt_t where,
const char *text,
unsigned int text_len,
uint text_len,
mui_color_t color)
{
struct stb_ttc_info * ttc = &font->ttc;
unsigned int state = 0;
uint state = 0;
float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size);
double xpos = 0;
unsigned int last = 0;
unsigned int cp = 0;
uint last = 0;
uint cp = 0;
if (!text_len)
text_len = strlen(text);
@ -132,7 +133,7 @@ mui_font_text_draw(
pixman_image_t * fill = pixman_image_create_solid_fill(&pc);
where.y += font->ttc.ascent * scale;
for (unsigned int ch = 0; text[ch] && ch < text_len; ch++) {
for (uint ch = 0; text[ch] && ch < text_len; ch++) {
if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT)
continue;
if (last) {
@ -171,40 +172,58 @@ mui_font_text_draw(
IMPLEMENT_C_ARRAY(mui_glyph_array);
IMPLEMENT_C_ARRAY(mui_glyph_line_array);
#define MUI_NARROW_ADVANCE_FACTOR 0.92
void
mui_font_measure(
mui_font_t *font,
c2_rect_t bbox,
const char *text,
unsigned int text_len,
uint text_len,
mui_glyph_line_array_t *lines,
uint16_t flags)
mui_text_e flags)
{
struct stb_ttc_info * ttc = &font->ttc;
unsigned int state = 0;
uint state = 0;
float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size);
unsigned int last = 0;
unsigned int cp = 0;
uint last = 0;
uint cp = 0;
int debug = flags & MUI_TEXT_DEBUG;
if (!text_len)
text_len = strlen(text);
//debug = !strncmp(text, "Titan", 5) || !strcmp(text, "Driver");
if (debug)
printf("Measure text %s\n", text);
lines->margin_left = c2_rect_width(&bbox);
lines->margin_right = 0;
lines->height = 0;
c2_pt_t where = {};
unsigned int ch = 0;
uint ch = 0;
int wrap_chi = 0;
int wrap_w = 0;
int wrap_count = 0;
float compact = flags & MUI_TEXT_ALIGN_COMPACT ? 0.85 : 1.0;
float narrow = flags & MUI_TEXT_STYLE_NARROW ?
MUI_NARROW_ADVANCE_FACTOR : 1.0;
mui_glyph_array_t * line = NULL;
do {
where.y += font->ttc.ascent * scale;
const mui_glyph_array_t zero = {};
mui_glyph_line_array_push(lines, zero);
mui_glyph_array_t * line = &lines->e[lines->count - 1];
line = &lines->e[lines->count - 1];
line->x = 0;
line->t = where.y;
where.y += (font->ttc.ascent * compact) * scale;
line->b = where.y - (font->ttc.descent * scale);
line->y = where.y;
line->w = 0;
wrap_chi = ch;
wrap_w = 0;
wrap_count = 0;
if (debug)
printf("line %d y:%3d ch:%3d\n", lines->count,
line->y, ch);
for (;text[ch]; ch++) {
if (stb_ttc__UTF8_Decode(&state, &cp, text[ch]) != UTF8_ACCEPT)
continue;
@ -213,7 +232,9 @@ mui_font_measure(
line->w += kern;
}
last = cp;
// printf("glyph %3d : %04x:%c\n", ch, cp, cp < 32 ? '.' : cp);
if (debug) printf(" glyph ch:%3d : %04x:%c S:%d L:%2d:%2d\n",
ch, cp, cp < 32 ? '.' : cp, state,
lines->count-1, line->count);
if (cp == '\n') {
ch++;
break;
@ -231,7 +252,10 @@ mui_font_measure(
continue;
if (gc->p_y == (unsigned short) -1)
stb_ttc__ScaledGlyphRenderToCache(ttc, gc);
if (((line->w + gc->advance) * scale) > c2_rect_width(&bbox)) {
float advance = gc->advance * narrow;
if (cp == ' ')
advance *= 0.9;
if (((line->w + advance) * scale) > c2_rect_width(&bbox)) {
if (wrap_count) {
ch = wrap_chi + 1;
line->count = wrap_count;
@ -239,26 +263,48 @@ mui_font_measure(
}
break;
}
line->w += gc->advance;
mui_glyph_array_push(line, gc->index);
mui_glyph_t g = {
.glyph = cp,
.pos = ch,
.index = gc->index,
.x = (line->w * scale) + gc->x0,
.w = advance * scale,
};
mui_glyph_array_push(line, g);
// printf(" PUSH[%2d] glyph %3d : %04x:%c x:%3d w:%3d\n",
// line->count - 1, g.pos, text[g.pos], text[g.pos],
// g.x, g.w);
line->w += advance;
};
// zero terminate the line, so there is a marker at the end
mui_glyph_t g = {
.glyph = 0,
.pos = ch,
.x = (line->w) * scale,
};
mui_glyph_array_push(line, g);
line->count--;
where.y += -font->ttc.descent * scale;
} while (text[ch] && ch < text_len);
int bh = 0;
for (int i = 0; i < (int)lines->count; i++) {
/*
* Finalise the lines, calculate the total height, and the margins
* Margins are the minimal x and maximal x of the lines
*/
for (uint i = 0; i < lines->count; i++) {
mui_glyph_array_t * line = &lines->e[i];
bh = line->y - (font->ttc.descent * scale);
lines->height = line->y - (font->ttc.descent * scale);
line->w *= scale;
// printf(" line %d y %3d size %d width %d\n", i,
// line->y, line->count, line->w);
}
// printf("box height is %d/%d\n", bh, c2_rect_height(&bbox));
int ydiff = 0;
if (flags & MUI_TEXT_ALIGN_MIDDLE) {
ydiff = (c2_rect_height(&bbox) - bh) / 2;
ydiff = (c2_rect_height(&bbox) - (int)lines->height) / 2;
} else if (flags & MUI_TEXT_ALIGN_BOTTOM) {
ydiff = c2_rect_height(&bbox) - bh;
ydiff = c2_rect_height(&bbox) - (int)lines->height;
}
for (int i = 0; i < (int)lines->count; i++) {
if (debug)
printf("box height is %d/%d ydiff:%d\n",
lines->height, c2_rect_height(&bbox), ydiff);
for (uint i = 0; i < lines->count; i++) {
mui_glyph_array_t * line = &lines->e[i];
line->y += ydiff;
if (flags & MUI_TEXT_ALIGN_RIGHT) {
@ -266,6 +312,13 @@ mui_font_measure(
} else if (flags & MUI_TEXT_ALIGN_CENTER) {
line->x = (c2_rect_width(&bbox) - line->w) / 2;
}
if (line->x < (int)lines->margin_left)
lines->margin_left = line->x;
if (line->x + line->w > lines->margin_right) // last x
lines->margin_right = line->x + line->w;
if (debug)
printf(" line %d y:%3d size %3d width %.2f\n", i,
line->y, line->count, line->w);
}
}
@ -275,7 +328,7 @@ mui_font_measure_clear(
{
if (!lines)
return;
for (int i = 0; i < (int)lines->count; i++) {
for (uint i = 0; i < lines->count; i++) {
mui_glyph_array_t * line = &lines->e[i];
mui_glyph_array_free(line);
}
@ -290,39 +343,67 @@ mui_font_measure_draw(
c2_rect_t bbox,
mui_glyph_line_array_t *lines,
mui_color_t color,
uint16_t flags)
mui_text_e flags)
{
pixman_color_t pc = PIXMAN_COLOR(color);
pixman_image_t * fill = pixman_image_create_solid_fill(&pc);
struct stb_ttc_info * ttc = &font->ttc;
float scale = stbtt_ScaleForPixelHeight(&ttc->font, font->size);
mui_drawable_t * src = &font->font;
mui_drawable_t * dst = dr;
// all glyphs we need were loaded, update the pixman texture
_mui_font_pixman_prep(font);
for (int li = 0; li < (int)lines->count; li++) {
for (uint li = 0; li < lines->count; li++) {
mui_glyph_array_t * line = &lines->e[li];
int xpos = 0;//where.x / scale;
for (int ci = 0; ci < (int)line->count; ci++) {
unsigned int cache_index = line->e[ci];
int lastu = line->x;
for (uint ci = 0; ci < line->count; ci++) {
uint cache_index = line->e[ci].index;
if (line->e[ci].glyph < ' ')
continue;
stb_ttc_g *gc = &ttc->glyph[cache_index];
// int pxpos = gc->x0 + ((xpos + gc->lsb) * scale);
int pxpos = gc->x0 + ((xpos + 0) * scale);
float pxpos = line->e[ci].x;
int ph = gc->y1 - gc->y0;
int pw = gc->x1 - gc->x0;
pixman_image_composite32(
PIXMAN_OP_OVER,
fill,
pixman_image_composite32(PIXMAN_OP_OVER, fill,
mui_drawable_get_pixman(src),
mui_drawable_get_pixman(dst),
0, 0, gc->p_x, gc->p_y,
bbox.l + line->x + pxpos,
bbox.t + line->y + gc->y0, pw, ph);
xpos += gc->advance;
bbox.l + (line->x + pxpos),
bbox.t + (line->y + gc->y0), pw, ph);
/*
* For 'cheap' bold, we just draw the glyph again over the
* same position, but shifted by one pixel in x.
* Works surprisingly well!
*/
if (flags & MUI_TEXT_STYLE_BOLD) {
pixman_image_composite32(PIXMAN_OP_OVER, fill,
mui_drawable_get_pixman(src),
mui_drawable_get_pixman(dst),
0, 0, gc->p_x, gc->p_y,
bbox.l + line->x + pxpos + 1,
bbox.t + line->y + gc->y0, pw, ph);
}
/*
* Underline is very primitive, it just draws a line
* under the glyphs, but it's enough for now. Skips the
* ones with obvious descenders. This is far from perfect
* obviously but it's a start.
*/
if (flags & MUI_TEXT_STYLE_ULINE) {
// don't draw under glyphs like qpygj etc
bool draw_underline = gc->y1 <= 2;
if (draw_underline) {
c2_rect_t u = C2_RECT(
bbox.l + lastu,
bbox.t + line->y + 2,
bbox.l + line->x + pxpos + pw,
bbox.t + line->y + 3);
pixman_image_fill_boxes(PIXMAN_OP_OVER,
mui_drawable_get_pixman(dst),
&pc, 1, (pixman_box32_t*)&u);
}
lastu = line->x + pxpos + pw;
}
}
}
pixman_image_unref(fill);
@ -334,9 +415,9 @@ mui_font_textbox(
mui_drawable_t *dr,
c2_rect_t bbox,
const char *text,
unsigned int text_len,
uint text_len,
mui_color_t color,
uint16_t flags)
mui_text_e flags)
{
mui_glyph_line_array_t lines = {};
@ -344,8 +425,6 @@ mui_font_textbox(
text_len = strlen(text);
mui_font_measure(font, bbox, text, text_len, &lines, flags);
mui_font_measure_draw(font, dr, bbox, &lines, color, flags);
mui_font_measure_clear(&lines);
}

View File

@ -37,33 +37,34 @@ enum mui_menu_action_e {
struct mui_menu_control_t;
struct mui_menubar_t;
typedef struct mui_menu_t {
mui_window_t win;
unsigned int click_inside : 1,
uint click_inside : 1,
drag_ev : 1,
closing: 1, // prevent double-delete
timer_call_count : 2; // used by mui_menu_close_timer_cb
mui_control_t * highlighted;
mui_control_ref_t highlighted; // mui_menuitem_control_t *
mui_time_t sub_open_stamp;
// currently open menu, if any
struct mui_menu_control_t * sub;
struct mui_menubar_t * menubar;
mui_control_ref_t sub; // mui_menu_control_t *
mui_window_ref_t menubar; // mui_menubar_t * window
} mui_menu_t;
typedef struct mui_menubar_t {
mui_window_t win;
unsigned int click_inside : 1,
uint click_inside : 1,
drag_ev : 1,
was_highlighted : 1,
timer_call_count : 2; // used by mui_menu_close_timer_cb
// currently open menu title
struct mui_menu_control_t * selected_title;
mui_control_ref_t selected_title; // mui_menu_control_t *
// keep track of the menus, and their own submenus as they are being opened
// this is to keep track of the 'hierarchy' of menus, so that we can close
// them all when the user clicks outside of them, or release the mouse.
mui_menu_t * open[8];
int open_count;
mui_window_ref_t open[8];
uint open_count;
bool delayed_closing;
} mui_menubar_t;
@ -111,20 +112,22 @@ mui_cdef_popup(
// any open menus (and their submenus, if any)
static bool
_mui_menubar_close_menu(
mui_menubar_t *mbar )
mui_menubar_t *mbar )
{
if (mbar->delayed_closing)
return false;
mbar->click_inside = false;
mui_control_set_state((mui_control_t*)mbar->selected_title, 0);
if (mbar->selected_title)
mbar->selected_title->menu_window = NULL;
mui_menu_control_t * m = (mui_menu_control_t*)mbar->selected_title.control;
D(printf("%s %s\n", __func__, m ? ((mui_control_t*)m)->title : "???");)
mui_control_set_state((mui_control_t*)m, 0);
if (m)
mui_window_deref(&m->menu_window);
if (!mbar->open_count)
return false;
mbar->selected_title = NULL;
for (int i = 0; i < mbar->open_count; i++) {
mui_menu_close(&mbar->open[i]->win);
mbar->open[i] = NULL;
mui_control_deref(&mbar->selected_title);
for (uint i = 0; i < mbar->open_count; i++) {
mui_menu_close(mbar->open[i].window);
mui_window_deref(&mbar->open[i]);
}
mbar->open_count = 0;
return true;
@ -133,17 +136,17 @@ _mui_menubar_close_menu(
// close the submenu from a hierarchical menu item
static bool
_mui_menu_close_submenu(
mui_menu_t * menu )
mui_menu_t * menu )
{
mui_menu_control_t * sub = menu->sub;
mui_menu_control_t * sub = (mui_menu_control_t*)menu->sub.control;
if (!sub)
return false;
menu->sub = NULL;
mui_control_deref(&menu->sub);
mui_control_set_state((mui_control_t*)sub, 0);
if (sub->menu_window) {
mui_menu_close(sub->menu_window);
if (sub->menu_window.window) {
mui_menu_close(sub->menu_window.window);
}
sub->menu_window = NULL;
mui_window_deref(&sub->menu_window);
return true;
}
@ -158,26 +161,27 @@ mui_menu_close_timer_cb(
void * param)
{
mui_menu_t * menu = param;
if (!menu->highlighted) {
printf("%s: no selected item, closing\n", __func__);
if (!menu->highlighted.control) {
D(printf("%s: no selected item, closing\n", __func__);)
mui_window_dispose(&menu->win);
return 0;
}
menu->timer_call_count++;
mui_control_set_state(menu->highlighted,
menu->highlighted->state == MUI_CONTROL_STATE_CLICKED ?
mui_control_set_state(menu->highlighted.control,
menu->highlighted.control->state == MUI_CONTROL_STATE_CLICKED ?
MUI_CONTROL_STATE_NORMAL : MUI_CONTROL_STATE_CLICKED);
if (menu->timer_call_count == 3) {
// we are done!
mui_menuitem_control_t * item = (mui_menuitem_control_t*)menu->highlighted;
mui_menuitem_control_t * item =
(mui_menuitem_control_t*)menu->highlighted.control;
mui_window_action(&menu->win, MUI_MENU_ACTION_SELECT, &item->item);
// mui_menu_close_all(&menu->win);
if (menu->menubar) {
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar;
if (menu->menubar.window) {
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar.window;
mui_window_action(&mbar->win,
MUI_MENUBAR_ACTION_SELECT, &item->item);
mbar->delayed_closing = false;
_mui_menubar_close_menu((mui_menubar_t*)menu->menubar);
_mui_menubar_close_menu(mbar);
} else
mui_menu_close(&menu->win);
return 0;
@ -260,12 +264,14 @@ mui_menubar_handle_mouse(
mui_window_t * win = &mbar->win;
bool inside = c2_rect_contains_pt(&win->frame, &ev->mouse.where);
mui_control_t * c = inside ? mui_control_locate(win, ev->mouse.where) : NULL;
mui_control_t * c = inside ?
mui_control_locate(win, ev->mouse.where) : NULL;
switch (ev->type) {
case MUI_EVENT_BUTTONUP: {
D(printf("%s up drag %d click in:%d high:%d was:%d\n", __func__,
mbar->drag_ev, mbar->click_inside,
mbar->selected_title ? 1 : 0, mbar->was_highlighted);)
mbar->selected_title.control ? 1 : 0,
mbar->was_highlighted);)
if (mbar->drag_ev == 0 && mbar->click_inside) {
if (mbar->was_highlighted) {
return _mui_menubar_close_menu(mbar);
@ -291,21 +297,25 @@ mui_menubar_handle_mouse(
D(printf("%s click inside %d\n", __func__, inside);)
mbar->drag_ev = 0;
mbar->click_inside = inside;
mbar->was_highlighted = mbar->selected_title != NULL;
mbar->was_highlighted = mbar->selected_title.control != NULL;
}
if (c && mui_control_get_state(c) != MUI_CONTROL_STATE_DISABLED) {
if (mbar->selected_title &&
c != (mui_control_t*)mbar->selected_title)
if (mbar->selected_title.control &&
c != mbar->selected_title.control) {
_mui_menubar_close_menu(mbar);
}
mbar->click_inside = true;
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
mui_menu_control_t *title = (mui_menu_control_t*)c;
mbar->selected_title = title;
mui_control_deref(&mbar->selected_title);
mui_control_ref(&mbar->selected_title, c, FCC('s','e','l','t'));
if (mui_control_get_type(c) == MUI_CONTROL_MENUTITLE) {
if (title->menu_window == NULL) {
title->menu_window = _mui_menu_create(
if (title->menu_window.window == NULL) {
mui_window_t *new = _mui_menu_create(
win->ui, mbar, C2_PT(c->frame.l, c->frame.b),
title->menu.e);
mui_window_ref(&title->menu_window, new,
FCC('m','e','n','u'));
}
}
return true;
@ -381,6 +391,13 @@ mui_wdef_menubar(
{
mui_menubar_t * mbar = (mui_menubar_t*)win;
switch (what) {
case MUI_WDEF_DISPOSE: {
mui_control_deref(&mbar->selected_title);
for (uint i = 0; i < mbar->open_count; i++) {
mui_menu_close(mbar->open[i].window);
mui_window_deref(&mbar->open[i]);
}
} break;
case MUI_WDEF_DRAW: {
mui_drawable_t * dr = param;
mui_wdef_menubar_draw(win, dr);
@ -423,9 +440,9 @@ mui_menu_handle_mouse(
if (menu->drag_ev == 0) {
// return true;
}
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar;
if (menu->highlighted &&
menu->highlighted->type != MUI_CONTROL_SUBMENUITEM) {
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar.window;
if (menu->highlighted.control &&
menu->highlighted.control->type != MUI_CONTROL_SUBMENUITEM) {
/*
* This tells the normal closing code that we are
* taking care of the closing with the timer, and not *now*
@ -436,7 +453,7 @@ mui_menu_handle_mouse(
mui_timer_register(win->ui, mui_menu_close_timer_cb, menu,
MUI_MENU_CLOSE_BLINK_DELAY);
} else {
menu->highlighted = NULL;
mui_control_deref(&menu->highlighted);
if (mbar)
_mui_menubar_close_menu(mbar);
else
@ -460,34 +477,39 @@ mui_menu_handle_mouse(
}
// printf("%s in:%d c:%s\n", __func__, inside, c ? c->title : "");
if (c && mui_control_get_state(c) != MUI_CONTROL_STATE_DISABLED) {
if (menu->sub && c != (mui_control_t*)menu->sub)
if (menu->sub.control && c != menu->sub.control)
_mui_menu_close_submenu(menu);
if (menu->highlighted && c != menu->highlighted)
mui_control_set_state(menu->highlighted, 0);
if (menu->highlighted.control &&
c != menu->highlighted.control)
mui_control_set_state(menu->highlighted.control, 0);
mui_control_set_state(c, MUI_CONTROL_STATE_CLICKED);
menu->highlighted = c;
mui_control_deref(&menu->highlighted);
mui_control_ref(&menu->highlighted, c, FCC('h','i','g','h'));
if (c->type == MUI_CONTROL_SUBMENUITEM) {
mui_menu_control_t *title = (mui_menu_control_t*)c;
if (title->menu_window == NULL) {
if (title->menu_window.window == NULL) {
c2_pt_t where = C2_PT(c->frame.r, c->frame.t);
c2_pt_offset(&where, win->content.l, win->content.t);
title->menu_window = _mui_menu_create(
mui_window_t *new = _mui_menu_create(
win->ui,
(mui_menubar_t*)menu->menubar, where,
(mui_menubar_t*)menu->menubar.window, where,
title->menu.e);
menu->sub = title;
mui_window_ref(&title->menu_window, new,
FCC('m','e','n','u'));
mui_control_ref(&menu->sub, c,
FCC('s','u','b','m'));
menu->sub_open_stamp = mui_get_time();
mui_window_action(&menu->win, MUI_MENU_ACTION_OPEN,
title->menu_window);
mui_window_set_action(title->menu_window,
title->menu_window.window);
mui_window_set_action(title->menu_window.window,
mui_submenu_action_cb, menu);
}
}
} else {
if (!menu->sub) {
if (menu->highlighted)
mui_control_set_state(menu->highlighted, 0);
menu->highlighted = NULL;
if (!menu->sub.control) {
if (menu->highlighted.control)
mui_control_set_state(menu->highlighted.control, 0);
mui_control_deref(&menu->highlighted);
}
}
} break;
@ -504,6 +526,7 @@ mui_wdef_menu(
mui_menu_t * menu = (mui_menu_t*)win;
switch (what) {
case MUI_WDEF_DISPOSE: {
mui_window_deref(&menu->menubar);
_mui_menu_close_submenu(menu);
} break;
case MUI_WDEF_DRAW: {
@ -539,7 +562,7 @@ mui_menubar_new(
ui, mbf,
mui_wdef_menubar, MUI_WINDOW_MENUBAR_LAYER,
"Menubar", sizeof(*mbar));
ui->menubar = &mbar->win;
mui_window_ref(&ui->menubar, &mbar->win, FCC('m','b','a','r'));
return &mbar->win;
}
@ -547,7 +570,7 @@ mui_window_t *
mui_menubar_get(
mui_t * ui )
{
return ui ? ui->menubar : NULL;
return ui->menubar.window;
}
bool
@ -586,7 +609,7 @@ mui_menubar_add_simple(
//printf("%s title %s rect %s\n", __func__, title, c2_rect_as_str(&title_rect));
mui_menu_control_t *menu = (mui_menu_control_t*)c;
menu->menubar = win;
mui_window_ref(&menu->menubar, win, FCC('m','b','a','r'));
/*
* We do not clone the items, so they must be static
* from somewhere -- we do not free them either.
@ -601,6 +624,63 @@ mui_menubar_add_simple(
return c;
}
/* 'count' can be zero, in which case *requires* NULL termination */
mui_control_t *
mui_menubar_add_menu(
mui_window_t * win,
uint32_t menu_uid,
mui_menu_item_t * items,
uint count )
{
c2_rect_t parts[MUI_MENUTITLE_PART_COUNT];
mui_menutitle_get_part_locations(win->ui, NULL, items, parts);
int title_width = c2_rect_width(&parts[MUI_MENUTITLE_PART_ALL]);
c2_rect_t title_rect = { .t = 2 };
mui_control_t * last = TAILQ_LAST(&win->controls, controls);
if (last) {
c2_rect_offset(&title_rect, last->frame.r, 0);
} else
title_rect.l = 4;
title_rect.r = title_rect.l + title_width + 6;
title_rect.b = win->content.b + 2;// title_rect.t + m.ascent - m.descent;
mui_control_t * c = mui_control_new(
win, MUI_CONTROL_MENUTITLE, mui_cdef_popup,
title_rect, items[0].title, menu_uid,
sizeof(mui_menu_control_t));
//printf("%s title %s rect %s\n", __func__, title, c2_rect_as_str(&title_rect));
mui_menu_control_t *menu = (mui_menu_control_t*)c;
mui_window_ref(&menu->menubar, win, FCC('m','b','a','r'));
menu->item.item = items[0];
/*
* We do not clone the items, so they must be static
* from somewhere -- we do not free them either.
*/
int sub_count = count ? count - 1 : 0;
for (int ii = 1; items[ii].title; ii++)
sub_count++;
menu->menu.count = count -1 ;
menu->menu.e = items + 1;
menu->menu.read_only = 1;
return c;
}
#if 0
void
mui_menu_set_title(
mui_control_t * c,
const mui_menu_item_t *title )
{
mui_menu_control_t * menu = (mui_menu_control_t*)c;
mui_control_set_title(c, title);
mui_window_set_title(&menu->menu_window, title);
}
#endif
mui_window_t *
mui_menubar_highlight(
mui_window_t * win,
@ -687,8 +767,10 @@ _mui_menu_create(
mui_wdef_menu, MUI_WINDOW_MENU_LAYER,
items[0].title, sizeof(*menu));
if (mbar) {
mbar->open[mbar->open_count++] = menu;
menu->menubar = mbar;
mui_window_ref(&mbar->open[mbar->open_count], &menu->win,
FCC('m','e','n','u'));
mbar->open_count++;
mui_window_ref(&menu->menubar, &mbar->win, FCC('m','b','a','r'));
}
/* Walk all the items in out static structure, and create the controls
* for each of them with their own corresponding item */
@ -739,13 +821,15 @@ static void
mui_menu_close(
mui_window_t * win )
{
if (!win)
return;
mui_menu_t * menu = (mui_menu_t*)win;
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar; // can be NULL
mui_menubar_t * mbar = (mui_menubar_t*)menu->menubar.window; // can be NULL
menu->highlighted = NULL;
mui_control_deref(&menu->highlighted);
if (mbar && mbar->open_count) {
mbar->open[mbar->open_count-1] = NULL;
mbar->open_count--;
mui_window_deref(&mbar->open[mbar->open_count]);
}
mui_window_dispose(win);
}
@ -759,7 +843,7 @@ mui_popupmenu_action_cb(
void * param) // popup control here
{
mui_menu_control_t * pop = cb_param;
printf("%s %4.4s\n", __func__, (char*)&what);
D(printf("%s %4.4s\n", __func__, (char*)&what);)
switch (what) {
case MUI_MENU_ACTION_SELECT: {
mui_menu_item_t * item = param;
@ -780,10 +864,11 @@ mui_popupmenu_handle_mouse(
mui_control_t * c = &pop->item.control;
switch (ev->type) {
case MUI_EVENT_BUTTONUP: {
printf("%s up has popup %d\n", __func__, pop->menu_window != NULL);
if (pop->menu_window) {
D(printf("%s up has popup %d\n", __func__,
pop->menu_window.window != NULL);)
if (pop->menu_window.window) {
// mui_menu_close(pop->menu_window);
pop->menu_window = NULL;
mui_window_deref(&pop->menu_window);
}
mui_control_set_state(c, 0);
} break;
@ -791,14 +876,16 @@ mui_popupmenu_handle_mouse(
mui_control_set_state(c,
ev->type != MUI_EVENT_BUTTONUP ?
MUI_CONTROL_STATE_CLICKED : 0);
if (!pop->menu_window) {
if (!pop->menu_window.window) {
c2_pt_t loc = pop->menu_frame.tl;
c2_pt_offset(&loc, c->win->content.l, c->win->content.t);
c2_pt_offset(&loc, 0, -pop->menu.e[c->value].location.y);
pop->menu_window = _mui_menu_create(
mui_window_t *new = _mui_menu_create(
c->win->ui, NULL, loc,
pop->menu.e);
mui_window_set_action(pop->menu_window,
mui_window_ref(&pop->menu_window, new,
FCC('m','e','n','u'));
mui_window_set_action(pop->menu_window.window,
mui_popupmenu_action_cb, pop);
// pass the mousedown to the new popup
// mui_window_handle_mouse(pop->menu_window, ev);
@ -823,13 +910,21 @@ mui_cdef_popup(
case MUI_CONTROL_POPUP:
case MUI_CONTROL_MENUTITLE: {
mui_menu_control_t *pop = (mui_menu_control_t*)c;
if (pop->menu_window) {
mui_menu_close(pop->menu_window);
pop->menu_window = NULL;
if (pop->menu_window.window) {
mui_menu_close(pop->menu_window.window);
mui_window_deref(&pop->menu_window);
}
mui_menu_items_clear(&pop->menu);
if (!pop->menu.read_only)
mui_menu_items_free(&pop->menu);
if (pop->item.color_icon)
mui_drawable_dispose(pop->item.color_icon);
} break;
case MUI_CONTROL_MENUITEM:
case MUI_CONTROL_SUBMENUITEM: {
mui_menuitem_control_t *mic = (mui_menuitem_control_t*)c;
if (mic->color_icon)
mui_drawable_dispose(mic->color_icon);
} break;
}
break;
@ -890,7 +985,7 @@ mui_popupmenu_get_items(
if (!c)
return NULL;
if (c->type != MUI_CONTROL_POPUP && c->type != MUI_CONTROL_MENUTITLE) {
printf("%s: not a popup or menutitle\n", __func__);
D(printf("%s: not a popup or menutitle\n", __func__);)
return NULL;
}
mui_menu_control_t *pop = (mui_menu_control_t*)c;
@ -902,9 +997,9 @@ mui_popupmenu_prepare(
mui_control_t * c)
{
mui_menu_control_t *pop = (mui_menu_control_t*)c;
if (pop->menu_window) {
mui_window_dispose(pop->menu_window);
pop->menu_window = NULL;
if (pop->menu_window.window) {
mui_window_dispose(pop->menu_window.window);
mui_window_deref(&pop->menu_window);
}
c2_rect_t frame = mui_menu_get_enclosing_rect(c->win->ui, pop->menu.e);
pop->menu_frame = frame;

View File

@ -24,7 +24,6 @@ mui_wdef_menubar_draw(
{
c2_rect_t content = win->frame;
win->content = content;
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_color_t frameColor = MUI_COLOR(0x000000ff);
@ -41,6 +40,104 @@ mui_wdef_menubar_draw(
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
enum {
MUI_MENUITEM_PART_ICON = 0,
MUI_MENUITEM_PART_TITLE,
MUI_MENUITEM_PART_KCOMBO,
MUI_MENUITEM_PART_COUNT,
};
/* this return the l,t coordinates for parts */
static void
mui_menuitem_get_part_locations(
mui_t * ui,
c2_rect_t * frame,
mui_menu_item_t * item,
c2_rect_t out[MUI_MENUITEM_PART_COUNT])
{
mui_font_t * main = mui_font_find(ui, "main");
const int margin_right = main->size / 3;
const int margin_left = main->size;
stb_ttc_measure m = {};
mui_font_text_measure(main, item->title, &m);
for (int i = 0; i < MUI_MENUITEM_PART_COUNT; i++)
out[i] = C2_RECT_WH(0, 0, 0, 0);
c2_rect_t title = *frame;
title.b = title.t + m.ascent - m.descent;
// center it vertically.
c2_rect_offset(&title, 0,
(c2_rect_height(frame) / 2) - (c2_rect_height(&title) / 2));
/* An icon shifts the title right, a 'mark' doesn't */
if (item->icon[0]) {
mui_font_t * icons = mui_font_find(ui, "icon_small");
mui_font_text_measure(icons, item->icon, &m);
title.l += 6;
c2_pt_t loc = title.tl;
loc.x += (icons->size / 2) - ((m.x1 - m.x0) / 2);
out[MUI_MENUITEM_PART_ICON].tl = loc;
title.l += 6;
} else if (item->mark[0]) {
mui_font_text_measure(main, item->mark, &m);
c2_pt_t loc = title.tl;
loc.x += (main->size / 2) - ((m.x1 - m.x0) / 2);
out[MUI_MENUITEM_PART_ICON].tl = loc;
}
// this is the 'left margin' for the menu item
title.l += margin_left;
if (item->kcombo[0]) {
mui_font_text_measure(main, item->kcombo, &m);
c2_pt_t loc = C2_PT(title.r - m.x1 - m.x0 - margin_right, title.t);
out[MUI_MENUITEM_PART_KCOMBO] = (c2_rect_t){
.l = loc.x, .t = loc.y,
.r = title.r - margin_right,
.b = title.b };
title.r = loc.x;
}
out[MUI_MENUITEM_PART_TITLE] = title;
}
void
mui_menutitle_get_part_locations(
mui_t * ui,
c2_rect_t * frame, // optional!
mui_menu_item_t * item,
c2_rect_t * out)
{
mui_font_t * main = mui_font_find(ui, "main");
const int margin = main->size / 3;
for (int i = 0; i < MUI_MENUTITLE_PART_COUNT; i++)
out[i] = C2_RECT_WH(0, 0, 0, 0);
if (item->color_icon) {
out[MUI_MENUTITLE_PART_ICON] = C2_RECT_WH(0, 0,
item->color_icon[0], item->color_icon[1]);
}
if (item->title) {
stb_ttc_measure m = {};
mui_font_text_measure(main, item->title, &m);
out[MUI_MENUTITLE_PART_TITLE] =
C2_RECT_WH(out[MUI_MENUTITLE_PART_ICON].r,
0, m.x1 - m.x0, m.ascent - m.descent);
}
out[MUI_MENUTITLE_PART_ALL] = out[MUI_MENUTITLE_PART_ICON];
c2_rect_union(
&out[MUI_MENUTITLE_PART_ALL],
&out[MUI_MENUTITLE_PART_TITLE]);
out[MUI_MENUTITLE_PART_ALL].r += margin;
if (frame) {
// center them all vertically at least
for (int i = 0; i < MUI_MENUTITLE_PART_COUNT; i++) {
c2_rect_offset(&out[i],
frame->l + margin, frame->t +
(c2_rect_height(frame) / 2) - (c2_rect_height(&out[i]) / 2));
}
}
}
void
mui_menutitle_draw(
mui_window_t * win,
@ -50,79 +147,48 @@ mui_menutitle_draw(
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_font_t * main = mui_font_find(win->ui, "main");
stb_ttc_measure m = {};
mui_font_text_measure(main, c->title, &m);
int title_width = m.x1 - m.x0;
c2_rect_t title = f;
title.r = title.l + title_width + 1;
title.b = title.t + m.ascent - m.descent;
c2_rect_offset(&title, //-m.x0 +
(int)((c2_rect_width(&f) / 2) - (c2_rect_width(&title)) / 2),
(c2_rect_height(&f) / 2) - (c2_rect_height(&title) / 2));
c2_rect_t loc[MUI_MENUTITLE_PART_COUNT];
mui_menuitem_control_t *mic = (mui_menuitem_control_t*)c;
if (!mic->item.title)
mic->item.title = mic->control.title;
mui_menutitle_get_part_locations(win->ui, &f, &mic->item, loc);
mui_drawable_clip_push(dr, &f);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
uint32_t state = mui_control_get_state(c);
if (state) {
cg_set_source_color(cg, &CG_COLOR(mui_control_color[state].fill));
cg_rectangle(cg, f.l, f.t, c2_rect_width(&f), c2_rect_height(&f));
cg_fill(cg);
}
mui_font_text_draw(main, dr,
C2_PT(title.l, title.t), c->title, strlen(c->title),
mui_control_color[state].text);
if (mic->item.color_icon) {
if (!mic->color_icon) {
c2_pt_t size = C2_PT(mic->item.color_icon[0],
mic->item.color_icon[1]);
mic->color_icon = mui_drawable_new(size, 32,
(void*)(mic->item.color_icon + 2), size.x * 4);
}
pixman_image_composite32(PIXMAN_OP_OVER,
mui_drawable_get_pixman(mic->color_icon),
NULL,
mui_drawable_get_pixman(dr),
0, 0, 0, 0,
loc[MUI_MENUTITLE_PART_ICON].l, loc[MUI_MENUTITLE_PART_ICON].t,
c2_rect_width(&loc[MUI_MENUTITLE_PART_ICON]),
c2_rect_height(&loc[MUI_MENUTITLE_PART_ICON]));
}
if (mic->item.title)
mui_font_text_draw(main, dr,
loc[MUI_MENUTITLE_PART_TITLE].tl,
mic->item.title, strlen(mic->item.title),
mui_control_color[state].text);
mui_drawable_clip_pop(dr);
}
static void
mui_menuitem_get_locations(
mui_t * ui,
c2_rect_t * frame,
mui_menu_item_t * item,
c2_pt_t out[3] )
{
mui_font_t * main = mui_font_find(ui, "main");
stb_ttc_measure m = {};
mui_font_text_measure(main, item->title, &m);
c2_rect_t title = *frame;
title.b = title.t + m.ascent - m.descent;
c2_rect_offset(&title, 0,
(c2_rect_height(frame) / 2) - (c2_rect_height(&title) / 2));
if (item->icon[0]) {
title.l += 6;
mui_font_t * icons = mui_font_find(ui, "icon_small");
mui_font_text_measure(icons, item->icon, &m);
c2_pt_t loc = title.tl;
loc.x = loc.x + (icons->size / 2) - ((m.x1 - m.x0) / 2);
out[0] = loc;
title.l += 6;
} else if (item->mark[0]) {
mui_font_text_measure(main, item->mark, &m);
c2_pt_t loc = title.tl;
loc.x = loc.x + (main->size / 2) - ((m.x1 - m.x0) / 2);
out[0] = loc;
}
title.l += main->size;
out[1] = title.tl;
if (item->kcombo[0]) {
mui_font_text_measure(main, item->kcombo, &m);
c2_pt_t loc = C2_PT(title.r - m.x1 - m.x0 - (main->size/3), title.t);
out[2] = loc;
}
}
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
void
mui_menuitem_draw(
mui_window_t * win,
@ -132,14 +198,14 @@ mui_menuitem_draw(
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_drawable_clip_push(dr, &f);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
mui_font_t * main = TAILQ_FIRST(&win->ui->fonts);
if (c->title && c->title[0] != '-') {
c2_pt_t loc[3];
c2_rect_t loc[MUI_MENUITEM_PART_COUNT];
mui_menuitem_control_t *mic = (mui_menuitem_control_t*)c;
mui_menuitem_get_locations(win->ui, &f, &mic->item, loc);
mui_menuitem_get_part_locations(win->ui, &f, &mic->item, loc);
uint32_t state = mui_control_get_state(c);
if (state && state != MUI_CONTROL_STATE_DISABLED) {
@ -152,20 +218,20 @@ mui_menuitem_draw(
if (mic->item.icon[0]) {
mui_font_t * icons = mui_font_find(win->ui, "icon_small");
mui_font_text_draw(icons, dr,
loc[0], mic->item.icon, 0,
loc[0].tl, mic->item.icon, 0,
mui_control_color[state].text);
} else if (mic->item.mark[0]) {
mui_font_text_draw(main, dr,
loc[0], mic->item.mark, 0,
loc[0].tl, mic->item.mark, 0,
mui_control_color[state].text);
}
mui_font_text_draw(main, dr,
loc[1], mic->item.title, 0,
loc[1].tl, mic->item.title, 0,
mui_control_color[state].text);
if (mic->item.kcombo[0]) {
mui_font_text_draw(main, dr,
loc[2], mic->item.kcombo, 0,
loc[2].tl, mic->item.kcombo, 0,
mui_control_color[state].text);
}
} else {
@ -218,16 +284,16 @@ mui_popuptitle_draw(
if (pop->menu.count) {
mui_menu_item_t item = pop->menu.e[c->value];
c2_pt_t loc[3];
c2_rect_t loc[MUI_MENUITEM_PART_COUNT];
c2_rect_offset(&f, 0, -1);
mui_menuitem_get_locations(win->ui, &f, &item, loc);
mui_menuitem_get_part_locations(win->ui, &f, &item, loc);
if (item.icon[0])
mui_font_text_draw(icons, dr,
loc[0], item.icon, 0,
loc[0].tl, item.icon, 0,
mui_control_color[state].text);
mui_font_text_draw(main, dr,
loc[1], item.title, 0,
loc[1].tl, item.title, 0,
mui_control_color[state].text);
}
mui_font_text_draw(icons, dr,

View File

@ -28,13 +28,24 @@ bool
mui_window_handle_keyboard(
mui_window_t *win,
mui_event_t *event);
void
mui_window_lock(
mui_window_t *win);
void
mui_window_unlock(
mui_window_t *win);
void
mui_control_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr );
void
mui_control_lock(
mui_control_t *c);
void
mui_control_unlock(
mui_control_t *c);
bool
mui_control_event(
@ -49,6 +60,7 @@ mui_control_event(
*/
typedef struct mui_menuitem_control_t {
mui_control_t control;
mui_drawable_t * color_icon; // if one had been provided
mui_menu_item_t item;
} mui_menuitem_control_t;
@ -57,8 +69,8 @@ typedef struct mui_menu_control_t {
mui_menuitem_control_t item;
mui_menu_items_t menu;
c2_rect_t menu_frame;
mui_window_t * menubar;
mui_window_t * menu_window; // when open
mui_window_ref_t menubar;
mui_window_ref_t menu_window; // when open
} mui_menu_control_t;
void
@ -81,7 +93,28 @@ mui_menutitle_draw(
mui_control_t * c,
mui_drawable_t *dr );
enum {
MUI_MENUTITLE_PART_ALL = 0,
MUI_MENUTITLE_PART_ICON,
MUI_MENUTITLE_PART_TITLE,
MUI_MENUTITLE_PART_COUNT,
};
void
mui_menutitle_get_part_locations(
mui_t * ui,
c2_rect_t * frame, // optional!
mui_menu_item_t * item,
c2_rect_t * out);
// return true if window win is the menubar
bool
mui_menubar_window(
mui_window_t * win);
void
mui_refqueue_init(
mui_refqueue_t *queue);
uint
mui_refqueue_dispose(
mui_refqueue_t *queue);

View File

@ -119,12 +119,14 @@ _mui_stdfile_populate(
free(std->current_path);
std->current_path = strdup(path);
path = NULL; // this COULD be in the list we are now deleting!
for (int i = 0; i < (int)std->pop_path.count; i++)
for (uint i = 0; i < std->pop_path.count; i++)
free(std->pop_path.e[i]);
std->pop_path.count = 0;
mui_control_t *pop = std->popup;
mui_menu_items_t * items = mui_popupmenu_get_items(pop);
for (uint i = 0; i < items->count; i++)
free(items->e[i].title);
mui_menu_items_clear(items);
char * p = strdup(std->current_path);
char * d;
@ -155,6 +157,8 @@ _mui_stdfile_populate(
mui_control_t * lb = std->listbox;
mui_listbox_elems_t * elems = mui_listbox_get_elems(lb);
for (uint i = 0; i < elems->count; i++)
free(elems->e[i].elem); // free all the strings
mui_listbox_elems_clear(elems);
struct dirent * ent;
while ((ent = readdir(dir))) {
@ -180,9 +184,9 @@ _mui_stdfile_populate(
// we enable all the files by default.
if (e.disabled && !std->re_pattern)
e.disabled = std->suffix[0].s[0] ? 1 : 0;
char *suf = strrchr(ent->d_name, '.');
// handle the case we have a list of dot suffixes to filter
if (e.disabled) {
char *suf = strrchr(ent->d_name, '.');
if (std->suffix[0].s[0] && suf) {
suf++;
uint32_t hash = mui_hash_nocase(suf);
@ -199,8 +203,19 @@ _mui_stdfile_populate(
e.elem = strdup(ent->d_name);
if (S_ISDIR(st.st_mode))
strcpy(e.icon, MUI_ICON_FOLDER);
else
else {
strcpy(e.icon, MUI_ICON_FILE);
if (suf) {
if (!strcasecmp(suf, ".woz") || !strcasecmp(suf, ".nib") ||
!strcasecmp(suf, ".do"))
strcpy(e.icon, MUI_ICON_FLOPPY5);
else if ((!strcasecmp(suf, ".dsk") ||
!strcasecmp(suf, ".po"))) {
if (st.st_size == 143360)
strcpy(e.icon, MUI_ICON_FLOPPY5);
}
}
}
mui_listbox_elems_push(elems, e);
}
qsort(elems->e, elems->count,
@ -223,7 +238,7 @@ _mui_stdfile_window_action(
switch (what) {
case MUI_WINDOW_ACTION_CLOSE: {
// dispose of anything we had allocated
printf("%s close\n", __func__);
// printf("%s close\n", __func__);
if (std->pref_file)
free(std->pref_file);
if (std->re_pattern)
@ -232,9 +247,21 @@ _mui_stdfile_window_action(
free(std->current_path);
if (std->selected_path)
free(std->selected_path);
#ifdef MUI_HAS_REGEXP
regfree(&std->re);
for (int i = 0; i < (int)std->pop_path.count; i++)
#endif
mui_control_t *pop = std->popup;
mui_menu_items_t * items = mui_popupmenu_get_items(pop);
for (uint i = 0; i < items->count; i++)
free(items->e[i].title);
for (uint i = 0; i < std->pop_path.count; i++)
free(std->pop_path.e[i]);
// free all the strings for all teh elems, its our responsibility
mui_listbox_elems_t * elems = mui_listbox_get_elems(std->listbox);
for (uint i = 0; i < elems->count; i++)
free(elems->e[i].elem); // free all the strings
string_array_free(&std->pop_path);
std->pop_path.count = 0;
} break;
}
@ -372,6 +399,7 @@ mui_stdfile_get(
std->suffix[di].hash = hash;
di++;
}
free(dup);
}
mui_control_t * c = NULL;
c2_rect_t cf;

View File

@ -103,6 +103,41 @@ mui_titled_window_draw(
cg_fill(cg);
}
static bool
mui_wdef_titlewindow(
struct mui_window_t * win,
uint8_t what,
void * param)
{
switch (what) {
case MUI_WDEF_DRAW:
mui_titled_window_draw(win->ui, win, param);
break;
case MUI_WDEF_SELECT:
// mui_window_inval(win, NULL);
if (win->control_focus.control) {
int activate = 1;
if (win->control_focus.control->cdef)
win->control_focus.control->cdef(
win->control_focus.control,
MUI_CDEF_ACTIVATE, &activate);
}
break;
case MUI_WDEF_DESELECT:
if (win->control_focus.control) {
int activate = 0;
if (win->control_focus.control->cdef)
win->control_focus.control->cdef(
win->control_focus.control,
MUI_CDEF_ACTIVATE, &activate);
}
break;
case MUI_WDEF_DISPOSE:
break;
}
return false;
}
mui_window_t *
mui_window_create(
struct mui_t *ui,
@ -118,10 +153,10 @@ mui_window_create(
w->ui = ui;
w->frame = frame;
w->title = title ? strdup(title) : NULL;
w->wdef = wdef;
w->wdef = wdef ? wdef : mui_wdef_titlewindow;
w->flags.layer = layer;
mui_refqueue_init(&w->refs);
TAILQ_INIT(&w->controls);
TAILQ_INIT(&w->zombies);
STAILQ_INIT(&w->actions);
pixman_region32_init(&w->inval);
TAILQ_INSERT_HEAD(&ui->windows, w, self);
@ -133,9 +168,6 @@ mui_window_create(
return w;
}
void
_mui_control_free(
mui_control_t * c );
void
_mui_window_free(
mui_window_t *win)
@ -147,45 +179,57 @@ _mui_window_free(
while ((c = TAILQ_FIRST(&win->controls))) {
mui_control_dispose(c);
}
while ((c = TAILQ_FIRST(&win->zombies))) {
TAILQ_REMOVE(&win->zombies, c, self);
_mui_control_free(c);
}
if (win->title)
free(win->title);
free(win);
}
void
mui_window_dispose_actions(
mui_window_t * win)
{
if (!win)
return;
mui_action_t *a;
while ((a = STAILQ_FIRST(&win->actions)) != NULL) {
STAILQ_REMOVE_HEAD(&win->actions, self);
free(a);
}
}
void
mui_window_dispose(
mui_window_t *win)
{
if (!win)
return;
if (win->flags.zombie) {
printf("%s: DOUBLE delete %s\n", __func__, win->title);
if (!win->flags.disposed) {
win->flags.disposed = true;
bool was_front = mui_window_isfront(win);
mui_window_action(win, MUI_WINDOW_ACTION_CLOSE, NULL);
mui_window_inval(win, NULL); // just to mark the UI dirty
if (win->wdef)
win->wdef(win, MUI_WDEF_DISPOSE, NULL);
win->flags.hidden = true;
struct mui_t *ui = win->ui;
TAILQ_REMOVE(&ui->windows, win, self);
mui_window_dispose_actions(win);
if (was_front) {
mui_window_t * front = mui_window_front(ui);
if (front) {
mui_window_inval(front, NULL);
if (front->wdef)
front->wdef(front, MUI_WDEF_SELECT, NULL);
}
}
}
if (mui_refqueue_dispose(&win->refs) != 0) {
// we have some references, we'll have to wait
// printf("%s Warning: window %s still has references\n",
// __func__, win->title);
return;
}
bool was_front = mui_window_isfront(win);
mui_window_action(win, MUI_WINDOW_ACTION_CLOSE, NULL);
mui_window_inval(win, NULL); // just to mark the UI dirty
if (win->wdef)
win->wdef(win, MUI_WDEF_DISPOSE, NULL);
struct mui_t *ui = win->ui;
TAILQ_REMOVE(&ui->windows, win, self);
if (ui->event_capture == win)
ui->event_capture = NULL;
if (ui->action_active) {
// printf("%s %s is now zombie\n", __func__, win->title);
win->flags.zombie = true;
TAILQ_INSERT_TAIL(&ui->zombies, win, self);
} else
_mui_window_free(win);
if (was_front) {
mui_window_t * front = mui_window_front(ui);
if (front)
mui_window_inval(front, NULL);
}
_mui_window_free(win);
}
void
@ -193,11 +237,13 @@ mui_window_draw(
mui_window_t *win,
mui_drawable_t *dr)
{
if (!win)
return;
if (win->flags.hidden)
return;
mui_drawable_clip_push(dr, &win->frame);
if (win->wdef)
win->wdef(win, MUI_WDEF_DRAW, dr);
else
mui_titled_window_draw(win->ui, win, dr);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_save(cg);
// cg_translate(cg, content.l, content.t);
@ -215,6 +261,8 @@ mui_window_handle_keyboard(
mui_window_t *win,
mui_event_t *event)
{
if (win->flags.hidden)
return false;
if (!mui_window_isfront(win))
return false;
if (win->wdef && win->wdef(win, MUI_WDEF_EVENT, event)) {
@ -222,8 +270,11 @@ mui_window_handle_keyboard(
return true;
}
// printf("%s %s checkint controls\n", __func__, win->title);
mui_control_t * c, *safe;
TAILQ_FOREACH_SAFE(c, &win->controls, self, safe) {
/*
* Start with the control in focus, if there's any
*/
mui_control_t * c = win->control_focus.control, *safe;
TAILQ_FOREACH_FROM_SAFE(c, &win->controls, self, safe) {
if (mui_control_event(c, event)) {
// printf("%s control %s handled it\n", __func__, c->title);
return true;
@ -237,6 +288,8 @@ mui_window_handle_mouse(
mui_window_t *win,
mui_event_t *event)
{
if (win->flags.hidden)
return false;
if (win->wdef && win->wdef(win, MUI_WDEF_EVENT, event))
return true;
switch (event->type) {
@ -261,7 +314,8 @@ mui_window_handle_mouse(
c = NULL;
if (!c) {
/* find where we clicked in the window */
win->ui->event_capture = win;
mui_window_ref(&win->ui->event_capture, win,
FCC('E', 'V', 'C', 'P'));
win->click_loc = event->mouse.where;
c2_pt_offset(&win->click_loc, -win->frame.l, -win->frame.t);
win->flags.hit_part = MUI_WINDOW_PART_FRAME;
@ -274,7 +328,8 @@ mui_window_handle_mouse(
if (c) {
if (c->cdef && c->cdef(c, MUI_CDEF_EVENT, event)) {
// c->state = MUI_CONTROL_STATE_CLICKED;
win->control_clicked = c;
mui_control_ref(&win->control_clicked, c,
FCC('E', 'V', 'C', 'C'));
}
}
return true;
@ -302,22 +357,22 @@ mui_window_handle_mouse(
// mui_window_inval(win, NULL);
return true;
}
if (win->control_clicked) {
mui_control_t * c = win->control_clicked;
if (win->control_clicked.control) {
mui_control_t * c = win->control_clicked.control;
if (c->cdef && c->cdef(c, MUI_CDEF_EVENT, event)) {
return true;
} else
win->control_clicked = NULL;
mui_control_deref(&win->control_clicked);
}
return win->flags.hit_part != MUI_WINDOW_PART_NONE;
break;
case MUI_EVENT_BUTTONUP: {
int part = win->flags.hit_part;
win->flags.hit_part = MUI_WINDOW_PART_NONE;
win->ui->event_capture = NULL;
if (win->control_clicked) {
mui_control_t * c = win->control_clicked;
win->control_clicked = NULL;
mui_window_deref(&win->ui->event_capture);
if (win->control_clicked.control) {
mui_control_t * c = win->control_clicked.control;
mui_control_deref(&win->control_clicked);
if (c->cdef && c->cdef(c, MUI_CDEF_EVENT, event))
return true;
}
@ -337,7 +392,7 @@ mui_window_inval(
mui_window_t *win,
c2_rect_t * r)
{
if (!win)
if (!win || win->flags.hidden)
return;
c2_rect_t frame = win->frame;
c2_rect_t forward = {};
@ -425,9 +480,14 @@ mui_window_select(
last = w;
}
TAILQ_INSERT_TAIL(&win->ui->windows, win, self);
if (win->wdef)
win->wdef(win, MUI_WDEF_SELECT, NULL);
done:
if (last) // we are deselecting this one, so redraw it
if (last) {// we are deselecting this one, so redraw it
mui_window_inval(last, NULL);
if (last->wdef)
win->wdef(last, MUI_WDEF_DESELECT, NULL);
}
#if 0
printf("%s %s res:%d stack is now:\n", __func__, win->title, res);
TAILQ_FOREACH(w, &win->ui->windows, self) {
@ -437,6 +497,50 @@ done:
return res;
}
void
mui_window_lock(
mui_window_t *win)
{
if (!win)
return;
if (!win->lock.window) {
mui_window_ref(&win->lock, win, FCC('l', 'o', 'c', 'k'));
win->lock.ref.count = 10; // prevent it from being deleted
} else {
win->lock.ref.count += 10;
}
// printf("%s: window %s locked %2d\n",
// __func__, win->title, win->lock.ref.count);
}
void
mui_window_unlock(
mui_window_t *win)
{
if (!win)
return;
if (win->lock.window) {
if (win->lock.ref.trace)
printf("%s: window %s was locked\n",
__func__, win->title);
if (win->lock.ref.count > 10) {
win->lock.ref.count -= 10;
} else { // window was disposed of
int delete = win->lock.ref.count < 10;
// we are the last one, remove the lock
if (win->lock.ref.trace)
printf("%s: window %s unlocked delete %d\n",
__func__, win->title, delete);
mui_window_deref(&win->lock);
if (delete)
mui_window_dispose(win);
}
} else {
// if (win->lock.ref.trace)
printf("%s: window %s was not locked\n", __func__, win->title);
}
}
void
mui_window_action(
mui_window_t * win,
@ -445,14 +549,14 @@ mui_window_action(
{
if (!win)
return;
win->ui->action_active++;
mui_action_t *a;
STAILQ_FOREACH(a, &win->actions, self) {
mui_window_lock(win);
mui_action_t *a, *safe;
STAILQ_FOREACH_SAFE(a, &win->actions, self, safe) {
if (!a->window_cb)
continue;
a->window_cb(win, a->cb_param, what, param);
}
win->ui->action_active--;
mui_window_unlock(win);
}
void

View File

@ -14,7 +14,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <ctype.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <xcb/xcb.h>
@ -34,6 +34,8 @@ struct xkb_state;
#include "mui.h"
#include "mui_plugin.h"
typedef struct mui_xcb_t {
mui_t ui;
mui_plug_t * plug;
@ -109,6 +111,7 @@ _mui_xcb_convert_keycode(
case XKB_KEY_Down: out->key.key = MUI_KEY_DOWN; break;
// XKB_KEY_Begin
case XKB_KEY_Insert: out->key.key = MUI_KEY_INSERT; break;
case XKB_KEY_Delete: out->key.key = MUI_KEY_DELETE; break;
case XKB_KEY_Home: out->key.key = MUI_KEY_HOME; break;
case XKB_KEY_End: out->key.key = MUI_KEY_END; break;
case XKB_KEY_Page_Up: out->key.key = MUI_KEY_PAGEUP; break;
@ -176,7 +179,7 @@ mui_xcb_list_physical_screens(
}
static bool
_cui_match_physical_screen(
_mui_match_physical_screen(
xcb_connection_t *xcb,
c2_pt_t want_size,
c2_pt_p found_pos )
@ -186,7 +189,7 @@ _cui_match_physical_screen(
mui_xcb_list_physical_screens(xcb, &sc);
for (unsigned int i = 0; i < sc.count; i++) {
for (uint i = 0; i < sc.count; i++) {
if (c2_rect_width(&sc.e[i]) == want_size.x &&
c2_rect_height(&sc.e[i]) == want_size.y) {
*found_pos = sc.e[i].tl;
@ -222,7 +225,7 @@ mui_xcb_init(
bool windowed = 1;
bool opaque = 1;
c2_pt_t found_position = {};
bool has_position = !windowed && _cui_match_physical_screen(
bool has_position = !windowed && _mui_match_physical_screen(
ui->xcb, ui->size, &found_position);
xcb_screen_iterator_t iter = xcb_setup_roots_iterator(
@ -322,7 +325,6 @@ mui_xcb_init(
xcb_change_property(ui->xcb, XCB_PROP_MODE_REPLACE,
ui->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
strlen(title), title);
// create a graphic context
value_mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
value_list[0] = screen->white_pixel;
@ -355,6 +357,26 @@ mui_xcb_init(
return &ui->ui;
}
static void
mui_read_clipboard(
struct mui_t *mui)
{
FILE *f = popen("xclip -selection clipboard -o", "r");
if (!f)
return;
mui_utf8_t clip = {};
char buf[1024];
size_t r = 0;
do {
r = fread(buf, 1, sizeof(buf), f);
if (r > 0)
mui_utf8_append(&clip, (uint8_t*)buf, r);
} while (r > 0);
pclose(f);
mui_utf8_free(&mui->clipboard);
mui->clipboard = clip;
}
int
mui_xcb_poll(
struct mui_t * mui,
@ -399,12 +421,17 @@ mui_xcb_poll(
ui->xkb_state, key->detail);
key_ev.type = MUI_EVENT_KEYDOWN;
key_ev.key.up = 0;
// printf("%s %08x\n", __func__, keysym);
printf("%s %08x\n", __func__, keysym);
if (_mui_xcb_convert_keycode(ui, keysym, &key_ev)) {
if (key_ev.key.key >= MUI_KEY_MODIFIERS &&
key_ev.key.key <= MUI_KEY_MODIFIERS_LAST) {
mui->modifier_keys |= (1 << (key_ev.key.key - MUI_KEY_MODIFIERS));
}
if (toupper(key_ev.key.key) == 'V' &&
(mui->modifier_keys & MUI_MODIFIER_CTRL)) {
printf("Get CLIPBOARD\n");
mui_read_clipboard(mui);
}
key_ev.modifiers = mui->modifier_keys;
// key_ev.modifiers |= MUI_MODIFIER_EVENT_TRACE;
// gameover = key_ev.key.key == 'q';
@ -492,7 +519,7 @@ mui_xcb_poll(
c2_rect_t *ra = (c2_rect_t*)pixman_region32_rectangles(&mui->redraw, &rc);
if (ui->redraw) {
ui->redraw = 0;
rc = 1;
rc = 1;
ra = &whole;
}
if (rc) {
@ -501,7 +528,7 @@ mui_xcb_poll(
c2_rect_t r = ra[i];
// printf("XCB: %d,%d %dx%d\n", r.l, r.t, c2_rect_width(&r), c2_rect_height(&r));
xcb_copy_area(
ui->xcb, ui->xcb_pix, ui->window, ui->xcb_context,
ui->xcb, ui->xcb_pix, ui->window, ui->xcb_context,
r.l, r.t, r.l, r.t, c2_rect_width(&r), c2_rect_height(&r));
}
}
@ -600,7 +627,7 @@ int main()
while (stamp < now)
stamp += (MUI_TIME_SECOND / 60);
usleep(stamp-now);
} while (1);
} while (!mui->quit_request);
if (dynload) {
if (ui->plug_data && ui->plug && ui->plug->dispose) {
ui->plug->dispose(ui->plug_data);
@ -608,7 +635,9 @@ int main()
ui->plug_data = NULL;
}
printf("Closed %s\n", filename);
dlclose(dynload);
// no need to dlclose, it prevents valgrind --leak-check=yes to find
// the symbols we want as they have been unloaded!
// dlclose(dynload);
}
mui_drawable_dispose(&dr);
mui_xcb_terminate(mui);

View File

@ -109,6 +109,7 @@ _test_menubar_action(
} break;
case FCC('q','u','i','t'): {
printf("%s Quit\n", __func__);
g->ui->quit_request = 1;
} break;
case FCC('s','l','o','t'): {
mii_mui_configure_slots(g->ui, &g_machine_conf);
@ -142,6 +143,58 @@ _test_menubar_action(
return 0;
}
static void
plain_test_window(
mui_t *mui)
{
mui_window_t *w = mui_window_get_by_id(mui, FCC('t','e','s','t'));
if (w) {
mui_window_select(w);
return;
}
c2_pt_t where = {};
c2_rect_t wpos = C2_RECT_WH(where.x, where.y, 510, 270);
if (where.x == 0 && where.y == 0)
c2_rect_offset(&wpos,
(mui->screen_size.x / 2) - (c2_rect_width(&wpos) / 2),
(mui->screen_size.y * 0.45) - (c2_rect_height(&wpos) / 2));
w = mui_window_create(mui, wpos, NULL, MUI_WINDOW_LAYER_NORMAL,
"Test", 0);
mui_window_set_id(w, FCC('t','e','s','t'));
mui_control_t * c = NULL;
c2_rect_t cf;
cf = C2_RECT_WH(10, 10, 480, 170);
c = mui_textedit_control_new(w, cf,
MUI_CONTROL_TEXTBOX_FRAME | MUI_CONTROL_TEXTEDIT_VERTICAL);
mui_textedit_set_text(c,
"The quick brown fox Jumps over the Lazy dog.\n"
"Lorem Ipsum is simply dummy text of the printing "
"and typesetting industry. Lorem Ipsum has been the "
"industry's standard dummy text ever since the 1500s.\n"
#if 1
"Now let's step back and look at what's happening. "
"Writing to the disk is a load and shift process, a "
"little like HIRES pattern outputs but much slower.\n"
"Also, the MPU takes a very active role in the loading "
"and shifting of disk write data. There are two 8-Htate "
"loops in the WRITE sequence. After initializing the "
"WRITE sequence, data is stored in the data register "
"at a critical point in the A7' loop. As (quickly "
"thereafter as the 6502 can do it, the sequencer is "
"configured to shift left at the critical point "
"instead of loading."
#endif
);
c2_rect_bottom_of(&cf, cf.b, 10);
cf.b = cf.t + 35;
c = mui_textedit_control_new(w, cf, MUI_CONTROL_TEXTBOX_FRAME);
mui_textedit_set_text(c,
"Fulling Mill Online Return Center.pdf");
}
static void *
_init(
struct mui_t * ui,
@ -158,9 +211,7 @@ _init(
mui_window_t * mbar = mui_menubar_new(ui);
mui_window_set_action(mbar, _test_menubar_action, g);
mui_menubar_add_simple(mbar, MUI_GLYPH_APPLE,
FCC('a','p','p','l'),
m_apple_menu);
mui_menubar_add_menu(mbar, FCC('a','p','p','l'), m_color_apple_menu, 2);
mui_menubar_add_simple(mbar, "File",
FCC('f','i','l','e'),
m_file_menu);
@ -170,12 +221,14 @@ _init(
mui_menubar_add_simple(mbar, "CPU",
FCC('c','p','u','m'),
m_cpu_menu);
plain_test_window(ui);
// mii_mui_configure_slots(g->ui, &g_machine_conf);
// mii_mui_load_binary(g->ui, &g_loadbin_conf);
// mii_mui_load_1mbrom(g->ui, &g_machine_conf.slot[0].conf.rom1mb);
// mii_mui_load_2dsk(g->ui, &g_machine_conf.slot[0].conf.disk2, MII_2DSK_DISKII);
mii_mui_about(g->ui);
// mii_mui_about(g->ui);
// mii_mui_configure_ssc(g->ui, &g_machine_conf.slot[0].conf.ssc);
#if 0
mui_alert(ui, C2_PT(0,0),
"Testing one Two",
@ -183,7 +236,7 @@ _init(
"This operation cannot be cancelled.",
MUI_ALERT_WARN);
#endif
#if 1
#if 0
mui_stdfile_get(ui,
C2_PT(0, 0),
"Select image for SmartPort card",
@ -199,6 +252,7 @@ _dispose(
void *_ui)
{
cg_ui_t *ui = _ui;
printf("%s\n", __func__);
mui_dispose(ui->ui);
free(ui);
}

View File

@ -20,13 +20,38 @@
enum {
// bits used to address the LSS ROM using lss_mode
WRITE_BIT = 0,
LOAD_BIT = 1,
QA_BIT = 2,
RP_BIT = 3,
Q7_WRITE_BIT = 3,
Q6_LOAD_BIT = 2,
QA_BIT = 1,
RP_BIT = 0,
};
enum {
SIG_DR, // data register
SIG_WR, // write register
SIG_DRIVE, // selected drive
SIG_MOTOR, // motor on/off
SIG_READ_DR,
SIG_LSS_CLK,
SIG_LSS_SEQ,
SIG_LSS_CMD,
SIG_LSS_WRITE,
SIG_LSS_LOAD,
SIG_LSS_QA,
SIG_LSS_RP, // read pulse
SIG_LSS_WB, // write bit
SIG_LSS_RANDOM,
SIG_WRITE_CYCLE,
SIG_COUNT
};
const char *sig_names[] = {
"D2_DR", "D2_WR", "DRIVE", "MOTOR", "READ_DR",
"LSS_CLK", "LSS_SEQ", "LSS_CMD", "LSS_WRITE", "LSS_LOAD",
"LSS_QA", "LSS_RP", "LSS_WB", "LSS_RANDOM", "WRITE_CYCLE",
};
typedef struct mii_card_disk2_t {
mii_t * mii;
mii_dd_t drive[2];
mii_floppy_t floppy[2];
uint8_t selected;
@ -42,12 +67,19 @@ typedef struct mii_card_disk2_t {
uint8_t lss_prev_state; // for write bit
uint8_t lss_skip;
uint8_t data_register;
} mii_card_disk2_t;
uint64_t debug_last_write, debug_last_duration;
mii_vcd_t *vcd;
mii_signal_t *sig;
} mii_card_disk2_t;
static void
_mii_disk2_lss_tick(
mii_card_disk2_t *c );
static void
_mii_disk2_vcd_debug(
mii_card_disk2_t *c,
int on);
// debug, used for mish, only supports one card tho (yet)
static mii_card_disk2_t *_mish_d2 = NULL;
@ -65,7 +97,8 @@ _mii_floppy_motor_off_cb(
// printf("%s drive %d off\n", __func__, c->selected);
if (c->drive[c->selected].file && f->seed_dirty != f->seed_saved)
mii_floppy_update_tracks(f, c->drive[c->selected].file);
f->motor = 0;
f->motor = 0;
mii_raise_signal(c->sig + SIG_MOTOR, 0);
return 0;
}
@ -113,6 +146,13 @@ _mii_disk2_switch_track(
// if (track_id != 0xff)
// printf("NEW TRACK D%d: %d\n", c->selected, track_id);
uint8_t track_id_new = f->track_id[qtrack];
if (track_id != track_id_new && track_id != MII_FLOPPY_NOISE_TRACK) {
if (track_id == 0 && c->vcd)
_mii_disk2_vcd_debug(c, 0);
if (f->seed_dirty != f->seed_saved) {
// mii_floppy_resync_track(f, track_id, 0);
}
}
if (track_id_new >= MII_FLOPPY_TRACK_COUNT)
track_id_new = MII_FLOPPY_NOISE_TRACK;
/* adapt the bit position from one track to the others, from WOZ specs */
@ -126,6 +166,7 @@ _mii_disk2_switch_track(
return f->qtrack;
}
static int
_mii_disk2_init(
mii_t * mii,
@ -133,13 +174,16 @@ _mii_disk2_init(
{
mii_card_disk2_t *c = calloc(1, sizeof(*c));
slot->drv_priv = c;
c->mii = mii;
printf("%s loading in slot %d\n", __func__, slot->id + 1);
uint16_t addr = 0xc100 + (slot->id * 0x100);
mii_bank_write(
&mii->bank[MII_BANK_CARD_ROM],
addr, mii_rom_disk2, 256);
c->sig = mii_alloc_signal(&mii->sig_pool, 0, SIG_COUNT, sig_names);
for (int i = 0; i < SIG_COUNT; i++)
c->sig[i].flags |= SIG_FLAG_FILTERED;
for (int i = 0; i < 2; i++) {
mii_dd_t *dd = &c->drive[i];
dd->slot_id = slot->id + 1;
@ -149,19 +193,32 @@ _mii_disk2_init(
dd->slot_id, dd->drive);
mii_floppy_init(&c->floppy[i]);
c->floppy[i].id = i;
dd->floppy = &c->floppy[i];
}
mii_dd_register_drives(&mii->dd, c->drive, 2);
c->timer_off = mii_timer_register(mii,
_mii_floppy_motor_off_cb, c, 0,
"Disk ][ motor off");
c->timer_lss = mii_timer_register(mii,
_mii_floppy_lss_cb, c, 0,
"Disk ][ LSS");
char *n;
asprintf(&n, "Disk ][ S:%d motor off", slot->id + 1);
c->timer_off = mii_timer_register(mii, _mii_floppy_motor_off_cb, c, 0, n);
asprintf(&n, "Disk ][ S:%d LSS", slot->id + 1);
c->timer_lss = mii_timer_register(mii, _mii_floppy_lss_cb, c, 0, n);
_mish_d2 = c;
return 0;
}
static void
_mii_disk2_reset(
mii_t * mii,
struct mii_slot_t *slot )
{
mii_card_disk2_t *c = slot->drv_priv;
printf("%s\n", __func__);
c->selected = 1;
_mii_floppy_motor_off_cb(mii, c);
c->selected = 0;
_mii_floppy_motor_off_cb(mii, c);
mii_raise_signal(c->sig + SIG_DRIVE, 0);
}
static uint8_t
_mii_disk2_access(
mii_t * mii, struct mii_slot_t *slot,
@ -172,11 +229,22 @@ _mii_disk2_access(
uint8_t ret = 0;
if (write) {
// printf("WRITE PC:%04x %4.4x: %2.2x\n", mii->cpu.PC, addr, byte);
// if (!(byte &0x80))
// printf("WRITE PC:%04x %4.4x: %2.2x\n", mii->cpu.PC, addr, byte);
c->write_register = byte;
mii_raise_signal(c->sig + SIG_WR, byte);
if (c->vcd) {
uint8_t delta = c->vcd->cycle - c->debug_last_write;
mii_raise_signal_float(
c->sig + SIG_WRITE_CYCLE, c->debug_last_duration != delta, 0);
c->debug_last_duration = delta;
c->debug_last_write = c->vcd->cycle;
}
}
int psw = addr & 0x0F;
int p = psw >> 1, on = psw & 1;
int read = 0;
switch (psw) {
case 0x00 ... 0x07: {
if (on) {
@ -194,10 +262,10 @@ _mii_disk2_access(
mii_timer_set(mii, c->timer_off, 0);
mii_timer_set(mii, c->timer_lss, 1);
f->motor = 1;
mii_raise_signal(c->sig + SIG_MOTOR, 1);
} else {
if (!mii_timer_get(mii, c->timer_off)) {
mii_timer_set(mii, c->timer_off, 1000000); // one second
}
int32_t timer = mii_timer_get(mii, c->timer_off);
mii_timer_set(mii, c->timer_off, timer + 1000000); // one second
}
} break;
case 0x0A:
@ -207,22 +275,24 @@ _mii_disk2_access(
printf("SELECTED DRIVE: %d\n", c->selected);
c->floppy[on].motor = f->motor;
f->motor = 0;
mii_raise_signal(c->sig + SIG_MOTOR, 0);
}
} break;
case 0x0C:
case 0x0D:
c->lss_mode = (c->lss_mode & ~(1 << LOAD_BIT)) | (!!on << LOAD_BIT);
if (!(c->lss_mode & (1 << WRITE_BIT)) && f->heat) {
c->lss_mode = (c->lss_mode & ~(1 << Q6_LOAD_BIT)) | (!!on << Q6_LOAD_BIT);
if (!(c->lss_mode & (1 << Q7_WRITE_BIT)) && f->heat) {
uint8_t track_id = f->track_id[f->qtrack];
uint32_t byte_index = f->bit_position >> 3;
unsigned int dstb = byte_index / MII_FLOPPY_HM_HIT_SIZE;
f->heat->read.map[track_id][dstb] = 255;
f->heat->read.seed++;
read++;
}
break;
case 0x0E:
case 0x0F:
c->lss_mode = (c->lss_mode & ~(1 << WRITE_BIT)) | (!!on << WRITE_BIT);
c->lss_mode = (c->lss_mode & ~(1 << Q7_WRITE_BIT)) | (!!on << Q7_WRITE_BIT);
break;
}
/*
@ -230,8 +300,10 @@ _mii_disk2_access(
* the write protect bit to be loaded if needed, it *has* to run before
* we return this value, so we marked a skip and run it here.
*/
_mii_disk2_lss_tick(c);
c->lss_skip++;
// _mii_disk2_lss_tick(c);
// c->lss_skip++;
if (read)
mii_raise_signal(c->sig + SIG_READ_DR, c->data_register);
ret = on ? byte : c->data_register;
return ret;
@ -304,11 +376,51 @@ static mii_slot_drv_t _driver = {
.name = "disk2",
.desc = "Apple Disk ][",
.init = _mii_disk2_init,
.reset = _mii_disk2_reset,
.access = _mii_disk2_access,
.command = _mii_disk2_command,
};
MI_DRIVER_REGISTER(_driver);
static void
_mii_disk2_vcd_debug(
mii_card_disk2_t *c,
int on)
{
if (c->vcd) {
mii_vcd_close(c->vcd);
c->vcd = NULL;
printf("VCD OFF\n");
} else {
c->vcd = calloc(1, sizeof(*c->vcd));
// 2Mhz clock
mii_vcd_init(c->mii, "/tmp/disk2.vcd", c->vcd, 500);
printf("VCD ON\n");
mii_vcd_add_signal(c->vcd, c->sig + SIG_DR, 8, "DR");
mii_vcd_add_signal(c->vcd, c->sig + SIG_WR, 8, "WR");
mii_vcd_add_signal(c->vcd, c->sig + SIG_DRIVE, 1, "DRIVE");
mii_vcd_add_signal(c->vcd, c->sig + SIG_MOTOR, 1, "MOTOR");
mii_vcd_add_signal(c->vcd, c->sig + SIG_READ_DR, 8, "READ");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_CLK, 1, "LSS_CLK");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_SEQ, 4, "LSS_SEQ");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_CMD, 4, "LSS_CMD");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_WRITE, 1, "LSS_W/R");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_LOAD, 1, "LSS_L/S");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_QA, 1, "LSS_QA");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_RP, 1, "LSS_RP");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_WB, 1, "LSS_WB");
mii_vcd_add_signal(c->vcd, c->sig + SIG_LSS_RANDOM, 1, "LSS_RANDOM");
mii_vcd_add_signal(c->vcd, c->sig + SIG_WRITE_CYCLE, 1, "WRITE_CYCLE");
mii_vcd_start(c->vcd);
// put the LSS state in the VCD to start with
mii_raise_signal(c->sig + SIG_LSS_QA,
!!(c->lss_mode & (1 << QA_BIT)));
mii_raise_signal(c->sig + SIG_LSS_WRITE,
!!(c->lss_mode & (1 << Q7_WRITE_BIT)));
mii_raise_signal(c->sig + SIG_LSS_LOAD,
!!(c->lss_mode & (1 << Q6_LOAD_BIT)));
}
}
/* Sather Infamous Table, pretty much verbatim
WRITE
@ -353,8 +465,8 @@ E- FD-SL1 FD-SL1 F8-NOP F8-NOP 0A-SR 0A-SR 0A-SR 0A-SR
F- DD-SL1 4D-SL1 E0-CLR E0-CLR 0A-SR 0A-SR 0A-SR 0A-SR
*/
enum {
WRITE = (1 << WRITE_BIT),
LOAD = (1 << LOAD_BIT),
WRITE = (1 << Q7_WRITE_BIT),
LOAD = (1 << Q6_LOAD_BIT),
QA1 = (1 << QA_BIT),
RP1 = (1 << RP_BIT),
/* This keeps the transposed table more readable, as it looks like the book */
@ -380,6 +492,35 @@ static const uint8_t lss_rom16s[16][16] = {
[READ|LOAD|QA1|RP0]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
};
static const uint8_t SEQUENCER_ROM_16[256] = {
// See Understanding the Apple IIe, Figure 9.11 The DOS 3.3 Logic State Sequencer
// Note that the column order here is NOT the same as in Figure 9.11 for Q7 H (Write).
//
// Q7 L (Read) Q7 H (Write)
// Q6 L (Shift) Q6 H (Load) Q6 L (Shift) Q6 H (Load)
// QA L QA H QA L QA H QA L QA H QA L QA H
// 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, // 0
0x2D, 0x2D, 0x38, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, // 1
0xD8, 0x38, 0x08, 0x28, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B, // 2
0xD8, 0x48, 0x48, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, // 3
0xD8, 0x58, 0xD8, 0x58, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, // 4
0xD8, 0x68, 0xD8, 0x68, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, // 5
0xD8, 0x78, 0xD8, 0x78, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, // 6
0xD8, 0x88, 0xD8, 0x88, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, // 7
0xD8, 0x98, 0xD8, 0x98, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // 8
0xD8, 0x29, 0xD8, 0xA8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, // 9
0xCD, 0xBD, 0xD8, 0xB8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB, // A
0xD9, 0x59, 0xD8, 0xC8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, // B
0xD9, 0xD9, 0xD8, 0xA0, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, // C
0xD8, 0x08, 0xE8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, // D
0xFD, 0xFD, 0xF8, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, // E
0xDD, 0x4D, 0xE0, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x88, 0x08, 0x08, 0x88, 0x88, 0x08, 0x08 // F
// 0 1 1 3 4 5 6 7 8 9 A B C D E F
};
FILE *debug = NULL;
static void
_mii_disk2_lss_tick(
mii_card_disk2_t *c )
@ -390,65 +531,69 @@ _mii_disk2_lss_tick(
}
mii_floppy_t *f = &c->floppy[c->selected];
c->lss_mode = (c->lss_mode & ~(1 << QA_BIT)) |
(!!(c->data_register & 0x80) << QA_BIT);
if (c->vcd)
c->vcd->cycle += 1;
c->clock += 4; // 4 is 0.5us.. we run at 2MHz
uint8_t track_id = f->track_id[f->qtrack];
uint8_t * track = f->track_data[track_id];
uint32_t byte_index = f->bit_position >> 3;
uint8_t bit_index = 7 - (f->bit_position & 7);
uint8_t rp = 0;
mii_raise_signal(c->sig + SIG_LSS_CLK, c->clock >= f->bit_timing);
if (c->clock >= f->bit_timing) {
c->clock -= f->bit_timing;
uint8_t track_id = f->track_id[f->qtrack];
uint8_t * track = f->track_data[track_id];
uint32_t byte_index = f->bit_position >> 3;
uint8_t bit_index = 7 - (f->bit_position & 7);
if (!(c->lss_mode & (1 << WRITE_BIT))) {
uint8_t bit = track[byte_index];
bit = (bit >> bit_index) & 1;
c->head = (c->head << 1) | bit;
// see WOZ spec for how we do this here
if ((c->head & 0xf) == 0) {
bit = f->track_data[MII_FLOPPY_NOISE_TRACK][byte_index];
bit = (bit >> bit_index) & 1;
uint8_t bit = track[byte_index];
bit = (bit >> bit_index) & 1;
c->head = (c->head << 1) | bit;
// see WOZ spec for how we do this here
if ((c->head & 0xf) == 0) {
bit = f->track_data[MII_FLOPPY_NOISE_TRACK][byte_index];
rp = (bit >> bit_index) & 1;
// printf("RANDOM TRACK %2d %2d %2d : %d\n",
// track_id, byte_index, bit_index, bit);
} else {
bit = (c->head >> 1) & 1;
}
c->lss_mode = (c->lss_mode & ~(1 << RP_BIT)) | (bit << RP_BIT);
mii_raise_signal_float(c->sig + SIG_LSS_RANDOM, rp, 0);
} else {
rp = (c->head >> 1) & 1;
mii_raise_signal_float(c->sig + SIG_LSS_RANDOM, rp, 1);
}
if ((c->lss_mode & (1 << WRITE_BIT)) && track_id != 0xff) {
uint8_t msb = c->data_register >> 7;
#if 0
printf("WRITE %2d %4d %d : %d LSS State %x mode %x\n",
track_id, byte_index, bit_index, msb,
c->lss_state, c->lss_mode);
#endif
if (!f->tracks[track_id].dirty) {
// printf("DIRTY TRACK %2d \n", track_id);
f->tracks[track_id].dirty = 1;
f->seed_dirty++;
}
f->track_data[track_id][byte_index] &= ~(1 << bit_index);
f->track_data[track_id][byte_index] |= (msb << bit_index);
}
f->bit_position = (f->bit_position + 1) % f->tracks[track_id].bit_count;
}
c->lss_mode = (c->lss_mode & ~(1 << RP_BIT)) | (rp << RP_BIT);
c->lss_mode = (c->lss_mode & ~(1 << QA_BIT)) |
(!!(c->data_register & 0x80) << QA_BIT);
mii_raise_signal(c->sig + SIG_LSS_RP, rp);
mii_raise_signal(c->sig + SIG_LSS_QA,
!!(c->lss_mode & (1 << QA_BIT)));
mii_raise_signal(c->sig + SIG_LSS_WRITE,
!!(c->lss_mode & (1 << Q7_WRITE_BIT)));
mii_raise_signal(c->sig + SIG_LSS_LOAD,
!!(c->lss_mode & (1 << Q6_LOAD_BIT)));
const uint8_t *rom = lss_rom16s[c->lss_mode];
uint8_t cmd = rom[c->lss_state];
uint8_t next = cmd >> 4;
uint8_t action = cmd & 0xF;
mii_raise_signal(c->sig + SIG_LSS_SEQ, c->lss_state);
mii_raise_signal(c->sig + SIG_LSS_CMD, action);
if (action & 0b1000) { // Table 9.3 in Sather's book
switch (action & 0b0011) {
case 1: // SL0/1
c->data_register <<= 1;
c->data_register |= !!(action & 0b0100);
mii_raise_signal(c->sig + SIG_DR, c->data_register);
break;
case 2: // SR
c->data_register = (c->data_register >> 1) |
(!!f->write_protected << 7);
mii_raise_signal(c->sig + SIG_DR, c->data_register);
break;
case 3: {// LD
uint8_t track_id = f->track_id[f->qtrack];
c->data_register = c->write_register;
mii_raise_signal(c->sig + SIG_DR, c->data_register);
f->seed_dirty++;
if (f->heat) {
uint32_t byte_index = f->bit_position >> 3;
@ -460,12 +605,47 @@ _mii_disk2_lss_tick(
}
} else { // CLR
c->data_register = 0;
mii_raise_signal(c->sig + SIG_DR, c->data_register);
}
if ((c->lss_mode & (1 << Q7_WRITE_BIT)) &&
track_id < MII_FLOPPY_TRACK_COUNT) {
// on state 0 and 8 we write a bit...
if ((c->lss_state & 0b0111) == 0) {
uint8_t bit = c->data_register >> 7;
// uint8_t bit = !!(c->lss_state & 0x8);
mii_raise_signal_float(c->sig + SIG_LSS_WB, 1, 0);
if (!f->tracks[track_id].dirty) {
// printf("DIRTY TRACK %2d \n", track_id);
f->tracks[track_id].dirty = 1;
/*
* This little trick allows to have all the track neatly aligned
* on bit zero when formatting a floppy or doing a copy, this helps
* debug quite a bit.
*/
if (f->tracks[track_id].virgin) {
f->tracks[track_id].virgin = 0;
f->bit_position = 0;
if (track_id == 0)
_mii_disk2_vcd_debug(c, 1);
}
f->seed_dirty++;
}
f->track_data[track_id][byte_index] &= ~(1 << bit_index);
f->track_data[track_id][byte_index] |= (bit << bit_index);
} else {
mii_raise_signal_float(c->sig + SIG_LSS_WB, 0, 0);
}
}
c->lss_state = next;
// read pulse only last one cycle..
c->lss_mode &= ~(1 << RP_BIT);
if (c->clock >= f->bit_timing) {
c->clock -= f->bit_timing;
f->bit_position = (f->bit_position + 1) % f->tracks[track_id].bit_count;
}
}
#include <fcntl.h>
#include <unistd.h>
static void
_mii_mish_d2(
@ -510,18 +690,31 @@ _mii_mish_d2(
}
// dump a track, specify track number and number of bytes
if (!strcmp(argv[1], "track")) {
mii_card_disk2_t *c = _mish_d2;
mii_floppy_t *f = &c->floppy[sel];
if (argv[2]) {
int track = atoi(argv[2]);
int count = 256;
if (argv[3])
if (argv[3]) {
if (!strcmp(argv[3], "save")) {
// save one binary file in tmp with just that track
uint8_t *data = f->track_data[track];
char *filename;
asprintf(&filename, "/tmp/track_%d.bin", track);
int fd = open(filename, O_CREAT | O_WRONLY, 0666);
write(fd, data, MII_FLOPPY_DEFAULT_TRACK_SIZE);
close(fd);
printf("Saved track %d to %s\n", track, filename);
free(filename);
return;
}
count = atoi(argv[3]);
mii_card_disk2_t *c = _mish_d2;
mii_floppy_t *f = &c->floppy[sel];
}
uint8_t *data = f->track_data[track];
for (int i = 0; i < count; i += 8) {
uint8_t *line = data + i;
#if 0
#if 1
for (int bi = 0; bi < 8; bi++) {
uint8_t b = line[bi];
for (int bbi = 0; bbi < 8; bbi++) {
@ -546,6 +739,51 @@ _mii_mish_d2(
f->seed_dirty = f->seed_saved = rand();
return;
}
if (!strcmp(argv[1], "resync")) {
mii_card_disk2_t *c = _mish_d2;
mii_floppy_t *f = &c->floppy[sel];
printf("Resyncing tracks\n");
for (int i = 0; i < MII_FLOPPY_TRACK_COUNT; i++)
mii_floppy_resync_track(f, i, 0);
return;
}
if (!strcmp(argv[1], "map")) {
mii_card_disk2_t *c = _mish_d2;
mii_floppy_t *f = &c->floppy[sel];
printf("Disk map:\n");
for (int i = 0; i < MII_FLOPPY_TRACK_COUNT; i++) {
mii_floppy_track_map_t map = {};
int r = mii_floppy_map_track(f, i, &map, 0);
printf("Track %2d: %7s\n",
i, r == 0 ? "OK" : "Invalid");
if (r != 0) {
printf("Track %d has %5d bits\n", i, f->tracks[i].bit_count);
for (int si = 0; si < 16; si++)
printf("[%2d hs:%4d h:%5d ds:%3d d:%5d]%s",
si, map.sector[si].hsync,
map.sector[si].header,
map.sector[si].dsync,
map.sector[si].data,
si & 1 ? "\n" : " ");
}
}
return;
}
if (!strcmp(argv[1], "trace")) {
if (debug == NULL)
debug = fopen("/tmp/disk2.log", "w");
else {
fclose(debug);
debug = NULL;
}
printf("Debug trace %s\n", debug ? "ON" : "OFF");
return;
}
if (!strcmp(argv[1], "vcd")) {
mii_card_disk2_t *c = _mish_d2;
_mii_disk2_vcd_debug(c, !c->vcd);
}
}
#include "mish.h"

View File

@ -114,6 +114,7 @@ _mii_mouse_vbl_handler(
return 1000000 / 60;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
// mii_bank_t * sw = &mii->bank[MII_BANK_SW];
uint8_t status = mii_bank_peek(main, MOUSE_STATUS + c->slot_offset);
uint8_t old = status;

View File

@ -169,7 +169,7 @@ _mii_nsc_probe(
/* ... A2Desktop requires the NSC to be on the main rom, the source
* claims it probe the slots, but in fact, it doesnt */
mii_bank_install_access_cb(&mii->bank[MII_BANK_ROM],
_mii_nsc_access, nsc, 0xc3, 0);
_mii_nsc_access, nsc, 0xc8, 0);
return 1;
}

View File

@ -6,36 +6,338 @@
* SPDX-License-Identifier: MIT
*
*/
/*
THIS IS A PLACEHOLDER DO NOT USE
*/
#include "mii.h"
Theory of operation:
There is a thread that is started for any/all cards in the system. Cards
themselves communicate via one 'command' FIFO to start/stop themselves,
which add/remove them from the private list of cards kept by the thread.
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
The cards attempts not to open the device (and start the thread) until
they are actually used, so it monitors for access from the ROM area,
(that will be happening if PR#x and IN#x are used), OR when the PC
calls into the Cx00-CxFF area.
Once running, the thread will monitor the 'tty' file descriptor for each
and will deal with 2 'data' FIFOs to send and receive data from the 6502.
The SSC driver itself just monitors the FIFO state and update the status
register, and raise IRQs as needed.
*/
/*
git clone https://github.com/colinleroy/a2tools.git
cd a2tools/src/surl-server
sudo apt-get install libcurl4-gnutls-dev libgumbo-dev libpng-dev libjq-dev libsdl-image1.2-dev
make && A2_TTY=/dev/tntX ./surl-server
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include "mii.h"
#include "mii_bank.h"
#include "mii_sw.h"
#include "mii_ssc.h"
#include "fifo_declare.h"
#include "bsd_queue.h"
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#define INCBIN_PREFIX mii_
#include "incbin.h"
INCBIN(ssc_rom, "roms/mii_rom_scc_3410065a.bin");
#include <termios.h>
#include <pty.h>
#include <fcntl.h>
static const mii_ssc_setconf_t _mii_ssc_default_conf = {
.baud = 9600,
.bits = 8,
.parity = 0,
.stop = 1,
.handshake = 0,
.is_device = 1,
.is_socket = 0,
.is_pty = 0,
.socket_port = 0,
.device = "/dev/tnt0",
};
// SW1-4 SW1 is MSB, switches are inverted ? (0=on, 1=off)
static const int _mii_ssc_to_baud[16] = {
[0] = B1152000, [1] = B50, [2] = B75, [3] = B110,
[4] = B134, [5] = B150, [6] = B300, [7] = B600,
[8] = B1200, [9] = B1800, [10] = B2400,
[12] = B4800, [14] = B9600, [15] = B19200,
};
static const unsigned int _mii_ssc_to_baud_rate[16] = {
[0] = 1152000, [1] = 50, [2] = 75, [3] = 110,
[4] = 134, [5] = 150, [6] = 300, [7] = 600,
[8] = 1200, [9] = 1800, [10] = 2400, [11] = -3600,
[12] = 4800, [13] = -7200, [14] = 9600, [15] = 19200,
};
enum {
SSC_6551_CONTROL_BAUD = 0, // see table
SSC_6551_CONTROL_CLOCK = 4, // always 1
SSC_6551_CONTROL_WLEN = 5, // 0=8 bits, 1=7 bits, 2=6 bits, 3=5 bits
SSC_6551_CONTROL_STOP = 7, // 0 = 1 stop bit, 1 = 2 stop bits
SSC_6551_CONTROL_RESET = 0,
SSC_6551_COMMAND_DTR = 0, // 0=Disable Receiver (DTR), 1=Enable
SSC_6551_COMMAND_IRQ_R = 1, // 0=IRQ Enabled, 1=IRQ Disabled
// 0:IRQ_TX=0 + RTS=1
// 1:IRQ_TX=1 + RTS=0
// 2:IRQ_TX=0 + RTS=0
// 3:IRQ_TX=0 + RTS=0 + BRK
SSC_6551_COMMAND_IRQ_T = 2,
SSC_6551_COMMAND_ECHO = 4, // 0=off, 1=on
SSC_6551_COMMAND_PARITY = 5, // None, Odd, Even, Mark, Space
SSC_6551_COMMAND_RESET = (1 << SSC_6551_COMMAND_IRQ_R),
SSC_6551_PARITY_ERROR = 0,
SSC_6551_FRAMING_ERROR = 1,
SSC_6551_OVERRUN = 2,
SSC_6551_RX_FULL = 3,
SSC_6551_TX_EMPTY = 4,
SSC_6551_DCD = 5,
SSC_6551_DSR = 6,
SSC_6551_IRQ = 7,
SSC_6551_STATUS_RESET = (1 << SSC_6551_TX_EMPTY),
};
enum {
SSC_SW2_STOPBITS = 1 << 7,
SSC_SW2_DATABITS = 1 << 6,
SSC_SW2_IRQEN = 1 << 0,
};
// SW2-1 is stop bits OFF = Two, ON = One (inverted)
static const unsigned int _mii_ssc_to_stop[2] = {
[0] = 0, [1] = CSTOPB,
};
// SW2-2 is data bits
static const unsigned int _mii_ssc_to_bits[4] = {
[0] = CS8, [1] = CS7, [2] = CS6, [3] = CS5,
};
static const int _mii_scc_to_bits_count[4] = {
[0] = 8, [1] = 7, [2] = 6, [3] = 5,
};
// SW2-3-4 is parity
static const unsigned int _mii_ssc_to_parity[4] = {
[0] = 0, [1] = PARODD, [2] = PARENB, [3] = PARENB|PARODD,
};
enum {
MII_SSC_STATE_INIT = 0,
MII_SSC_STATE_START,
MII_SSC_STATE_RUNNING,
MII_SSC_STATE_STOP,
MII_SSC_STATE_STOPPED,
MII_THREAD_TERMINATE,
};
struct mii_card_ssc_t;
typedef struct mii_ssc_cmd_t {
int cmd;
union {
struct mii_card_ssc_t * card;
};
} mii_ssc_cmd_t;
DECLARE_FIFO(mii_ssc_cmd_t, mii_ssc_cmd_fifo, 8);
DEFINE_FIFO(mii_ssc_cmd_t, mii_ssc_cmd_fifo);
DECLARE_FIFO(uint8_t, mii_ssc_fifo, 16);
DEFINE_FIFO(uint8_t, mii_ssc_fifo);
typedef struct mii_card_ssc_t {
// queued when first allocated, to keep a list of all cards
STAILQ_ENTRY(mii_card_ssc_t) self;
// queued when started, for the thread
STAILQ_ENTRY(mii_card_ssc_t) started;
struct mii_slot_t * slot;
struct mii_bank_t * rom;
mii_t * mii;
uint8_t slot_offset;
mii_ssc_setconf_t conf;
int state; // current state, MII_SSC_STATE_*
char tty_path[128];
int tty_fd; // <= 0 is not opened yet
char human_config[32];
// global counter of bytes sent/received. No functional use
uint32_t total_rx, total_tx;
uint8_t timer_check;
uint32_t timer_delay;
mii_ssc_fifo_t rx,tx;
// 6551 registers
uint8_t dipsw1, dipsw2, control, command, status;
} mii_card_ssc_t;
STAILQ_HEAD(, mii_card_ssc_t)
_mii_card_ssc_slots = STAILQ_HEAD_INITIALIZER(_mii_card_ssc_slots);
/*
* These bits are only meant to communicate with the thread
*/
STAILQ_HEAD(, mii_card_ssc_t)
_mii_card_ssc_started = STAILQ_HEAD_INITIALIZER(_mii_card_ssc_started);
pthread_t _mii_ssc_thread_id = 0;
mii_ssc_cmd_fifo_t _mii_ssc_cmd = {};
static int
_mii_scc_set_conf(
mii_card_ssc_t *c,
const mii_ssc_setconf_t *conf,
int re_open);
static void*
_mii_ssc_thread(
void *param)
{
printf("%s: start\n", __func__);
// ignore the signal, we use it to wake up the thread
sigaction(SIGUSR1, &(struct sigaction){
.sa_handler = SIG_IGN,
.sa_flags = SA_RESTART,
}, NULL);
do {
/*
* Get commands from the MII running thread. Add/remove cards
* from the 'running' list, and a TERMINATE to kill the thread
*/
while (!mii_ssc_cmd_fifo_isempty(&_mii_ssc_cmd)) {
mii_ssc_cmd_t cmd = mii_ssc_cmd_fifo_read(&_mii_ssc_cmd);
switch (cmd.cmd) {
case MII_SSC_STATE_START: {
mii_card_ssc_t *c = cmd.card;
printf("%s: start slot %d\n", __func__, c->slot->id);
STAILQ_INSERT_TAIL(&_mii_card_ssc_started, c, self);
c->state = MII_SSC_STATE_RUNNING;
} break;
case MII_SSC_STATE_STOP: {
mii_card_ssc_t *c = cmd.card;
printf("%s: stop slot %d\n", __func__, c->slot->id);
STAILQ_REMOVE(&_mii_card_ssc_started, c, mii_card_ssc_t, self);
c->state = MII_SSC_STATE_STOPPED;
} break;
case MII_THREAD_TERMINATE:
printf("%s: terminate\n", __func__);
return NULL;
}
}
/* here we use select. This is not optimal on linux, but it is portable
to other OSes -- perhaps I'll add an epoll() version later */
fd_set rfds, wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
int maxfd = 0;
mii_card_ssc_t *c = NULL, *safe;
STAILQ_FOREACH_SAFE(c, &_mii_card_ssc_started, self, safe) {
// guy might be being reconfigured, or perhaps had an error
if (c->tty_fd < 0)
continue;
if (!mii_ssc_fifo_isempty(&c->tx)) {
FD_SET(c->tty_fd, &wfds);
if (c->tty_fd > maxfd)
maxfd = c->tty_fd;
}
if (!mii_ssc_fifo_isfull(&c->rx)) {
FD_SET(c->tty_fd, &rfds);
if (c->tty_fd > maxfd)
maxfd = c->tty_fd;
}
}
struct timeval tv = { .tv_sec = 0, .tv_usec = 1000 };
int res = select(maxfd + 1, &rfds, &wfds, NULL, &tv);
if (res < 0) {
// there are OK errors, we just ignore them
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
continue;
printf("%s ssc select: %s\n", __func__, strerror(errno));
break;
}
if (res == 0) // timeout
continue;
STAILQ_FOREACH(c, &_mii_card_ssc_started, self) {
/* Here we know the read fifo isn't full, otherwise we wouldn'ty
have asked for more data.
See what space we have in the fifo, try reading as much as that,
and push it to the FIFO */
if (FD_ISSET(c->tty_fd, &rfds)) {
uint8_t buf[mii_ssc_cmd_fifo_fifo_size];
int max = mii_ssc_fifo_get_write_size(&c->rx);
int res = read(c->tty_fd, buf, max);
if (res < 0) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
break;
c->tty_fd = -1;
printf("%s ssc read: %s\n", __func__, strerror(errno));
break;
}
for (int i = 0; i < res; i++)
mii_ssc_fifo_write(&c->rx, buf[i]);
}
/* here as well, this wouldn't be set if we hadn't got stuff
to send -- see what's in the fifo, 'peek' the bytes into
an aligned buffer, try to write it all, and then *actually*
remove them (read) the one that were sent from the fifo */
if (FD_ISSET(c->tty_fd, &wfds)) {
uint8_t buf[mii_ssc_cmd_fifo_fifo_size];
int max = mii_ssc_fifo_get_read_size(&c->tx);
for (int i = 0; i < max; i++)
buf[i] = mii_ssc_fifo_read_at(&c->tx, i);
res = write(c->tty_fd, buf, max);
if (res < 0) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
break;
printf("%s ssc write: %s\n", __func__, strerror(errno));
break;
}
// flush what we've just written
while (res--)
mii_ssc_fifo_read(&c->tx);
}
}
} while (1);
return NULL;
}
static void
_mii_ssc_thread_start(
mii_card_ssc_t *c)
{
if (c->state > MII_SSC_STATE_INIT && c->state < MII_SSC_STATE_STOP)
return;
if (c->tty_fd < 0) {
printf("%s TTY not open, skip\n", __func__);
return;
}
c->state = MII_SSC_STATE_START;
mii_ssc_cmd_t cmd = { .cmd = MII_SSC_STATE_START, .card = c };
mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd);
// start timer that'll check out card status
mii_timer_set(c->mii, c->timer_check, c->timer_delay);
// start, or kick the thread awake
if (!_mii_ssc_thread_id) {
printf("%s: starting thread\n", __func__);
pthread_create(&_mii_ssc_thread_id, NULL, _mii_ssc_thread, NULL);
} else {
pthread_kill(_mii_ssc_thread_id, SIGUSR1);
}
}
/*
* This is called when the CPU touches the CX00-CXFF ROM area, and we
* need to install the secondary part of the ROM.
* Also, if the access was from the ROM, we start the card as there's no
* other way to know when the card is started -- we don't want to create
* a thread fro SSC if the card is being ignored by the Apple IIe
*/
static bool
_mii_ssc_select(
struct mii_bank_t *bank,
@ -44,17 +346,182 @@ _mii_ssc_select(
uint8_t * byte,
bool write)
{
if (bank == NULL) // this is normal, called on dispose
return false;
mii_card_ssc_t *c = param;
printf("%s selected:%d\n", __func__, c->slot->aux_rom_selected);
if (c->slot->aux_rom_selected)
return false;
uint16_t pc = c->mii->cpu.PC;
printf("SSC%d SELECT auxrom PC:%04x\n", c->slot->id+1, pc);
/* Supports when the ROM starts prodding into the ROM */
if (c->state != MII_SSC_STATE_RUNNING) {
printf("SSC%d: start card from ROM poke? (PC $%04x)?\n",
c->slot->id+1, pc);
if ((pc & 0xff00) == (0xc100 + (c->slot->id << 8)) ||
(pc >> 12) >= 0xc) {
_mii_scc_set_conf(c, &c->conf, 1);
_mii_ssc_thread_start(c);
}
}
mii_bank_write(c->rom, 0xc800, mii_ssc_rom_data, 2048);
c->slot->aux_rom_selected = true;
SW_SETSTATE(c->mii, SLOTAUXROM, 1);
c->mii->mem_dirty = true;
return false;
}
/* Called a some sort of proportional number of cycles related to the
baudrate to check the FIFOs and update the status/raise IRQs.
this doesn't have to be exact, it just have to be often enough to
not miss any data.
*/
static uint64_t
_mii_ssc_timer_cb(
mii_t * mii,
void * param )
{
mii_card_ssc_t *c = param;
// stop timer
if (c->state != MII_SSC_STATE_RUNNING)
return 0;
// check the FIFOs -- not technically 'true' we raise an IRQ as soon as
// theres some bytes to proceed, but we'll do it here for simplicity
uint8_t rx_full = !mii_ssc_fifo_isempty(&c->rx);
// what it really mean is 'there room for more data', not 'full'
uint8_t tx_empty = !mii_ssc_fifo_isfull(&c->tx);
uint8_t old = c->status;
c->status = (c->status & ~(1 << SSC_6551_RX_FULL)) |
(rx_full << SSC_6551_RX_FULL);
c->status = (c->status & ~(1 << SSC_6551_TX_EMPTY)) |
(tx_empty << SSC_6551_TX_EMPTY);
uint8_t irq = 0;//(c->status & (1 << SSC_6551_IRQ));
uint8_t t_irqen = ((c->command >> SSC_6551_COMMAND_IRQ_T) & 3) == 1;
uint8_t r_irqen = !(c->command & (1 << SSC_6551_COMMAND_IRQ_R));
if (old != c->status)
printf("SSC%d New Status %08b RX:%2d TX:%2d t_irqen:%d r_irqen:%d\n",
c->slot->id+1, c->status,
mii_ssc_fifo_get_read_size(&c->rx), mii_ssc_fifo_get_write_size(&c->tx),
t_irqen, r_irqen);
// we set the IRQ flag even if the real IRQs are disabled.
// rising edge triggers the IRQR
if (!irq && rx_full) {
// raise the IRQ
if (r_irqen) {
irq = 1;
printf("SSC%d: IRQ RX\n", c->slot->id+1);
mii->cpu_state.irq = 1;
}
}
if (!irq && (tx_empty)) {
// raise the IRQ
if (t_irqen) {
irq = 1;
printf("SSC%d: IRQ TX\n", c->slot->id+1);
mii->cpu_state.irq = 1;
}
}
if (irq)
c->status |= 1 << SSC_6551_IRQ;
return c->timer_delay;
}
static int
_mii_scc_set_conf(
mii_card_ssc_t *c,
const mii_ssc_setconf_t *conf,
int re_open)
{
if (conf == NULL)
conf = &_mii_ssc_default_conf;
if (!re_open && strcmp(c->conf.device, conf->device) == 0 &&
c->conf.baud == conf->baud &&
c->conf.bits == conf->bits &&
c->conf.parity == conf->parity &&
c->conf.stop == conf->stop &&
c->conf.handshake == conf->handshake &&
c->conf.is_device == conf->is_device &&
c->conf.is_socket == conf->is_socket &&
c->conf.is_pty == conf->is_pty &&
c->conf.socket_port == conf->socket_port)
return 0;
if (re_open || strcmp(c->conf.device, conf->device) != 0) {
if (c->tty_fd > 0) {
int tty = c->tty_fd;
c->tty_fd = -1;
// this will wake up the thread as well
close(tty);
}
}
c->conf = *conf;
strcpy(c->tty_path, conf->device);
if (c->tty_fd < 0) {
int new_fd = -1;
if (conf->is_pty) {
int master = 0;
int res = openpty(&master, &new_fd, c->tty_path, NULL, NULL);
if (res < 0) {
printf("SSC%d openpty: %s\n", c->slot->id+1, strerror(errno));
return -1;
}
} else {
int res = open(c->tty_path, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (res < 0) {
printf("SSC%d open(%s): %s\n", c->slot->id+1,
c->tty_path, strerror(errno));
return -1;
}
new_fd = res;
}
// set non-blocking mode
int flags = fcntl(new_fd, F_GETFL, 0);
fcntl(new_fd, F_SETFL, flags | O_NONBLOCK);
c->tty_fd = new_fd;
}
if (c->tty_fd < 0) {
printf("SSC%d: %s TTY not open, skip\n", c->slot->id+1, __func__);
return -1;
}
// get current terminal settings
struct termios tio;
tcgetattr(c->tty_fd, &tio);
// set raw mode
cfmakeraw(&tio);
c->human_config[0] = 0;
// set speed
for (int i = 0; i < 16; i++) {
if (_mii_ssc_to_baud_rate[i] == conf->baud) {
c->dipsw1 = 0x80 | i;
cfsetospeed(&tio, _mii_ssc_to_baud[i]);
cfsetispeed(&tio, _mii_ssc_to_baud[i]);
sprintf(c->human_config, "Baud:%d ", conf->baud);
break;
}
}
// set 8N1
tio.c_cflag = (tio.c_cflag & ~PARENB) | _mii_ssc_to_parity[conf->parity];
tio.c_cflag = (tio.c_cflag & ~CSTOPB) | _mii_ssc_to_stop[conf->stop];
tio.c_cflag = (tio.c_cflag & ~CSIZE);
tio.c_cflag |= _mii_ssc_to_bits[conf->bits];
sprintf(c->human_config + strlen(c->human_config), "%d",
_mii_scc_to_bits_count[conf->bits]);
static const char *parity = "noeb";
sprintf(c->human_config + strlen(c->human_config), "%c%c",
parity[conf->parity], conf->stop ? '2' : '1');
// Hardware Handshake
tio.c_cflag = (tio.c_cflag & ~CRTSCTS) | (conf->handshake ? CRTSCTS : 0);
// set the new settings
tcsetattr(c->tty_fd, TCSANOW, &tio);
c->dipsw1 = 0x80 | conf->baud;
c->dipsw2 = SSC_SW2_IRQEN;
c->control = 0;
mii_ssc_fifo_reset(&c->rx);
mii_ssc_fifo_reset(&c->tx);
return 0;
}
static int
_mii_ssc_init(
mii_t * mii,
@ -65,9 +532,6 @@ _mii_ssc_init(
slot->drv_priv = c;
c->mii = mii;
printf("%s: THIS IS A PLACEHOLDER DO NOT USE\n", __func__);
printf("%s loading in slot %d\n", __func__, slot->id + 1);
c->slot_offset = slot->id + 1 + 0xc0;
uint16_t addr = 0xc100 + (slot->id * 0x100);
@ -81,6 +545,28 @@ _mii_ssc_init(
mii_bank_install_access_cb(c->rom,
_mii_ssc_select, c, addr >> 8, addr >> 8);
/*
* And this is the timer that will check the status of FIFOs and update
* the status of the card, and raise IRQs if needed
*/
char name[32];
snprintf(name, sizeof(name), "SSC %d", slot->id+1);
c->timer_check = mii_timer_register(mii,
_mii_ssc_timer_cb, c, 0, strdup(name));
// fastest speed we could get to?
c->timer_delay = 11520;
c->tty_fd = -1;
STAILQ_INSERT_TAIL(&_mii_card_ssc_slots, c, self);
c->dipsw1 = 0x80 | 14; // communication mode, 9600
// in case progs read that to decide to use IRQs or not
c->dipsw2 = SSC_SW2_IRQEN;
c->state = MII_SSC_STATE_INIT;
c->status = SSC_6551_STATUS_RESET;
c->command = SSC_6551_COMMAND_RESET;
c->control = 0;
_mii_scc_set_conf(c, &c->conf, 1);
return 0;
}
@ -90,10 +576,86 @@ _mii_ssc_dispose(
struct mii_slot_t *slot )
{
mii_card_ssc_t *c = slot->drv_priv;
STAILQ_REMOVE(&_mii_card_ssc_slots, c, mii_card_ssc_t, self);
if (c->state == MII_SSC_STATE_RUNNING) {
mii_ssc_cmd_t cmd = { .cmd = MII_SSC_STATE_STOP, .card = c };
mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd);
pthread_kill(_mii_ssc_thread_id, SIGUSR1);
while (c->state == MII_SSC_STATE_RUNNING)
usleep(1000);
printf("SSC%d: stopped\n", c->slot->id+1);
}
if (STAILQ_FIRST(&_mii_card_ssc_slots) == NULL && _mii_ssc_thread_id) {
printf("SSC%d: stopping thread\n", c->slot->id+1);
pthread_t id = _mii_ssc_thread_id;
_mii_ssc_thread_id = 0;
mii_ssc_cmd_t cmd = { .cmd = MII_THREAD_TERMINATE };
mii_ssc_cmd_fifo_write(&_mii_ssc_cmd, cmd);
pthread_kill(id, SIGUSR1);
pthread_join(id, NULL);
printf("SSC%d: thread stopped\n", c->slot->id+1);
}
free(c);
slot->drv_priv = NULL;
}
static void
_mii_ssc_command_set(
mii_card_ssc_t *c,
uint8_t byte)
{
mii_t * mii = c->mii;
if (!(c->command & (1 << SSC_6551_COMMAND_DTR)) &&
(byte & (1 << SSC_6551_COMMAND_DTR))) {
_mii_scc_set_conf(c, &c->conf, 1);
_mii_ssc_thread_start(c);
}
if (c->tty_fd < 0) {
printf("SSC%d: %s TTY not open, skip\n", c->slot->id+1, __func__);
return;
}
/* This triggers the IRQ if it enabled when there is a IRQ flag on,
* this make it behave more like a 'level' IRQ instead of an edge IRQ
*/
if ((c->command & (1 << SSC_6551_COMMAND_IRQ_R)) &&
!(byte & (1 << SSC_6551_COMMAND_IRQ_R))) {
if (c->status & (1 << SSC_6551_IRQ))
mii->cpu_state.irq = 1;
}
int status;
if (ioctl(c->tty_fd, TIOCMGET, &status) == -1) {
printf("SSC%d: DTR/RTS: %s\n", c->slot->id+1, strerror(errno));
}
int old = status;
status = (status & ~TIOCM_DTR) |
((byte & (1 << SSC_6551_COMMAND_DTR)) ? TIOCM_DTR : 0);
switch ((byte >> SSC_6551_COMMAND_IRQ_T) & 3) {
case 0: // IRQ_TX=0 + RTS=1
status |= TIOCM_RTS;
break;
case 1: // IRQ_TX=1 + RTS=0
status &= ~TIOCM_RTS;
break;
case 2: // IRQ_TX=0 + RTS=0
status &= ~TIOCM_RTS;
break;
case 3: // IRQ_TX=0 + RTS=0 + BRK
status &= ~TIOCM_RTS;
break;
}
if (old != status) {
printf("%s%d: $%04x DTR %d RTS %d\n", __func__, c->slot->id+1,
c->mii->cpu.PC,
(status & TIOCM_DTR) ? 1 : 0,
(status & TIOCM_RTS) ? 1 : 0); // 0=on, 1=off
if (ioctl(c->tty_fd, TIOCMSET, &status) == -1) {
printf("SSC%d: DTR/RTS: %s\n", c->slot->id+1, strerror(errno));
}
}
c->command = byte;
}
static uint8_t
_mii_ssc_access(
mii_t * mii,
@ -102,17 +664,121 @@ _mii_ssc_access(
uint8_t byte,
bool write)
{
// mii_card_ssc_t *c = slot->drv_priv;
mii_card_ssc_t *c = slot->drv_priv;
uint8_t res = 0;
int psw = addr & 0x0F;
// mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
switch (psw) {
case 0x1: // DIPSW1
if (!write) {
printf("%s%d: $%04x read DIPSW1 : %02x\n",
__func__, slot->id+1, mii->cpu.PC, c->dipsw1);
res = c->dipsw1;
/* this handle access by the ROM via PR#x and IN#x */
if (c->state == MII_SSC_STATE_INIT &&
(mii->cpu.PC & 0xff00) == 0xcb00)
_mii_ssc_thread_start(c);
}
break;
case 0x2: // DIPSW2
if (!write) {
printf("%s%d: $%04x read DIPSW2 : %02x\n",
__func__, slot->id+1, mii->cpu.PC, c->dipsw2);
res = c->dipsw2;
}
break;
case 0x8: { // TD/RD
if (c->state != MII_SSC_STATE_RUNNING)
break;
if (write) {
bool tx_empty = mii_ssc_fifo_isempty(&c->tx);
// printf("%s: write %02x '%c'\n", __func__,
// byte, byte <= ' ' ? '.' : byte);
c->total_tx++;
mii_ssc_fifo_write(&c->tx, byte);
if (tx_empty) // wake thread if it's sleeping
pthread_kill(_mii_ssc_thread_id, SIGUSR1);
bool isfull = mii_ssc_fifo_isfull(&c->tx);
if (isfull) {
c->status &= ~(1 << SSC_6551_TX_EMPTY);
}
} else {
if (mii_ssc_fifo_isempty(&c->rx)) {
res = 0;
} else {
c->total_rx++;
bool wasfull = mii_ssc_fifo_isfull(&c->rx);
res = mii_ssc_fifo_read(&c->rx);
bool isempty = mii_ssc_fifo_isempty(&c->rx);
if (isempty) {
c->status &= ~(1 << SSC_6551_RX_FULL);
} else {
if (wasfull) // wake thread to read more
pthread_kill(_mii_ssc_thread_id, SIGUSR1);
// send another irq?
uint8_t r_irqen =
!(c->command & (1 << SSC_6551_COMMAND_IRQ_R));
if (r_irqen) {
mii->cpu_state.irq = 1;
}
}
}
}
} break;
case 0x9: {// STATUS
if (write) {
printf("SSC%d: RESET requesdt\n",c->slot->id+1);
_mii_ssc_command_set(c, 0x10);
break;
}
res = c->status;
// if it was set before, clear it.
c->status &= ~(1 << SSC_6551_IRQ);
} break;
case 0xa: {// COMMAND
if (!write) {
res = c->command;
break;
}
_mii_ssc_command_set(c, byte);
} break;
case 0xb: { // CONTROL
if (!write) {
res = c->control;
break;
}
c->control = byte;
struct termios tio;
tcgetattr(c->tty_fd, &tio);
// Update speed
int baud = _mii_ssc_to_baud[c->control & 0x0F];
cfsetospeed(&tio, baud);
cfsetispeed(&tio, baud);
// Update stop bits bit 7: 0 = 1 stop bit, 1 = 2 stop bits
tio.c_cflag &= ~CSTOPB;
tio.c_cflag |= _mii_ssc_to_stop[(c->control >> 7) & 1];
// Update data bits bit 5-6 0=8 bits, 1=7 bits, 2=6 bits, 3=5 bits
tio.c_cflag &= ~CSIZE;
tio.c_cflag |= _mii_ssc_to_bits[(c->control >> 6) & 3];
// parity are in c->command, bits 5-7,
// 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space
tio.c_cflag &= ~PARENB;
tio.c_cflag &= ~PARODD;
tio.c_cflag |= _mii_ssc_to_parity[(c->command >> 5) & 3];
tcsetattr(c->tty_fd, TCSANOW, &tio);
printf("SSC%d: set %02x baud %d stop %d bits %d parity %d\n",
c->slot->id+1, byte,
_mii_ssc_to_baud_rate[c->control & 0x0F],
(c->control >> 7) & 1 ? 2 : 1,
_mii_scc_to_bits_count[(c->control >> 5) & 3],
(c->command >> 5) & 3);
} break;
default:
printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
mii->cpu.PC, addr, byte, write);
// printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__,
// mii->cpu.PC, addr, byte, write);
break;
}
return 0;
return res;
}
static int
@ -126,9 +792,11 @@ _mii_ssc_command(
int res = -1;
switch (cmd) {
case MII_SLOT_SSC_SET_TTY: {
const char * tty = param;
printf("%s: set tty %s\n", __func__, tty);
res = 0;
const mii_ssc_setconf_t * conf = param;
mii_card_ssc_t *c = slot->drv_priv;
res = _mii_scc_set_conf(c, conf, 0);
printf("SSC%d: set tty %s: %s\n",
slot->id+1, conf->device, c->human_config);
} break;
}
return res;
@ -143,3 +811,41 @@ static mii_slot_drv_t _driver = {
.command = _mii_ssc_command,
};
MI_DRIVER_REGISTER(_driver);
#include "mish.h"
static void
_mii_mish_ssc(
void * param,
int argc,
const char * argv[])
{
if (!argv[1] || !strcmp(argv[1], "status")) {
mii_card_ssc_t *c;
printf("SSC: cards:\n");
STAILQ_FOREACH(c, &_mii_card_ssc_slots, self) {
printf("SSC %d: %s FD: %2d path:%s %s\n", c->slot->id+1,
c->state == MII_SSC_STATE_RUNNING ? "running" : "stopped",
c->tty_fd, c->tty_path, c->human_config);
// print FIFO status, fd status, registers etc
printf(" RX: %2d/%2d TX: %2d/%2d -- total rx:%6d tx:%6d\n",
mii_ssc_fifo_get_read_size(&c->rx),
mii_ssc_fifo_get_write_size(&c->rx),
mii_ssc_fifo_get_read_size(&c->tx),
mii_ssc_fifo_get_write_size(&c->tx),
c->total_rx, c->total_tx);
printf(" DIPSW1: %08b DIPSW2: %08b\n", c->dipsw1, c->dipsw2);
printf(" CONTROL: %08b COMMAND: %08b STATUS: %08b\n",
c->control, c->command, c->status);
}
return;
}
}
MISH_CMD_NAMES(_ssc, "ssc");
MISH_CMD_HELP(_ssc,
"ssc: Super Serial internals",
" <default>: dump status"
);
MII_MISH(_ssc, _mii_mish_ssc);

23
src/drivers/mii_ssc.h Normal file
View File

@ -0,0 +1,23 @@
/*
* mii_ssc.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
/*
* This is mostly a duplicate from the UI one in mii_mui_settings.h, but it is
* part of the way we decouple the UI from the emulator, so we can test the UI
* without having to link against the emulator.
*/
// this is to be used with MII_SLOT_SSC_SET_TTY and mii_slot_command()
typedef struct mii_ssc_setconf_t {
unsigned int baud, bits : 4, parity : 4, stop : 4, handshake : 4,
is_device : 1, is_socket : 1, is_pty : 1;
unsigned socket_port;
char device[256];
} mii_ssc_setconf_t;

View File

@ -31,21 +31,21 @@ _mii_titan_access(
{
mii_t *mii = param;
bool res = false;
mii_bank_t *main = &mii->bank[MII_BANK_MAIN];
mii_bank_t *sw = &mii->bank[MII_BANK_SW];
if (write) {
printf("titan: write %02x to %04x\n", *byte, addr);
switch (*byte) {
case 5:
mii->speed = 3.58;
mii_bank_poke(main, 0xc086, *byte);
mii->speed = MII_SPEED_TITAN;
mii_bank_poke(sw, 0xc086, *byte);
break;
case 1:
mii_bank_poke(main, 0xc086, *byte);
mii->speed = 1;
mii_bank_poke(sw, 0xc086, *byte);
mii->speed = MII_SPEED_NTSC;
break;
case 0xa: // supposed to lock it too...
mii_bank_poke(main, 0xc086, *byte);
mii->speed = 1;
mii_bank_poke(sw, 0xc086, *byte);
mii->speed = MII_SPEED_NTSC;
break;
default:
printf("titan: unknown speed %02x\n", *byte);

View File

@ -59,11 +59,18 @@ mii_dd_register_drives(
uint8_t count )
{
// printf("%s: registering %d drives\n", __func__, count);
mii_dd_t * last = dd->drive;
while (last && last->next)
last = last->next;
for (int i = 0; i < count; i++) {
mii_dd_t *d = &drives[i];
d->dd = dd;
d->next = dd->drive;
dd->drive = d;
d->next = NULL;
if (last)
last->next = d;
else
dd->drive = d;
last = d;
}
}
@ -194,6 +201,8 @@ mii_dd_file_load(
res->format = MII_DD_FILE_PO;
} else if (!strcasecmp(suffix, ".nib")) {
res->format = MII_DD_FILE_NIB;
} else if (!strcasecmp(suffix, ".do")) {
res->format = MII_DD_FILE_DO;
} else if (!strcasecmp(suffix, ".woz")) {
res->format = MII_DD_FILE_WOZ;
} else if (!strcasecmp(suffix, ".2mg")) {
@ -236,6 +245,10 @@ mii_dd_overlay_load(
return 0;
if (!dd->file)
return -1;
// no overlay for PO disk images (floppy)
if (!(dd->file->format == MII_DD_FILE_PO &&
dd->file->size != 143360))
return -1;
char *filename = NULL;
char *suffix = strrchr(dd->file->pathname, '.');
@ -246,7 +259,7 @@ mii_dd_overlay_load(
}
int fd = open(filename, O_RDWR, 0666);
if (fd == -1) {
fprintf(stderr, "%s: overlay %s: %s\n", __func__,
printf("%s: overlay %s: %s\n", __func__,
filename, strerror(errno));
free(filename);
return -1;
@ -258,17 +271,17 @@ mii_dd_overlay_load(
mii_dd_overlay_header_t * h = (mii_dd_overlay_header_t *)file->start;
if (h->magic != FCC('M','I','O','V')) {
fprintf(stderr, "Overlay file %s has invalid magic\n", filename);
printf("Overlay file %s has invalid magic\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
if (h->version != 1) {
fprintf(stderr, "Overlay file %s has invalid version\n", filename);
printf("Overlay file %s has invalid version\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
if (h->size != dd->file->size / 512) {
fprintf(stderr, "Overlay file %s has invalid size\n", filename);
printf("Overlay file %s has invalid size\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
@ -280,7 +293,7 @@ mii_dd_overlay_load(
MD5_Final(md5, &d5);
if (memcmp(md5, h->src_md5, 16)) {
fprintf(stderr, "Overlay file %s has mismatched HASH!\n", filename);
printf("Overlay file %s has mismatched HASH!\n", filename);
mii_dd_file_dispose(dd->dd, file);
return -1;
}
@ -302,6 +315,10 @@ mii_dd_overlay_prepare(
return 0;
if (!dd->file)
return -1;
// no overlay for PO disk images (floppy)
if (!(dd->file->format == MII_DD_FILE_PO &&
dd->file->size != 143360))
return 0;
printf("%s: %s Preparing Overlay file\n", __func__, dd->name);
uint32_t src_blocks = dd->file->size / 512;
uint32_t bitmap_size = (src_blocks + 63) / 64;
@ -317,9 +334,9 @@ mii_dd_overlay_prepare(
}
int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd == -1) {
fprintf(stderr, "%s: Failed to create overlay file %s: %s\n", __func__,
printf("%s: Failed to create overlay file %s: %s\n", __func__,
filename, strerror(errno));
fprintf(stderr, "%s: Allocating a RAM one, lost on quit!\n", __func__);
printf("%s: Allocating a RAM one, lost on quit!\n", __func__);
dd->overlay.file = mii_dd_file_in_ram(dd->dd, filename, size, O_RDWR);
} else {
ftruncate(fd, size);

View File

@ -14,11 +14,12 @@ struct mii_dd_t;
enum {
// MII_DD_FILE_OVERLAY = 1,
MII_DD_FILE_RAM,
MII_DD_FILE_RAM = 1,
MII_DD_FILE_ROM,
MII_DD_FILE_PO,
MII_DD_FILE_2MG = 5,
MII_DD_FILE_2MG,
MII_DD_FILE_DSK,
MII_DD_FILE_DO,
MII_DD_FILE_NIB,
MII_DD_FILE_WOZ
};
@ -65,12 +66,14 @@ typedef struct mii_dd_overlay_t {
struct mii_slot_t;
struct mii_dd_system_t;
struct mii_floppy_t;
// a disk drive, with a slot, a drive number, and a file
typedef struct mii_dd_t {
struct mii_dd_t * next;
struct mii_dd_system_t *dd;
const char * name; // ie "Disk ][ D:2"
const char * name; // ie "Disk ][ D:2"
struct mii_floppy_t * floppy; // if it's a floppy drive
uint8_t slot_id : 4, drive : 4;
struct mii_slot_t * slot;
unsigned int ro : 1, wp : 1, can_eject : 1;

View File

@ -55,11 +55,12 @@ mii_floppy_init(
// important, the +1 means we initialize the random track too
for (int i = 0; i < MII_FLOPPY_TRACK_COUNT + 1; i++) {
f->tracks[i].dirty = 0;
f->tracks[i].bit_count = 6500 * 8;
f->tracks[i].virgin = 1;
f->tracks[i].bit_count = 6550 * 8;
// fill the whole array up to the end..
uint8_t *track = f->track_data[i];
if (i != MII_FLOPPY_NOISE_TRACK) {
#if 1
#if 0
memset(track, 0, MII_FLOPPY_DEFAULT_TRACK_SIZE);
#else
for (int bi = 0; bi < MII_FLOPPY_DEFAULT_TRACK_SIZE; bi++)
@ -73,32 +74,289 @@ static void
mii_track_write_bits(
mii_floppy_track_t * dst,
uint8_t * track_data,
uint8_t bits,
uint32_t bits,
uint8_t count )
{
while (count--) {
uint32_t byte_index = dst->bit_count >> 3;
uint8_t bit_index = 7 - (dst->bit_count & 7);
track_data[byte_index] &= ~(1 << bit_index);
track_data[byte_index] |= (!!(bits >> 7) << bit_index);
track_data[byte_index] |= !!(bits & (1L << (count))) << bit_index;
dst->bit_count++;
bits <<= 1;
}
}
static uint32_t
mii_track_read_bits(
mii_floppy_track_t * src,
uint8_t * track_data,
uint32_t pos,
uint8_t count )
{
uint32_t bits = 0;
while (count--) {
pos = pos % src->bit_count;
uint32_t byte_index = pos >> 3;
uint8_t bit_index = 7 - (pos & 7);
bits <<= 1;
bits |= !!(track_data[byte_index] & (1 << bit_index));
// we CAN have a wrap around here, but it's ok
pos++;
}
return bits;
}
/*
* given a track a a starting position, look for a run of 0b1111111100,
* return the number of sync bits found, and also update the position to
* the end of the sync bits.
*/
static uint32_t
mii_floppy_find_next_sync(
mii_floppy_track_t * src,
uint8_t * track_data,
uint32_t *io_pos)
{
/* First we need to sync ourselves by finding 5 * 0b1111111100 's */
uint32_t window = 0;
/* get one bit at a time until we get one sync word */
uint32_t wi = 0;
uint32_t pos = *io_pos;
// give up after 2000 bits really, it's either there, or not
// otherwise we could be 'fooled' looping over the whole track
int tries = 10000;
do {
do {
window = (window << 1) |
mii_track_read_bits(src, track_data, pos, 1);
pos++;
if ((window & 0x3ff) == 0b1111111100)
break;
} while (tries-- > 0 );
wi = 10;
if (mii_track_read_bits(src, track_data, pos, 1) == 0) {
pos++;
wi++;
}
do {
uint16_t w = mii_track_read_bits(src, track_data, pos + wi, 9);
if (w == 0b111111110)
wi += 9;
else if ((w & 0b111111110) == 0b111111110) {
wi += 8;
break;
}
if (mii_track_read_bits(src, track_data, pos + wi, 1) == 0) {
wi++;
} else
break;
if (mii_track_read_bits(src, track_data, pos + wi, 1) == 0) {
wi++;
}
} while (tries-- > 0 && wi < 2000);
/* if this is a sector header, we're in sync here! */
if (wi >= 2 * 10)
break;
else {
wi = 0;
}
} while (tries-- > 0 && wi < 2000);
// this CAN overflow the bit count, but it's ok
pos += wi;
*io_pos = pos;
return wi;
}
#define DE44(a, b) ((((a) & 0x55) << 1) | ((b) & 0x55))
/*
* this creates a sector+data map of a bitstream, and returns the positions
* of header and data blocks, as well as how many sync bits were found.
* Function return 0 if 16 headers + data were found, -1 if not.
*/
int
mii_floppy_map_track(
mii_floppy_t *f,
uint8_t track_id,
mii_floppy_track_map_t *map,
uint8_t flags )
{
mii_floppy_track_t * src = &f->tracks[track_id];
uint8_t * track_data = f->track_data[track_id];
uint16_t hmap = 0, dmap = 0;
uint32_t pos = 0;
uint32_t wi = 0;
int sect_count = 0;
int sect_current = -1; // current sector
// do 2 passes, in case a data sector appears before it's header
int pass = 0;
do {
wi = mii_floppy_find_next_sync(src, track_data, &pos);
uint32_t header = mii_track_read_bits(src, track_data, pos, 24);
if (wi == 0) {
printf("T%2d pos:%5d hmap:%04x dmap:%04x done?\n",
track_id, pos, hmap, dmap);
return -1;
}
if (header == 0xd5aaad) { // data sector, update current sector
if (sect_current == -1) {
if (flags & 1)
printf("%s: track %2d data sector before header\n",
__func__, track_id);
} else {
dmap |= 1 << sect_current;
map->sector[sect_current].dsync = wi;
map->sector[sect_current].data = pos;
}
int skippy = 3 + 342 + 1 + 3; // header, data nibble, chk, tailer
pos += skippy * 8;
goto get_new_sync;
}
if (header != 0xd5aa96) { // not a header section? maybe DOS3.2?
if (flags & 1)
printf("%s: track %2d bizare sync found %06x\n",
__func__, track_id, header);
pos += 10; // first 10 bits aren't sync anyway
goto get_new_sync;
}
if (flags & 1)
printf("Track %2d sync %d sync bits at bit %d/%d next %08x\n",
track_id, wi, pos, src->bit_count, header);
uint8_t hb[8];
for (int hi = 0; hi < 8; hi++)
hb[hi] = mii_track_read_bits(src, track_data,
pos + 24 + (hi * 8), 8);
uint32_t tailer = mii_track_read_bits(src, track_data,
pos + 24 + (8 * 8), 20);
uint8_t vol = DE44(hb[0], hb[1]);
uint8_t track = DE44(hb[2], hb[3]);
uint8_t sector = DE44(hb[4], hb[5]);
uint8_t chk = DE44(hb[6], hb[7]);
uint8_t want = vol ^ track ^ sector;
if (chk != want) {
if (flags & 1)
printf("T%2d S%2d V%2d chk:%2x/%02x tailer %06x INVALID header\n",
track, sector, vol, chk, want, tailer);
goto get_new_sync;
}
sect_current = sector;
sect_count++;
// if we already have a header for this sector (with it's matching data)
if ((hmap & (1 << sector)) && !(dmap & (1 << sector))) {
printf("T%2d S%2d DUPLICATE sector pos:%5d hmap:%04x dmap:%04x\n",
track, sector, pos, hmap, dmap);
printf("\thsync: %3d pos:%5d dsync: %3d pos:%5d\n",
map->sector[sector].hsync, map->sector[sector].header,
map->sector[sector].dsync, map->sector[sector].data);
return -1;
}
hmap |= 1 << sector;
map->sector[sector].hsync = wi;
map->sector[sector].header = pos;
map->sector[sector].data = 0;
if (flags & 1)
printf("T%2d S%2d V%2d chk:%2x/%02x pos %5d tailer %06x hm:%04x dm:%04x\n",
track, sector, vol, chk, want, pos, tailer, hmap, dmap);
if (sect_count > 16) {
// something fishy going on there, too many sector found,
// and none of them is zero? let's bail.
printf("T%2d S%2d Too many sectors\n",
track, sector);
return -1;
}
// printf("T%2d S%2d V%2d chk:%2x/%02x tailer %06x Skipping sector\n",
// track, sector, vol, chk, want, tailer);
pos += 24 + 8 * 8 + 24; // skip the whole header
if (track_id == 0 && sector == 0) {
printf("pos %5d/%5d\n", pos, src->bit_count);
for (int bi = 0; bi < 10; bi++) {
uint32_t bits = mii_track_read_bits(src, track_data,
pos + (bi * 10), 10);
printf("%010b\n", bits);
}
printf("\n");
}
get_new_sync:
if (hmap == 0xffff && dmap == 0xffff)
break;
if (pos >= src->bit_count) {
if (pass == 0) {
printf("%s: T%2d has %d sectors hmap:%04x dmap:%04x LOOPING\n",
__func__, track_id, sect_count, hmap, dmap);
pass++;
pos = pos % src->bit_count;
}
}
} while (pos < src->bit_count);
int res = hmap == 0xffff && dmap == 0xffff ? 0 : -1;
if (res != 0) {
// if (flags & 1)
printf("%s: T%2d has %d sectors hmap:%04x dmap:%04x\n",
__func__, track_id, sect_count, hmap, dmap);
}
return res;
}
/* This reposition the sector 0 to the beginning of the track,
hopefully also realign the nibbles to something readable.
See Sather 9-28 for details
*/
void
mii_floppy_resync_track(
mii_floppy_t *f,
uint8_t track_id,
uint8_t flags )
{
mii_floppy_track_map_t map = {};
if (mii_floppy_map_track(f, track_id, &map, flags) != 0) {
printf("%s: track %2d has no sync\n", __func__, track_id);
return;
}
int32_t pos = map.sector[0].header;
int32_t wi = map.sector[0].hsync;
/* We got a sector zero, we know the number of sync bits in front, and we
know it's header position, so we can reposition it at the beginning
of the track by basically reconstructing it */
pos -= wi;
if (pos <= 10) { // already at the beginning, really.
if (flags & 1)
printf("T%2d Sector 0 at pos %d\n", track_id, pos);
return;
}
mii_floppy_track_t * src = &f->tracks[track_id];
uint8_t * track_data = f->track_data[track_id];
if (flags & 1)
printf("%s: track %2d resync from bit %5d/%5d\n",
__func__, track_id, pos, src->bit_count);
mii_floppy_track_t new = {.dirty = 1, .virgin = 0, .bit_count = 0};
uint8_t *new_track = malloc(6656);
while (new.bit_count < src->bit_count) {
int cnt = src->bit_count - new.bit_count > 32 ? 32 : src->bit_count - new.bit_count;
uint32_t bits = mii_track_read_bits(src, track_data, pos, cnt);
mii_track_write_bits(&new, new_track, bits, cnt);
pos += cnt;
}
// printf("%s: Track %2d has been resynced!\n", __func__, track_id);
memcpy(track_data, new_track, MII_FLOPPY_DEFAULT_TRACK_SIZE);
free(new_track);
src->dirty = 1;
}
/*
* NIB isn't ideal to use with our bitstream, as it's lacking the sync
* bits. It was made to use in something like our previous emulator that
* was just iterating uint8_ts.
* bits.
* Anyway, We can recreate the proper bitstream by finding sectors headers,
* filling up a few 'correct' 10 bits sync uint8_ts, then plonk said sector
* as is.
*/
static uint8_t _de44(uint8_t a, uint8_t b) {
return ((a & 0x55) << 1) | (b & 0x55);
}
static void
mii_nib_rebit_track(
uint8_t *src_track,
@ -106,22 +364,27 @@ mii_nib_rebit_track(
uint8_t * dst_track)
{
dst->bit_count = 0;
dst->virgin = 0;
uint32_t window = 0;
int srci = 0;
int seccount = 0;
int state = 0; // look for address field
int tid = 0, sid;
uint16_t hmap = 0, dmap = 0;
do {
window = (window << 8) | src_track[srci++];
switch (state) {
case 0: {
if (window != 0xffd5aa96)
break;
// uint32_t pos = dst->bit_count;
for (int i = 0; i < (seccount == 0 ? 40 : 20); i++)
mii_track_write_bits(dst, dst_track, 0xff, 10);
mii_track_write_bits(dst, dst_track, 0xff << 2, 10);
uint8_t * h = src_track + srci - 4; // incs first 0xff
// int tid = _de44(h[6], h[7]);
// int sid = _de44(h[8], h[9]);
// printf("Track %2d sector %2d\n", tid, sid);
tid = DE44(h[6], h[7]);
sid = DE44(h[8], h[9]);
// printf("Track %2d sector %2d pos %5d\n", tid, sid, pos);
hmap |= 1 << sid;
memcpy(dst_track + (dst->bit_count >> 3), h, 15);
dst->bit_count += 15 * 8;
srci += 11;
@ -131,7 +394,9 @@ mii_nib_rebit_track(
if (window != 0xffd5aaad)
break;
for (int i = 0; i < 4; i++)
mii_track_write_bits(dst, dst_track, 0xff, 10);
mii_track_write_bits(dst, dst_track, 0xff << 2, 10);
// printf("\tdata at %d\n", dst->bit_count);
dmap |= 1 << sid;
uint8_t *h = src_track + srci - 4;
memcpy(dst_track + (dst->bit_count >> 3), h, 4 + 342 + 4);
dst->bit_count += (4 + 342 + 4) * 8;
@ -141,6 +406,11 @@ mii_nib_rebit_track(
} break;
}
} while (srci < 6656);
printf("%s %d sectors found hmap %04x dmap %04x - %5d bits\n",
__func__, seccount, hmap, dmap, dst->bit_count);
if (hmap != 0xffff || dmap != 0xffff)
printf("%s: track %2d incomplete? (header 0x%04x data 0x%04x)\n",
__func__, tid, ~hmap, ~dmap);
}
static int
@ -275,6 +545,7 @@ mii_floppy_load_woz(
// printf("WOZ: Track %d not used\n", i);
continue;
}
f->tracks[i].virgin = 0;
memcpy(f->track_data[i], track, le16toh(trks->track[i].byte_count_le));
f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le);
}
@ -298,7 +569,7 @@ mii_floppy_load_woz(
(char*)&trks->chunk.id_le, le32toh(trks->chunk.size_le));
#endif
/* TODO: this doesn't work yet... */
// f->bit_timing = info->optimal_bit_timing;
//f->bit_timing = info->optimal_bit_timing;
for (int i = 0; i < MII_FLOPPY_TRACK_COUNT; i++) {
if (!(used_tracks & (1L << i))) {
// printf("WOZ: Track %d not used\n", i);
@ -307,6 +578,7 @@ mii_floppy_load_woz(
uint8_t *track = file->map +
(le16toh(trks->track[i].start_block_le) << 9);
uint32_t byte_count = (le32toh(trks->track[i].bit_count_le) + 7) >> 3;
f->tracks[i].virgin = 0;
memcpy(f->track_data[i], track, byte_count);
f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le);
}
@ -336,6 +608,7 @@ static const uint8_t PO[] = {
0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb,
0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf
};
static const uint8_t TRANS62[] = {
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
@ -349,49 +622,43 @@ static const uint8_t TRANS62[] = {
// This function is derived from Scullin Steel Co.'s apple2js code
// https://github.com/whscullin/apple2js/blob/e280c3d/js/formats/format_utils.ts#L140
/* Further recycled for MII .DSK decoding
* We use this function to convert the sector from byte to nibble (8 bits), then
* we pass that track to the mii_nib_rebit_track() to add 10 bit headers and
* such. It could be done in one pass, but really, it's easier to reuse it as is.
*/
/* Further recycled for MII .DSK decoding, using 10 bits sync words etc. */
static void
mii_floppy_nibblize_sector(
uint8_t vol, uint8_t track, uint8_t sector,
uint8_t **nibSec, const uint8_t *data)
const uint8_t *data,
mii_floppy_track_t *dst,
uint8_t * track_data )
{
uint8_t *wr = *nibSec;
unsigned int gap;
// Gap 1/3 (40/0x28 uint8_ts)
if (sector == 0) // Gap 1
gap = 0x80;
else { // Gap 3
gap = track == 0? 0x28 : 0x26;
}
for (uint8_t i = 0; i != gap; ++i)
*wr++ = 0xFF;
if (track == 0 )
printf("NIB: vol %d track %d sector %d pos %5d\n",
vol, track, sector, dst->bit_count);
gap = sector == 0 ? 120 : track == 0 ? 30 : 20;
for (uint8_t i = 0; i < gap; ++i)
mii_track_write_bits(dst, track_data, 0xFF << 2, 10);
// Address Field
const uint8_t checksum = vol ^ track ^ sector;
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0x96; // Address Prolog D5 AA 96
*wr++ = (vol >> 1) | 0xAA; *wr++ = vol | 0xAA;
*wr++ = (track >> 1) | 0xAA; *wr++ = track | 0xAA;
*wr++ = (sector >> 1) | 0xAA; *wr++ = sector | 0xAA;
*wr++ = (checksum >> 1) | 0xAA; *wr++ = checksum | 0xAA;
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
// Gap 2 (5 uint8_ts)
for (int i = 0; i != 5; ++i)
*wr++ = 0xFF;
mii_track_write_bits(dst, track_data, 0xd5aa96, 24);
mii_track_write_bits(dst, track_data, (vol >> 1) | 0xAA, 8);
mii_track_write_bits(dst, track_data, vol | 0xAA, 8);
mii_track_write_bits(dst, track_data, (track >> 1) | 0xAA, 8);
mii_track_write_bits(dst, track_data, track | 0xAA, 8);
mii_track_write_bits(dst, track_data, (sector >> 1) | 0xAA, 8);
mii_track_write_bits(dst, track_data, sector | 0xAA, 8);
mii_track_write_bits(dst, track_data, (checksum >> 1) | 0xAA, 8);
mii_track_write_bits(dst, track_data, checksum | 0xAA, 8);
mii_track_write_bits(dst, track_data, 0xdeaaeb, 24);
// Gap 2 (5)
for (int i = 0; i < 5; ++i)
mii_track_write_bits(dst, track_data, 0xFF << 2, 10);
// Data Field
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0xAD; // Data Prolog D5 AA AD
uint8_t *nibbles = wr;
mii_track_write_bits(dst, track_data, 0xd5aaad, 24);
uint8_t nibbles[0x156] = {};
const unsigned ptr2 = 0;
const unsigned ptr6 = 0x56;
for (int i = 0; i != 0x156; ++i)
nibbles[i] = 0;
int i2 = 0x55;
for (int i6 = 0x101; i6 >= 0; --i6) {
uint8_t val6 = data[i6 % 0x100];
@ -406,15 +673,13 @@ mii_floppy_nibblize_sector(
uint8_t last = 0;
for (int i = 0; i != 0x156; ++i) {
const uint8_t val = nibbles[i];
nibbles[i] = TRANS62[last ^ val];
mii_track_write_bits(dst, track_data, TRANS62[last ^ val], 8);
last = val;
}
wr += 0x156; // advance write-pointer
*wr++ = TRANS62[last];
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
mii_track_write_bits(dst, track_data, TRANS62[last], 8);
mii_track_write_bits(dst, track_data, 0xdeaaeb, 24);
// Gap 3
*wr++ = 0xFF;
*nibSec = wr;
mii_track_write_bits(dst, track_data, 0xFF << 2, 10);
}
static int
@ -422,31 +687,32 @@ mii_floppy_load_dsk(
mii_floppy_t *f,
mii_dd_file_t *file )
{
uint8_t *nibbleBuf = malloc(NIBBLE_TRACK_SIZE);
const char *filename = basename(file->pathname);
const char *ext = rindex(filename, '.');
ext = ext ? ext+1 : "";
const uint8_t * secmap = DO;
if (!strcasecmp(ext, "PO")) {
printf("%s Opening %s as PO.\n", __func__, filename);
printf("%s opening %s as PO.\n", __func__, filename);
secmap = PO;
} else {
printf("%s Opening %s as DO.\n", __func__, filename);
printf("%s opening %s as DO.\n", __func__, filename);
}
for (int i = 0; i < 35; ++i) {
memset(nibbleBuf, 0xff, NIBBLE_TRACK_SIZE);
uint8_t *writePtr = nibbleBuf;
for (int phys_sector = 0; phys_sector < MAX_SECTORS; ++phys_sector) {
mii_floppy_track_t *dst = &f->tracks[i];
uint8_t *track_data = f->track_data[i];
dst->bit_count = 0;
dst->virgin = 0;
for (int phys_sector = 0; phys_sector < MAX_SECTORS; phys_sector++) {
const uint8_t dos_sector = secmap[phys_sector];
uint32_t off = ((MAX_SECTORS * i + dos_sector) * DSK_SECTOR_SIZE);
uint8_t *track = file->map + off;
uint8_t *src = file->map + off;
mii_floppy_nibblize_sector(VOLUME_NUMBER, i, phys_sector,
&writePtr, track);
src, dst, track_data);
}
mii_nib_rebit_track(nibbleBuf, &f->tracks[i], f->track_data[i]);
// printf("%s: track %2d has %d bits %d bytes\n",
// __func__, i, dst->bit_count, dst->bit_count >> 3);
}
free(nibbleBuf);
// DSK is read only
f->write_protected |= MII_FLOPPY_WP_RO_FORMAT;
@ -498,6 +764,8 @@ mii_floppy_load(
res = mii_floppy_load_woz(f, file);
break;
case MII_DD_FILE_DSK:
case MII_DD_FILE_PO:
case MII_DD_FILE_DO:
res = mii_floppy_load_dsk(f, file);
break;
default:

View File

@ -25,7 +25,8 @@ enum {
};
typedef struct mii_floppy_track_t {
uint8_t dirty : 1; // track has been written to
uint8_t dirty : 1, // track has been written to
virgin : 1; // track is not loaded/formatted
uint32_t bit_count;
} mii_floppy_track_t;
@ -66,14 +67,17 @@ typedef struct mii_floppy_t {
mii_floppy_track_t tracks[MII_FLOPPY_TRACK_COUNT + 1];
// keep all the data together, we'll use it to make a texture
// the last track is used for noise
uint8_t track_data[MII_FLOPPY_TRACK_COUNT + 1][MII_FLOPPY_DEFAULT_TRACK_SIZE];
/* This is set by the UI to trakc the head movements, no functional use */
uint8_t track_data[MII_FLOPPY_TRACK_COUNT + 1]
[MII_FLOPPY_DEFAULT_TRACK_SIZE];
/* This is set by the UI to track the head movements,
* no functional use */
mii_floppy_heatmap_t * heat; // optional heatmap
} mii_floppy_t;
/*
* Initialize a floppy structure with random data. It is not formatted, just
* ready to use for loading a disk image, or formatting as a 'virgin' disk.
* Initialize a floppy structure with random data. It is not formatted,
* just ready to use for loading a disk image, or formatting as a
* 'virgin' disk.
*/
void
mii_floppy_init(
@ -88,3 +92,27 @@ int
mii_floppy_update_tracks(
mii_floppy_t *f,
mii_dd_file_t *file );
void
mii_floppy_resync_track(
mii_floppy_t *f,
uint8_t track_id,
uint8_t flags );
typedef struct mii_floppy_track_map_t {
struct {
int32_t hsync, dsync; // number of sync bits
uint32_t header, data; // position of the header and data
} sector[16];
} mii_floppy_track_map_t;
/*
* this creates a sector+data map of a bitstream, and returns the positions
* of header and data blocks, as well as how many sync bits were found.
* Function return 0 if 16 headers + data were found, -1 if not.
*/
int
mii_floppy_map_track(
mii_floppy_t *f,
uint8_t track_id,
mii_floppy_track_map_t *map,
uint8_t flags );

249
src/mii.c
View File

@ -26,32 +26,47 @@ static const mii_bank_t _mii_banks_init[MII_BANK_COUNT] = {
[MII_BANK_MAIN] = {
.name = "MAIN",
.base = 0x0000,
.size = 0xd0, // 208 pages, 48KB
.size = 0xc0,
},
[MII_BANK_BSR] = {
.name = "BSR",
.base = 0xd000,
.size = 64,
.mem_offset = 0xd000,
.no_alloc = 1,
},
[MII_BANK_BSR_P2] = {
.name = "BSR P2",
.base = 0xd000,
.size = 16,
.mem_offset = 0xc000,
.no_alloc = 1,
},
[MII_BANK_AUX_BASE] = {
.name = "AUX_BASE",
.base = 0x0000,
.size = 0xd0, // 208 pages, 48KB
.no_alloc = 1,
},
[MII_BANK_AUX] = {
.name = "AUX",
.base = 0x0000,
.size = 0xd0, // 208 pages, 48KB
.no_alloc = 1,
},
[MII_BANK_AUX_BSR] = {
.name = "AUX BSR",
.base = 0xd000,
.size = 64,
.mem_offset = 0xd000,
.no_alloc = 1,
},
[MII_BANK_AUX_BSR_P2] = {
.name = "AUX BSR P2",
.base = 0xd000,
.size = 16,
.mem_offset = 0xc000,
.no_alloc = 1,
},
[MII_BANK_ROM] = {
.name = "ROM",
@ -62,9 +77,17 @@ static const mii_bank_t _mii_banks_init[MII_BANK_COUNT] = {
[MII_BANK_CARD_ROM] = {
.name = "CARD ROM",
.base = 0xc100,
.size = 15,
// c100-cfff = 15 pages
// 7 * 2KB for extended ROMs for cards (not addressable directly)
// Car roms are 'banked' as well, so we don't need to copy them around
.size = 15,// + (7 * 8),
.ro = 1,
},
[MII_BANK_SW] = {
.name = "SW",
.base = 0xc000,
.size = 0x1,
},
};
@ -147,7 +170,7 @@ mii_sw(
mii_t *mii,
uint16_t sw)
{
return mii_bank_peek(&mii->bank[MII_BANK_MAIN], sw);
return mii_bank_peek(&mii->bank[MII_BANK_SW], sw);
}
static void
@ -165,7 +188,7 @@ mii_page_table_update(
bool ramwrt = SW_GETSTATE(mii, SWRAMWRT);
bool intcxrom = SW_GETSTATE(mii, SWINTCXROM);
bool slotc3rom = SW_GETSTATE(mii, SWSLOTC3ROM);
bool slotauxrom = SW_GETSTATE(mii, SLOTAUXROM);
bool intc8rom = SW_GETSTATE(mii, INTC8ROM);
if (unlikely(mii->trace_cpu))
printf("%04x: MEM update altzp:%d page2:%d store80:%d "
@ -173,7 +196,8 @@ mii_page_table_update(
"slotc3rom:%d\n", mii->cpu.PC,
altzp, page2, store80, hires, ramrd, ramwrt, intcxrom, slotc3rom);
// clean slate
mii_page_set(mii, MII_BANK_MAIN, MII_BANK_MAIN, 0x00, 0xc0);
mii_page_set(mii, MII_BANK_MAIN, MII_BANK_MAIN, 0x00, 0xbf);
mii_page_set(mii, MII_BANK_SW, MII_BANK_SW, 0xc0, 0xc0);
mii_page_set(mii, MII_BANK_ROM, MII_BANK_ROM, 0xc1, 0xff);
if (altzp)
mii_page_set(mii, MII_BANK_AUX, MII_BANK_AUX, 0x00, 0x01);
@ -189,13 +213,14 @@ mii_page_table_update(
page2 ? MII_BANK_AUX : MII_BANK_MAIN,
page2 ? MII_BANK_AUX : MII_BANK_MAIN, 0x20, 0x3f);
}
// c1-cf are at ROM state when we arrive here
if (!intcxrom) {
mii_page_set(mii, MII_BANK_CARD_ROM, _SAME, 0xc1, 0xc7);
if (slotauxrom)
mii_page_set(mii, MII_BANK_CARD_ROM, _SAME, 0xc8, 0xcf);
mii_page_set(mii, MII_BANK_CARD_ROM, _SAME, 0xc1, 0xcf);
if (!slotc3rom)
mii_page_set(mii, MII_BANK_ROM, _SAME, 0xc3, 0xc3);
if (intc8rom)
mii_page_set(mii, MII_BANK_ROM, _SAME, 0xc8, 0xcf);
}
mii_page_set(mii,
slotc3rom ? MII_BANK_CARD_ROM : MII_BANK_ROM, _SAME, 0xc3, 0xc3);
bool bsrread = SW_GETSTATE(mii, BSRREAD);
bool bsrwrite = SW_GETSTATE(mii, BSRWRITE);
bool bsrpage2 = SW_GETSTATE(mii, BSRPAGE2);
@ -218,6 +243,32 @@ mii_page_table_update(
0xd0, 0xdf);
}
static void
mii_bank_update_ramworks(
mii_t *mii,
uint8_t bank)
{
if (bank > 127 ||
!(mii->ramworks.avail & ((unsigned __int128)1ULL << bank)))
bank = 0;
if (!mii->ramworks.bank[bank]) {
mii->ramworks.bank[bank] = malloc(0x10000);
int c = 0, a = 0;
for (int i = 0; i < 128; i++ ) {
if (mii->ramworks.bank[i])
c++;
if (mii->ramworks.avail & ((unsigned __int128)1ULL << i))
a++;
}
printf("%s: RAMWORKS alloc bank %2d (%dKB / %dKB)\n", __func__,
bank, c * 64, a * 64);
}
mii->bank[MII_BANK_AUX_BASE].mem = mii->ramworks.bank[0];
mii->bank[MII_BANK_AUX].mem = mii->ramworks.bank[bank];
mii->bank[MII_BANK_AUX_BSR].mem = mii->ramworks.bank[bank];
mii->bank[MII_BANK_AUX_BSR_P2].mem = mii->ramworks.bank[bank];
}
void
mii_set_sw_override(
mii_t *mii,
@ -238,18 +289,15 @@ mii_set_sw_override(
* selected, it will deselect it.
*/
static bool
_mii_deselect_auxrom(
struct mii_bank_t *bank,
void *param,
_mii_deselect_cXrom(
mii_t * mii,
uint16_t addr,
uint8_t * byte,
bool write)
{
if (addr != 0xcfff)
return false;
mii_t * mii = param;
// printf("%s AUXROM:%d\n", __func__, !!(mii->sw_state & M_SLOTAUXROM));
if (!(mii->sw_state & M_SLOTAUXROM))
if (!SW_GETSTATE(mii, INTC8ROM))
return false;
for (int i = 0; i < 7; i++) {
mii_slot_t * slot = &mii->slot[i];
@ -259,13 +307,14 @@ _mii_deselect_auxrom(
slot->aux_rom_selected = false;
}
}
mii->sw_state &= ~M_SLOTAUXROM;
SW_SETSTATE(mii, INTC8ROM, 0);
mii->mem_dirty = true;
mii_page_table_update(mii);
return false;
}
static bool
_mii_select_c3rom(
_mii_select_c3introm(
struct mii_bank_t *bank,
void *param,
uint16_t addr,
@ -273,12 +322,11 @@ _mii_select_c3rom(
bool write)
{
mii_t * mii = param;
printf("%s\n", __func__);
if (mii->sw_state & M_SLOTAUXROM) {
// printf("%s: C3 aux rom re-selected\n", __func__);
mii->sw_state &= ~M_SLOTAUXROM;
if (!SW_GETSTATE(mii, SWSLOTC3ROM) && !SW_GETSTATE(mii, INTC8ROM)) {
SW_SETSTATE(mii, INTC8ROM, 1);
mii->mem_dirty = true;
mii_page_table_update(mii);
}
mii->mem_dirty = true;
return false;
}
@ -293,7 +341,7 @@ mii_access_soft_switches(
return false;
bool res = false;
uint8_t on = 0;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
/*
* This allows driver (titan accelerator etc) to have their own
@ -301,7 +349,7 @@ mii_access_soft_switches(
*/
if (mii->soft_switches_override && mii->soft_switches_override[addr & 0xff].cb) {
res = mii->soft_switches_override[addr & 0xff].cb(
main, mii->soft_switches_override[addr & 0xff].param,
sw, mii->soft_switches_override[addr & 0xff].param,
addr, byte, write);
if (res)
return res;
@ -336,47 +384,39 @@ mii_access_soft_switches(
*/
case 0xc080 ... 0xc08f: {
res = true;
uint8_t mode = addr & 0x0f;
static const int write_modes[4] = { 0, 1, 0, 1, };
static const int read_modes[4] = { 1, 0, 0, 1, };
uint8_t rd = read_modes[mode & 3];
uint8_t wr = write_modes[mode & 3];
if (write) {
SW_SETSTATE(mii, BSRPREWRITE, 0);
const int offSwitch = addr & 0x02;
if (addr & 0x01) { // Write switch
// 0xC081, 0xC083
if (!write) {
if (SW_GETSTATE(mii, BSRPREWRITE)) {
SW_SETSTATE(mii, BSRWRITE, 1);
}
}
SW_SETSTATE(mii, BSRPREWRITE, !write);
// 0xC08B
SW_SETSTATE(mii, BSRREAD, offSwitch);
} else {
SW_SETSTATE(mii, BSRPREWRITE, mode & 1);
// 0xC080, 0xC082
SW_SETSTATE(mii, BSRWRITE, 0);
SW_SETSTATE(mii, BSRPREWRITE, 0);
// 0xC082
SW_SETSTATE(mii, BSRREAD, !offSwitch);
}
// if (SW_GETSTATE(mii, BSRPREWRITE))
// ;
SW_SETSTATE(mii, BSRWRITE, wr);
SW_SETSTATE(mii, BSRREAD, rd);
SW_SETSTATE(mii, BSRPAGE2, !(mode & 0x08)); // A3
SW_SETSTATE(mii, BSRPAGE2, !(addr & 0x08));
mii->mem_dirty = 1;
// mii->trace_cpu = 1;
// mii->state = MII_STOPPED;
if (unlikely(mii->trace_cpu))
printf("%04x: BSR mode %c%04x pre:%d read:%s write:%s %s altzp:%02x\n",
mii->cpu.PC, write ? 'W' : 'R',
addr,
SW_GETSTATE(mii, BSRPREWRITE),
rd ? "BSR" : "ROM",
wr ? "BSR" : "ROM",
SW_GETSTATE(mii, BSRPAGE2) ? "page2" : "page1",
mii_sw(mii, SWALTPZ));
} break;
case SWPAGE2OFF:
case SWPAGE2ON:
res = true;
SW_SETSTATE(mii, SWPAGE2, addr & 1);
mii_bank_poke(main, SWPAGE2, (addr & 1) << 7);
mii_bank_poke(sw, SWPAGE2, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWHIRESOFF:
case SWHIRESON:
res = true;
SW_SETSTATE(mii, SWHIRES, addr & 1);
mii_bank_poke(main, SWHIRES, (addr & 1) << 7);
mii_bank_poke(sw, SWHIRES, (addr & 1) << 7);
mii->mem_dirty = 1;
// printf("HIRES %s\n", (addr & 1) ? "ON" : "OFF");
break;
@ -402,44 +442,48 @@ mii_access_soft_switches(
case SW80STOREON:
res = true;
SW_SETSTATE(mii, SW80STORE, addr & 1);
mii_bank_poke(main, SW80STORE, (addr & 1) << 7);
mii_bank_poke(sw, SW80STORE, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWRAMRDOFF:
case SWRAMRDON:
res = true;
SW_SETSTATE(mii, SWRAMRD, addr & 1);
mii_bank_poke(main, SWRAMRD, (addr & 1) << 7);
mii_bank_poke(sw, SWRAMRD, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWRAMWRTOFF:
case SWRAMWRTON:
res = true;
SW_SETSTATE(mii, SWRAMWRT, addr & 1);
mii_bank_poke(main, SWRAMWRT, (addr & 1) << 7);
mii_bank_poke(sw, SWRAMWRT, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWALTPZOFF:
case SWALTPZON:
res = true;
SW_SETSTATE(mii, SWALTPZ, addr & 1);
mii_bank_poke(main, SWALTPZ, (addr & 1) << 7);
mii_bank_poke(sw, SWALTPZ, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWINTCXROMOFF:
case SWINTCXROMON:
res = true;
SW_SETSTATE(mii, SWINTCXROM, addr & 1);
mii_bank_poke(main, SWINTCXROM, (addr & 1) << 7);
mii_bank_poke(sw, SWINTCXROM, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWSLOTC3ROMOFF:
case SWSLOTC3ROMON:
res = true;
SW_SETSTATE(mii, SWSLOTC3ROM, addr & 1);
mii_bank_poke(main, SWSLOTC3ROM, (addr & 1) << 7);
mii_bank_poke(sw, SWSLOTC3ROM, (addr & 1) << 7);
mii->mem_dirty = 1;
break;
case SWRAMWORKS_BANK:
mii_bank_poke(sw, SWRAMWORKS_BANK, *byte);
mii_bank_update_ramworks(mii, *byte);
break;
}
} else {
switch (addr) {
@ -458,7 +502,7 @@ mii_access_soft_switches(
case SWALTPZ:
case SWSLOTC3ROM:
res = true;
*byte = mii_bank_peek(main, addr);
*byte = mii_bank_peek(sw, addr);
break;
case 0xc020: // toggle TAPE output ?!?!
res = true;
@ -495,27 +539,27 @@ mii_access_keyboard(
bool write)
{
bool res = false;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
switch (addr) {
case SWKBD:
if (!write) {
res = true;
*byte = mii_bank_peek(main, SWKBD);
*byte = mii_bank_peek(sw, SWKBD);
}
break;
case SWAKD: {
res = true;
uint8_t r = mii_bank_peek(main, SWAKD);
uint8_t r = mii_bank_peek(sw, SWAKD);
if (!write)
*byte = r;
r &= 0x7f;
mii_bank_poke(main, SWAKD, r);
mii_bank_poke(main, SWKBD, r);
mii_bank_poke(sw, SWAKD, r);
mii_bank_poke(sw, SWKBD, r);
} break;
case 0xc061 ... 0xc063: // Push Button 0, 1, 2 (Apple Keys)
res = true;
if (!write)
*byte = mii_bank_peek(main, addr);
*byte = mii_bank_peek(sw, addr);
break;
}
return res;
@ -526,25 +570,52 @@ mii_keypress(
mii_t *mii,
uint8_t key)
{
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
key |= 0x80;
mii_bank_poke(main, SWAKD, key);
mii_bank_poke(main, SWKBD, key);
mii_bank_poke(sw, SWAKD, key);
mii_bank_poke(sw, SWKBD, key);
}
#define B(x) ((unsigned __int128)1ULL << (x))
static const unsigned __int128 _mii_ramworks3_config[] = {
B(0x00)|B(0x01)|B(0x02)|B(0x03),
B(0x04)|B(0x05)|B(0x06)|B(0x07),
B(0x08)|B(0x09)|B(0x0a)|B(0x0b),
B(0x0c)|B(0x0d)|B(0x0e)|B(0x0f),
// 512K Expander
B(0x10)|B(0x11)|B(0x12)|B(0x13),
B(0x14)|B(0x15)|B(0x16)|B(0x17),
// 2MB Expander A to one meg
B(0x30),B(0x31),B(0x32),B(0x33),
B(0x34),B(0x35),B(0x36),B(0x37),
// 2MB Expander B
B(0x50),B(0x51),B(0x52),B(0x53),
B(0x54),B(0x55),B(0x56),B(0x57),
B(0x70),B(0x71),B(0x72),B(0x73),
B(0x74),B(0x75),B(0x76),B(0x77),
};
#undef B
void
mii_init(
mii_t *mii )
{
memset(mii, 0, sizeof(*mii));
mii->speed = 1.023;
mii->speed = MII_SPEED_NTSC;
mii->timer.map = 0;
for (int i = 0; i < MII_BANK_COUNT; i++)
mii->bank[i] = _mii_banks_init[i];
mii->bank[MII_BANK_ROM].mem = (uint8_t*)&iie_enhanced_rom_bin[0];
for (int i = 0; i < MII_BANK_COUNT; i++)
mii_bank_init(&mii->bank[i]);
uint8_t *mem = realloc(mii->bank[MII_BANK_MAIN].mem, 0x10000);
mii->bank[MII_BANK_MAIN].mem = mem;
mii->bank[MII_BANK_BSR].mem = mem;
mii->bank[MII_BANK_BSR_P2].mem = mem;
mii->ramworks.avail = 0;
mii_bank_update_ramworks(mii, 0);
mii->cpu.trap = MII_TRAP;
// these are called once, regardless of reset
mii_dd_system_init(mii, &mii->dd);
@ -556,17 +627,12 @@ mii_init(
mii->cpu_state = mii_cpu_init(&mii->cpu);
for (int i = 0; i < 7; i++)
mii->slot[i].id = i;
// srandom(time(NULL));
for (int i = 0; i < 256; i++)
mii->random[i] = random();
mii_bank_install_access_cb(&mii->bank[MII_BANK_CARD_ROM],
_mii_deselect_auxrom, mii, 0xcf, 0xcf);
mii_bank_install_access_cb(&mii->bank[MII_BANK_ROM],
_mii_deselect_auxrom, mii, 0xcf, 0xcf);
mii_bank_install_access_cb(&mii->bank[MII_BANK_ROM],
_mii_select_c3rom, mii, 0xc3, 0xc3);
_mii_select_c3introm, mii, 0xc3, 0xc3);
}
void
@ -574,7 +640,13 @@ mii_prepare(
mii_t *mii,
uint32_t flags )
{
// printf("%s driver table\n", __func__);
int banks = (flags >> MII_INIT_RAMWORKS_BIT) & 0xf;
banks = 12;
if (banks > 12)
banks = 12;
for (int i = 0; i < banks; i++) // add available banks
mii->ramworks.avail |= _mii_ramworks3_config[i];
mii_slot_drv_t * drv = mii_slot_drv_list;
while (drv) {
printf("%s driver: %s\n", __func__, drv->name);
@ -595,6 +667,12 @@ mii_dispose(
}
for (int i = 0; i < MII_BANK_COUNT; i++)
mii_bank_dispose(&mii->bank[i]);
for (int i = 0; i < 128; i++ ) {
if (mii->ramworks.bank[i]) {
free(mii->ramworks.bank[i]);
mii->ramworks.bank[i] = NULL;
}
}
mii_speaker_dispose(&mii->speaker);
mii_dd_system_dispose(&mii->dd);
mii->state = MII_INIT;
@ -609,17 +687,19 @@ mii_reset(
mii->state = MII_RUNNING;
mii->cpu_state.reset = 1;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
mii->sw_state = M_BSRWRITE | M_BSRPAGE2;
mii_bank_poke(main, SWSLOTC3ROM, 0);
mii_bank_poke(main, SWRAMRD, 0);
mii_bank_poke(main, SWRAMWRT, 0);
mii_bank_poke(main, SWALTPZ, 0);
mii_bank_poke(main, SW80STORE, 0);
mii_bank_poke(main, SW80COL, 0);
mii_bank_poke(sw, SWSLOTC3ROM, 0);
mii_bank_poke(sw, SWRAMRD, 0);
mii_bank_poke(sw, SWRAMWRT, 0);
mii_bank_poke(sw, SWALTPZ, 0);
mii_bank_poke(sw, SW80STORE, 0);
mii_bank_poke(sw, SW80COL, 0);
mii_bank_poke(sw, SWRAMWORKS_BANK, 0);
mii->mem_dirty = 1;
if (cold) {
/* these HAS to be reset in that state somehow */
mii_bank_poke(main, SWINTCXROM, 0);
mii_bank_poke(sw, SWINTCXROM, 0);
uint8_t z[2] = {0x55,0x55};
mii_bank_write(main, 0x3f2, z, 2);
}
@ -639,9 +719,10 @@ mii_mem_access(
bool wr,
bool do_sw)
{
if (!do_sw && addr >= 0xc000 && addr <= 0xc0ff)
if (!do_sw && addr >= 0xc000 && addr <= 0xc0ff && addr != 0xcfff)
return;
uint8_t done =
_mii_deselect_cXrom(mii, addr, d, wr) ||
mii_access_keyboard(mii, addr, d, wr) ||
mii_access_video(mii, addr, d, wr) ||
mii_access_soft_switches(mii, addr, d, wr);

View File

@ -18,6 +18,7 @@
#include "mii_speaker.h"
#include "mii_mouse.h"
#include "mii_analog.h"
#include "mii_vcd.h"
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
@ -26,13 +27,16 @@ enum {
MII_BANK_MAIN = 0, // main 48K address space
MII_BANK_BSR, // 0xd000 - 0xffff bank switched RAM 16KB
MII_BANK_BSR_P2, // 0xd000 - 0xe000 bank switched RAM aux 4KB
// this one is the fixed one, used by video
MII_BANK_AUX_BASE, // aux 48K address space (80 cols card)
// these one can 'move' in the block of ramworks ram
MII_BANK_AUX, // aux 48K address space (80 cols card)
MII_BANK_AUX_BSR, // 0xd000 - 0xffff bank switched RAM aux 16KB
MII_BANK_AUX_BSR_P2, // 0xd000 - 0xe000 bank switched RAM aux 4KB (aux bank)
MII_BANK_ROM, // 0xc000 - 0xffff 16K ROM
MII_BANK_CARD_ROM, // 0xc100 - 0xcfff Card ROM access
MII_BANK_SW, // 0xc000 - 0xc0ff Softswitches
MII_BANK_COUNT,
};
@ -84,6 +88,11 @@ typedef struct mii_trace_t {
typedef uint64_t (*mii_timer_p)(
mii_t * mii,
void * param );
#define MII_SPEED_NTSC 1.0227271429 // 14.31818 MHz / 14
#define MII_SPEED_PAL 1.0178571429 // 14.25 MHz / 14
#define MII_SPEED_TITAN 3.58
/*
* principal emulator state, for a faceless emulation
*/
@ -111,16 +120,26 @@ typedef struct mii_t {
mii_cpu_state_t cpu_state;
/*
* bank index for each memory page number, this is recalculated
* everytime a soft switch is triggered
* everytime a MMU soft switch is triggered
*/
struct {
uint8_t read : 4, write : 4;
union {
struct {
uint8_t write : 4, read : 4;
};
uint8_t both;
};
} mem[256];
int mem_dirty; // recalculate mem[] on next access
struct {
unsigned __int128 avail;
uint8_t * bank[128];
} ramworks;
uint32_t sw_state; // B_SW* bitfield
mii_trace_t trace;
int trace_cpu;
mii_trap_t trap;
mii_signal_pool_t sig_pool;
/*
* Used for debugging only
*/
@ -150,8 +169,12 @@ typedef struct mii_t {
} mii_t;
enum {
MII_INIT_NSC = (1 << 0), // Install no slot clock
MII_INIT_TITAN = (1 << 1), // Install Titan 'card'
MII_INIT_NSC = (1 << 0), // Install no slot clock
MII_INIT_TITAN = (1 << 1), // Install Titan 'card'
MII_INIT_SILENT = (1 << 2), // No audio, ever
// number of 256KB banks added to the ramworks
MII_INIT_RAMWORKS_BIT = 4, // bit 4 in flags. Can be up to 12
MII_INIT_DEFAULT = MII_INIT_NSC,
};

View File

@ -96,6 +96,7 @@ next_instruction:
// we dont' reset the cycle here, that way calling code has a way of knowing
// how many cycles were used by the previous instruction
_FETCH(cpu->PC);
cpu->total_cycle += cpu->cycle;
s.sync = 0;
cpu->cycle = 0;
cpu->PC++;
@ -126,16 +127,26 @@ next_instruction:
case ABS_X: { // $xxxx,X
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
/*
* this seems to be only used by a2audit, ever, which is bloody
* annoying, so we just fake it to pass the test
*/
if (cpu->IR == 0xfe && cpu->X == 0 && cpu->_P == 0xc083) {
// printf("Fooling a2audit\n");
_FETCH(cpu->_P); // false read
}
cpu->_P += cpu->X;
if ((cpu->_P & 0xff00) != (s.data << 8))
cpu->cycle++;
if ((cpu->_P & 0xff00) != (s.data << 8)) {
_FETCH(cpu->PC); // false read
}
} break;
case ABS_Y: { // $xxxx,Y
_FETCH(cpu->PC++); cpu->_P = s.data;
_FETCH(cpu->PC++); cpu->_P |= s.data << 8;
cpu->_P += cpu->Y;
if ((cpu->_P & 0xff00) != (s.data << 8))
cpu->cycle++;
if ((cpu->_P & 0xff00) != (s.data << 8)) {
_FETCH(cpu->PC); // false read
}
} break;
case IND_X: { // ($xx,X)
_FETCH(cpu->PC++); cpu->_D = s.data;
@ -255,7 +266,7 @@ next_instruction:
case 0x80:
{ // BRA
cpu->_P = cpu->PC + (int8_t)cpu->_P;
_FETCH(cpu->PC);// cpu->cycle++;
_FETCH(cpu->PC);
if ((cpu->_P & 0xff00) != (cpu->PC & 0xff00))
cpu->cycle++;
cpu->PC = cpu->_P;
@ -274,7 +285,7 @@ next_instruction:
{ // BRK
// Turns out BRK is a 2 byte opcode, who knew? well that guy did:
// https://www.nesdev.org/the%20'B'%20flag%20&%20BRK%20instruction.txt#:~:text=A%20note%20on%20the%20BRK,opcode%2C%20and%20not%20just%201.
_FETCH(cpu->PC++); // cpu->cycle++;
_FETCH(cpu->PC++);
s.irq = 1;
cpu->IRQ = 2; // BRK sort of IRQ interrupt
} break;

View File

@ -98,6 +98,7 @@ typedef struct mii_cpu_t {
// last 4 instructions, as a shift register, used for traps or debug
uint32_t ir_log;
uint64_t total_cycle;
/* Debug only; Only used by the test units. */
uint8_t * ram; // DEBUG
} mii_cpu_t;

View File

@ -137,6 +137,7 @@ mii_argv_parse(
!strcmp(arg, "--no-audio") ||
!strcmp(arg, "--silent")) {
mii->speaker.off = true;
*ioFlags |= MII_INIT_SILENT;
} else if (!strcmp(arg, "-vol") || !strcmp(arg, "--volume")) {
if (i < argc-1) {
float vol = atof(argv[++i]);
@ -151,7 +152,7 @@ mii_argv_parse(
if (i < argc-1) {
mii->speed = atof(argv[++i]);
if (mii->speed <= 0.0)
mii->speed = 1.0;
mii->speed = MII_SPEED_NTSC;
} else {
printf("mii: missing speed value\n");
return 1;

View File

@ -14,14 +14,17 @@
#include "mii.h"
#include "mii_bank.h"
void
mii_bank_init(
mii_bank_t *bank)
{
if (bank->mem)
return;
bank->mem = calloc(1, bank->size * 256);
bank->alloc = 1;
if (bank->mem_offset == 0 && !bank->no_alloc) {
bank->mem = calloc(1, bank->size * 256);
bank->alloc = 1;
}
}
void
@ -50,10 +53,11 @@ mii_bank_write(
uint16_t len)
{
uint32_t end = bank->base + (bank->size << 8);
if (unlikely(addr < bank->base) || unlikely((addr + len) > end)) {
if (unlikely(addr < bank->base || (addr + len) > end)) {
printf("%s %s INVALID write addr %04x len %d %04x:%04x\n",
__func__, bank->name, addr, (int)len,
bank->base, bank->base + (bank->size * 256));
bank->base, end);
abort();
return;
}
uint8_t page_index = (addr - bank->base) >> 8;
@ -62,10 +66,10 @@ mii_bank_write(
addr, (uint8_t *)data, true))
return;
}
addr -= bank->base;
uint32_t phy = bank->mem_offset + addr - bank->base;
do {
bank->mem[addr++] = *data++;
} while (unlikely(--len));
bank->mem[phy++] = *data++;
} while (likely(--len));
}
void
@ -75,12 +79,12 @@ mii_bank_read(
uint8_t *data,
uint16_t len)
{
#if 0 // rather expensive test when profiling!
#if 1 // rather expensive test when profiling!
uint32_t end = bank->base + (bank->size << 8);
if (unlikely(addr < bank->base) || unlikely((addr + len) > end)) {
printf("%s %s INVALID read addr %04x len %d %04x-%04x\n",
__func__, bank->name, addr, (int)len,
bank->base, bank->base + (bank->size * 256));
bank->base, end);
return;
}
#endif
@ -90,10 +94,10 @@ mii_bank_read(
addr, data, false))
return;
}
addr -= bank->base;
uint32_t phy = bank->mem_offset + addr - bank->base;
do {
*data++ = bank->mem[addr++];
} while (unlikely(--len));
*data++ = bank->mem[phy++];
} while (likely(--len));
}
@ -117,9 +121,12 @@ mii_bank_install_access_cb(
if (!bank->access) {
bank->access = calloc(1, bank->size * sizeof(bank->access[0]));
}
// printf("%s %s install access cb page %02x:%02x\n",
// __func__, bank->name, page, end);
printf("%s %s install access cb page %02x:%02x\n",
__func__, bank->name, page, end);
for (int i = page; i <= end; i++) {
if (bank->access[i].cb)
printf("%s %s page %02x already has a callback\n",
__func__, bank->name, i);
bank->access[i].cb = cb;
bank->access[i].param = param;
}

View File

@ -38,11 +38,13 @@ typedef struct mii_bank_access_t {
typedef struct mii_bank_t {
uint64_t base : 16, // base address
size : 9, // in pages
alloc : 1, // been calloced()
no_alloc: 1, // don't allocate memory
alloc : 1, // been malloced()
ro : 1; // read only
char * name;
mii_bank_access_t * access;
uint8_t *mem;
uint32_t mem_offset;
} mii_bank_t;
void

View File

@ -90,7 +90,7 @@ show_state:
}
if (!strcmp(argv[1], "mem")) {
printf("mii: memory map: ");
printf("mii: memory map:\n");
for (int i = 0; i < MII_BANK_COUNT; i++)
printf("%0d:%s ", i, mii->bank[i].name);
printf("\n");
@ -345,6 +345,38 @@ _mii_mish_dm(
}
static void
_mii_mish_sr(
void * param,
int argc,
const char * argv[])
{
// set register
mii_t * mii = param;
if (argc < 3) {
printf("sr: missing argument <A,X,Y,S,PC,P> <Value>)\n");
return;
}
uint16_t val = strtol(argv[2], NULL, 16);
if (!strcasecmp(argv[1], "A"))
mii->cpu.A = val;
else if (!strcasecmp(argv[1], "X"))
mii->cpu.X = val;
else if (!strcasecmp(argv[1], "Y"))
mii->cpu.Y = val;
else if (!strcasecmp(argv[1], "S"))
mii->cpu.S = val;
else if (!strcasecmp(argv[1], "PC"))
mii->cpu.PC = val;
else if (!strcasecmp(argv[1], "P")) {
MII_SET_P(&mii->cpu, val);
} else {
printf("sr: unknown register %s\n", argv[1]);
return;
}
mii_dump_trace_state(mii);
}
static void
_mii_mish_step(
void * param,
@ -467,7 +499,7 @@ MISH_CMD_NAMES(mii, "mii");
MISH_CMD_HELP(mii,
"mii: access internals, trace, reset, speed, etc",
" <default> : dump current state",
" rgb <val>: Set RGB mode to <val>(0:color,1:green,2:amber)"
" rgb <val>: Set RGB mode to <val>(0:color,1:green,2:amber)",
" reset : reset the cpu",
" t|trace : toggle trace_cpu (WARNING HUGE traces!))",
" mem : dump memory and bank map",
@ -527,6 +559,14 @@ MISH_CMD_HELP(step,
);
MII_MISH(step, _mii_mish_step);
MISH_CMD_NAMES(sr, "sr");
MISH_CMD_HELP(sr,
"mii: set register",
" <reg> <val>: set register to value",
" <reg>: A, X, Y, S, PC, P"
);
MII_MISH(sr, _mii_mish_sr);
MISH_CMD_NAMES(text, "text");
MISH_CMD_HELP(text,
"mii: show text page [buggy]",

View File

@ -99,6 +99,7 @@ mii_speaker_init(
s->timer_id = mii_timer_register(mii,
_mii_speaker_timer_cb, s, 0, __func__);
#ifdef HAS_ALSA
printf("%s audio is %s\n", __func__, s->off ? "off" : "on");
if (!s->off)
_alsa_init(s); // this can/will change fsize
#endif
@ -107,7 +108,6 @@ mii_speaker_init(
s->findex = 0;
for (int i = 0; i < MII_SPEAKER_FRAME_COUNT; i++)
s->frame[i].audio = calloc(sizeof(s->frame[i].audio[0]), s->fsize);
// s->frame[0].start = mii->cycles;
}
void

View File

@ -55,6 +55,7 @@ enum {
SWDHIRESOFF = 0xc05f, // AN3_ON
SWAN3 = 0xc05e, // AN3 status
SWAN3_REGISTER = 0xc05f, // AN3 register for video mode
SWRAMWORKS_BANK = 0xc073,
SWRDDHIRES = 0xc07f,
};
@ -82,7 +83,7 @@ enum {
B_SWDHIRES = (16),
// this is no 'real' softwitch, but a bit to mention a card has
// it's secondary rom online in pages c800-cfff
B_SLOTAUXROM = (17),
B_INTC8ROM = (17),
M_SW80STORE = (1 << B_SW80STORE),
M_SWALTCHARSET = (1 << B_SWALTCHARSET),
@ -101,7 +102,7 @@ enum {
M_BSRPAGE2 = (1 << B_BSRPAGE2),
M_BSRPREWRITE = (1 << B_BSRPREWRITE),
M_SWDHIRES = (1 << B_SWDHIRES),
M_SLOTAUXROM = (1 << B_SLOTAUXROM),
M_INTC8ROM = (1 << B_INTC8ROM),
};
#define __unused__ __attribute__((unused))
@ -125,12 +126,17 @@ static const char __unused__ *mii_sw_names[] = {
"BSRPAGE2",
"BSRPREWRITE",
"DHIRES",
"AUXROMON",
"INTC8ROM",
NULL,
} ;
#define SW_SETSTATE(_mii, _sw, _state) \
(_mii)->sw_state = ((_mii)->sw_state & ~(M_##_sw)) | \
#define SWW_SETSTATE(_bits, _sw, _state) \
(_bits) = ((_bits) & ~(M_##_sw)) | \
(!!(_state) << B_##_sw)
#define SWW_GETSTATE(_bits, _sw) \
(!!((_bits) & M_##_sw))
#define SW_SETSTATE(_mii, _sw, _state) \
SWW_SETSTATE((_mii)->sw_state, _sw, _state)
#define SW_GETSTATE(_mii, _sw) \
(!!((_mii)->sw_state & M_##_sw))
SWW_GETSTATE((_mii)->sw_state, _sw)

546
src/mii_vcd.c Normal file
View File

@ -0,0 +1,546 @@
/*
* mii_vcd.c
*
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <inttypes.h>
#include "mii.h"
#include "mii_vcd.h"
DEFINE_FIFO(mii_vcd_log_t, mii_vcd_fifo);
#define strdupa(__s) strcpy(alloca(strlen(__s)+1), __s)
static void
_mii_vcd_notify(
struct mii_signal_t * sig,
uint32_t value,
void * param);
int
mii_vcd_init(
struct mii_t * mii,
const char * filename,
mii_vcd_t * vcd,
uint32_t cycle_to_nsec)
{
memset(vcd, 0, sizeof(mii_vcd_t));
vcd->mii = mii;
vcd->filename = strdup(filename);
vcd->cycle_to_nsec = cycle_to_nsec; // mii_usec_to_cycles(vcd->mii, period);
return 0;
}
void
mii_vcd_close(
mii_vcd_t * vcd)
{
mii_vcd_stop(vcd);
/* dispose of any link and hooks */
for (int i = 0; i < vcd->signal_count; i++) {
mii_vcd_signal_t * s = &vcd->signal[i];
mii_free_signal(&s->sig, 1);
}
if (vcd->filename) {
free(vcd->filename);
vcd->filename = NULL;
}
}
static char *
_mii_vcd_get_float_signal_text(
mii_vcd_signal_t * s,
char * out)
{
char * dst = out;
if (s->size > 1)
*dst++ = 'b';
for (int i = s->size; i > 0; i--)
*dst++ = 'x';
if (s->size > 1)
*dst++ = ' ';
*dst++ = s->alias;
*dst = 0;
return out;
}
static char *
_mii_vcd_get_signal_text(
mii_vcd_signal_t * s,
char * out,
uint32_t value)
{
char * dst = out;
if (s->size > 1)
*dst++ = 'b';
for (int i = s->size; i > 0; i--)
*dst++ = value & (1 << (i-1)) ? '1' : '0';
if (s->size > 1)
*dst++ = ' ';
*dst++ = s->alias;
*dst = 0;
return out;
}
#define mii_cycles_to_nsec(mii, c) ((c) * vcd->cycle_to_nsec)
/* Write queued output to the VCD file. */
static void
mii_vcd_flush_log(
mii_vcd_t * vcd)
{
uint64_t seen = 0;
uint64_t oldbase = 0; // make sure it's different
char out[48];
if (mii_vcd_fifo_isempty(&vcd->log) || !vcd->output)
return;
while (!mii_vcd_fifo_isempty(&vcd->log)) {
mii_vcd_log_t l = mii_vcd_fifo_read(&vcd->log);
// 10ns base -- 100MHz should be enough
uint64_t base = mii_cycles_to_nsec(vcd->mii, l.when - vcd->start) / 10;
/*
* if that trace was seen in this nsec already, we fudge the
* base time to make sure the new value is offset by one nsec,
* to make sure we get at least a small pulse on the waveform.
*
* This is a bit of a fudge, but it is the only way to represent
* very short "pulses" that are still visible on the waveform.
*/
if (base == oldbase &&
(seen & (1 << l.sigindex)))
base++; // this forces a new timestamp
if (base > oldbase || !seen) {
seen = 0;
fprintf(vcd->output, "#%" PRIu64 "\n", base);
oldbase = base;
}
// mark this trace as seen for this timestamp
seen |= (1 << l.sigindex);
fprintf(vcd->output, "%s\n",
l.floating ?
_mii_vcd_get_float_signal_text(
&vcd->signal[l.sigindex],
out) :
_mii_vcd_get_signal_text(
&vcd->signal[l.sigindex],
out, l.value));
}
}
/* Called for an IRQ that is being recorded. */
static void
_mii_vcd_notify(
struct mii_signal_t * sig,
uint32_t value,
void * param)
{
mii_vcd_t * vcd = (mii_vcd_t *)param;
if (!vcd->output) {
printf("%s: no output\n",
__func__);
return;
}
mii_vcd_signal_t * s = (mii_vcd_signal_t*)sig;
mii_vcd_log_t l = {
.sigindex = s->sig.sig,
.when = vcd->cycle,
.value = value,
.floating = !!(mii_signal_get_flags(sig) & SIG_FLAG_FLOATING),
};
if (mii_vcd_fifo_isfull(&vcd->log)) {
// printf("%s FIFO Overload, flushing!\n", __func__);
/* Decrease period by a quarter, for next time */
// vcd->period -= vcd->period >> 2;
mii_vcd_flush_log(vcd);
}
mii_vcd_fifo_write(&vcd->log, l);
}
/* Register an IRQ whose value is to be logged. */
int
mii_vcd_add_signal(
mii_vcd_t * vcd,
mii_signal_t * signal_sig,
uint signal_bit_size,
const char * name )
{
if (vcd->signal_count == MII_VCD_MAX_SIGNALS) {
printf(" %s: unable add signal '%s'\n", __func__, name);
return -1;
}
int index = vcd->signal_count++;
mii_vcd_signal_t * s = &vcd->signal[index];
strncpy(s->name, name, sizeof(s->name));
s->size = signal_bit_size;
s->alias = ' ' + vcd->signal_count ;
/* manufacture a nice IRQ name */
int l = strlen(name);
char iname[10 + l + 1];
if (signal_bit_size > 1)
sprintf(iname, "%d>vcd.%s", signal_bit_size, name);
else
sprintf(iname, ">vcd.%s", name);
const char * names[1] = { iname };
mii_init_signal(&vcd->mii->sig_pool, &s->sig, index, 1, names);
mii_signal_register_notify(&s->sig, _mii_vcd_notify, vcd);
mii_connect_signal(signal_sig, &s->sig);
return 0;
}
/* Open the VCD output file and write header. Does nothing for input. */
int
mii_vcd_start(
mii_vcd_t * vcd)
{
time_t now;
vcd->start = 0;
mii_vcd_fifo_reset(&vcd->log);
if (vcd->output)
mii_vcd_stop(vcd);
vcd->output = fopen(vcd->filename, "w");
if (vcd->output == NULL) {
perror(vcd->filename);
return -1;
}
time(&now);
fprintf(vcd->output, "$date %s$end\n", ctime(&now));
fprintf(vcd->output,
"$version Simmii " "1.0.0" " $end\n");
fprintf(vcd->output, "$timescale 10ns $end\n"); // 10ns base, aka 100MHz
fprintf(vcd->output, "$scope module logic $end\n");
for (int i = 0; i < vcd->signal_count; i++) {
fprintf(vcd->output, "$var wire %d %c %s $end\n",
vcd->signal[i].size, vcd->signal[i].alias, vcd->signal[i].name);
}
fprintf(vcd->output, "$upscope $end\n");
fprintf(vcd->output, "$enddefinitions $end\n");
fprintf(vcd->output, "$dumpvars\n");
for (int i = 0; i < vcd->signal_count; i++) {
mii_vcd_signal_t * s = &vcd->signal[i];
char out[48];
fprintf(vcd->output, "%s\n",
_mii_vcd_get_float_signal_text(s, out));
}
fprintf(vcd->output, "$end\n");
// mii_cycle_timer_register(vcd->mii, vcd->period, _mii_vcd_timer, vcd);
return 0;
}
int
mii_vcd_stop(
mii_vcd_t * vcd)
{
// mii_cycle_timer_cancel(vcd->mii, _mii_vcd_timer, vcd);
mii_vcd_flush_log(vcd);
if (vcd->output)
fclose(vcd->output);
vcd->output = NULL;
return 0;
}
// internal structure for a hook, never seen by the notify procs
typedef struct mii_signal_hook_t {
struct mii_signal_hook_t * next;
int busy; // prevent reentrance of callbacks
struct mii_signal_t * chain; // raise the IRQ on this too - optional if "notify" is on
mii_signal_notify_t notify; // called when IRQ is raised - optional if "chain" is on
void * param; // "notify" parameter
} mii_signal_hook_t;
static void
_mii_signal_pool_add(
mii_signal_pool_t * pool,
mii_signal_t * sig)
{
uint insert = 0;
/* lookup a slot */
for (; insert < pool->count && pool->sig[insert]; insert++)
;
if (insert == pool->count) {
if ((pool->count & 0xf) == 0) {
pool->sig = (mii_signal_t**)realloc(pool->sig,
(pool->count + 16) * sizeof(mii_signal_t *));
}
pool->count++;
}
pool->sig[insert] = sig;
sig->pool = pool;
}
static void
_mii_signal_pool_remove(
mii_signal_pool_t * pool,
mii_signal_t * sig)
{
for (uint i = 0; i < pool->count; i++)
if (pool->sig[i] == sig) {
pool->sig[i] = 0;
return;
}
}
void
mii_init_signal(
mii_signal_pool_t * pool,
mii_signal_t * sig,
uint32_t base,
uint32_t count,
const char ** names /* optional */)
{
memset(sig, 0, sizeof(mii_signal_t) * count);
for (uint i = 0; i < count; i++) {
sig[i].sig = base + i;
sig[i].flags = SIG_FLAG_INIT;
if (pool)
_mii_signal_pool_add(pool, &sig[i]);
if (names && names[i])
sig[i].name = strdup(names[i]);
else {
printf("WARNING %s() with NULL name for sig %d.\n",
__func__, sig[i].sig);
}
}
}
mii_signal_t *
mii_alloc_signal(
mii_signal_pool_t * pool,
uint32_t base,
uint32_t count,
const char ** names /* optional */)
{
mii_signal_t * sig = (mii_signal_t*)malloc(sizeof(mii_signal_t) * count);
mii_init_signal(pool, sig, base, count, names);
for (uint i = 0; i < count; i++)
sig[i].flags |= SIG_FLAG_ALLOC;
return sig;
}
static mii_signal_hook_t *
_mii_alloc_signal_hook(
mii_signal_t * sig)
{
mii_signal_hook_t *hook = malloc(sizeof(mii_signal_hook_t));
memset(hook, 0, sizeof(mii_signal_hook_t));
hook->next = sig->hook;
sig->hook = hook;
return hook;
}
void
mii_free_signal(
mii_signal_t * sig,
uint32_t count)
{
if (!sig || !count)
return;
for (uint i = 0; i < count; i++) {
mii_signal_t * iq = sig + i;
if (iq->pool)
_mii_signal_pool_remove(iq->pool, iq);
if (iq->name)
free((char*)iq->name);
iq->name = NULL;
// purge hooks
mii_signal_hook_t *hook = iq->hook;
while (hook) {
mii_signal_hook_t * next = hook->next;
free(hook);
hook = next;
}
iq->hook = NULL;
}
// if that sig list was allocated by us, free it
if (sig->flags & SIG_FLAG_ALLOC)
free(sig);
}
void
mii_signal_register_notify(
mii_signal_t * sig,
mii_signal_notify_t notify,
void * param)
{
if (!sig || !notify)
return;
mii_signal_hook_t *hook = sig->hook;
while (hook) {
if (hook->notify == notify && hook->param == param)
return; // already there
hook = hook->next;
}
hook = _mii_alloc_signal_hook(sig);
hook->notify = notify;
hook->param = param;
}
void
mii_signal_unregister_notify(
mii_signal_t * sig,
mii_signal_notify_t notify,
void * param)
{
mii_signal_hook_t *hook, *prev;
if (!sig || !notify)
return;
hook = sig->hook;
prev = NULL;
while (hook) {
if (hook->notify == notify && hook->param == param) {
if ( prev )
prev->next = hook->next;
else
sig->hook = hook->next;
free(hook);
return;
}
prev = hook;
hook = hook->next;
}
}
void
mii_raise_signal_float(
mii_signal_t * sig,
uint32_t value,
int floating)
{
if (!sig)
return ;
uint32_t output = (sig->flags & SIG_FLAG_NOT) ? !value : value;
// if value is the same but it's the first time, raise it anyway
if (sig->value == output &&
(sig->flags & SIG_FLAG_FILTERED) && !(sig->flags & SIG_FLAG_INIT))
return;
sig->flags &= ~(SIG_FLAG_INIT | SIG_FLAG_FLOATING);
if (floating)
sig->flags |= SIG_FLAG_FLOATING;
mii_signal_hook_t *hook = sig->hook;
while (hook) {
mii_signal_hook_t * next = hook->next;
// prevents reentrance / endless calling loops
if (hook->busy == 0) {
hook->busy++;
if (hook->notify)
hook->notify(sig, output, hook->param);
if (hook->chain)
mii_raise_signal_float(hook->chain, output, floating);
hook->busy--;
}
hook = next;
}
// the value is set after the callbacks are called, so the callbacks
// can themselves compare for old/new values between their parameter
// they are passed (new value) and the previous sig->value
sig->value = output;
}
void
mii_raise_signal(
mii_signal_t * sig,
uint32_t value)
{
mii_raise_signal_float(sig, value, !!(sig->flags & SIG_FLAG_FLOATING));
}
void
mii_connect_signal(
mii_signal_t * src,
mii_signal_t * dst)
{
if (!src || !dst || src == dst) {
fprintf(stderr, "error: %s invalid sig %p/%p", __func__, src, dst);
return;
}
mii_signal_hook_t *hook = src->hook;
while (hook) {
if (hook->chain == dst)
return; // already there
hook = hook->next;
}
hook = _mii_alloc_signal_hook(src);
hook->chain = dst;
}
void
mii_unconnect_signal(
mii_signal_t * src,
mii_signal_t * dst)
{
mii_signal_hook_t *hook, *prev;
if (!src || !dst || src == dst) {
fprintf(stderr, "error: %s invalid sig %p/%p", __func__, src, dst);
return;
}
hook = src->hook;
prev = NULL;
while (hook) {
if (hook->chain == dst) {
if ( prev )
prev->next = hook->next;
else
src->hook = hook->next;
free(hook);
return;
}
prev = hook;
hook = hook->next;
}
}
uint8_t
mii_signal_get_flags(
mii_signal_t * sig )
{
return sig->flags;
}
void
mii_signal_set_flags(
mii_signal_t * sig,
uint8_t flags )
{
sig->flags = flags;
}

212
src/mii_vcd.h Normal file
View File

@ -0,0 +1,212 @@
/*
* mii_vcd.h
*
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
/*
* Value change dump (VCD) file format generator for debug purpose
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <sys/types.h>
#include "fifo_declare.h"
#define MII_VCD_MAX_SIGNALS 64
struct mii_signal_t;
struct mii_signal_pool_t;
/*!
* Public SIGNAL structure
*/
typedef struct mii_signal_t {
struct mii_signal_pool_t * pool;
const char * name;
uint32_t sig; //!< any value the user needs
uint32_t value; //!< current value
uint8_t flags; //!< SIG_* flags
struct mii_signal_hook_t * hook; //!< list of hooks to be notified
} mii_signal_t;
typedef struct mii_vcd_signal_t {
/*
* For VCD output this is the IRQ we receive new values from.
*/
mii_signal_t sig;
char alias; // vcd one character alias
uint8_t size; // in bits
char name[32]; // full human name
} mii_vcd_signal_t, *mii_vcd_signal_p;
typedef struct mii_vcd_log_t {
uint64_t when; // Cycles for output,
// nS for input.
uint64_t sigindex : 8, // index in signal table
floating : 1,
value : 32;
} mii_vcd_log_t, *mii_vcd_log_p;
DECLARE_FIFO(mii_vcd_log_t, mii_vcd_fifo, 256);
typedef struct mii_vcd_t {
struct mii_t * mii;
char * filename; // .vcd filename
FILE * output;
int signal_count;
mii_vcd_signal_t signal[MII_VCD_MAX_SIGNALS];
uint64_t cycle;
uint64_t start;
uint64_t cycle_to_nsec; // for output cycles
uint64_t vcd_to_ns; // for input unit mapping
mii_vcd_fifo_t log;
} mii_vcd_t;
// initializes a new VCD trace file, and returns zero if all is well
int
mii_vcd_init(
struct mii_t * mii,
const char * filename, // filename to write
mii_vcd_t * vcd, // vcd struct to initialize
uint32_t cycle_to_nsec ); // 1000 for 1Mhz
int
mii_vcd_init_input(
struct mii_t * mii,
const char * filename, // filename to read
mii_vcd_t * vcd ); // vcd struct to initialize
void
mii_vcd_close(
mii_vcd_t * vcd );
// Add a trace signal to the vcd file. Must be called before mii_vcd_start()
int
mii_vcd_add_signal(
mii_vcd_t * vcd,
mii_signal_t * signal_sig,
uint signal_bit_size,
const char * name );
// Starts recording the signal value into the file
int
mii_vcd_start(
mii_vcd_t * vcd);
// stops recording signal values into the file
int
mii_vcd_stop(
mii_vcd_t * vcd);
/*
* Internal IRQ system
*
* This subsystem allows any piece of code to "register" a hook to be called when an IRQ is
* raised. The IRQ definition is up to the module defining it, for example a IOPORT pin change
* might be an IRQ in which case any piece of code can be notified when a pin has changed state
*
* The notify hooks are chained, and duplicates are filtered out so you can't register a
* notify hook twice on one particular IRQ
*
* IRQ calling order is not defined, so don't rely on it.
*
* IRQ hook needs to be registered in reset() handlers, ie after all modules init() bits
* have been called, to prevent race condition of the initialization order.
*/
struct mii_signal_t;
typedef void (*mii_signal_notify_t)(
struct mii_signal_t * sig,
uint32_t value,
void * param);
enum {
SIG_FLAG_NOT = (1 << 0), //!< change polarity of the IRQ
SIG_FLAG_FILTERED = (1 << 1), //!< do not "notify" if "value" is the same as previous raise
SIG_FLAG_ALLOC = (1 << 2), //!< this sig structure was malloced via mii_alloc_sig
SIG_FLAG_INIT = (1 << 3), //!< this sig hasn't been used yet
SIG_FLAG_FLOATING = (1 << 4), //!< this 'pin'/signal is floating
SIG_FLAG_USER = (1 << 5), //!< Can be used by sig users
};
/*
* IRQ Pool structure
*/
typedef struct mii_signal_pool_t {
uint count; //!< number of sigs living in the pool
struct mii_signal_t ** sig; //!< sigs belonging in this pool
} mii_signal_pool_t;
//! allocates 'count' IRQs, initializes their "sig" starting from 'base'
// and increment
mii_signal_t *
mii_alloc_signal(
mii_signal_pool_t * pool,
uint32_t base,
uint32_t count,
const char ** names /* optional */);
void
mii_free_signal(
mii_signal_t * sig,
uint32_t count);
//! init 'count' IRQs, initializes their "sig" starting from 'base' and increment
void
mii_init_signal(
mii_signal_pool_t * pool,
mii_signal_t * sig,
uint32_t base,
uint32_t count,
const char ** names /* optional */);
//! Returns the current IRQ flags
uint8_t
mii_signal_get_flags(
mii_signal_t * sig );
//! Sets this sig's flags
void
mii_signal_set_flags(
mii_signal_t * sig,
uint8_t flags );
//! 'raise' an IRQ. Ie call their 'hooks', and raise any chained IRQs, and set the new 'value'
void
mii_raise_signal(
mii_signal_t * sig,
uint32_t value);
//! Same as mii_raise_signal(), but also allow setting the float status
void
mii_raise_signal_float(
mii_signal_t * sig,
uint32_t value,
int floating);
//! this connects a "source" IRQ to a "destination" IRQ
void
mii_connect_signal(
mii_signal_t * src,
mii_signal_t * dst);
void
mii_unconnect_signal(
mii_signal_t * src,
mii_signal_t * dst);
//! register a notification 'hook' for 'sig' -- 'param' is anything that your want passed back as argument
void
mii_signal_register_notify(
mii_signal_t * sig,
mii_signal_notify_t notify,
void * param);
void
mii_signal_unregister_notify(
mii_signal_t * sig,
mii_signal_notify_t notify,
void * param);

View File

@ -17,13 +17,6 @@
#include "mii_sw.h"
#include "minipt.h"
#define VIDEO_RESOLUTION_X 280
#define VIDEO_RESOLUTION_Y 192
#define VIDEO_BYTES_PER_LINE 40
#define SCREEN_LINE_OFFSET 0x80
#define VIDEO_SEGMENT_OFFSET 0x28
enum {
// https://rich12345.tripod.com/aiivideo/vbl.html
@ -47,6 +40,10 @@ typedef struct mii_color_t {
#define HI_LUMA(r,g,b) \
((uint8_t)(0.2126 * (r) + 0.7152 * (g) + 0.0722 * (b)))
/*
* You migth have to tweak this for performance reasons. At least on nVidia
* cards, GL_BGRA is faster than GL_RGBA.
*/
#define HI_RGB(r,g,b) { \
.rgb = (0xff000000 | ((b) << 16) | ((g) << 8) | (r)), \
.l = HI_LUMA(r,g,b) \
@ -57,6 +54,14 @@ typedef struct mii_color_t {
*/
#define C_SCANLINE_MASK 0xffc0c0c0
typedef struct mii_video_clut_t {
mii_color_t lores[2][16]; // lores (main, and aux page)
mii_color_t dhires[16];
mii_color_t hires[10];
mii_color_t text[2]; // text
mii_color_t mono[2]; // DHRES mono mode
} mii_video_clut_t;
/* These are the 'official' RGB colors for apple II,
* Well not really, it is just ONE interpreation of many, we could possibly
* make some sort of color lookup table to allow switching them on the fly?
@ -78,6 +83,33 @@ typedef struct mii_color_t {
#define C_YELLOW HI_RGB(0xd0, 0xdd, 0x8d)
#define C_AQUA HI_RGB(0x72, 0xff, 0xd0)
enum mii_video_color_mode_e {
CI_BLACK = 0,
CI_PURPLE, CI_GREEN, CI_BLUE, CI_ORANGE, CI_WHITE, CI_MAGENTA,
CI_DARKBLUE,CI_DARKGREEN,CI_GRAY1,CI_GRAY2,CI_LIGHTBLUE,
CI_BROWN,CI_PINK,CI_YELLOW,CI_AQUA,
};
static const mii_color_t base_color[16] = {
[CI_BLACK] = C_BLACK,
[CI_PURPLE] = C_PURPLE,
[CI_GREEN] = C_GREEN,
[CI_BLUE] = C_BLUE,
[CI_ORANGE] = C_ORANGE,
[CI_WHITE] = C_WHITE,
[CI_MAGENTA] = C_MAGENTA,
[CI_DARKBLUE] = C_DARKBLUE,
[CI_DARKGREEN] = C_DARKGREEN,
[CI_GRAY1] = C_GRAY1,
[CI_GRAY2] = C_GRAY2,
[CI_LIGHTBLUE] = C_LIGHTBLUE,
[CI_BROWN] = C_BROWN,
[CI_PINK] = C_PINK,
[CI_YELLOW] = C_YELLOW,
[CI_AQUA] = C_AQUA,
};
// this is not an official color, just 'my' interpretation of an amber screen
#define C_AMBER HI_RGB(0xfd, 0xcf, 0x14) // amber
@ -92,26 +124,16 @@ static const mii_color_t lores_colors[2][16] = { {
[0x8] = C_MAGENTA, [0x9] = C_PURPLE, [0xa] = C_GRAY1, [0xb] = C_LIGHTBLUE,
[0xc] = C_ORANGE, [0xd] = C_PINK, [0xe] = C_YELLOW, [0xf] = C_WHITE,
} };
static const mii_color_t dhires_colors[] = {
static const mii_color_t dhires_colors[16] = {
[0x0] = C_BLACK, [0x1] = C_MAGENTA, [0x2] = C_BROWN, [0x3] = C_ORANGE,
[0x4] = C_DARKGREEN,[0x5] = C_GRAY1, [0x6] = C_GREEN, [0x7] = C_YELLOW,
[0x8] = C_DARKBLUE, [0x9] = C_PURPLE, [0xa] = C_GRAY2, [0xb] = C_PINK,
[0xc] = C_BLUE, [0xd] = C_LIGHTBLUE,[0xe] = C_AQUA, [0xf] = C_WHITE,
};
static const mii_color_t hires_colors[] = {
C_BLACK,
C_PURPLE,
C_GREEN,
C_GREEN,
C_PURPLE,
C_BLUE,
C_ORANGE,
C_ORANGE,
C_BLUE,
C_WHITE,
static const mii_color_t hires_colors[10] = {
C_BLACK, C_PURPLE, C_GREEN, C_GREEN, C_PURPLE,
C_BLUE, C_ORANGE, C_ORANGE, C_BLUE, C_WHITE,
};
static const mii_color_t mono[3][2] = {
{ C_BLACK, C_WHITE },
{ C_BLACK, C_GREEN },
@ -120,19 +142,16 @@ static const mii_color_t mono[3][2] = {
// TODO redo the hires decoder by reversing bits line by line...
static inline uint8_t reverse8(uint8_t b) {
b = (b & 0b11110000) >> 4 | (b & 0b00001111) << 4;
b = (b & 0b11001100) >> 2 | (b & 0b00110011) << 2;
b = (b & 0b10101010) >> 1 | (b & 0b01010101) << 1;
return b;
}
// Used for DHRES decoding
static inline uint8_t reverse4(uint8_t b) {
b = (b & 0b0001) << 3 | (b & 0b0010) << 1 |
(b & 0b0100) >> 1 | (b & 0b1000) >> 3;
return b;
}
static inline uint8_t reverse8(uint8_t b) {
b = reverse4(b) << 4 | reverse4(b >> 4);
return b;
}
static inline uint16_t
_mii_line_to_video_addr(
uint16_t addr,
@ -144,6 +163,302 @@ _mii_line_to_video_addr(
return addr;
}
#define MII_VIDEO_BANK MII_BANK_AUX_BASE
static void
_mii_line_render_dhires_mono(
mii_t *mii,
uint8_t mode,
bool page2 )
{
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * aux = &mii->bank[MII_VIDEO_BANK];
uint16_t a = (0x2000 + (0x2000 * page2));
a = _mii_line_to_video_addr(a, mii->video.line);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
const uint32_t clut[2] = {
mono[mode][0].rgb,
mono[mode][1].rgb };
for (int x = 0; x < 40; x++) {
uint32_t ext = (mii_bank_peek(aux, a + x) & 0x7f) |
((mii_bank_peek(main, a + x) & 0x7f) << 7);
for (int bi = 0; bi < 14; bi++) {
uint8_t pixel = (ext >> bi) & 1;
uint32_t col = clut[pixel];
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
}
}
}
/* get exactly 4 bits from position bit from the buffer */
static inline uint8_t
_mii_get_1bits(
uint8_t * buffer,
int bit)
{
int in_byte = (bit) / 8;
int in_bit = 7 - ((bit) % 8);
uint8_t b = (buffer[in_byte] >> in_bit) & 1;
return b;
}
static void
_mii_line_render_dhires_color(
mii_t *mii,
uint8_t mode,
bool page2 )
{
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * aux = &mii->bank[MII_VIDEO_BANK];
uint16_t a = (0x2000 + (0x2000 * page2));
a = _mii_line_to_video_addr(a, mii->video.line);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
uint8_t bits[71] = { 0 };
for (int x = 0; x < 80; x++) {
uint8_t b = mii_bank_peek(x & 1 ? main : aux, a + (x / 2));
// this reverse the 7 bits of each bytes into the bit buffer
for (int i = 0; i < 7; i++) {
int out_index = 2 + (x * 7) + i;
int out_byte = out_index / 8;
int out_bit = 7 - (out_index % 8);
int bit = (b >> i) & 1;
bits[out_byte] |= bit << out_bit;
}
}
for (int i = 0, d = 2; i < 560; i++, d++) {
const uint8_t pixel =
(_mii_get_1bits(bits, i + 3) << (3 - ((d + 3) % 4))) +
(_mii_get_1bits(bits, i + 2) << (3 - ((d + 2) % 4))) +
(_mii_get_1bits(bits, i + 1) << (3 - ((d + 1) % 4))) +
(_mii_get_1bits(bits, i) << (3 - (d % 4)));
uint32_t col = dhires_colors[pixel].rgb;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
}
}
static void
_mii_line_render_hires(
mii_t *mii,
uint8_t mode,
bool page2 )
{
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
uint16_t a = (0x2000 + (0x2000 * page2));
a = _mii_line_to_video_addr(a, mii->video.line);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
uint8_t b0 = 0;
uint8_t b1 = mii_bank_peek(main, a + 0);
for (int x = 0; x < 40; x++) {
uint8_t b2 = mii_bank_peek(main, a + x + 1);
// last 2 pixels, current 7 pixels, next 2 pixels
uint16_t run = ((b0 & 0x60) >> ( 5 )) |
((b1 & 0x7f) << ( 2 )) |
((b2 & 0x03) << ( 9 ));
int odd = (x & 1) << 1;
int offset = (b1 & 0x80) >> 5;
if (mode == MII_VIDEO_COLOR) {
for (int i = 0; i < 7; i++) {
uint8_t left = (run >> (1 + i)) & 1;
uint8_t pixel = (run >> (2 + i)) & 1;
uint8_t right = (run >> (3 + i)) & 1;
int idx = 0; // black
if (pixel) {
if (left || right) {
idx = 9; // white
} else {
idx = offset + odd + (i & 1) + 1;
}
} else {
if (left && right) {
idx = offset + odd + 1 - (i & 1) + 1;
}
}
uint32_t col = hires_colors[idx].rgb;
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
} else {
for (int i = 0; i < 7; i++) {
uint8_t pixel = (run >> (2 + i)) & 1;
uint32_t col = mono[mode][pixel].rgb;
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
}
b0 = b1;
b1 = b2;
}
}
static void
_mii_line_render_text(
mii_t *mii,
uint8_t mode,
bool page2 )
{
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * aux = &mii->bank[MII_VIDEO_BANK];
uint16_t a = (0x400 + (0x400 * page2));
int i = mii->video.line >> 3;
a += ((i & 0x07) << 7) | ((i >> 3) << 5) | ((i >> 3) << 3);
bool col80 = SW_GETSTATE(mii, SW80COL);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
for (int x = 0; x < 40 + (40 * col80); x++) {
uint8_t c = 0;
if (col80)
c = mii_bank_peek(x & 1 ? main : aux, a + (x >> 1));
else
c = mii_bank_peek(main, a + x);
bool altset = SW_GETSTATE(mii, SWALTCHARSET);
int flash = mii->video.frame_count & 0x10;
if (!altset) {
if (c >= 0x40 && c <= 0x7f) {
if (flash)
c -= 0x40;
else
c += 0x40;
}
}
const uint8_t * rom = iie_enhanced_video + (c << 3);
uint8_t bits = rom[mii->video.line & 0x07];
for (int pi = 0; pi < 7; pi++) {
uint8_t pixel = (bits >> pi) & 1;
uint32_t col = mono[mode][!pixel].rgb;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
if (!col80) {
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
}
}
}
}
static void
_mii_line_render_lores(
mii_t *mii,
uint8_t mode,
bool page2 )
{
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * aux = &mii->bank[MII_VIDEO_BANK];
uint16_t a = (0x400 + (0x400 * page2));
int i = mii->video.line >> 3;
a += ((i & 0x07) << 7) | ((i >> 3) << 5) | ((i >> 3) << 3);
bool col80 = SW_GETSTATE(mii, SW80COL);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
for (int x = 0; x < 40 + (40 * col80); x++) {
uint8_t c = 0;
if (col80)
c = mii_bank_peek(x & 1 ? main : aux, a + (x >> 1));
else
c = mii_bank_peek(main, a + x);
int lo_line = mii->video.line / 4;
c = c >> ((lo_line & 1) * 4);
c |= (c << 4);
if (mode == MII_VIDEO_COLOR) {
uint32_t pixel = lores_colors[(x & col80) ^ col80][c & 0x0f].rgb;
for (int pi = 0; pi < 7; pi++) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
if (!col80) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
}
}
} else {
/* Not sure at all this should be rendered like this,
* but I can't find a reference on how to render low
* res graphics in mono */
for (int pi = 0; pi < 7; pi++) {
uint32_t pixel = mono[mode][c & 1].rgb;
c >>= 1;
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
if (!col80) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
}
}
}
}
}
/*
* This return the correct line drawing function callback for the mode
* and softswitches
*/
static mii_video_line_drawing_cb
_mii_video_get_line_render_cb(
mii_t *mii,
uint32_t sw_state)
{
bool text = SWW_GETSTATE(sw_state, SWTEXT);
bool col80 = SWW_GETSTATE(sw_state, SW80COL);
bool hires = SWW_GETSTATE(sw_state, SWHIRES);
bool dhires = SWW_GETSTATE(sw_state, SWDHIRES);
mii_video_line_drawing_cb res = _mii_line_render_lores;
if (hires && !text && col80 && dhires) {
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
uint8_t reg = mii_bank_peek(sw, SWAN3_REGISTER);
if (reg != 0 && mii->video.color_mode == MII_VIDEO_COLOR)
res = _mii_line_render_dhires_color;
else
res = _mii_line_render_dhires_mono;
} else if (hires && !text) {
res = _mii_line_render_hires;
} else if (text)
res = _mii_line_render_text;
return res;
}
/*
* This is called when the video mode changes, and we need to update the
* line drawing callback
*/
static void
_mii_video_mode_changed(
mii_t *mii)
{
uint32_t sw_state = mii->sw_state;
mii->video.line_drawing = _mii_video_get_line_render_cb(mii, sw_state);
}
/*
* This is the state of the video output
* All timings lifted from https://rich12345.tripod.com/aiivideo/vbl.html
@ -172,14 +487,10 @@ mii_video_timer_cb(
void *param)
{
uint64_t res = MII_VIDEO_H_CYCLES * mii->speed;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
bool text = SW_GETSTATE(mii, SWTEXT);
bool page2 = SW_GETSTATE(mii, SWPAGE2);
bool col80 = SW_GETSTATE(mii, SW80COL);
bool store80 = SW_GETSTATE(mii, SW80STORE);
bool mixed = SW_GETSTATE(mii, SWMIXED);
bool hires = SW_GETSTATE(mii, SWHIRES);
bool dhires = SW_GETSTATE(mii, SWDHIRES);
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
uint32_t sw_state = mii->sw_state;
bool store80 = SWW_GETSTATE(sw_state, SW80STORE);
bool page2 = store80 ? 0 : SWW_GETSTATE(sw_state, SWPAGE2);
pt_start(mii->video.state);
/*
@ -188,192 +499,36 @@ mii_video_timer_cb(
*/
do {
// 'clear' VBL flag. Flag is 0 during retrace
mii_bank_poke(main, SWVBL, 0x80);
if (mixed && !text) {
text = mii->video.line >= 192 - (4 * 8);
mii_bank_poke(sw, SWVBL, 0x80);
mii_video_line_drawing_cb line_drawing = mii->video.line_drawing;
/* If we are in mixed mode past line 160, check if we need to
* switch from the 'main mode' callback to the text callback */
const int mixed_line = 192 - (4 * 8);
if (mii->video.line >= mixed_line) {
bool mixed = SWW_GETSTATE(sw_state, SWMIXED);
if (mixed) {
uint32_t sw = sw_state;
SWW_SETSTATE(sw, SWTEXT, 1);
if (sw != sw_state)
line_drawing = _mii_video_get_line_render_cb(mii, sw);
}
}
const int mode = mii->video.color_mode;
// http://www.1000bit.it/support/manuali/apple/technotes/aiie/tn.aiie.03.html
if (hires && !text && col80 && dhires) {
if (store80)
page2 = 0;
uint8_t reg = mii_bank_peek(main, SWAN3_REGISTER);
uint16_t a = (0x2000 + (0x2000 * page2));
a = _mii_line_to_video_addr(a, mii->video.line);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
line_drawing(mii, mode, page2);
mii_bank_t * aux = &mii->bank[MII_BANK_AUX];
if (reg == 0 || mode != MII_VIDEO_COLOR) {
const uint32_t clut[2] = {
mono[mode][0].rgb,
mono[mode][1].rgb };
for (int x = 0; x < 40; x++) {
uint32_t ext = (mii_bank_peek(aux, a + x) & 0x7f) |
((mii_bank_peek(main, a + x) & 0x7f) << 7);
for (int bi = 0; bi < 14; bi++) {
uint8_t pixel = (ext >> bi) & 1;
uint32_t col = clut[pixel];
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
}
}
} else { // color mode
int x = 0, dx = 0;
do {
uint64_t run = 0;
// get 8 bytes, so we get 8*7=56 bits for 14 pixels
for (int bx = 0; bx < 8 && x < 80; bx++, x++) {
uint8_t b = mii_bank_peek(
x & 1 ? main : aux, a + (x / 2));
run |= ((uint64_t)(b & 0x7f) << (bx * 7));
}
for (int px = 0; px < 14 && dx < 80*2; px++, dx++) {
uint8_t pixel = reverse4(run & 0xf);
run >>= 4;
uint32_t col = dhires_colors[pixel].rgb;
*screen++ = col;
*screen++ = col;
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
} while (x < 80);
}
} else if (hires && !text) {
if (store80)
page2 = 0;
uint16_t a = (0x2000 + (0x2000 * page2));
a = _mii_line_to_video_addr(a, mii->video.line);
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
uint8_t b0 = 0;
uint8_t b1 = mii_bank_peek(main, a + 0);
for (int x = 0; x < 40; x++) {
uint8_t b2 = mii_bank_peek(main, a + x + 1);
// last 2 pixels, current 7 pixels, next 2 pixels
uint16_t run = ((b0 & 0x60) >> ( 5 )) |
((b1 & 0x7f) << ( 2 )) |
((b2 & 0x03) << ( 9 ));
int odd = (x & 1) << 1;
int offset = (b1 & 0x80) >> 5;
if (mode == MII_VIDEO_COLOR) {
for (int i = 0; i < 7; i++) {
uint8_t left = (run >> (1 + i)) & 1;
uint8_t pixel = (run >> (2 + i)) & 1;
uint8_t right = (run >> (3 + i)) & 1;
int idx = 0; // black
if (pixel) {
if (left || right) {
idx = 9; // white
} else {
idx = offset + odd + (i & 1) + 1;
}
} else {
if (left && right) {
idx = offset + odd + 1 - (i & 1) + 1;
}
}
uint32_t col = hires_colors[idx].rgb;
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
} else {
for (int i = 0; i < 7; i++) {
uint8_t pixel = (run >> (2 + i)) & 1;
uint32_t col = mono[mode][pixel].rgb;
*screen++ = col;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
*l2++ = col & C_SCANLINE_MASK;
}
}
b0 = b1;
b1 = b2;
}
} else {
if (store80)
page2 = 0;
uint16_t a = (0x400 + (0x400 * page2));
int i = mii->video.line >> 3;
a += ((i & 0x07) << 7) | ((i >> 3) << 5) | ((i >> 3) << 3);
mii_bank_t * aux = &mii->bank[MII_BANK_AUX];
uint32_t * screen = mii->video.pixels +
(mii->video.line * MII_VRAM_WIDTH * 2);
uint32_t * l2 = screen + MII_VRAM_WIDTH;
for (int x = 0; x < 40 + (40 * col80); x++) {
uint8_t c = 0;
if (col80)
c = mii_bank_peek(
x & 1 ? main : aux, a + (x >> 1));
else
c = mii_bank_peek(main, a + x);
if (text) {
const uint8_t * rom = iie_enhanced_video + (c << 3);
uint8_t bits = rom[mii->video.line & 0x07];
for (int pi = 0; pi < 7; pi++) {
uint8_t pixel = (bits >> pi) & 1;
uint32_t col = mono[mode][!pixel].rgb;
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
if (!col80) {
*screen++ = col;
*l2++ = col & C_SCANLINE_MASK;
}
}
} else { // lores graphics
int lo_line = mii->video.line / 4;
c = c >> ((lo_line & 1) * 4);
if (mode == MII_VIDEO_COLOR) {
uint32_t pixel = lores_colors[(x & col80) ^ col80][c & 0x0f].rgb;
for (int pi = 0; pi < 7; pi++) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
if (!col80) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
}
}
} else {
/* Not sure at all this should be rendered like this,
* but I can't find a reference on how to render low
* res graphics in mono */
c |= (c << 4);
for (int pi = 0; pi < 7; pi++) {
uint32_t pixel = mono[mode][c & 1].rgb;
c >>= 1;
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
if (!col80) {
*screen++ = pixel;
*l2++ = pixel & C_SCANLINE_MASK;
}
}
}
}
}
}
mii->video.line++;
if (mii->video.line == 192) {
mii->video.line = 0;
res = MII_VIDEO_H_CYCLES * mii->speed;
pt_yield(mii->video.state);
mii_bank_poke(main, SWVBL, 0x00);
mii_bank_poke(sw, SWVBL, 0x00);
res = MII_VBL_UP_CYCLES * mii->speed;
mii->video.frame_count++;
pt_yield(mii->video.state);
// check if we need to switch the video mode, in case the UI switches
// Color/mono palette etc
_mii_video_mode_changed(mii);
} else {
res = (MII_VIDEO_H_CYCLES + MII_VIDEO_HB_CYCLES) *
mii->speed;
@ -392,14 +547,14 @@ mii_access_video(
bool write)
{
bool res = false;
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
switch (addr) {
case SWALTCHARSETOFF:
case SWALTCHARSETON:
if (!write) break;
res = true;
SW_SETSTATE(mii, SWALTCHARSET, addr & 1);
mii_bank_poke(main, SWALTCHARSET, (addr & 1) << 7);
mii_bank_poke(sw, SWALTCHARSET, (addr & 1) << 7);
break;
case SWVBL:
case SW80COL:
@ -411,46 +566,50 @@ mii_access_video(
case SWRDDHIRES:
res = true;
if (!write)
*byte = mii_bank_peek(main, addr);
*byte = mii_bank_peek(sw, addr);
break;
case SW80COLOFF:
case SW80COLON:
if (!write) break;
res = true;
SW_SETSTATE(mii, SW80COL, addr & 1);
mii_bank_poke(main, SW80COL, (addr & 1) << 7);
mii_bank_poke(sw, SW80COL, (addr & 1) << 7);
_mii_video_mode_changed(mii);
break;
case SWDHIRESOFF: // 0xc05f,
case SWDHIRESON: { // = 0xc05e,
res = true;
uint8_t an3 = !!mii_bank_peek(main, SWAN3);
uint8_t an3 = !!mii_bank_peek(sw, SWAN3);
bool an3_on = !!(addr & 1); // 5f is ON, 5e is OFF
uint8_t reg = mii_bank_peek(main, SWAN3_REGISTER);
uint8_t reg = mii_bank_peek(sw, SWAN3_REGISTER);
if (an3_on && !an3) {
uint8_t bit = !!mii_bank_peek(main, SW80COL);
uint8_t bit = !!mii_bank_peek(sw, SW80COL);
reg = ((reg << 1) | bit) & 3;
// printf("VIDEO 80:%d REG now %x\n", bit, reg);
mii_bank_poke(main, SWAN3_REGISTER, reg);
printf("VIDEO 80:%d REG now %x\n", bit, reg);
mii_bank_poke(sw, SWAN3_REGISTER, reg);
}
mii_bank_poke(main, SWAN3, an3_on);
// printf("DHRES IS %s mode:%d\n",
// (addr & 1) ? "OFF" : "ON", reg);
mii_bank_poke(sw, SWAN3, an3_on);
printf("DHRES IS %s mode:%d\n",
(addr & 1) ? "OFF" : "ON", reg);
mii->sw_state = (mii->sw_state & ~M_SWDHIRES) |
(!(addr & 1) << B_SWDHIRES);
SW_SETSTATE(mii, SWDHIRES, !(addr & 1));
mii_bank_poke(main, SWRDDHIRES, (!(addr & 1)) << 7);
mii_bank_poke(sw, SWRDDHIRES, (!(addr & 1)) << 7);
_mii_video_mode_changed(mii);
} break;
case SWTEXTOFF:
case SWTEXTON:
res = true;
SW_SETSTATE(mii, SWTEXT, addr & 1);
mii_bank_poke(main, SWTEXT, (addr & 1) << 7);
mii_bank_poke(sw, SWTEXT, (addr & 1) << 7);
_mii_video_mode_changed(mii);
break;
case SWMIXEDOFF:
case SWMIXEDON:
res = true;
SW_SETSTATE(mii, SWMIXED, addr & 1);
mii_bank_poke(main, SWMIXED, (addr & 1) << 7);
mii_bank_poke(sw, SWMIXED, (addr & 1) << 7);
_mii_video_mode_changed(mii);
break;
}
return res;
@ -463,8 +622,10 @@ mii_video_init(
mii->video.timer_id = mii_timer_register(mii,
mii_video_timer_cb, NULL, MII_VIDEO_H_CYCLES, __func__);
// start the DHRES in color
mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_poke(main, SWAN3_REGISTER, 1);
// mii_bank_t * main = &mii->bank[MII_BANK_MAIN];
mii_bank_t * sw = &mii->bank[MII_BANK_SW];
mii_bank_poke(sw, SWAN3_REGISTER, 1);
_mii_video_mode_changed(mii);
}
/* given a RGB color r,g,b, print a table of 16 RGB colors that are graded

View File

@ -33,6 +33,9 @@ enum {
struct mii_t;
typedef void (*mii_video_line_drawing_cb)(
mii_t *mii, uint8_t mode, bool page2 );
typedef struct mii_video_t {
void * state; // protothread state in mii_video.c
uint8_t timer_id; // timer id for the video thread
@ -41,6 +44,8 @@ typedef struct mii_video_t {
uint32_t frame_count; // incremented every frame
uint32_t frame_drawn;
uint8_t color_mode; // color, green, amber
// function pointer to the line drawing function
mii_video_line_drawing_cb line_drawing;
} mii_video_t;
bool

View File

@ -25,7 +25,7 @@
#include "mii_mui.h"
#include "mish.h"
#include "mii_thread.h"
#include "mii_ssc.h"
#include "mii_mui_gl.h"
#include "miigl_counter.h"
#define MII_ICON64_DEFINE
@ -356,10 +356,16 @@ mii_x11_handle_event(
}
ui->video.key.modifiers = mui->modifier_keys;
switch (ui->video.key.key.key) {
#if 0
case MUI_KEY_RALT:
case MUI_KEY_LALT: {
int apple = ui->video.key.key.key - MUI_KEY_RALT;
#else
case MUI_KEY_RSUPER:
case MUI_KEY_LSUPER: {
int apple = ui->video.key.key.key - MUI_KEY_RSUPER;
mii_bank_t *bank = &mii->bank[MII_BANK_MAIN];
int apple = ui->video.key.key.key - MUI_KEY_LSUPER;
#endif
mii_bank_t *bank = &mii->bank[MII_BANK_SW];
uint8_t old = mii_bank_peek(bank, 0xc061 + apple);
mii_bank_poke(bank, 0xc061 + apple, down ? 0x80 : 0);
if (!!down != !!old) {
@ -494,7 +500,7 @@ static const struct {
[MII_SLOT_DRIVER_SMARTPORT] = { "smartport", },
[MII_SLOT_DRIVER_DISK2] = { "disk2", },
[MII_SLOT_DRIVER_MOUSE] = { "mouse", },
[MII_SLOT_DRIVER_SUPERSERIAL] = { "ssc", },
[MII_SLOT_DRIVER_SSC] = { "ssc", },
[MII_SLOT_DRIVER_ROM1MB] = { "eecard", },
};
@ -533,6 +539,24 @@ mii_ui_reconfigure_slot(
"" :
(void*)config->slot[i].conf.rom1mb.drive.disk);
} break;
case MII_SLOT_DRIVER_SSC: {
mii_ssc_setconf_t ssc = {
.baud = config->slot[i].conf.ssc.baud,
.bits = config->slot[i].conf.ssc.bits,
.parity = config->slot[i].conf.ssc.parity,
.stop = config->slot[i].conf.ssc.stop,
.handshake = config->slot[i].conf.ssc.hw_handshake,
.is_device = config->slot[i].conf.ssc.kind == MII_SSC_KIND_DEVICE,
.is_socket = config->slot[i].conf.ssc.kind == MII_SSC_KIND_SOCKET,
.is_pty = config->slot[i].conf.ssc.kind == MII_SSC_KIND_PTY,
.socket_port = config->slot[i].conf.ssc.socket_port,
};
strncpy(ssc.device, config->slot[i].conf.ssc.device,
sizeof(ssc.device)-1);
mii_slot_command(mii, slot,
MII_SLOT_SSC_SET_TTY,
(void*)&ssc);
} break;
}
}
@ -569,13 +593,16 @@ _mii_ui_load_config(
}
}
// I want at least the 'silent' flags to be 'sticky'
static uint32_t g_startup_flags = 0;
void
mii_x11_reload_config(
struct mii_x11_t *ui )
{
mii_t * mii = &ui->video.mii;
mii_machine_config_t * config = &ui->video.config;
uint32_t flags = MII_INIT_DEFAULT;
uint32_t flags = MII_INIT_DEFAULT | (g_startup_flags & MII_INIT_SILENT);
if (mii->state != MII_INIT) {
printf("%s mii is running, terminating thread\n", __func__);
@ -585,6 +612,8 @@ mii_x11_reload_config(
}
mii_mui_menu_slot_menu_update(&ui->video);
printf("%s (re)loading config\n", __func__);
// if we're silent from the command line, we are *always* silent.
mii->speaker.off = !!(g_startup_flags & MII_INIT_SILENT);
mii_init(mii);
_mii_ui_load_config(mii, config, &flags);
mii_prepare(mii, flags);
@ -639,6 +668,7 @@ main(
} else if (r == -1)
exit(1);
mii_prepare(mii, flags);
g_startup_flags = flags;
}
{
mish_prepare(1);
@ -674,8 +704,14 @@ main(
XEvent evt;
while (XPending(ui->dpy)) {
XNextEvent(ui->dpy, &evt);
if (evt.type == ClientMessage)
goto cleanup;
if (evt.type == ClientMessage) {
if (evt.xclient.message_type ==
XInternAtom(ui->dpy, "WM_PROTOCOLS", False) &&
(Atom)evt.xclient.data.l[0] == ui->wm_delete_window) {
printf("Window close requested!\n");
goto cleanup;
}
}
if (XFilterEvent(&evt, ui->win))
continue;
mii_x11_handle_event(ui, &evt);

View File

@ -30,6 +30,15 @@ enum mii_mui_transition_e {
#define MII_PIXEL_LAYERS 8
DECLARE_C_ARRAY(float, float_array, 16);
IMPLEMENT_C_ARRAY(float_array);
typedef struct mii_vtx_t {
unsigned int kind; // TRIANGLES, QUADS, etc
float_array_t pos;
float_array_t tex;
} mii_vtx_t;
typedef struct mii_mui_t {
mui_t mui; // mui interface
mii_t mii; // apple II emulator
@ -67,7 +76,10 @@ typedef struct mii_mui_t {
mii_floppy_t * floppy;
uint32_t seed_load;
float max_width;
/* technically speaking we'd only need one set of vertices, but hey */
mii_vtx_t vtx;
} floppy[2];
float_array_t floppy_base;
mii_machine_config_t config;
mii_loadbin_conf_t loadbin_conf;

View File

@ -87,8 +87,14 @@ _mii_floppy_check_file(
int want_size = 0;
int iswoz = 0;
if (!strcasecmp(suffix, ".nib")) {
want_size = NIB_SIZE;
out->file_ro_format = 1;
} else if (!strcasecmp(suffix, ".dsk")) {
want_size = DSK_SIZE;
out->file_ro_format = 1;
} else if (!strcasecmp(suffix, ".po") ||
!strcasecmp(suffix, ".do")) {
want_size = DSK_SIZE;
out->file_ro_format = 1;
} else if (!strcasecmp(suffix, ".woz") ||
!strcasecmp(suffix, ".woz1") ||
@ -282,10 +288,10 @@ _mii_2dsk_action_cb(
C2_PT(0, 0),
m->drive_kind == MII_2DSK_SMARTPORT ?
"Select PO/HDV/2MG file to load" :
"Select DSK file to load",
"Select WOZ/DSK/NIB/PO/DO file to load",
m->drive_kind == MII_2DSK_SMARTPORT ?
"\\.(po|hdv|2mg)$" :
"\\.(woz|nib|dsk)$",
"\\.(woz|nib|dsk|po|do)$",
getenv("HOME"),
MUI_STDF_FLAG_REGEXP);
mui_window_set_action(w, _mii_2dsk_stdfile_cb, m);
@ -317,7 +323,7 @@ mii_mui_load_2dsk(
return w;
}
c2_pt_t where = {};
c2_rect_t wpos = C2_RECT_WH(where.x, where.y, 640, 355);
c2_rect_t wpos = C2_RECT_WH(where.x, where.y, 640, 340);
c2_rect_offset(&wpos,
(ui->screen_size.x / 2) - (c2_rect_width(&wpos) / 2),
(ui->screen_size.y * 0.45) - (c2_rect_height(&wpos) / 2));
@ -327,8 +333,7 @@ mii_mui_load_2dsk(
drive_kind == MII_2DSK_SMARTPORT ? "SmartPort" : "Disk II",
config->slot_id + 1);
w = mui_window_create(mui, wpos, NULL, MUI_WINDOW_LAYER_MODAL,
label,
sizeof(mii_mui_2dsk_t));
label, sizeof(mii_mui_2dsk_t));
free(label);
mui_window_set_id(w, MII_2DSK_WINDOW_ID);
mii_mui_2dsk_t * m = (mii_mui_2dsk_t*)w;
@ -387,6 +392,7 @@ mii_mui_load_2dsk(
c2_rect_bottom_of(&cf, cp.b, margin * 0.4);
cf.l = cp.l + (margin * 0.7);
cf.r = cf.l + 200;
cf.b = cf.t + base_size;
m->drive[i].wp = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_CHECKBOX,
"Write Protect",

View File

@ -102,7 +102,7 @@ _mii_about_action_cb(
static const char * about =
"\n"
"The MII " MUI_GLYPH_IIE " Emulator\n"
"Version " MII_VERSION "\n"
"" MII_VERSION "\n"
"Built " __DATE__ " " __TIME__ "\n"
"© Michel Pollet 2023-2024\n\n"
"Thanks to:\n"
@ -134,8 +134,9 @@ mii_mui_about(
mui_window_select(w);
return w;
}
printf("%s version: '%s'\n", __func__, MII_VERSION);
c2_pt_t where = {};
c2_rect_t wpos = C2_RECT_WH(where.x, where.y, 500, 240);
c2_rect_t wpos = C2_RECT_WH(where.x, where.y, 500, 255);
if (where.x == 0 && where.y == 0)
c2_rect_offset(&wpos,
(ui->screen_size.x / 2) - (c2_rect_width(&wpos) / 2),
@ -194,7 +195,8 @@ mii_mui_about(
mui_geneva_font_data, mui_geneva_font_size);
}
mui_glyph_line_array_t lines_about = {};
mui_font_measure(font, tbox, about, 0, &lines_about, MUI_TEXT_ALIGN_CENTER);
mui_font_measure(font, tbox, about, 0, &lines_about,
MUI_TEXT_ALIGN_CENTER);
c2_rect_t about_frame = tbox;
about_frame.b = 0;
@ -211,7 +213,7 @@ mii_mui_about(
mui_glyph_array_t * line = &lines_thanks.e[li];
frame_thanks.b = line->y;
}
c2_rect_offset(&frame_thanks, 0, about_frame.b + 0);
c2_rect_offset(&frame_thanks, 0, about_frame.b + 4);
tbox.b = frame_thanks.b + 2;
dr = mui_drawable_new(

105
ui_gl/mii_mui_apple_logo.h Normal file
View File

@ -0,0 +1,105 @@
// Autogenerated from docs/Apple_logo_rainbow_version2_28x28.png with utils/png2raw.c
// Image with a W:28px, H:28px and 4 channels
// Converted to ARGB8888 and premultiplied alpha
#pragma once
static const uint32_t mii_color_apple_pixels[] = {
28, 28, // width, height
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x481b3413,0x99396f29,0x270e1c0a,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x13070d05,0xb3438230,0xff60ba45,0xff60ba45,0x240d1a09,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x02000100,0xbe478a33,0xff60ba45,
0xff60ba45,0xe155a43d,0x03010200,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x5d234319,0xff60ba45,0xff60ba45,0xff60ba45,0x6425491b,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xac417d2f,0xff60ba45,0xff60ba45,
0x9d3b722a,0x02010100,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x01000000,0x260d1b09,0x3614270e,0x260d1b09,0x02000100,
0x00000000,0xbe478a33,0xbe478a33,0x56203e17,0x03010200,0x270e1c09,0x38162911,0x280e1c09,
0x02000100,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x57203f17,0xda529f3b,
0xff60ba45,0xff60ba45,0xff60ba45,0xe054a33d,0x8d356626,0x4d1d3815,0x4b1a3612,0x90366927,
0xe556a73e,0xff60ba45,0xff60ba45,0xff60ba45,0xdf54a23c,0x6225471a,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x86326124,0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,
0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,0xff60ba45,
0xff60ba45,0xff60ba45,0x99396f29,0x01000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x6c524e16,0xffc3b834,0xffc3b834,0xffc3b834,
0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,
0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,0xffc3b834,0x933b6d2d,0x08030502,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x100f0b03,
0xf0edac24,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,
0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,
0xfffcb726,0x8d8b6515,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x59573f0d,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,
0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,
0xfffcb726,0xfffcb726,0xfffcb726,0xfffcb726,0xe8e5a623,0x0b0a0803,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x89855713,
0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,
0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,0xfff9a323,
0xa09c6616,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0xa79f5214,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,
0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,
0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0x76703b0e,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xa49c5313,
0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,
0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,0xfff4811e,
0x9d964f13,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x7f773b12,0xfff07724,0xfff07724,0xfff07724,0xfff07724,
0xfff07724,0xfff07724,0xfff07724,0xfff07724,0xfff07724,0xfff07724,0xfff07724,0xfff07724,
0xfff07724,0xfff07724,0xfff07724,0xfff07724,0xe3d56a20,0x05040101,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x4e441112,
0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,
0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,
0xffdf393d,0x79691b1d,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x13100404,0xf8d9383c,0xffdf393d,0xffdf393d,0xffdf393d,
0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,
0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xffdf393d,0xfddd393d,0x74651a1c,0x01000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0xb49e3335,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,
0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,0xffe1484b,
0xffe1484b,0xffe1484b,0xffe1484b,0xab983d3f,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x3e240e24,0xff953c96,0xff953c96,0xff953c96,
0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,
0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0x5c351536,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0xb86b2b6c,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,
0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,0xff953c96,
0xff953c96,0xff953c96,0xc8752f75,0x04020102,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x26160a16,0xf4a265a2,0xff5ca4d9,
0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,
0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xff5ca4d9,0xf7a466a4,0x2c1a0b1a,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x630b3a52,0xff009cdb,0xff009cdb,0xff009cdb,0xff009cdb,0xff009cdb,
0xff009cdb,0xff009cdb,0xff009cdb,0xff009cdb,0xff009cdb,0xff009cdb,0xff009cdb,0xff009cdb,
0xff009cdb,0x670c3c56,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x780e4664,
0xfb0099d7,0xff009cdb,0xff009cdb,0xff009cdb,0xd90085ba,0xaa13638e,0xa913638d,0xca007bad,
0xfe009bda,0xff009cdb,0xff009cdb,0xfd009bd9,0x7f0e4a6a,0x01000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x30001c29,0x8a005476,0x8a005476,0x3c072332,
0x00000000,0x00000000,0x00000000,0x00000000,0x30081e29,0x87005274,0x8c005578,0x3500202d,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
};

View File

@ -27,6 +27,85 @@ typedef struct c2_rect_f {
float l,t,r,b;
} c2_rect_f;
#define MII_GL_FLOPPY_SEGMENT_COUNT 32
#define MII_GL_FLOPPY_DISC_RADIUS_IN 1.8
#define MII_GL_FLOPPY_DISC_RADIUS_OUT 10
#define MII_GL_FLOPPY_FLUX_RADIUS_IN 2.0
#define MII_GL_FLOPPY_FLUX_RADIUS_OUT 9.8
#include <math.h>
static void
mii_gl_make_disc(
float_array_t * pos,
const double radius_out,
const double radius_in,
const int count)
{
float_array_clear(pos);
const double astep = 2 * M_PI / count; // Angle step for each blade
for (int i = 0; i < MII_GL_FLOPPY_SEGMENT_COUNT; ++i) {
double a = i * astep, b = (i + 1) * astep;
// Outer vertex
double x_out = radius_out * cos(a);
double y_out = radius_out * sin(a);
double x_out2 = radius_out * cos(b);
double y_out2 = radius_out * sin(b);
// Inner vertex
double x_in = radius_in * cos(a);
double y_in = radius_in * sin(a);
double x_in2 = radius_in * cos(b);
double y_in2 = radius_in * sin(b);
// add two triangles winded in the right direction
float_array_push(pos, x_out); float_array_push(pos, y_out);
float_array_push(pos, x_in); float_array_push(pos, y_in);
float_array_push(pos, x_out2); float_array_push(pos, y_out2);
float_array_push(pos, x_in2); float_array_push(pos, y_in2);
float_array_push(pos, x_out2); float_array_push(pos, y_out2);
float_array_push(pos, x_in); float_array_push(pos, y_in);
}
}
static void
mii_gl_make_floppy(
mii_vtx_t * vtx,
float tex_width,
bool do_pos,
bool do_tex)
{
vtx->kind = GL_TRIANGLES;
if (do_pos) {
mii_gl_make_disc(&vtx->pos,
MII_GL_FLOPPY_FLUX_RADIUS_OUT,
MII_GL_FLOPPY_FLUX_RADIUS_IN,
MII_GL_FLOPPY_SEGMENT_COUNT);
}
if (!do_tex)
return;
const double tex_y_in = 1;
const double tex_y_out = 0;
float_array_t * tex = &vtx->tex;
float_array_clear(tex);
for (int i = 0; i < MII_GL_FLOPPY_SEGMENT_COUNT; ++i) {
float_array_push(tex, (i * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT);
float_array_push(tex, tex_y_out);
float_array_push(tex, (i * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT);
float_array_push(tex, tex_y_in);
float_array_push(tex, ((i + 1) * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT);
float_array_push(tex, tex_y_out);
float_array_push(tex, ((i + 1) * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT);
float_array_push(tex, tex_y_in);
float_array_push(tex, ((i + 1) * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT);
float_array_push(tex, tex_y_out);
float_array_push(tex, (i * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT);
float_array_push(tex, tex_y_in);
}
}
void
mii_mui_gl_init(
mii_mui_t *ui)
@ -34,11 +113,14 @@ mii_mui_gl_init(
GLuint tex[MII_PIXEL_LAYERS];
glGenTextures(MII_PIXEL_LAYERS, tex);
for (int i = 0; i < MII_PIXEL_LAYERS; i++) {
printf("Texture %d created %d\n", i, tex[i]);
// printf("Texture %d created %d\n", i, tex[i]);
ui->pixels.v[i].texture.id = tex[i];
ui->tex_id[i] = tex[i];
}
mii_gl_make_disc(&ui->floppy_base,
MII_GL_FLOPPY_DISC_RADIUS_OUT,
MII_GL_FLOPPY_DISC_RADIUS_IN,
MII_GL_FLOPPY_SEGMENT_COUNT);
mii_mui_gl_prepare_textures(ui);
}
@ -47,10 +129,8 @@ _prep_grayscale_texture(
mui_drawable_t * dr)
{
dr->texture.size = dr->pix.size;
printf("Creating texture %4d %4dx%3d row_byte %4d\n",
dr->texture.id,
dr->pix.size.x, dr->pix.size.y,
dr->pix.row_bytes);
// printf("Creating texture %4d %4dx%3d row_byte %4d\n",
// dr->texture.id, dr->pix.size.x, dr->pix.size.y, dr->pix.row_bytes);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
@ -58,8 +138,9 @@ _prep_grayscale_texture(
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
dr->texture.kind = GL_LUMINANCE;
glTexImage2D(GL_TEXTURE_2D, 0, 1,
dr->pix.row_bytes, dr->texture.size.y, 0, GL_LUMINANCE,
dr->pix.row_bytes, dr->texture.size.y, 0, dr->texture.kind,
GL_UNSIGNED_BYTE,
dr->pix.pixels);
}
@ -72,40 +153,32 @@ mii_mui_gl_prepare_textures(
glEnable(GL_TEXTURE_2D);
mui_drawable_t * dr = &ui->pixels.mii;
// bind the mii texture using the GL_ARB_texture_rectangle extension
printf("Creating texture %4d %4dx%3d row_byte %4d (MII)\n",
dr->texture.id,
dr->pix.size.x, dr->pix.size.y,
dr->pix.row_bytes);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// disable the repeat of textures
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
dr->texture.kind = GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, 4,
MII_VRAM_WIDTH,
MII_VRAM_HEIGHT, 0, GL_BGRA, // note BGRA here, not RGBA
GL_UNSIGNED_BYTE,
MII_VRAM_HEIGHT, 0, dr->texture.kind, // note RGBA here, it's quicker!!
GL_UNSIGNED_BYTE, // GL_UNSIGNED_INT_8_8_8_8_REV
mii->video.pixels);
// bind the mui texture using the GL_ARB_texture_rectangle as well
dr = &ui->pixels.mui;
printf("Creating texture %4d %4dx%3d row_byte %4d (MUI)\n",
dr->texture.id,
dr->pix.size.x, dr->pix.size.y,
dr->pix.row_bytes);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
dr->texture.kind = GL_BGRA;
glTexImage2D(GL_TEXTURE_2D, 0, 4,
dr->pix.row_bytes / 4, // already power of two.
dr->texture.size.y, 0, GL_BGRA,
GL_UNSIGNED_BYTE,
dr->texture.size.y, 0, dr->texture.kind,
GL_UNSIGNED_INT_8_8_8_8_REV,
dr->pix.pixels);
mii_floppy_t * floppy[2] = {};
@ -142,21 +215,21 @@ mii_mui_gl_prepare_textures(
8, f->heat->write.map, MII_FLOPPY_HM_TRACK_SIZE);
dr->texture.id = tex;
_prep_grayscale_texture(dr);
mii_gl_make_floppy(&ui->floppy[fi].vtx, 1.0, true, true);
}
} else {
printf("No floppy found\n");
printf("%s No floppy found\n", __func__);
for (int fi = 0; fi < 2; fi++) {
ui->floppy[fi].floppy = NULL;
mui_drawable_clear(&ui->pixels.floppy[fi].bits);
mui_drawable_clear(&ui->pixels.floppy[fi].hm_read);
mui_drawable_clear(&ui->pixels.floppy[fi].hm_write);
mii_gl_make_floppy(&ui->floppy[fi].vtx, 1.0, true, true);
}
}
// printf("%s texture created %d\n", __func__, mii_apple_screen_tex);
// display opengl error
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
printf("Error creating texture: %d\n", err);
printf("%s Error creating texture: %d\n", __func__, err);
}
}
@ -166,10 +239,11 @@ _mii_decay_heatmap_one(
mii_track_heatmap_t *hm)
{
uint32_t count = 0;
const int decay = 4;
#ifdef __SSE2__
const int size = (MII_FLOPPY_TRACK_COUNT * MII_FLOPPY_HM_TRACK_SIZE) / 16;
__m128i * hmw = (__m128i*)&hm->map[0];
const __m128i s = _mm_set1_epi8(2);
const __m128i s = _mm_set1_epi8(decay);
for (int i = 0; i < size; i++) {
__m128i b = _mm_load_si128(hmw + i);
__m128i c = _mm_subs_epu8(b, s);
@ -181,7 +255,7 @@ _mii_decay_heatmap_one(
uint8_t * hmb = (uint8_t*)&hm->map[0];
for (int i = 0; i < size; i++) {
uint8_t b = hmb[i];
b = b > 2 ? b - 2 : 0;
b = b > decay ? b - decay : 0;
hmb[i] = b;
count += !!b;
}
@ -203,6 +277,7 @@ _mii_decay_heatmap(
}
}
bool
mii_mui_gl_run(
mii_mui_t *ui)
@ -231,7 +306,8 @@ mii_mui_gl_run(
glPixelStorei(GL_UNPACK_ROW_LENGTH, dr->pix.row_bytes / 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, r.l, r.t,
c2_rect_width(&r), c2_rect_height(&r),
GL_BGRA, GL_UNSIGNED_BYTE,
dr->texture.kind,
GL_UNSIGNED_INT_8_8_8_8_REV,
dr->pix.pixels + (r.t * dr->pix.row_bytes) + (r.l * 4));
}
}
@ -248,8 +324,8 @@ mii_mui_gl_run(
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
MII_VRAM_WIDTH,
MII_VIDEO_HEIGHT, GL_BGRA,
GL_UNSIGNED_BYTE,
MII_VIDEO_HEIGHT, dr->texture.kind,
GL_UNSIGNED_INT_8_8_8_8_REV,
mii->video.pixels);
}
for (int fi = 0; fi < 2; fi++) {
@ -268,29 +344,37 @@ mii_mui_gl_run(
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
dr->pix.row_bytes, dr->pix.size.y,
GL_LUMINANCE, GL_UNSIGNED_BYTE,
dr->texture.kind, GL_UNSIGNED_BYTE,
f->track_data);
// dont recalculate the vertices, just the texture coordinates
mii_gl_make_floppy(&ui->floppy[fi].vtx,
ui->floppy[fi].max_width, false, true);
}
// int rm = f->heat->read.tex != f->heat->read.seed;
// int wm = f->heat->write.tex != f->heat->write.seed;
_mii_decay_heatmap(f->heat);
glPixelStorei(GL_UNPACK_ROW_LENGTH, MII_FLOPPY_HM_TRACK_SIZE);
// if (rm) {
int rm = f->heat->read.tex != f->heat->read.seed ||
!f->heat->read.cleared;
int wm = f->heat->write.tex != f->heat->write.seed ||
!f->heat->write.cleared;
// don't decay if we're not running
if (ui->mii.state == MII_RUNNING)
_mii_decay_heatmap(f->heat);
if (rm) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, MII_FLOPPY_HM_TRACK_SIZE);
dr = &ui->pixels.floppy[fi].hm_read;
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
dr->pix.row_bytes, dr->pix.size.y,
GL_LUMINANCE, GL_UNSIGNED_BYTE,
dr->texture.kind, GL_UNSIGNED_BYTE,
f->heat->read.map);
// }
// if (wm) {
}
if (wm) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, MII_FLOPPY_HM_TRACK_SIZE);
dr = &ui->pixels.floppy[fi].hm_write;
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
dr->pix.row_bytes, dr->pix.size.y,
GL_LUMINANCE, GL_UNSIGNED_BYTE,
dr->texture.kind, GL_UNSIGNED_BYTE,
f->heat->write.map);
// }
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
return draw;
@ -333,6 +417,7 @@ mii_mui_gl_render(
/* draw mii texture */
glColor3f(1.0f, 1.0f, 1.0f);
mui_drawable_t * dr = &ui->pixels.mii;
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glBegin(GL_QUADS);
c2_rect_t r = ui->video_frame;
@ -355,60 +440,91 @@ mii_mui_gl_render(
mii_floppy_t *f = ui->floppy[i].floppy;
if (!f || !dr->pix.pixels)
continue;
if (f->motor) {
dr->texture.opacity = 1.0f;
} else {
if (dr->texture.opacity > 0.0f)
dr->texture.opacity -= 0.01f;
if (dr->texture.opacity < 0.0f)
dr->texture.opacity = 0.0f;
if (ui->mii.state == MII_RUNNING) {
if (f->motor) {
if (dr->texture.opacity < 1.0f)
dr->texture.opacity += 0.10f;
if (dr->texture.opacity > 1.0f)
dr->texture.opacity = 1.0f;
} else {
if (dr->texture.opacity > 0.0f)
dr->texture.opacity -= 0.01f;
if (dr->texture.opacity < 0.0f)
dr->texture.opacity = 0.0f;
}
}
if (dr->texture.opacity <= 0.0f)
float main_opacity = dr->texture.opacity;
if (main_opacity <= 0.0f)
continue;
c2_rect_t r = C2_RECT_WH( 0, 0,
ui->video_frame.l - 20,
c2_rect_height(&ui->video_frame) - 22);
c2_rect_f tr = { 0, 0, ui->floppy[i].max_width, 1 };
if (i == 0)
c2_rect_offset(&r,
ui->video_frame.l - c2_rect_width(&r) - 10,
ui->video_frame.t + 10);
else
c2_rect_offset(&r, ui->video_frame.r + 10,
ui->video_frame.t + 10);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1.0f, 1.0f, 1.0f, dr->texture.opacity);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glBegin(GL_QUADS);
// rotate texture 90 clockwise, and mirror left-right
glTexCoord2f(tr.l, tr.t); glVertex2f(r.l, r.t);
glTexCoord2f(tr.l, tr.b); glVertex2f(r.r, r.t);
glTexCoord2f(tr.r, tr.b); glVertex2f(r.r, r.b);
glTexCoord2f(tr.r, tr.t); glVertex2f(r.l, r.b);
glEnd();
if (f->heat) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR);
dr = &ui->pixels.floppy[i].hm_read;
glColor4f(0.0f, 1.0f, 0.0f, 1.0);
const float angle_offset = 60; // head angle offset on display
if (1) {
glPushMatrix();
// make floppy slide in/out with opacity
glTranslatef(-10 - (100.0 * ( 1.0f - main_opacity) ),
200 + (i * 350), 0);
glScalef(15, 15, 1);
{
glColor4f(0.0f, 0.0f, 0.0f, main_opacity);
glDisable(GL_TEXTURE_2D);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, ui->floppy_base.e);
int element_count = ui->floppy_base.count / 2;
glDrawArrays(GL_TRIANGLES, 0, element_count);
}
int track_id = f->track_id[f->qtrack];
double bc = (double)f->bit_position /
(double)f->tracks[track_id].bit_count;
bc = 360 - (bc * 360.0);
bc += angle_offset;
if (bc >= 360.0)
bc -= 360.0;
glRotatef(bc, 0, 0, 1);
dr = &ui->pixels.floppy[i].bits;
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glBegin(GL_QUADS);
// rotate texture 90 clockwise, and mirror left-right
glTexCoord2f(tr.l, tr.t); glVertex2f(r.l, r.t);
glTexCoord2f(tr.l, tr.b); glVertex2f(r.r, r.t);
glTexCoord2f(tr.r, tr.b); glVertex2f(r.r, r.b);
glTexCoord2f(tr.r, tr.t); glVertex2f(r.l, r.b);
glEnd();
dr = &ui->pixels.floppy[i].hm_write;
glColor4f(1.0f, 0.0f, 0.0f, 1.0);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glBegin(GL_QUADS);
// rotate texture 90 clockwise, and mirror left-right
glTexCoord2f(tr.l, tr.t); glVertex2f(r.l, r.t);
glTexCoord2f(tr.l, tr.b); glVertex2f(r.r, r.t);
glTexCoord2f(tr.r, tr.b); glVertex2f(r.r, r.b);
glTexCoord2f(tr.r, tr.t); glVertex2f(r.l, r.b);
glEnd();
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1.0f, 1.0f, 1.0f, main_opacity);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, ui->floppy[i].vtx.pos.e);
glTexCoordPointer(2, GL_FLOAT, 0, ui->floppy[i].vtx.tex.e);
int element_count = ui->floppy[i].vtx.pos.count / 2;
glDrawArrays(ui->floppy[i].vtx.kind, 0, element_count);
// draw heatmap and head with full opacity
// otherwise we get wierd artifacts
if (f->heat) {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR);
dr = &ui->pixels.floppy[i].hm_read;
glColor4f(0.0f, 1.0f, 0.0f, 1.0);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glDrawArrays(ui->floppy[i].vtx.kind, 0, element_count);
dr = &ui->pixels.floppy[i].hm_write;
glColor4f(1.0f, 0.0f, 0.0f, 1.0);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glDrawArrays(ui->floppy[i].vtx.kind, 0, element_count);
}
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
if (main_opacity > 0.8f) {
// Draw head small rectangle
dr = &ui->pixels.floppy[i].bits;
track_id = f->qtrack / 4;
glDisable(GL_TEXTURE_2D);
glRotatef(-bc + angle_offset, 0, 0, 1);
glTranslatef(MII_GL_FLOPPY_FLUX_RADIUS_IN +
(((35 - track_id) / 35.0) *
(MII_GL_FLOPPY_FLUX_RADIUS_OUT-
MII_GL_FLOPPY_FLUX_RADIUS_IN)), 0, 0);
const float r = 0.3;
glColor4f(1.0f, 0.0f, 0.0f, main_opacity);
glBegin(GL_QUADS);
glVertex2f(-r, -r); glVertex2f(-r, r);
glVertex2f(r, r); glVertex2f(r, -r);
glEnd();
}
glPopMatrix();
}
}
/* draw mui texture */
@ -416,6 +532,7 @@ mii_mui_gl_render(
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1.0f, 1.0f, 1.0f, ui->mui_alpha);
dr = &ui->pixels.mui;
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, dr->texture.id);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex2f(0, 0);

View File

@ -8,9 +8,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <libgen.h>
#include "mui.h"
@ -27,6 +24,7 @@ enum {
typedef struct mii_mui_loadbin_t {
mui_window_t win;
mui_control_t * load, *icon, *fname;
mii_loadbin_conf_t * dst, config;
} mii_mui_loadbin_t;
static int
@ -75,6 +73,9 @@ _mii_loadbin_action_cb(
case MII_LBIN_SAVE: {
// save the config
printf("%s save\n", __func__);
if (m->dst)
*m->dst = m->config;
mui_window_action(&m->win, MII_MUI_LOADBIN_SAVE, m->dst);
mui_window_dispose(&m->win);
} break;
case MII_LBIN_CANCEL: {
@ -125,6 +126,7 @@ mii_mui_load_binary(
sizeof(mii_mui_loadbin_t));
mui_window_set_id(w, MII_LBIN_WINDOW_ID);
mii_mui_loadbin_t * m = (mii_mui_loadbin_t*)w;
m->dst = config;
mui_control_t * c = NULL;
c2_rect_t cf;

View File

@ -65,6 +65,7 @@ mii_config_save_cb(
case MII_MUI_SLOTS_SAVE:
printf("%s *** Rebooting\n", __func__);
mii_x11_reload_config((void*)ui);
mii_emu_save(&ui->cf, &ui->config);
break;
case MII_MUI_DISK2_SAVE:
case MII_MUI_SMARTPORT_SAVE: {
@ -72,12 +73,21 @@ mii_config_save_cb(
mii_t * mii = &ui->mii;
mii_machine_config_t * config = &ui->config;
mii_ui_reconfigure_slot(mii, config, conf->slot_id + 1);
mii_emu_save(&ui->cf, &ui->config);
} break;
case MII_MUI_1MB_SAVE: {
mii_1mb_conf_t * conf = param;
mii_t * mii = &ui->mii;
mii_machine_config_t * config = &ui->config;
mii_ui_reconfigure_slot(mii, config, conf->slot_id + 1);
mii_emu_save(&ui->cf, &ui->config);
} break;
case MII_MUI_SSC_SAVE: {
mii_ssc_conf_t * conf = param;
mii_t * mii = &ui->mii;
mii_machine_config_t * config = &ui->config;
mii_ui_reconfigure_slot(mii, config, conf->slot_id + 1);
mii_emu_save(&ui->cf, &ui->config);
} break;
}
return 0;
@ -218,13 +228,13 @@ mii_menubar_action(
ui->transition.state = MII_MUI_TRANSITION_SHOW_UI;
}
} break;
case FCC('d','s','k','0'):
case FCC('d','s','k','1'):
case FCC('d','s','k','2'):
case FCC('d','s','k','3'):
case FCC('d','s','k','4'):
case FCC('d','s','k','5'):
case FCC('d','s','k','6'): {
case FCC('s','l','t','0'):
case FCC('s','l','t','1'):
case FCC('s','l','t','2'):
case FCC('s','l','t','3'):
case FCC('s','l','t','4'):
case FCC('s','l','t','5'):
case FCC('s','l','t','6'): {
int slot = FCC_INDEX(item->uid);
printf("%s configure slot %d\n", __func__, slot);
mui_window_set_action(
@ -269,10 +279,10 @@ mii_menubar_action(
ui->config.video_mode = mii->video.color_mode;
break;
case FCC('m','h','z','1'):
mii->speed = 1;
mii->speed = MII_SPEED_NTSC;
break;
case FCC('m','h','z','3'):
mii->speed = 3.58;
mii->speed = MII_SPEED_TITAN;
break;
case FCC('s','t','o','p'): {
mii_th_signal_t sig = {
@ -327,7 +337,11 @@ mii_mui_menu_slot_menu_update(
char * label = static_label[i];
label[0] = 0;
int slot = i + 1;
switch (ui->config.slot[i].driver) {
case MII_SLOT_DRIVER_SSC:
sprintf(label, "%d: Super Serial…", slot);
break;
case MII_SLOT_DRIVER_SMARTPORT:
sprintf(label, "%d: SmartPort…", slot);
break;
@ -343,7 +357,7 @@ mii_mui_menu_slot_menu_update(
if (label[0]) {
mui_menu_items_push(items, (mui_menu_item_t){
.title = label,
.uid = FCC('d','s','k','0' + i),
.uid = FCC('s','l','t','0' + i),
});
}
}
@ -363,9 +377,7 @@ mii_mui_menus_init(
mui_window_t * mbar = mui_menubar_new(&ui->mui);
mui_window_set_action(mbar, mii_menubar_action, ui);
mui_menubar_add_simple(mbar, MUI_GLYPH_APPLE,
FCC('a','p','p','l'),
m_apple_menu);
mui_menubar_add_menu(mbar, FCC('a','p','p','l'), m_color_apple_menu, 2);
mui_menubar_add_simple(mbar, "File",
FCC('f','i','l','e'),
m_file_menu);

View File

@ -17,12 +17,15 @@ extern mui_menu_item_t m_machine_menu[];
extern mui_menu_item_t m_cpu_menu[];
#ifdef MII_MUI_MENUS_C
mui_menu_item_t m_apple_menu[] = {
#include "mii_mui_apple_logo.h"
mui_menu_item_t m_color_apple_menu[] = {
{ .color_icon = mii_color_apple_pixels, .is_menutitle = 1, },
{ .title = "About MII…",
.uid = FCC('a','b','o','t') },
// { .title = "-", },
{ },
};
mui_menu_item_t m_file_menu[] = {
/* these two don't do anything for the moment */
{ .title = "No Drives Installed…",

0
ui_gl/mii_mui_prefs.c Normal file
View File

View File

@ -194,7 +194,7 @@ static const struct {
[MII_SLOT_DRIVER_SMARTPORT] = { "smartport", },
[MII_SLOT_DRIVER_DISK2] = { "disk2", },
[MII_SLOT_DRIVER_MOUSE] = { "mouse", },
[MII_SLOT_DRIVER_SUPERSERIAL] = { "ssc", },
[MII_SLOT_DRIVER_SSC] = { "ssc", },
[MII_SLOT_DRIVER_ROM1MB] = { "eecard", },
};
@ -261,9 +261,23 @@ mii_emu_save(
mii_config_set(cf, section, "wp1",
config->slot[i].conf.disk2.drive[1].wp ? "1" : "0");
break;
case MII_SLOT_DRIVER_SUPERSERIAL:
case MII_SLOT_DRIVER_SSC:
sprintf(label, "%u", config->slot[i].conf.ssc.kind);
mii_config_set(cf, section, "kind", label);
mii_config_set(cf, section, "device",
config->slot[i].conf.ssc.device);
sprintf(label, "%u", config->slot[i].conf.ssc.socket_port);
mii_config_set(cf, section, "port", label);
sprintf(label, "%u", config->slot[i].conf.ssc.baud);
mii_config_set(cf, section, "baud", label);
sprintf(label, "%u", config->slot[i].conf.ssc.bits);
mii_config_set(cf, section, "bits", label);
sprintf(label, "%u", config->slot[i].conf.ssc.parity);
mii_config_set(cf, section, "parity", label);
sprintf(label, "%u", config->slot[i].conf.ssc.stop);
mii_config_set(cf, section, "stop", label);
sprintf(label, "%u", config->slot[i].conf.ssc.hw_handshake);
mii_config_set(cf, section, "hw_handshake", label);
break;
case MII_SLOT_DRIVER_ROM1MB:
mii_config_set(cf, section, "use_default",
@ -372,10 +386,28 @@ mii_emu_load(
if (cl)
config->slot[i].conf.disk2.drive[1].wp = !!strtoul(cl->value, NULL, 0);
break;
case MII_SLOT_DRIVER_SUPERSERIAL:
case MII_SLOT_DRIVER_SSC:
cl = mii_config_get(cf, section, "kind");
if (cl)
config->slot[i].conf.ssc.kind = strtoul(cl->value, NULL, 0);
cl = mii_config_get(cf, section, "device");
if (cl)
strcpy(config->slot[i].conf.ssc.device, cl->value);
cl = mii_config_get(cf, section, "port");
if (cl)
config->slot[i].conf.ssc.socket_port = strtoul(cl->value, NULL, 0);
cl = mii_config_get(cf, section, "baud");
if (cl)
config->slot[i].conf.ssc.baud = strtoul(cl->value, NULL, 0);
cl = mii_config_get(cf, section, "bits");
if (cl)
config->slot[i].conf.ssc.bits = strtoul(cl->value, NULL, 0);
cl = mii_config_get(cf, section, "parity");
if (cl)
config->slot[i].conf.ssc.parity = strtoul(cl->value, NULL, 0);
cl = mii_config_get(cf, section, "stop");
if (cl)
config->slot[i].conf.ssc.stop = strtoul(cl->value, NULL, 0);
break;
case MII_SLOT_DRIVER_ROM1MB:
cl = mii_config_get(cf, section, "use_default");

View File

@ -55,8 +55,16 @@ typedef struct mii_2dsk_conf_t {
mii_drive_conf_t drive[2];
} mii_2dsk_conf_t;
enum {
MII_SSC_KIND_DEVICE = 0,
MII_SSC_KIND_PTY,
MII_SSC_KIND_SOCKET,
};
typedef struct mii_ssc_conf_t {
uint8_t kind; // device, pty, socket
uint32_t slot_id : 3,
kind : 3, // device, pty, socket
hw_handshake : 1;
int socket_port;
char device[MII_PATH_SIZE_MAX];
// rom/card configuration
@ -82,7 +90,7 @@ enum mii_mui_driver_e {
MII_SLOT_DRIVER_SMARTPORT,
MII_SLOT_DRIVER_DISK2,
MII_SLOT_DRIVER_MOUSE,
MII_SLOT_DRIVER_SUPERSERIAL,
MII_SLOT_DRIVER_SSC,
MII_SLOT_DRIVER_ROM1MB,
MII_SLOT_DRIVER_COUNT
};
@ -121,6 +129,7 @@ enum mii_mui_dialog_e {
MII_MUI_1MB_SAVE = FCC('1','M','B',' '),
MII_MUI_DISK2_SAVE = FCC('2','D','S','K'),
MII_MUI_SMARTPORT_SAVE = FCC('S','M','P','T'),
MII_MUI_SSC_SAVE = FCC('S','S','C',' '),
};
struct mui_window_t *
@ -155,6 +164,10 @@ mii_mui_load_2dsk(
struct mui_t *mui,
mii_2dsk_conf_t *config,
uint8_t drive_kind);
struct mui_window_t *
mii_mui_configure_ssc(
struct mui_t *mui,
mii_ssc_conf_t *config);
/*
* Config file related

View File

@ -32,7 +32,7 @@ static const mii_machine_config_t _default_config = {
.titan_accelerator = 0,
.slot = {
[0] = {
// .driver = MII_SLOT_DRIVER_SUPERSERIAL,
// .driver = MII_SLOT_DRIVER_SSC,
.conf.ssc = {
.kind = 0,
.socket_port = 1969,
@ -40,7 +40,7 @@ static const mii_machine_config_t _default_config = {
.baud = 9600,
.bits = 8,
.parity = 0,
.stop = 1,
.stop = 0,
}
},
[1] = { .driver = 0, },
@ -63,7 +63,6 @@ enum {
MII_SLOT_SAVE = FCC('s','a','v','e'),
MII_SLOT_CANCEL = FCC('c','a','n','c'),
MII_SLOT_DEFAULT = FCC('d','e','f','a'),
// MII_SLOT_REBOOT = FCC('r','b','o','o'),
MII_SLOT_NSC = FCC('n','s','c','l'),
MII_SLOT_TITAN = FCC('t','i','t','a'),
MII_SLOT_DRIVER_POP = FCC('d','r','v','p'),
@ -78,7 +77,7 @@ static const struct {
[MII_SLOT_DRIVER_SMARTPORT] = { "SmartPort", 1 },
[MII_SLOT_DRIVER_DISK2] = { "Disk II", 1 },
[MII_SLOT_DRIVER_MOUSE] = { "Mouse", 0 },
[MII_SLOT_DRIVER_SUPERSERIAL] = { "Super Serial", 1 },
[MII_SLOT_DRIVER_SSC] = { "Super Serial", 1 },
[MII_SLOT_DRIVER_ROM1MB] = { "ROM 1MB", 1 },
{ NULL, 0 },
};
@ -90,8 +89,6 @@ _mii_config_win_to_conf(
mui_window_t * w = &m->win;
mii_machine_config_t * cf = &m->config;
// cf->reboot_on_load = !!mui_control_get_value(
// mui_control_get_by_id(w, MII_SLOT_REBOOT));
cf->titan_accelerator = !!mui_control_get_value(
mui_control_get_by_id(w, MII_SLOT_TITAN));
cf->no_slot_clock = !!mui_control_get_value(
@ -191,12 +188,10 @@ mii_mui_configure_slot(
res = mii_mui_load_1mbrom(mui,
&config->slot[slot_id].conf.rom1mb);
} break;
case MII_SLOT_DRIVER_SUPERSERIAL: {
mui_alert(mui, C2_PT(0,0),
"Not implemented yet",
"SSC is not implemented yet!\n"
"Driver is present just a placeholder, sorry.",
MUI_ALERT_FLAG_OK);
case MII_SLOT_DRIVER_SSC: {
config->slot[slot_id].conf.ssc.slot_id = slot_id;
res = mii_mui_configure_ssc(mui,
&config->slot[slot_id].conf.ssc);
} break;
}
return res;
@ -282,12 +277,12 @@ _mii_config_slot_action_cb(
&m->config.slot[slot_id].conf.rom1mb),
_mii_config_sub_save_cb, m);
} break;
case MII_SLOT_DRIVER_SUPERSERIAL: {
mui_alert(m->win.ui, C2_PT(0,0),
"Not implemented yet",
"SSC is not implemented yet!\n"
"Driver is present just a placeholder, sorry.",
MUI_ALERT_FLAG_OK);
case MII_SLOT_DRIVER_SSC: {
m->config.slot[slot_id].conf.ssc.slot_id = slot_id;
mui_window_set_action(
mii_mui_configure_ssc(m->win.ui,
&m->config.slot[slot_id].conf.ssc),
_mii_config_sub_save_cb, m);
} break;
}
} break;
@ -312,6 +307,8 @@ mii_mui_configure_slots(
mii_machine_config_t *config)
{
mui_t *ui = mui;
float base_size = mui_font_find(ui, "main")->size;
float margin = base_size * 0.7;
mui_window_t *w = mui_window_get_by_id(mui, MII_SLOT_WINDOW_ID);
if (w) {
@ -360,13 +357,15 @@ mii_mui_configure_slots(
cf, MUI_BUTTON_STYLE_NORMAL,
"Default", MII_SLOT_DEFAULT);
c2_rect_right_of(&cf, cf.r, 30);
c2_rect_t rad = cf;
rad.b += 5;
c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_CHECKBOX,
rad, MUI_BUTTON_STYLE_CHECKBOX,
"No Slot Clock", MII_SLOT_NSC);
c2_rect_right_of(&cf, cf.r, 30);
cf.r = cf.l + 200;
c2_rect_right_of(&rad, rad.r, margin);
rad.r = rad.l + 180;
c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_CHECKBOX,
rad, MUI_BUTTON_STYLE_CHECKBOX,
"Titan Accelerator", MII_SLOT_TITAN);
c2_rect_bottom_of(&cf, cf.b, 16);
@ -377,17 +376,20 @@ mii_mui_configure_slots(
c2_rect_t slot_line_rect = C2_RECT_WH(0, 0, 500, 34);
cf = slot_line_rect;
cf.r = cf.l + 50;
cf.r = cf.l + 56;
c = mui_textbox_new(w, cf, "Slot", NULL,
MUI_TEXT_ALIGN_RIGHT | MUI_TEXT_ALIGN_MIDDLE);
c2_rect_right_of(&cf, cf.r, 6);
MUI_TEXT_ALIGN_RIGHT | MUI_TEXT_ALIGN_MIDDLE |
MUI_TEXT_STYLE_ULINE);
c2_rect_right_of(&cf, cf.r, 0);
cf.r = cf.l + 240;
c = mui_textbox_new(w, cf, "Driver", NULL,
MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE);
MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE |
MUI_TEXT_STYLE_ULINE);
c2_rect_right_of(&cf, cf.r, 30);
cf.r = cf.l + 150;
c = mui_textbox_new(w, cf, "Config", NULL,
MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE);
MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE |
MUI_TEXT_STYLE_ULINE);
c2_rect_offset(&slot_line_rect, 0, 36);
for (int i = 1; i < 8; i++) {
@ -396,7 +398,8 @@ mii_mui_configure_slots(
char idx[16];
sprintf(idx, "%d:", i);
c = mui_textbox_new(w, cf, idx, NULL,
MUI_TEXT_ALIGN_RIGHT | MUI_TEXT_ALIGN_MIDDLE);
MUI_TEXT_ALIGN_RIGHT | MUI_TEXT_ALIGN_MIDDLE |
MUI_TEXT_STYLE_ULINE);
c2_rect_right_of(&cf, cf.r, 6);
cf.r = cf.l + 240;
c = mui_popupmenu_new(w, cf,
@ -405,7 +408,8 @@ mii_mui_configure_slots(
mui_menu_items_clear(items);
for (int j = 0; _mii_slot_drivers[j].label; j++) {
mui_menu_item_t i = {
.title = strdup(_mii_slot_drivers[j].label),
// .title = strdup(_mii_slot_drivers[j].label),
.title = _mii_slot_drivers[j].label,
.uid = j,
};
mui_menu_items_push(items, i);

419
ui_gl/mii_mui_ssc.c Normal file
View File

@ -0,0 +1,419 @@
/*
* mui_mui_ssc.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include "mui.h"
#include "mii_mui_settings.h"
enum {
MII_SSC_WINDOW_ID = FCC('s','s','c','c'),
MII_SSC_SAVE = FCC('s','a','v','e'),
MII_SSC_CANCEL = FCC('c','a','n','c'),
MII_SSC_SELECT = FCC('s','e','l','e'),
MII_SSC_BAUD = FCC('b','a','u','d'),
MII_SSC_BITS = FCC('b','i','t','0'),
MII_SSC_PARITY = FCC('p','a','r','i'),
MII_SSC_STOPS = FCC('s','t','o','p'),
MII_SSC_HANDSHAKE = FCC('h','s','h','k'),
};
typedef struct mii_mui_ssc_t {
mui_window_t win;
mui_control_t * load, *icon, *fname, *select, *baud, *handshake;
mui_control_t * parity[3];
mui_control_t * bits[2];
mui_control_t * stops[2];
mii_ssc_conf_t * dst, config;
} mii_mui_ssc_t;
#include <termios.h>
#include <pty.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
static int
_mii_ssc_check_device_file(
mui_t * mui,
char * path)
{
char *error = NULL;
// try tyo open device and do a control call on it
// to see if it's a serial device
int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
asprintf(&error, "Failed to open %s: %s", path, strerror(errno));
}
if (!error) {
struct termios tio;
if (tcgetattr(fd, &tio) < 0) {
asprintf(&error, "Not a TTY device? %s: %s", path, strerror(errno));
}
}
if (error) {
mui_alert(mui, C2_PT(0,0), "Error", error, MUI_ALERT_FLAG_OK);
free(error);
}
if (fd >= 0)
close(fd);
return error ? -1 : 0;
}
static void
mii_mui_ssc_load_conf(
mii_mui_ssc_t * m,
mii_ssc_conf_t * config)
{
int ok = 1;
mui_menu_items_t *items = mui_popupmenu_get_items(m->baud);
printf("%s config %s %d bits %d parity %d stop %d hw %d\n",
__func__, config->device, config->baud, config->bits,
config->parity, config->stop, config->hw_handshake);
for (int i = 0; items->e[i].title; i++) {
if (items->e[i].uid == config->baud) {
mui_control_set_value(m->baud, i);
break;
}
}
char *fname = config->device;
if (fname[0] == 0) {
mui_control_set_title(m->fname, "Click \"Select\" to pick a device file");
mui_control_set_state(m->icon, MUI_CONTROL_STATE_DISABLED);
mui_control_set_state(m->fname, MUI_CONTROL_STATE_DISABLED);
ok = 0;
} else {
char *dup = strdup(fname);
mui_control_set_title(m->fname, basename(dup));
free(dup);
if (_mii_ssc_check_device_file(m->win.ui, config->device) < 0) {
ok = 0;
mui_control_set_state(m->icon, MUI_CONTROL_STATE_DISABLED);
mui_control_set_state(m->fname, MUI_CONTROL_STATE_DISABLED);
} else {
mui_control_set_state(m->icon, MUI_CONTROL_STATE_NORMAL);
mui_control_set_state(m->fname, MUI_CONTROL_STATE_NORMAL);
}
}
for (uint8_t i = 0; i < 2; i++)
mui_control_set_value(m->bits[i], i == config->bits);
for (uint8_t i = 0; i < 3; i++)
mui_control_set_value(m->parity[i], i == config->parity);
for (uint8_t i = 0; i < 2; i++)
mui_control_set_value(m->stops[i], i == config->stop);
mui_control_set_value(m->handshake, config->hw_handshake);
if (ok)
mui_control_set_state(m->load, MUI_CONTROL_STATE_NORMAL);
else
mui_control_set_state(m->load, MUI_CONTROL_STATE_DISABLED);
}
static int
_mii_ssc_stdfile_cb(
mui_window_t * w,
void * cb_param, // mii_mui_ssc_t
uint32_t what,
void * param) // not used
{
mii_mui_ssc_t * m = cb_param;
switch (what) {
case MUI_STDF_ACTION_SELECT: {
char * path = mui_stdfile_get_selected_path(w);
printf("%s select %s\n", __func__, path);
if (_mii_ssc_check_device_file(m->win.ui, path) < 0) {
mui_control_set_state(m->icon, MUI_CONTROL_STATE_DISABLED);
mui_control_set_state(m->fname, MUI_CONTROL_STATE_DISABLED);
mui_control_set_state(m->load, MUI_CONTROL_STATE_DISABLED);
} else {
strcpy(m->config.device, path);
mui_control_set_state(m->fname, MUI_CONTROL_STATE_NORMAL);
char *dup = strdup(path);
mui_control_set_title(m->fname, basename(dup));
free(dup);
mui_control_set_state(m->icon, MUI_CONTROL_STATE_NORMAL);
mui_control_set_state(m->load, MUI_CONTROL_STATE_NORMAL);
mui_control_set_state(m->load, MUI_CONTROL_STATE_NORMAL);
}
mui_control_set_state(m->icon, MUI_CONTROL_STATE_NORMAL);
mui_control_set_state(m->load, MUI_CONTROL_STATE_NORMAL);
mui_window_dispose(w);
} break;
case MUI_STDF_ACTION_CANCEL:
printf("%s cancel\n", __func__);
mui_window_dispose(w);
break;
}
return 0;
}
static int
_mii_ssc_action_cb(
mui_control_t * c,
void * cb_param, // mii_mui_ssc_t
uint32_t what,
void * param) // not used
{
printf("%s %4.4s\n", __func__, (char*)&what);
mii_mui_ssc_t * m = cb_param;
uint32_t uid = mui_control_get_uid(c);
switch (what) {
case MUI_CONTROL_ACTION_SELECT:
printf("%s control %4.4s\n", __func__, (char*)&uid);
switch (uid) {
case MII_SSC_SAVE: {
// save the config
printf("%s save\n", __func__);
if (m->dst)
*m->dst = m->config;
mui_window_action(&m->win, MII_MUI_SSC_SAVE, m->dst);
mui_window_dispose(&m->win);
} break;
case MII_SSC_CANCEL: {
// cancel the config
printf("%s cancel\n", __func__);
mui_window_dispose(&m->win);
} break;
case MII_SSC_SELECT: {
// select a file
printf("%s select\n", __func__);
mui_window_t * w = mui_stdfile_get(m->win.ui,
C2_PT(0, 0),
"Select a Serial device in /dev",
"(ttyS[0-9]+|tnt[0-9]+|ttyUSB[0-9]+|ttyACM[0-9]+)$",
"/dev", MUI_STDF_FLAG_REGEXP|MUI_STDF_FLAG_NOPREF);
mui_window_set_action(w, _mii_ssc_stdfile_cb, m);
} break;
case MII_SSC_HANDSHAKE: {
// toggle handshake
printf("%s handshake\n", __func__);
m->config.hw_handshake = mui_control_get_value(c);
} break;
case FCC('b','i','t','0'):
case FCC('b','i','t','1'):
if (mui_control_get_value(c)) {
m->config.bits = uid == FCC('b','i','t','1');
} break;
case FCC('p','a','r','0'):
case FCC('p','a','r','1'):
case FCC('p','a','r','2'):
if (mui_control_get_value(c)) {
m->config.parity = uid == FCC('p','a','r','0') ? 0 :
uid == FCC('p','a','r','1') ? 1 : 2;
} break;
case FCC('s','t','o','0'):
case FCC('s','t','o','1'):
if (mui_control_get_value(c)) {
m->config.stop = uid == FCC('s','t','o','1');
} break;
}
break;
case MUI_CONTROL_ACTION_VALUE_CHANGED: {
switch (uid) {
case MII_SSC_BAUD: {
// select a baud rate
printf("%s baud\n", __func__);
mui_menu_items_t *items = mui_popupmenu_get_items(c);
// get value
int32_t val = mui_control_get_value(c);
printf("%s baud %d\n", __func__, items->e[val].uid);
m->config.baud = items->e[val].uid;
} break;
}
} break;
}
return 0;
}
struct mui_window_t *
mii_mui_configure_ssc(
struct mui_t *mui,
mii_ssc_conf_t *config)
{
mui_t *ui = mui;
float base_size = mui_font_find(ui, "main")->size;
float margin = base_size * 0.7;
float base_height = base_size * 1.4;
mui_window_t *w = mui_window_get_by_id(mui, MII_SSC_WINDOW_ID);
if (w) {
mui_window_select(w);
return w;
}
c2_pt_t where = {};
c2_rect_t wpos = C2_RECT_WH(where.x, where.y, 620, 320);
if (where.x == 0 && where.y == 0)
c2_rect_offset(&wpos,
(ui->screen_size.x / 2) - (c2_rect_width(&wpos) / 2),
(ui->screen_size.y * 0.4) - (c2_rect_height(&wpos) / 2));
char *label;
asprintf(&label, "Super Serial Card (Slot %d)",
config->slot_id + 1);
w = mui_window_create(mui, wpos, NULL, MUI_WINDOW_LAYER_MODAL,
label, sizeof(mii_mui_ssc_t));
mui_window_set_id(w, MII_SSC_WINDOW_ID);
mii_mui_ssc_t * m = (mii_mui_ssc_t*)w;
m->dst = config;
m->config = *config;
mui_control_t * c = NULL;
c2_rect_t cf;
cf = C2_RECT_WH(0, 0, base_size * 4, base_height);
c2_rect_left_of(&cf, c2_rect_width(&w->content), margin);
c2_rect_top_of(&cf, c2_rect_height(&w->content), margin);
m->load = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_DEFAULT,
"OK", MII_SSC_SAVE);
c->key_equ = MUI_KEY_EQU(0, 13);
c->state = MUI_CONTROL_STATE_DISABLED;
c2_rect_left_of(&cf, cf.l, margin);
c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_NORMAL,
"Cancel", MII_SSC_CANCEL);
c->key_equ = MUI_KEY_EQU(0, 27);
c2_rect_set(&cf, margin, (margin/2),
c2_rect_width(&w->frame) - margin - (base_size*4) - margin,
(margin/2) + base_size);
c2_rect_t cp = cf;
cp.l -= margin * 0.2;
cp.b += base_size * 1.3;
c = mui_groupbox_new(w, cp, "Device:", MUI_CONTROL_TEXTBOX_FRAME);
float icons_size = mui_font_find(ui, "icon_small")->size;
c2_rect_bottom_of(&cf, cf.b, 0);
cf.b = cf.t + icons_size;
cf.r = cf.l + icons_size;
m->icon = c = mui_textbox_new(w, cf, MUI_ICON_FILE, "icon_small",
MUI_TEXT_ALIGN_MIDDLE | MUI_TEXT_ALIGN_CENTER | 0);
c->state = MUI_CONTROL_STATE_DISABLED;
cf.l = cf.r;
cf.r = c2_rect_width(&w->content) - margin - (base_size*4) - margin;
m->fname = c = mui_textbox_new(w, cf,
"Click \"Select\" to pick a device file", NULL,
MUI_TEXT_ALIGN_MIDDLE);
c->state = MUI_CONTROL_STATE_DISABLED;
cf = C2_RECT_WH(0, 0, base_size * 4, base_height);
c2_rect_bottom_of(&cf, cp.t, margin * 1.2);
c2_rect_right_of(&cf, cp.r, margin);
m->select = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_NORMAL,
"Select…" , MII_SSC_SELECT);
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, 's');
c2_rect_right_of(&cf, 0, margin);
c2_rect_bottom_of(&cf, cf.b, margin);
cf.r = cf.l + 80;
cf.b = cf.t + 34;
c = mui_textbox_new(w, cf, "Baud:", NULL,
MUI_TEXT_ALIGN_RIGHT|MUI_TEXT_ALIGN_MIDDLE);
c2_rect_t popr = cf;
c2_rect_right_of(&popr, cf.r, margin/2);
popr.b = cf.t + 34;
popr.r = popr.l + 160;
m->baud = c = mui_popupmenu_new(w, popr, "Popup", MII_SSC_BAUD);
mui_menu_items_t *items = mui_popupmenu_get_items(c);
mui_menu_items_add(items, (mui_menu_item_t){.title="1200", .uid=1200 });
mui_menu_items_add(items, (mui_menu_item_t){.title="2400", .uid=2400 });
mui_menu_items_add(items, (mui_menu_item_t){.title="4800", .uid=4800 });
mui_menu_items_add(items, (mui_menu_item_t){.title="9600", .uid=9600 });
mui_menu_items_add(items, (mui_menu_item_t){.title="19200", .uid=19200 });
// popup needs to be NULL terminated, AND prepared()
mui_menu_items_add(items, (mui_menu_item_t){.title=NULL });
mui_popupmenu_prepare(c);
// this tell libmui that it can clear the radio values of the 'sister'
// radio buttons when one matching the uid&mask is selected
uint32_t uid_mask = FCC(0xff,0xff,0xff,0);
c2_rect_right_of(&cf, popr.r, margin);
cf.t += 1;
cf.b = cf.t + base_size;
cf.r = cf.l + 95;
c2_rect_t base_radio = cf;
base_radio.l = margin;
m->bits[0] = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_RADIO,
"8 bits", FCC('b','i','t','0'));
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, '8');
c->uid_mask = uid_mask;
c->value = 1;
c2_rect_right_of(&cf, cf.r, margin);
cf.r = cf.l + 160;
m->bits[1] = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_RADIO,
"7 bits Data", FCC('b','i','t','1'));
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, '7');
c->uid_mask = uid_mask;
cf = base_radio;
c2_rect_bottom_of(&cf, cf.b, margin);
cf.r = cf.l + 70;
m->parity[0] = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_RADIO,
"No", FCC('p','a','r','0'));
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, 'n');
c->uid_mask = uid_mask;
c->value = 1;
c2_rect_right_of(&cf, cf.r, margin);
cf.r = cf.l + 80;
m->parity[1] = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_RADIO,
"Odd", FCC('p','a','r','1'));
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, 'o');
c->uid_mask = uid_mask;
c2_rect_right_of(&cf, cf.r, margin);
cf.r = cf.l + 170;
m->parity[2] = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_RADIO,
"Even Parity", FCC('p','a','r','2'));
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, 'e');
c->uid_mask = uid_mask;
c2_rect_right_of(&cf, cf.r, margin);
// cf.l = base_radio.l;
cf.r = cf.l + 50;
m->stops[0] = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_RADIO,
"1", FCC('s','t','o','0'));
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, '1');
c->uid_mask = uid_mask;
c->value = 1;
c2_rect_right_of(&cf, cf.r, margin);
cf.r = cf.l + 120;
m->stops[1] = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_RADIO,
"2 Stops", FCC('s','t','o','1'));
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, '2');
c->uid_mask = uid_mask;
cf.b = cf.t + base_size;
c2_rect_bottom_of(&cf, cf.b, margin);
c2_rect_right_of(&cf, 0, margin);
cf.r = cf.l + 280;
m->handshake = c = mui_button_new(w,
cf, MUI_BUTTON_STYLE_CHECKBOX,
"Handware Handshake",
MII_SSC_HANDSHAKE);
c->key_equ = MUI_KEY_EQU(MUI_MODIFIER_ALT, 'h');
c = NULL;
TAILQ_FOREACH(c, &w->controls, self) {
if (mui_control_get_uid(c) == 0)
continue;
mui_control_set_action(c, _mii_ssc_action_cb, m);
}
mii_mui_ssc_load_conf(m, &m->config);
return w;
}

View File

@ -15,6 +15,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include <sys/timerfd.h>
#include <math.h>
// probably should wrap these into a HAVE_JOYSTICK define for non-linux
#ifndef HAVE_JOYSTICK
#define HAVE_JOYSTICK 1
@ -179,6 +180,8 @@ mii_thread_joystick(
mii_t *mii = (mii_t *)arg;
mii->analog.v[0].value = 127;
mii->analog.v[1].value = 127;
short axis[2] = { 0, 0 };
float reprojected[2] = { 0, 0 };
do {
ssize_t rd = read(fd, &event, sizeof(event));
if (rd != sizeof(event)) {
@ -190,29 +193,51 @@ mii_thread_joystick(
// printf("button %u %s\n", event.number, event.value ? "pressed" : "released");
switch (event.number) {
case 2 ... 3:
mii_bank_poke(&mii->bank[MII_BANK_MAIN],
mii_bank_poke(&mii->bank[MII_BANK_SW],
0xc061 + (event.number - 2),
event.value ? 0x80 : 0);
break;
case 4 ... 5:
mii_bank_poke(&mii->bank[MII_BANK_MAIN],
mii_bank_poke(&mii->bank[MII_BANK_SW],
0xc061 + (event.number - 4),
event.value ? 0x80 : 0);
break;
}
break;
case JS_EVENT_AXIS:
// TODO: Use some sort of settings on which axis to use
switch (event.number) {
case 0 ... 1: {// X
uint32_t v = (event.value + 0x8000) / 256;
if (v > 255)
v = 255;
mii->analog.v[event.number ? 1 : 0].value = v;
// printf("axis %u %6d %3dx%3d\n"
// event.number, event.value,
// mii->analog.v[0].value, mii->analog.v[1].value);
axis[event.number] = event.value;
} break;
}
for (int i = 0; i < 2; i++)
reprojected[i] = axis[i] / 256;
/*
* This remaps the circular coordinates of the joystick to
* a square, the 'modern' joystick I use has a top left corner of
* -94,-94, bottom 130,130, so we need to remap the values to
* -127,127 - 127,127 to be able to use them as a joystick
* otherwise some games aren't happy (Wings of Fury for example)
*
* The formula is something I thrown together, I'm sure there's
* a better way to do this, but there isn't many of these events
* so it's not a big deal.
*/
if (1) {
float x = (float)reprojected[0] / 256.0f;
float y = (float)reprojected[1] / 256.0f;
reprojected[0] = reprojected[0] + (fabs(reprojected[1]) * x);
reprojected[1] = reprojected[1] + (fabs(reprojected[0]) * y);
}
for (int i = 0; i < 2; i++) {
int32_t v = reprojected[i] + 127;
if (v > 255)
v = 255;
else if (v < 0)
v = 0;
mii->analog.v[i].value = v;
}
break;
default:
/* Ignore init events. */