Version 1.8, see changelog for details
Tons of changes, see changelog Signed-off-by: Michel Pollet <buserror@gmail.com>
2
.gitignore
vendored
@ -6,3 +6,5 @@ cachegrind.out.*
|
||||
callgrind.out.*
|
||||
.cache
|
||||
*.miov
|
||||
hezner
|
||||
*_scrot.png
|
||||
|
97
CHANGELOG.md
Normal 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
|
5
Makefile
@ -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)
|
||||
|
87
README.md
@ -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:
|
||||
|
@ -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.
|
BIN
docs/mui_emulator.drawio.png
Normal file
After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 621 KiB After Width: | Height: | Size: 621 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
BIN
docs/screen/v18colorapple.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
docs/screen/v18new_display.gif
Normal file
After Width: | Height: | Size: 2.0 MiB |
BIN
docs/screen/v18ssc_dialog.png
Normal file
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 790 KiB After Width: | Height: | Size: 790 KiB |
Before Width: | Height: | Size: 510 KiB |
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)) {
|
||||
|
@ -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) \
|
||||
|
234
libmui/mui/mui.c
@ -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);
|
||||
}
|
||||
|
388
libmui/mui/mui.h
@ -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 );
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
1107
libmui/mui/mui_cdef_textedit.c
Normal 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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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
@ -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);
|
||||
|
33
src/mii.h
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]",
|
||||
|
@ -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
|
||||
|
18
src/mii_sw.h
@ -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
@ -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
@ -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);
|
617
src/mii_video.c
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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
@ -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,
|
||||
};
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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
@ -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;
|
||||
}
|
||||
|
@ -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. */
|
||||
|