├── .github └── workflows │ └── c-cpp.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── contrib ├── fifo_declare.h ├── md5.c ├── md5.h ├── mii-icon-64.png ├── mii_emu.desktop └── sokol_audio.h ├── disks ├── GamesWithFirmware.po ├── dos33master.nib └── prodos242.dsk ├── docs ├── Apple_logo_rainbow_version2_28x28.png ├── Compiling.md ├── mui_emulator.drawio.png └── screen │ ├── screen_color.png │ ├── screen_config.png │ ├── screen_green.png │ ├── screen_mish.png │ ├── screen_total.png │ ├── v17heatmap.png │ ├── v18colorapple.png │ ├── v18new_display.gif │ ├── v18ssc_dialog.png │ ├── v196versiontel.png │ ├── v197epistole.png │ ├── v197mousecalc.png │ ├── v19artifacts.png │ ├── v19mega2.png │ ├── v19ntsc.png │ └── video_main.gif ├── libmish ├── .gitignore ├── LICENSE-2.0.txt ├── Makefile ├── Makefile.common ├── README.md ├── doc │ ├── Makefile │ ├── demo.gif │ ├── libmish_callgraph.pdf │ └── tags_to_dot.rb ├── mish.pc ├── src │ ├── bsd_queue.h │ ├── fifo_declare.h │ ├── minipt.h │ ├── mish.h │ ├── mish_capture_select.c │ ├── mish_client.c │ ├── mish_client_input.c │ ├── mish_cmd.c │ ├── mish_cmd_env.c │ ├── mish_input.c │ ├── mish_line.c │ ├── mish_priv.h │ ├── mish_priv_cmd.h │ ├── mish_priv_line.h │ ├── mish_priv_vt.h │ ├── mish_send.c │ ├── mish_session.c │ ├── mish_telnet.c │ └── mish_vt.c └── tests │ ├── mish_argv_make_test.c │ ├── mish_cmd_test.c │ ├── mish_debug_test.c │ ├── mish_input_test.c │ ├── mish_test.c │ └── mish_vt_test.c ├── libmui ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── Makefile.common ├── README.md ├── contrib │ └── bsd_queue.h ├── doc │ ├── alert.png │ ├── control_demo.png │ ├── standard_get_file.png │ ├── static_text.png │ └── widgets.gif ├── fonts │ ├── Cairo.ttf │ ├── Charcoal_mui.sfd │ ├── Charcoal_mui.ttf │ ├── Charcoal_mui_italic.sfd │ ├── Dingbat.ttf │ ├── Geneva.ttf │ ├── typicon.ttf │ └── typicons.sfd ├── mui_shell │ ├── mui_shell.c │ └── mui_shell_plugin.h ├── src │ ├── c2_arrays.c │ ├── c2_arrays.h │ ├── c2_geometry.c │ ├── c2_geometry.h │ ├── c2_geometry_inline.h │ ├── c2_geometry_poly.h │ ├── c_array.h │ ├── cg.c │ ├── cg.h │ ├── control │ │ ├── box.h │ │ ├── button.h │ │ ├── drawable.h │ │ ├── listbox.h │ │ ├── popup.h │ │ ├── scrollbar.h │ │ └── textedit.h │ ├── fonts │ │ ├── mui_geneva_font.h │ │ ├── mui_icon_font.h │ │ └── mui_main_font.h │ ├── mui.c │ ├── mui.h │ ├── mui │ │ ├── mui_action.h │ │ ├── mui_alert.h │ │ ├── mui_control.h │ │ ├── mui_control_group.h │ │ ├── mui_drawable.h │ │ ├── mui_event.h │ │ ├── mui_menu.h │ │ ├── mui_ref.h │ │ ├── mui_stdfile.h │ │ ├── mui_text.h │ │ ├── mui_timer.h │ │ ├── mui_types.h │ │ └── mui_window.h │ ├── mui_alert.c │ ├── mui_cdef_boxes.c │ ├── mui_cdef_buttons.c │ ├── mui_cdef_drawable.c │ ├── mui_cdef_listbox.c │ ├── mui_cdef_scrollbar.c │ ├── mui_cdef_te.c │ ├── mui_cdef_te_keys.c │ ├── mui_cdef_te_metrics.c │ ├── mui_cdef_te_priv.h │ ├── mui_control_group.c │ ├── mui_controls.c │ ├── mui_drawable.c │ ├── mui_font.c │ ├── mui_menus.c │ ├── mui_menus_draw.c │ ├── mui_priv.h │ ├── mui_stdfile.c │ ├── mui_utils.c │ ├── mui_window.c │ ├── stb_truetype.h │ ├── stb_ttc.h │ ├── xft.c │ └── xft.h ├── tests │ ├── Makefile │ ├── mii_ui │ │ ├── Makefile │ │ ├── README.md │ │ └── mii_ui_tests.c │ └── mui_widgets_demo │ │ ├── Makefile │ │ ├── mui_color_apple.h │ │ └── mui_widgets_demo.c └── utils │ └── png2raw.c ├── src ├── drivers │ ├── mii_disk2.c │ ├── mii_disk2.h │ ├── mii_disk2_mish.c │ ├── mii_epromcard.c │ ├── mii_mb.c │ ├── mii_mouse.c │ ├── mii_mouse.h │ ├── mii_noslotclock.c │ ├── mii_smartport.c │ ├── mii_ssc.c │ ├── mii_ssc.h │ ├── mii_titan_iie.c │ ├── mockingboard.c │ └── mockingboard.h ├── format │ ├── mii_dd.c │ ├── mii_dd.h │ ├── mii_dsk.c │ ├── mii_dsk.h │ ├── mii_floppy.c │ ├── mii_floppy.h │ ├── mii_nib.c │ ├── mii_nib.h │ ├── mii_woz.c │ └── mii_woz.h ├── mii.c ├── mii.h ├── mii_65c02.c ├── mii_65c02.h ├── mii_65c02_asm.c ├── mii_65c02_asm.h ├── mii_65c02_disasm.c ├── mii_65c02_disasm.h ├── mii_65c02_ops.h ├── mii_analog.c ├── mii_analog.h ├── mii_argv.c ├── mii_audio.c ├── mii_audio.h ├── mii_bank.c ├── mii_bank.h ├── mii_mish.c ├── mii_mish_dd.c ├── mii_rom.c ├── mii_rom.h ├── mii_slot.c ├── mii_slot.h ├── mii_speaker.c ├── mii_speaker.h ├── mii_sw.h ├── mii_vcd.c ├── mii_vcd.h ├── mii_video.c ├── mii_video.h └── roms │ ├── Makefile.inc │ ├── mii_rom_disk2_p5.c │ ├── mii_rom_epromcard.c │ ├── mii_rom_iic.c │ ├── mii_rom_iic_video.c │ ├── mii_rom_iiee.c │ ├── mii_rom_iiee_video.c │ ├── mii_rom_iiee_video_fr.c │ ├── mii_rom_iiee_video_uk.c │ ├── mii_rom_smartport.c │ └── mii_rom_ssc.c ├── test ├── asm │ ├── 001_jmp_indirect.asm │ ├── 002_jsr_rts.asm │ ├── 003_zp_addressing.asm │ ├── 004_adc_sbc.asm │ ├── 6502_functional_test.bin │ ├── 65C02_extended_opcodes_test.bin │ ├── mii_smartport_driver.asm │ └── mii_smartport_driver.bin ├── mii_asm.c ├── mii_cpu_test.c └── mii_test.c ├── ui_gl ├── mii_emu_gl.c ├── mii_icon64.h ├── mii_loadbin.c ├── mii_mui.c ├── mii_mui.h ├── mii_mui_1mb.c ├── mii_mui_2dsk.c ├── mii_mui_about.c ├── mii_mui_apple_logo.h ├── mii_mui_gl.c ├── mii_mui_gl.h ├── mii_mui_loadbin.c ├── mii_mui_menus.c ├── mii_mui_menus.h ├── mii_mui_settings.c ├── mii_mui_settings.h ├── mii_mui_slots.c ├── mii_mui_ssc.c ├── mii_mui_utils.c ├── mii_mui_utils.h ├── mii_sokol_audio.c ├── mii_sokol_audio.h ├── mii_thread.c ├── mii_thread.h └── miigl_counter.h └── utils ├── clangd_gen.sh ├── convert-rom-tcc.c └── icon-convert-tcc.c /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: apt 17 | run: sudo apt-get install libglu1-mesa-dev libasound2-dev libx11-dev 18 | - name: make 19 | run: make 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bash_history* 2 | build-* 3 | .vscode 4 | compile_commands.json 5 | cachegrind.out.* 6 | callgrind.out.* 7 | .cache 8 | *.miov 9 | hezner 10 | *_scrot.png 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Michel Pollet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contrib/md5.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. 3 | * MD5 Message-Digest Algorithm (RFC 1321). 4 | * 5 | * Homepage: 6 | * http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 7 | * 8 | * Author: 9 | * Alexander Peslyak, better known as Solar Designer 10 | * 11 | * This software was written by Alexander Peslyak in 2001. No copyright is 12 | * claimed, and the software is hereby placed in the public domain. 13 | * In case this attempt to disclaim copyright and place the software in the 14 | * public domain is deemed null and void, then the software is 15 | * Copyright (c) 2001 Alexander Peslyak and it is hereby released to the 16 | * general public under the following terms: 17 | * 18 | * Redistribution and use in source and binary forms, with or without 19 | * modification, are permitted. 20 | * 21 | * There's ABSOLUTELY NO WARRANTY, express or implied. 22 | * 23 | * See md5.c for more information. 24 | */ 25 | 26 | #ifndef _MD5_H 27 | #define _MD5_H 28 | 29 | /* Any 32-bit or wider unsigned integer data type will do */ 30 | typedef unsigned int MD5_u32plus; 31 | 32 | typedef struct { 33 | MD5_u32plus lo, hi; 34 | MD5_u32plus a, b, c, d; 35 | unsigned char buffer[64]; 36 | MD5_u32plus block[16]; 37 | } MD5_CTX; 38 | 39 | void MD5_Init(MD5_CTX *ctx); 40 | void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); 41 | void MD5_Final(unsigned char *result, MD5_CTX *ctx); 42 | 43 | #endif /* _MD5_H */ 44 | -------------------------------------------------------------------------------- /contrib/mii-icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/contrib/mii-icon-64.png -------------------------------------------------------------------------------- /contrib/mii_emu.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | 3 | # The type as listed above 4 | Type=Application 5 | 6 | # The version of the desktop entry specification to which this file complies 7 | Version=0.8.0-1 8 | 9 | # The name of the application 10 | Name=MII Apple //e Emulator for Linux 11 | 12 | # A comment which can/will be used as a tooltip 13 | Comment=Modern, clean code, modular Apple //e Emulator 14 | 15 | # The path to the folder in which the executable is run 16 | #Path= 17 | 18 | # The executable of the application, possibly with arguments. 19 | Exec=/usr/bin/mii_emu 20 | 21 | # The name of the icon that will be used to display this entry 22 | Icon=mii-icon-64 23 | 24 | # Describes whether this application needs to be run in a terminal or not 25 | Terminal=false 26 | 27 | # Describes the categories in which this entry should be shown 28 | Categories=Emulator;System; 29 | -------------------------------------------------------------------------------- /disks/GamesWithFirmware.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/disks/GamesWithFirmware.po -------------------------------------------------------------------------------- /disks/dos33master.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/disks/dos33master.nib -------------------------------------------------------------------------------- /disks/prodos242.dsk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/disks/prodos242.dsk -------------------------------------------------------------------------------- /docs/Apple_logo_rainbow_version2_28x28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/Apple_logo_rainbow_version2_28x28.png -------------------------------------------------------------------------------- /docs/Compiling.md: -------------------------------------------------------------------------------- 1 | ## Top down view 2 | 3 |
4 | Top down view 5 | Here how it's supposed to work! 6 |
7 | 8 | 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. 9 | 10 | It evolved from the original mess, and I organized it into bits which would make porting it to other platform easier. 11 | 12 | 13 | ## How to I compile it and run it? 14 | * You need a C compiler, make, and a few libraries: 15 | * libasound2-dev [ optional, for audio ] 16 | * libgl-dev 17 | * libglu-dev 18 | * libx11-dev 19 | * pkg-config 20 | * Many of them will probably be installed already if you regulargly compile stuff. 21 | * Then just type `make` and it should compile. 22 | * To run it, just type `build-x86_64-linux-gnu/bin/mii_emu` and it should start. 23 | 24 | ### WSL 25 | MII Is reported to work on WSL, but I haven't tested it myself. You will need to install the X11 server for Windows, and set the DISPLAY variable to the correct value. I'm sure there are tutorials on the internet on how to do that. 26 | Also: 27 | * sudo apt install libasound2-plugins 28 | * nano ~/.asoundrc, and add these two lines: 29 | 30 | ``` 31 | pcm.default pulse 32 | ctl.default pulse 33 | ``` 34 | 35 | 36 | * Restart WSL 37 | 38 | 39 | ## Development 40 | * This is the first project I made using vscode as my main IDE; I used Eclipse for many years as the indexer was pretty much unbeatable (still is). 41 | * To support vscode I needed some tooling to create the '*compile_commands.json*' file, which is used by the C/C++ extension to provide intellisense. So to create that file, you use `make lsp` and it will create the file in the project directory. 42 | * Another thing vscode sucks at is Makefiles (despite the extension, building is painful and next to useless). So there is a target called `make watch` that you can run in a separate terminal to auto-build as soon as you save any files in vscode. That is when you realize for example that vscode re-save the file anytime you press control-S, regardless of wether the file has changed or not. So you end up with a lot of unnecessary builds. I'm sure there is a way to fix that, but I haven't found it yet. 43 | 44 | ## Code Style 45 | I have pretty consistent code style across my projects. 46 | * tabs=4. Just because. 47 | * 80 columns lines or so. I don't like to scroll horizontally and I like splitting my screen vertically. (also see, tabs=4!). 48 | * K&R style exclusively. None of that Allman/GNU horribleness. 49 | * GNU99 Dialect. 50 | * No Yoda, we are no longer in 1975. `if (0 == foo)` is just stupid. 51 | * I use "!!" to convert to 1 or zero from an expression. I call it the 'normalisation operator'. it is not commonly seen, apart from places like the Linux kernel. 52 | * I use *minipt.h* for protothreads. I like protothreads, and when you are aware of their limitations, they are a great tool. They are used for the video rendering, the no-slot-clock etc. 53 | 54 | ## What needs doing? 55 | * 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! 56 | * In the code there are a few bits which needs fixing 57 | * 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. 58 | 59 | -------------------------------------------------------------------------------- /docs/mui_emulator.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/mui_emulator.drawio.png -------------------------------------------------------------------------------- /docs/screen/screen_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/screen_color.png -------------------------------------------------------------------------------- /docs/screen/screen_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/screen_config.png -------------------------------------------------------------------------------- /docs/screen/screen_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/screen_green.png -------------------------------------------------------------------------------- /docs/screen/screen_mish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/screen_mish.png -------------------------------------------------------------------------------- /docs/screen/screen_total.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/screen_total.png -------------------------------------------------------------------------------- /docs/screen/v17heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v17heatmap.png -------------------------------------------------------------------------------- /docs/screen/v18colorapple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v18colorapple.png -------------------------------------------------------------------------------- /docs/screen/v18new_display.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v18new_display.gif -------------------------------------------------------------------------------- /docs/screen/v18ssc_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v18ssc_dialog.png -------------------------------------------------------------------------------- /docs/screen/v196versiontel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v196versiontel.png -------------------------------------------------------------------------------- /docs/screen/v197epistole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v197epistole.png -------------------------------------------------------------------------------- /docs/screen/v197mousecalc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v197mousecalc.png -------------------------------------------------------------------------------- /docs/screen/v19artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v19artifacts.png -------------------------------------------------------------------------------- /docs/screen/v19mega2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v19mega2.png -------------------------------------------------------------------------------- /docs/screen/v19ntsc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/v19ntsc.png -------------------------------------------------------------------------------- /docs/screen/video_main.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/docs/screen/video_main.gif -------------------------------------------------------------------------------- /libmish/.gitignore: -------------------------------------------------------------------------------- 1 | build-* 2 | doc/.tags* 3 | -------------------------------------------------------------------------------- /libmish/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright (C) 2020 Michel Pollet 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | SHELL = /bin/bash 8 | 9 | SOV = 1 10 | VERSION = 0.10 11 | PKG = 1 12 | 13 | TARGET = libmish 14 | DESC = Program Shell Access 15 | 16 | ifneq ($(CC), emcc) 17 | BASE_LDFLAGS += -lutil 18 | endif 19 | BASE_LDFLAGS += -lpthread 20 | EXTRA_LDFLAGS = $(BASE_LDFLAGS) 21 | 22 | # Auto load all the .c files dependencies, and object files 23 | LIBSRC := ${notdir ${wildcard src/*.c}} 24 | 25 | # Tell make/gcc to find the files in VPATH 26 | SRC_VPATH = src 27 | SRC_VPATH += tests 28 | vpath %.c $(SRC_VPATH) 29 | 30 | IPATH = src 31 | 32 | include ./Makefile.common 33 | 34 | TOOLS = 35 | TESTS = ${BIN}/mish_test \ 36 | ${BIN}/mish_vt_test ${BIN}/mish_cmd_test \ 37 | ${BIN}/mish_input_test \ 38 | ${BIN}/mish_argv_make_test 39 | 40 | all : tools tests 41 | 42 | debug: all ${BIN}/mish_debug_test 43 | @echo "*** mish_debug_test is dangerous, do not install anywhere" 44 | 45 | .PHONY: static shared tools tests 46 | static: $(LIB)/$(TARGET).a 47 | shared: ${LIB}/$(TARGET).so.$(SOV) 48 | tools: $(TOOLS) 49 | tests: $(TESTS) 50 | 51 | LIBOBJ := ${patsubst %, ${OBJ}/%, ${notdir ${LIBSRC:.c=.o}}} 52 | $(LIBOBJ) : | $(OBJ) 53 | 54 | $(LIB)/$(TARGET).a : $(LIBOBJ) | $(LIB) 55 | $(LIB)/$(TARGET).so.$(SOV) : $(LIBOBJ) | $(LIB)/$(TARGET).a 56 | 57 | # This rule OUGHT to be enough, but somehow it fails to take into account 58 | # the 'shared' dependency, if its written explicitely as it is now, it 59 | # works. I see no reason why it should not work, as the one a line down 60 | # has no problem!! 61 | #$(BIN)/%: | shared 62 | $(TOOLS) $(TESTS): | shared 63 | ifeq ($(CC), emcc) 64 | $(BIN)/%: LDFLAGS_TARGET+=-L${LIB} 65 | endif 66 | $(BIN)/%: LDFLAGS_TARGET += -lmish -lrt 67 | 68 | $(BIN)/mish_input_test: LDFLAGS_TARGET = 69 | 70 | clean:: 71 | rm -f $(LIB)/$(TARGET).* $(TOOLS) $(TESTS) 72 | 73 | 74 | install: 75 | mkdir -p $(DESTDIR)/bin/ $(DESTDIR)/lib/ $(DESTDIR)/include/ 76 | for bin in $(TOOLS); do $(INSTALL) -m 0755 $$bin $(DESTDIR)/bin/$$(basename $$bin) ; done 77 | $(INSTALL) -m 644 $(LIB)/$(TARGET).a $(DESTDIR)/lib/ 78 | $(INSTALL) -m 644 src/mish.h $(DESTDIR)/include/ 79 | rm -rf $(DESTDIR)/lib/$(TARGET).so* 80 | $(INSTALL) -m 644 $(LIB)/$(TARGET).so.$(VERSION).$(SOV) $(DESTDIR)/lib/ 81 | cp -f -d $(LIB)/{$(TARGET).so.$(SOV),$(TARGET).so} $(DESTDIR)/lib/ 82 | mkdir -p $(DESTDIR)/lib/pkgconfig 83 | sed -e "s|PREFIX|$(PREFIX)|" -e "s|VERSION|$(VERSION)|" \ 84 | mish.pc >$(DESTDIR)/lib/pkgconfig/mish.pc 85 | 86 | deb : 87 | rm -rf /tmp/$(TARGET) *.deb 88 | make clean && make all && make install DESTDIR=/tmp/$(TARGET)/usr 89 | mkdir -p $(BUILD)/debian; (cd $(BUILD)/debian; \ 90 | fpm -s dir -t deb -C /tmp/$(TARGET) -n $(TARGET) -v $(VERSION) \ 91 | --iteration $(PKG) \ 92 | --description "$(DESC)" \ 93 | usr/lib/{$(TARGET).so.$(SOV),$(TARGET).so.$(VERSION).$(SOV),$(TARGET).so} && \ 94 | fpm -s dir -t deb -C /tmp/$(TARGET) -n $(TARGET)-dev -v $(VERSION) \ 95 | --iteration $(PKG) \ 96 | --description "$(DESC) - development files" \ 97 | -d "$(TARGET) (= $(VERSION))" \ 98 | usr/lib/$(TARGET).a \ 99 | usr/lib/pkgconfig \ 100 | usr/include ; \ 101 | ) 102 | -------------------------------------------------------------------------------- /libmish/Makefile.common: -------------------------------------------------------------------------------- 1 | # Makefile.common 2 | # 3 | # Copyright (C) 2012 Michel Pollet 4 | # 5 | 6 | SHELL = /bin/bash 7 | INSTALL ?= install 8 | CC ?= gcc 9 | CFLAGS ?= -O2 -Wall 10 | 11 | O ?= . 12 | PREFIX ?= /usr/local 13 | 14 | ARCH := $(shell $(CC) -dumpmachine) 15 | BUILDNAME := build-$(ARCH) 16 | BUILD := $(O)/$(BUILDNAME) 17 | OBJ = $(BUILD)/obj/$(TARGET) 18 | BIN = $(BUILD)/bin 19 | LIB = $(BUILD)/lib 20 | 21 | DESTDIR = $(O)/build-$(ARCH) 22 | 23 | EXTRA_CFLAGS += -g -fPIC --std=gnu99 24 | EXTRA_CFLAGS += -ffunction-sections -fdata-sections 25 | EXTRA_CFLAGS += ${patsubst %,-I%,${subst :, ,${IPATH}}} 26 | 27 | ifeq (${shell uname},Linux) 28 | EXTRA_LDFLAGS += -Wl,--relax,--gc-sections 29 | READLINK = readlink -f 30 | endif 31 | ifeq (${shell uname},Darwin) 32 | READLINK = echo 33 | endif 34 | 35 | ifeq ($(V),1) 36 | Q := 37 | else 38 | Q := @ 39 | endif 40 | 41 | EXTRA_LDFLAGS += -L$(LIB) -Wl,-rpath,${shell $(READLINK) ${LIB}} 42 | 43 | all: ${OBJ} 44 | 45 | # 46 | # Generic rules 47 | # 48 | $(OBJ) $(BIN) $(LIB): 49 | +$(Q)echo " MKDIR" $@ && mkdir -p $@ 50 | 51 | $(LIB)/%.a : | $(LIB) 52 | $(Q)echo " LIB ${@}" && \ 53 | $(AR) cr $@ $^ \ 54 | || echo Error: $(AR) cru $@ $^ 55 | 56 | .SECONDARY: 57 | 58 | $(OBJ)/%.o: %.c | $(OBJ) 59 | $(Q)echo " CC" ${filter -O%, ${EXTRA_CFLAGS} $(CFLAGS) ${CFLAGS_TARGET}} "${<}" && \ 60 | ${CC} -MMD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<} \ 61 | || echo Error: ${CC} -MD ${EXTRA_CFLAGS} ${CPPFLAGS} ${CFLAGS} ${CFLAGS_TARGET} -c -o ${@} ${<} 62 | 63 | $(BIN)/%: $(OBJ)/%.o | $(BIN) 64 | $(Q)echo " LD ${*}" && \ 65 | ${CC} -o ${@} $(filter %.o %.a,$^) \ 66 | ${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS_TARGET} ${LDFLAGS} ${LIBS_${*}} \ 67 | || echo Error: ${CC} -o $(filter %.o %.a,$^) \ 68 | ${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS_TARGET} ${LDFLAGS} ${LIBS_${*}} 69 | 70 | ifeq (${shell uname},Linux) 71 | define soname 72 | "-Wl,-soname,$(1)" 73 | endef 74 | else 75 | define soname 76 | endef 77 | endif 78 | 79 | $(LIB)/%.so.$(SOV) $(LIB)/%.so.$(VERSION).$(SOV) $(LIB)/%.so: | $(LIB) 80 | $(Q)echo " LDSO $@" && \ 81 | { \ 82 | naked=${shell basename ${basename $@}}; \ 83 | ${CC} -shared \ 84 | ${call soname,$$naked} \ 85 | -o $(LIB)/$$naked.$(VERSION).$(SOV) \ 86 | $(filter %.o %.a,$^) \ 87 | ${EXTRA_LDFLAGS} ${BASE_LDFLAGS} ${LDFLAGS} && \ 88 | ln -sf $$naked.$(VERSION).$(SOV) ${LIB}/$$naked.$(SOV) && \ 89 | ln -sf $$naked.$(SOV) ${LIB}/$$naked ; \ 90 | } || echo ERROR: $@ -- relaunch with V=1 91 | 92 | clean:: 93 | # rm -rf $(OBJ) $(BIN)/$(TARGET) 94 | rm -rf $(BUILD) 95 | 96 | # Include autogenerated dependencies 97 | -include ${wildcard $(OBJ)/*.d} 98 | -------------------------------------------------------------------------------- /libmish/doc/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2008, 2009 Michel Pollet 3 | # 4 | # This file is part of libmish. 5 | # 6 | # SPDX-License-Identifier: Apache-2.0 7 | 8 | # For the callgraph, you need Graphviz, ruby and exuberant ctags. 9 | # This is not generated in the normal code build 10 | 11 | 12 | all: libmish_callgraph.pdf 13 | 14 | libmish_callgraph.pdf: 15 | ctags -f .tags ../src/*.[ch] 2>/dev/null && \ 16 | ruby ./tags_to_dot.rb .tags \ 17 | ../src/*.c ../src/mish.h >.tags.dot && \ 18 | dot -Tpdf .tags.dot -o $@ 19 | 20 | clean: 21 | rm -f .tags* 22 | -------------------------------------------------------------------------------- /libmish/doc/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmish/doc/demo.gif -------------------------------------------------------------------------------- /libmish/doc/libmish_callgraph.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmish/doc/libmish_callgraph.pdf -------------------------------------------------------------------------------- /libmish/doc/tags_to_dot.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # 3 | # Copyright 2008, 2009 Michel Pollet 4 | # 5 | # This file is part of libmish. 6 | # 7 | # SPDX-License-Identifier: Apache-2.0 8 | 9 | $files = Hash.new 10 | $syms = Hash.new 11 | 12 | tags = File.new(ARGV[0]) 13 | 14 | key = Array.new 15 | 16 | while !tags.eof? 17 | next unless tags.readline.chomp.match(/([^\t]+)\t([^\t]+)\t(.*)\$\/;"\t([^\t]+)/); 18 | key[0] = $1; 19 | key[1] = $2; 20 | key[3] = $3; 21 | key[4] = $4; 22 | 23 | next if key[4] == 'm' or key[4] == 't' or key[4] == 's' or key[4] == 'e' or key[4] == 'v'; 24 | next if key[0].match(/[us]?int[0-9]+_t/); 25 | next if key[0] == "ROM_BASED"; 26 | 27 | key[1].gsub!(/.*\/|\.[ch]$/,""); 28 | 29 | unless $files.key? key[1] 30 | $files[key[1]] = Hash.new 31 | end 32 | unless $files[key[1]].key? key[0] 33 | $files[key[1]][key[0]] = Hash.new 34 | $syms[key[0]] = key[1] 35 | end 36 | #puts key[0] + " = '#{key[4]}'" 37 | end 38 | 39 | puts "digraph dump { node [shape=rect]; compound=true; nodesep=.1; ranksep=2; rankdir=LR; concentrate=true; " 40 | 41 | modules = Hash.new; 42 | links = Array.new; 43 | 44 | 1.upto(ARGV.length-1) { |i| 45 | 46 | use = File.new(ARGV[i]) 47 | # puts "<<<<<<<>>" + line 58 | words = line.split(/[ \t]+/); 59 | words.each { |w| 60 | if $syms.key? w and $syms[w] != fil 61 | unless $files[$syms[w]][w].key? fil 62 | # puts w + " is in " + $syms[w] 63 | $files[$syms[w]][w][fil] = 1 64 | 65 | sym=w 66 | unless modules.key? fil 67 | modules[fil] = Array.new 68 | end 69 | modules[fil] += [ "\"cc_#{fil}_#{sym}\" [label=\"#{sym}\",color=\"gray\",height=\".08\",style=dotted];" ] 70 | links += ["\"cc_#{fil}_#{sym}\" -> \"dd_#{$syms[w]}_#{sym}\";"] 71 | end 72 | end 73 | } 74 | end 75 | } 76 | 77 | $files.keys.each { |fil| 78 | # puts "File #{fil} ?" 79 | $files[fil].keys.each { |sym| 80 | # puts "\tSym #{sym} : #{$files[fil][sym].length} ?" 81 | if $files[fil][sym].length > 0 82 | unless modules.key? fil 83 | modules[fil] = Array.new 84 | end 85 | modules[fil] += [ "\"dd_#{fil}_#{sym}\" [label=\"#{sym}\"];" ] 86 | end 87 | } 88 | } 89 | modules.keys.each {|fil| 90 | puts "\tsubgraph cluster_#{fil} {\n\t\tlabel=\"#{fil}\"; fontsize=\"18\"; " 91 | modules[fil].each { |lin| 92 | puts "\t\t#{lin}" 93 | } 94 | puts "\t}" 95 | } 96 | links.each { |lin| 97 | puts "\t#{lin}" 98 | } 99 | puts "}" 100 | -------------------------------------------------------------------------------- /libmish/mish.pc: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | mish.pc: 4 | prefix=PREFIX 5 | exec_prefix=${prefix} 6 | includedir=${prefix}/include 7 | libdir=${exec_prefix}/lib 8 | 9 | Name: libmish 10 | Description: Adds command prompt for your program(s) 11 | Version: VERSION 12 | Cflags: -I${includedir} 13 | Libs: -L${libdir} -lmish -lrt 14 | -------------------------------------------------------------------------------- /libmish/src/minipt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * minipt.h 3 | * 4 | * Created on: 1 Apr 2018 5 | * Copyright (C) 2020 Michel Pollet 6 | * 7 | * SPDX-License-Identifier: Apache-2.0 8 | */ 9 | 10 | #ifndef MPTOOLS_INCLUDE_MINIPT_H_ 11 | #define MPTOOLS_INCLUDE_MINIPT_H_ 12 | 13 | /* 14 | * Mini Protothread. 15 | * 16 | * A thread or a coroutine would use a stack; this won't, 17 | * Use an old gcc trick of being able to goto and indirect label. 18 | * There are a few caveats: no persistent local variables, as you can't 19 | * have a consistent stack frame. It's easy to work around tho. 20 | */ 21 | #define _CONCAT2(s1, s2) s1##s2 22 | #define _CONCAT(s1, s2) _CONCAT2(s1, s2) 23 | 24 | /* this wierd thing with the union is for gcc 12, which doesn't like us 25 | * storing the address of a 'local variable' (which is the label!) */ 26 | static inline void _set_gcc_ptr_workaround(void **d, void *s) { 27 | #pragma GCC diagnostic push 28 | #pragma GCC diagnostic ignored "-Wdangling-pointer" 29 | *d = s; 30 | #pragma GCC diagnostic pop 31 | } 32 | #define pt_start(_pt) do { \ 33 | if (_pt) goto *_pt; \ 34 | } while (0); 35 | #define pt_end(_pt) do { \ 36 | (_pt) = NULL; \ 37 | _pt_exit: ; \ 38 | } while(0); 39 | #define pt_yield(_pt) do { \ 40 | _set_gcc_ptr_workaround(&(_pt), &&_CONCAT(_label, __LINE__));\ 41 | goto _pt_exit;\ 42 | _CONCAT(_label, __LINE__): ; \ 43 | } while (0); 44 | 45 | #define pt_wait(_pt, _condition) do { \ 46 | while (!(_condition)) \ 47 | pt_yield(_pt); \ 48 | } while (0); 49 | 50 | 51 | #ifdef NEVER 52 | /* 53 | * This version is superior as it allows calling functions and keeping 54 | * a context, but I never actually had a /need/ for this, yet 55 | */ 56 | 57 | struct pt_t { 58 | unsigned int sp; 59 | void * st[32]; 60 | void * ctx[32]; 61 | } pt_t; 62 | 63 | #define pt_start(_pt) do { \ 64 | if ((_pt)->st[(_pt)->sp]) goto *((_pt)->st[(_pt)->sp]); \ 65 | } while (0); 66 | #define pt_end(_pt) do { (_pt)->st[(_pt)->sp] = NULL; return; } while(0); 67 | #define pt_yield(_pt) do { \ 68 | (_pt)->st[(_pt)->sp] = &&_CONCAT(_label, __LINE__);\ 69 | return;\ 70 | _CONCAT(_label, __LINE__): ; \ 71 | } while (0); 72 | #define pt_wait(_pt, _condition) do { \ 73 | while (!(_condition)) \ 74 | pt_yield(_pt); \ 75 | } while (0); 76 | #define pt_call(_pt, _func) do { \ 77 | (_pt)->sp++; \ 78 | (_pt)->st[(_pt)->sp] = NULL; \ 79 | do { \ 80 | _func(_pt); \ 81 | } while ((_pt)->st[(_pt)->sp]); \ 82 | (_pt)->sp--; \ 83 | } while (0); 84 | #define pt_ctx(_pt) ((_pt)->ctx[(_pt)->sp]) 85 | 86 | void my_minit_func(struct pt_t * p) { 87 | pt_start(p); 88 | pt_ctx(p) = calloc(1, sizeof(int)); 89 | printf("%s start %p\n", __func__, pt_ctx(p)); 90 | pt_yield(p); 91 | int * ctx = pt_ctx(p); 92 | printf("%s loop %p\n", __func__, pt_ctx(p)); 93 | for (; *ctx < 10; ctx[0]++) { 94 | printf(" loop %d\n", *ctx); 95 | pt_yield(p); 96 | ctx = pt_ctx(p); 97 | } 98 | printf("%s done %p\n", __func__, pt_ctx(p)); 99 | free(pt_ctx(p)); 100 | pt_ctx(p) = NULL; 101 | pt_end(p); 102 | } 103 | 104 | void my_minit(struct pt_t * p) { 105 | pt_start(p); 106 | printf("%s start\n", __func__); 107 | pt_call(p, my_minit_func); 108 | printf("%s done\n", __func__); 109 | pt_end(p); 110 | } 111 | 112 | int main() { 113 | struct pt_t pt = {}; 114 | 115 | pt_call(&pt, my_minit); 116 | } 117 | /* 118 | tcc -run pt_call_test.c 119 | my_minit start 120 | my_minit_func start 0x555a68d970b0 121 | my_minit_func loop 0x555a68d970b0 122 | loop 0 123 | loop 1 124 | loop 2 125 | loop 3 126 | loop 4 127 | loop 5 128 | loop 6 129 | loop 7 130 | loop 8 131 | loop 9 132 | my_minit_func done 0x555a68d970b0 133 | my_minit done 134 | */ 135 | 136 | #endif // NEVER 137 | 138 | #endif /* MPTOOLS_INCLUDE_MINIPT_H_ */ 139 | -------------------------------------------------------------------------------- /libmish/src/mish_cmd_env.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_cmd_env.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include "mish.h" 13 | 14 | extern char ** environ; 15 | 16 | static void 17 | _mish_cmd_env( 18 | void * param, 19 | int argc, 20 | const char * argv[]) 21 | { 22 | if (argc < 2) { 23 | for (int i = 0; environ[i]; i++) 24 | if (strncmp(environ[i], "LS_COLORS=", 10)) 25 | printf("%s\n", environ[i]); 26 | return; 27 | } 28 | for (int i = 0; environ[i]; i++) 29 | for (int ei = 1; ei < argc; ei++) { 30 | int l = strlen(argv[ei]); 31 | if (!strncmp(environ[i], argv[ei], l)) 32 | printf("%s\n", environ[i]); 33 | } 34 | } 35 | 36 | MISH_CMD_NAMES(env, "env"); 37 | MISH_CMD_HELP(env, 38 | "[names ...] display all environment, or variables", 39 | "Apart from LS_COLORS: that is just spam.", 40 | "If you specify names it'll just show the ones whose name", 41 | "start with that prefix"); 42 | MISH_CMD_REGISTER(env, _mish_cmd_env); 43 | 44 | static void 45 | _mish_cmd_setenv( 46 | void * param, 47 | int argc, 48 | const char * argv[]) 49 | { 50 | for (int ei = 1; ei < argc; ei++) { 51 | char *equal = strchr(argv[ei], '='); 52 | if (!equal) { 53 | printf("mish: setenv: '%s' requires an '='\n", argv[ei]); 54 | return; 55 | } 56 | *equal++ = 0; 57 | printf("mish: %s%s%s%s\n", *equal ? "" : "unset ", 58 | argv[ei], *equal ? " = ": "", 59 | *equal ? equal : ""); 60 | if (!*equal) 61 | unsetenv(argv[ei]); 62 | else 63 | setenv(argv[ei], equal, 1); 64 | } 65 | } 66 | 67 | MISH_CMD_NAMES(setenv, "setenv"); 68 | MISH_CMD_HELP(setenv, 69 | "[=...] set/clear environment variable(s)", 70 | "Set to .. if is omitted, clears it.", 71 | "The '=' is required, even when clearing."); 72 | MISH_CMD_REGISTER(setenv, _mish_cmd_setenv); 73 | -------------------------------------------------------------------------------- /libmish/src/mish_input.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_input.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "mish_priv.h" 16 | #include "mish_priv_line.h" 17 | 18 | #ifdef MISH_INPUT_TEST 19 | #define D(_w) _w 20 | #else 21 | #define D(_w) 22 | #endif 23 | 24 | void 25 | _mish_input_init( 26 | mish_p m, 27 | mish_input_p in, 28 | int fd) 29 | { 30 | TAILQ_INIT(&in->backlog); 31 | // fprintf(stderr, "%s %d\n", __func__, fd); 32 | in->fd = fd; 33 | in->line = NULL; 34 | FD_SET(in->fd, &m->select.read); 35 | if (in->fd >= m->select.max - 1) 36 | m->select.max = in->fd +1; 37 | 38 | int flags = fcntl(fd, F_GETFL, NULL); 39 | if (flags == 1) { 40 | // perror("mish: input F_GETFL"); 41 | flags = 0; 42 | } 43 | if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) 44 | perror("mish: input F_SETFL"); 45 | } 46 | 47 | void 48 | _mish_input_clear( 49 | mish_p m, 50 | mish_input_p in) 51 | { 52 | mish_line_p l; 53 | while ((l = TAILQ_FIRST(&in->backlog)) != NULL) { 54 | TAILQ_REMOVE(&in->backlog, l, self); 55 | free(l); 56 | } 57 | if (in->line) 58 | free(in->line); 59 | in->line = NULL; 60 | if (in->fd != -1) { 61 | FD_CLR(in->fd, &m->select.read); 62 | FD_CLR(in->fd, &m->select.write); 63 | close(in->fd); 64 | in->fd = -1; 65 | } 66 | } 67 | 68 | int 69 | _mish_input_read( 70 | mish_p m, 71 | fd_set * fds, 72 | mish_input_p in) 73 | { 74 | if (!FD_ISSET(in->fd, fds)) 75 | return 0; 76 | do { 77 | if (_mish_line_reserve(&in->line, 80)) { 78 | D(printf(" reserve bailed us\n");) 79 | break; 80 | } 81 | ssize_t rd = read(in->fd, 82 | in->line->line + in->line->len, 83 | in->line->size - in->line->len - 1); 84 | if (rd == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) 85 | break; 86 | if (rd <= 0) { 87 | close(in->fd); 88 | FD_CLR(in->fd, &m->select.read); 89 | FD_CLR(in->fd, &m->select.write); 90 | in->fd = -1; 91 | printf(MISH_COLOR_RED "mish: telnet: disconnected" 92 | MISH_COLOR_RESET "\n"); 93 | return -1; 94 | } 95 | in->line->len += rd; 96 | } while (1); 97 | uint8_t * s = (uint8_t*) in->line->line + in->line->done; 98 | uint8_t * d = s; 99 | int added = in->line->len - in->line->done; 100 | D(printf(" buffer added %d done %d len %d size %d\n", added, 101 | (int)in->line->done, (int)in->line->len, (int)in->line->size); 102 | if (in->line->done) 103 | printf(" buffer: '%.*s'\n", in->line->done, in->line->line);) 104 | /* 105 | * This loop re-parse the data, passes it to the optional handler, 106 | * and then store processed lines into the backlog. This is not super 107 | * optimal in the case there isn't a process_char() callback, so perhaps 108 | * I should make another less 'generic' input parser that doesn't copy 109 | * stuff around (in place, often). 110 | */ 111 | while (added) { 112 | int r = MISH_IN_STORE; 113 | if (in->process_char) 114 | r = in->process_char(m, in, *s); 115 | else 116 | r = *s == '\n' ? MISH_IN_SPLIT : MISH_IN_STORE; 117 | 118 | if (r == MISH_IN_STORE || r == MISH_IN_SPLIT) { 119 | *d++ = *s; 120 | in->line->done++; 121 | } 122 | if (r == MISH_IN_SPLIT) { 123 | D(printf(" split size %d remains %d : '%.*s'\n", in->line->done, 124 | (int)added, in->line->done-1, in->line->line);) 125 | _mish_line_add(&in->backlog, in->line->line, in->line->done); 126 | d = (uint8_t*)in->line->line; 127 | in->line->len = in->line->done = 0; 128 | } 129 | s++; added--; 130 | } 131 | *d = 0; // NUL terminate for debug purpose! 132 | D(printf(" exit added %d done %d len %d size %d\n", added, 133 | (int)in->line->done, (int)in->line->len, (int)in->line->size); 134 | in->line->len = in->line->done; 135 | if (in->line->done) 136 | printf(" buffer: '%s'\n", in->line->line);) 137 | 138 | return TAILQ_FIRST(&in->backlog) != NULL; 139 | } 140 | -------------------------------------------------------------------------------- /libmish/src/mish_line.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_line.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #include 10 | #include 11 | #include "mish_priv.h" 12 | #include "mish_priv_line.h" 13 | 14 | static const mish_line_t zero = {}; 15 | 16 | mish_line_p 17 | _mish_line_add( 18 | mish_line_queue_t * q, 19 | char * buffer, 20 | size_t length ) 21 | { 22 | size_t l = length + 1; 23 | mish_line_p nl = (mish_line_p)malloc(sizeof(*nl) + l); 24 | *nl = zero; 25 | nl->size = l; 26 | nl->len = length; 27 | memcpy(nl->line, buffer, length); 28 | nl->line[l] = 0; 29 | nl->stamp = _mish_stamp_ms(); 30 | TAILQ_INSERT_TAIL(q, nl, self); 31 | return nl; 32 | } 33 | 34 | int 35 | _mish_line_reserve( 36 | mish_line_p *line, 37 | uint32_t count) 38 | { 39 | if (!line) 40 | return -1; 41 | mish_line_p l = *line; 42 | if (count < 40) 43 | count = 40; 44 | if (!l) { 45 | l = (mish_line_p)calloc(1, sizeof(mish_line_t) + count); 46 | *l = zero; 47 | l->size = count; 48 | *line = l; 49 | } 50 | if (l->size + count >= MISH_MAX_LINE_SIZE) 51 | return 1; 52 | if (l->size - l->len < count) { 53 | l = (mish_line_p)realloc(l, 54 | sizeof(mish_line_t) + l->size + count); 55 | l->size += count; 56 | *line = l; 57 | } 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /libmish/src/mish_priv_cmd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_priv_cmd.h 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #ifndef LIBMISH_SRC_MISH_PRIV_CMD_H_ 10 | #define LIBMISH_SRC_MISH_PRIV_CMD_H_ 11 | 12 | /* 13 | * I decided that the command list would't be attached to the mish_t, 14 | * 15 | * In any other API I would, for consistency sake, but here I'm more 16 | * interested in having a convenient way to add commands, regardless of the 17 | * state of mish_t, and have macro that register them before main() is called 18 | * and that sort of things. 19 | * 20 | * In the same vein, I also don't provide a way to remove a command, I don't 21 | * think it's terribly necessary at the minute 22 | */ 23 | #include "bsd_queue.h" 24 | 25 | typedef void (*mish_cmd_handler_p)( 26 | void * param, 27 | int argc, 28 | const char *argv[]); 29 | #define _LIBMISH_HAS_CMD_HANDLER_ 30 | 31 | int 32 | mish_cmd_call( 33 | const char * cmd_line, 34 | void * c); 35 | 36 | 37 | #endif /* LIBMISH_SRC_MISH_PRIV_CMD_H_ */ 38 | -------------------------------------------------------------------------------- /libmish/src/mish_priv_line.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_priv_line.h 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #ifndef LIBMISH_SRC_MISH_PRIV_LINE_H_ 10 | #define LIBMISH_SRC_MISH_PRIV_LINE_H_ 11 | 12 | #include 13 | #include "bsd_queue.h" 14 | 15 | #define MISH_MAX_LINE_SIZE 0xffff 16 | 17 | struct mish_line_t { 18 | TAILQ_ENTRY(mish_line_t) self; 19 | /* The stamp is made with _mish_get_stamp, which returns epoch 20 | * milliseconds, so we don't need the 64 bits for the stamp here, 21 | * lets say 34 bits seconds + 10 bits milliseconds 22 | */ 23 | uint64_t stamp : 44; 24 | uint64_t err: 1, use: 4, draw_stamp: 1, 25 | size : 16, len: 16, // len <= size 26 | done: 16; // done <= len 27 | char line[0]; 28 | }; 29 | 30 | typedef struct mish_line_t mish_line_t, *mish_line_p; 31 | 32 | typedef TAILQ_HEAD(mish_line_queue_t, mish_line_t) mish_line_queue_t; 33 | 34 | /* 35 | * mish_line tools 36 | */ 37 | int 38 | _mish_line_reserve( 39 | mish_line_p *line, 40 | uint32_t count); 41 | mish_line_p 42 | _mish_line_add( 43 | mish_line_queue_t * q, 44 | char * buffer, 45 | size_t length ); 46 | 47 | #endif /* LIBMISH_SRC_MISH_PRIV_LINE_H_ */ 48 | -------------------------------------------------------------------------------- /libmish/src/mish_priv_vt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_priv_vt.h 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #ifndef LIBMISH_SRC_MISH_PRIV_VT_H_ 10 | #define LIBMISH_SRC_MISH_PRIV_VT_H_ 11 | 12 | #include 13 | 14 | /* 15 | * Input sequence decoder, deals with all the ESC bits and 16 | * any UTF8 bits that gets encountered 17 | */ 18 | #define MISH_VT_RAW 0 19 | #define MISH_VT_TELNET (0xff) 20 | #define MISH_VT_ESC (0x1b) 21 | #define MISH_VT_CSI ((MISH_VT_ESC << 8) | ('[')) 22 | #define MISH_VT_CSIQ ((MISH_VT_CSI << 8) | ('?')) 23 | #define MISH_VT_UTF8 0x80 24 | 25 | // allow writing MISH_VT_SEQ(CSI, 'H') 26 | #define MISH_VT_SEQ(_kind, _ch) (((MISH_VT_ ## _kind) << 8) | (_ch)) 27 | 28 | typedef struct mish_vt_sequence_t { 29 | uint32_t seq; // ESC, CSI, CSI? 30 | int p[8]; 31 | union { 32 | struct { 33 | uint32_t pc : 4, // CSI; parameter count 34 | seq_want : 4, // how many more bytes we want (UTF8) 35 | done : 1, // sequence is done 36 | error : 1; // sequence was not valid 37 | }; 38 | uint32_t flags; 39 | }; 40 | uint32_t glyph; // for RAW and UTF8 41 | } mish_vt_sequence_t, *mish_vt_sequence_p; 42 | 43 | int 44 | _mish_vt_sequence_char( 45 | mish_vt_sequence_p s, 46 | uint8_t ch); 47 | 48 | #endif /* LIBMISH_SRC_MISH_PRIV_VT_H_ */ 49 | -------------------------------------------------------------------------------- /libmish/src/mish_vt.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_vt.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #include 10 | #include "mish_priv_vt.h" 11 | 12 | #ifndef ARRAY_SIZE 13 | #define ARRAY_SIZE(_a) (sizeof(_a) / sizeof((_a)[0])) 14 | #endif 15 | 16 | /* 17 | * Handle sequences of VT100 and UTF8 chars. 18 | * 19 | * Uses the sequence 's' as temporary buffer, and returns 1 when a complete 20 | * sequence OR a glyph has been decoded. 21 | */ 22 | int 23 | _mish_vt_sequence_char( 24 | mish_vt_sequence_p s, 25 | uint8_t ch) 26 | { 27 | // if previous sequence was done, reset it. 28 | if (s->done) 29 | s->flags = s->seq = s->glyph = s->pc = s->p[0] = 0; 30 | switch (s->seq) { 31 | case MISH_VT_RAW: { 32 | switch (ch) { 33 | case 0x1b: 34 | s->seq = MISH_VT_ESC; 35 | break; 36 | default: { 37 | if (ch & 0x80) { // UTF8 !??! 38 | int mask = 0x40; 39 | s->seq_want = 0; 40 | while ((ch & mask) && s->seq_want < 5) { 41 | s->seq_want++; 42 | mask >>= 1; 43 | } 44 | // clear header bits (ought to be 2; 1 allows encoding of 0xffffff) 45 | s->glyph = ch & (0xff >> (s->seq_want + 1)); 46 | s->seq = MISH_VT_UTF8; 47 | } else { 48 | s->seq = (s->seq << 8) | ch; 49 | s->glyph = ch; // technically it's a glyph 50 | s->done = 1; 51 | } 52 | } 53 | } 54 | } break; 55 | case MISH_VT_ESC: { 56 | if (ch == '[') 57 | s->seq = MISH_VT_CSI; 58 | else { 59 | s->seq = (s->seq << 8) | ch; 60 | s->done = 1; 61 | } 62 | } break; 63 | case MISH_VT_CSIQ: 64 | case MISH_VT_CSI: { 65 | switch (ch) { 66 | case '?': { 67 | // note this would still accept somelike like CSI ? 00?; 1 68 | if ((s->seq == MISH_VT_CSIQ) || s->pc) { 69 | s->done = s->error = 1; 70 | } else 71 | s->seq = MISH_VT_CSIQ; 72 | } break; 73 | case '0' ... '9': 74 | s->p[s->pc] = (s->p[s->pc] * 10) + (ch - '0'); 75 | break; 76 | case ';': { 77 | if (s->pc < ARRAY_SIZE(s->p)) { 78 | s->pc++; 79 | s->p[s->pc] = 0; 80 | } else { 81 | s->done = s->error = 1; 82 | } 83 | } break; 84 | default: 85 | if (s->p[s->pc]) 86 | s->pc++; 87 | s->seq = (s->seq << 8) | ch; 88 | s->done = 1; 89 | break; 90 | } 91 | } break; 92 | case MISH_VT_UTF8: { 93 | s->glyph = (s->glyph << 6) | (ch & 0x3f); 94 | if (s->seq_want) s->seq_want--; 95 | s->done = s->seq_want == 0; 96 | } break; 97 | } 98 | #ifdef MISH_VT_DEBUG 99 | if (s->done) 100 | fprintf(stderr, "in %x(%c) state %x done:%d error:%d pc:%d/%d\n", 101 | ch, ch < ' '?'.':ch,s->seq, s->done, s->error, 102 | s->pc, (int)ARRAY_SIZE(s->p)); 103 | #endif 104 | return s->done; 105 | } 106 | -------------------------------------------------------------------------------- /libmish/tests/mish_argv_make_test.c: -------------------------------------------------------------------------------- 1 | // small test unit for mish_argv_make 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | typedef struct _mish_argv_t { 8 | char * line; 9 | int ac; 10 | char * av[0]; 11 | } _mish_argv_t; 12 | /* 13 | * Duplicate 'line', split it into words, store word pointers in an array, 14 | * NULL terminate it. Also return the number of words in the array in argc. 15 | * 16 | * The returned value is made of two malloc()ed blocks. use mish_argv_free 17 | * to free the memory. 18 | * It's OK to change any of the pointers. But no not try to realloc() the 19 | * vector as it hides a structure 20 | */ 21 | static char ** 22 | mish_argv_make( 23 | const char * line, 24 | int * argc ) 25 | { 26 | const char separator = ' '; 27 | _mish_argv_t * r = calloc(1, sizeof(*r)); 28 | r->line = strdup(line); 29 | char *dup = r->line; 30 | char quote; 31 | enum { s_newarg = 0, s_startarg, s_copyquote, s_skip, s_copy }; 32 | int state = s_newarg; 33 | do { 34 | switch (state) { 35 | case s_newarg: 36 | r = realloc(r, sizeof(*r) + ((r->ac + 2) * sizeof(char*))); 37 | while (*dup == ' ' || *dup == separator) 38 | dup++; 39 | r->av[r->ac++] = dup; 40 | state = s_startarg; 41 | break; 42 | case s_startarg: 43 | if (*dup == '"' || *dup == '\'') { 44 | quote = *dup++; 45 | state = s_copyquote; 46 | } else 47 | state = s_copy; 48 | break; 49 | case s_copyquote: 50 | if (*dup == '\\') { 51 | state = s_skip; 52 | dup++; 53 | } else if (*dup == quote) { 54 | state = s_newarg; 55 | dup++; 56 | if (*dup) *dup++ = 0; 57 | } else if (*dup) 58 | dup++; 59 | break; 60 | case s_skip: 61 | dup++; 62 | state = s_copyquote; 63 | break; 64 | case s_copy: 65 | if (*dup == 0) 66 | break; 67 | if (*dup != separator) 68 | dup++; 69 | else { 70 | state = s_newarg; 71 | if (*dup) *dup++ = 0; 72 | } 73 | break; 74 | } 75 | } while (*dup); 76 | r->av[r->ac] = NULL; 77 | if (argc) 78 | *argc = r->ac; 79 | return r->av; 80 | } 81 | 82 | int main() { 83 | int argc = 0; 84 | char **argv = mish_argv_make("testing \"one escape two\" lala ", &argc); 85 | 86 | printf("argc = %d\n", argc); 87 | for (int i = 0; argv[i]; i++) 88 | printf("%2d:'%s'\n", i, argv[i]); 89 | 90 | argv = mish_argv_make("command with some \" quoted\\\"words \" should work\n", &argc); 91 | 92 | printf("argc = %d\n", argc); 93 | for (int i = 0; argv[i]; i++) 94 | printf("%2d:'%s'\n", i, argv[i]); 95 | 96 | } 97 | -------------------------------------------------------------------------------- /libmish/tests/mish_cmd_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_cmd_test.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | #define MISH_DEBUG 1 9 | #include "mish_cmd.c" 10 | 11 | int main() 12 | { 13 | int argc; 14 | char ** argv = mish_argv_make( 15 | "command with some \" quoted\\\"words \" should work\n", &argc); 16 | 17 | for (int i = 0; i < argc; i++) 18 | printf("%2d: '%s'\n", i, argv[i]); 19 | mish_argv_free(argv); 20 | } 21 | -------------------------------------------------------------------------------- /libmish/tests/mish_debug_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_debug_test.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | /* 10 | * NOTE this program is made for debug purpose only, it basically give you 11 | * telnet command access remotely 12 | * IT IS JUST MADE TO DEBUG LIBMISH, DO NOT USE IN PROD! 13 | */ 14 | #define _GNU_SOURCE // for asprintf 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "mish.h" 23 | 24 | static void 25 | sigchld_handler(int s) 26 | { 27 | int status = 0; 28 | pid_t pid = waitpid(-1, &status, WNOHANG); 29 | if (pid > 0) { 30 | if (WIFEXITED(status) && WEXITSTATUS(status)) 31 | fprintf(stderr, "pid %d returned %d\n", (int)pid, WEXITSTATUS(status)); 32 | if (WIFSIGNALED(status)) 33 | fprintf(stderr, "pid %d terminated with %s\n", 34 | (int)pid, strsignal(WTERMSIG(status))); 35 | } 36 | } 37 | 38 | int main() 39 | { 40 | signal(SIGCHLD, sigchld_handler); 41 | mish_prepare(0); 42 | 43 | while (1) { 44 | sleep(1); 45 | } 46 | } 47 | 48 | static const char * 49 | _shell_which( 50 | const char * cmd) 51 | { 52 | if (cmd[0] == '.' || cmd[0] == '/') 53 | return strdup(cmd); 54 | char * path = strdup(getenv("PATH")); 55 | char * cur = path, *e; 56 | while ((e = strsep(&cur, ":")) != NULL) { 57 | char *pe; 58 | asprintf(&pe, "%s/%s", e, cmd); 59 | struct stat st = {}; 60 | if (stat(pe, &st) == 0) { 61 | return pe; 62 | } 63 | free(pe); 64 | } 65 | fprintf(stderr, "%s: command not found\n", cmd); 66 | return NULL; 67 | } 68 | 69 | static void 70 | _test_shell_cnt( 71 | void * param, 72 | int argc, 73 | const char * argv[]) 74 | { 75 | const char * av[argc + 1]; 76 | av[0] = _shell_which(argv[1]); 77 | if (av[0] == NULL) 78 | return; 79 | for (int ai = 1; ai <= argc; ai++) 80 | av[ai] = argv[ai+1]; 81 | pid_t pid = fork(); 82 | if (pid == 0) { 83 | execv(av[0], (void*)av); 84 | perror(av[0]); 85 | close(0);close(1);close(2); 86 | exit(1); 87 | } 88 | } 89 | 90 | MISH_CMD_NAMES(shell, "shell"); 91 | MISH_CMD_HELP(shell, 92 | "run a shell command", 93 | "test command for libmish!"); 94 | MISH_CMD_REGISTER(shell, _test_shell_cnt); 95 | -------------------------------------------------------------------------------- /libmish/tests/mish_input_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a stress/scenario test for the mish_input_read function 3 | */ 4 | #define MISH_INPUT_TEST 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "mish.h" 18 | 19 | static uint8_t *words = NULL; 20 | static size_t words_size = 0; 21 | static size_t words_offset = 0; 22 | 23 | static int ccount = 0; 24 | 25 | static ssize_t _test_read(int fd, void *w, size_t count) { 26 | errno = 0; 27 | 28 | if (!(ccount++ % 17)) { 29 | errno = EAGAIN; 30 | printf("%s fake EAGAIN\n", __func__); 31 | return -1; 32 | } 33 | 34 | if (words_offset + count > words_size) 35 | count = words_size - words_offset; 36 | 37 | memcpy(w, words + words_offset, count); 38 | words_offset += count; 39 | if (!count) { 40 | printf("%s all input sent\n", __func__); 41 | } 42 | return count; 43 | } 44 | 45 | #define read(_a,_b,_c) _test_read(_a,_b,_c) 46 | 47 | #include "mish_input.c" 48 | #include "mish_line.c" 49 | 50 | #undef read 51 | 52 | /* duplicate from mish_session.c */ 53 | /* 54 | * get a localtime equivalent, with milliseconds added 55 | */ 56 | uint64_t 57 | _mish_stamp_ms() 58 | { 59 | return 0; 60 | } 61 | 62 | 63 | int main() 64 | { 65 | const char *filen = "/usr/share/dict/american-english"; 66 | struct stat st; 67 | int fd; 68 | if ((lstat(filen, &st) == -1) || (fd = open(filen, O_RDONLY)) == -1) { 69 | perror(filen); 70 | exit(1); 71 | } 72 | words_size = st.st_size; 73 | words = malloc(words_size + 1); 74 | if (read(fd, words, st.st_size) != st.st_size) { 75 | perror("EOF unexpected"); 76 | exit(1); 77 | } 78 | words[words_size] = 0; 79 | printf("File %s read\n", filen); 80 | 81 | /* That's not needed for the test, but init() relies on it */ 82 | mish_t mish = {}; 83 | mish_p m = &mish; 84 | FD_ZERO(&m->select.read); 85 | FD_ZERO(&m->select.write); 86 | TAILQ_INIT(&m->backlog.log); 87 | TAILQ_INIT(&m->clients); 88 | 89 | /* we don't need the file descriptor as we bypass "read" but the 90 | * init function relies on it so here is a socketpair for it to play 91 | * with */ 92 | int sk[2]; 93 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk)) { 94 | perror("socketpair"); 95 | exit(1); 96 | } 97 | mish_input_t input = {}; 98 | _mish_input_init(m, &input, sk[0]); 99 | 100 | uint8_t *cursor = words; 101 | int linec = 0; 102 | mish_line_p ln = NULL; 103 | 104 | /* 105 | * Right so now we can actually test the function. We keep a running 106 | * pointer on the source data, and the "last" line we compared, and 107 | * compared line's content with the source buffer. If all goes 108 | * well, they should match exactly. 109 | */ 110 | do { 111 | _mish_input_read(m, &m->select.read, &input); 112 | printf(" read up to o: %d/%d\n", (int)words_offset, (int)words_size); 113 | 114 | if (!ln) 115 | ln = TAILQ_FIRST(&input.backlog); 116 | else if (TAILQ_NEXT(ln, self)) 117 | ln = TAILQ_NEXT(ln, self); 118 | else 119 | continue; 120 | while (ln) { 121 | linec++; 122 | // printf("line %4d: %s", linec, ln->line); 123 | 124 | uint8_t * l = (uint8_t*)ln->line; 125 | for (int ic = 0; ic < ln->len; ic++, cursor++) { 126 | if (l[ic] != *cursor) { 127 | printf("ERROR: Mismatch Offset %ld char #%d/%d (%x/%x) line %d: %s\n", 128 | cursor - words, ic, 129 | ln->len, 130 | l[ic], *cursor, 131 | linec, l); 132 | goto done; 133 | } 134 | } 135 | 136 | if (TAILQ_LAST(&input.backlog, mish_line_queue_t) == ln) 137 | break; 138 | ln = TAILQ_NEXT(ln, self); 139 | } 140 | } while (words_offset < words_size); 141 | printf("%d lines were read and compared\n", linec); 142 | done: 143 | exit(0); 144 | 145 | } 146 | 147 | -------------------------------------------------------------------------------- /libmish/tests/mish_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_test.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "mish.h" 14 | 15 | int cnt = 0; 16 | 17 | int main() 18 | { 19 | mish_prepare(0); 20 | 21 | while (1) { 22 | sleep(1); 23 | printf("Count %d\n", cnt++); 24 | } 25 | } 26 | 27 | 28 | /* And here is a command line action that can reset the 'cnt' variable */ 29 | static void _test_set_cnt(void * param, int argc, const char * argv[]) 30 | { 31 | if (argc > 1) { 32 | cnt = atoi(argv[1]); 33 | } else { 34 | fprintf(stderr, 35 | "%s: syntax 'set XXX' to set the variable\n", argv[0]); 36 | } 37 | } 38 | 39 | MISH_CMD_NAMES(set, "set"); 40 | MISH_CMD_HELP(set, 41 | "set 'cnt' variable", 42 | "test command for libmish!"); 43 | MISH_CMD_REGISTER(set, _test_set_cnt); 44 | -------------------------------------------------------------------------------- /libmish/tests/mish_vt_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mish_vt_test.c 3 | * 4 | * Copyright (C) 2020 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: Apache-2.0 7 | */ 8 | 9 | 10 | #include 11 | #include 12 | #include "../src/mish_vt.c" 13 | 14 | /* small test unit for the VT decoder, mostly for UTF8 */ 15 | int main() 16 | { 17 | const char * input = "Hello 😈 \033[1mThere\033[0m"; 18 | 19 | mish_vt_sequence_t sq = {}; 20 | 21 | printf("%s\n", input); 22 | const char *s = input; 23 | int cg = 0; 24 | while (*s) { 25 | if (_mish_vt_sequence_char(&sq, *s)) { 26 | printf("glyph s:%08x g:%08x\n", sq.seq, sq.glyph); 27 | if (sq.glyph) 28 | cg++; 29 | } 30 | s++; 31 | } 32 | printf("%d glyphs in string\n", cg); 33 | } 34 | -------------------------------------------------------------------------------- /libmui/.gitignore: -------------------------------------------------------------------------------- 1 | .bash_history 2 | build-* 3 | .vscode 4 | compile_commands.json 5 | cachegrind.out.* 6 | callgrind.out.* 7 | .cache 8 | *_scrot.png 9 | -------------------------------------------------------------------------------- /libmui/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # MUI Version Changelog 2 | 3 | ## 1.3 4 | * Split the big mui.h header into smaller headers. It was just getting too big really. 5 | * Removed *incbin.h* -- this was incompatible with several linkers, and didn't work for webassemly. Now the fonts are just included as C arrays. 6 | * Introduced a new *mui_control_group*. Basically the list of control is now made of (optional) multiple groups. A normal window only uses one, but for tab controls for example, that'll be quite handy. 7 | * This introduces a new API to iterate the window/control list, you can selectively iterate all, or just the visible controls. 8 | * There is now a standard PUT file dialog, with a "New" folder button and all. 9 | * Added an optional close box to the window title bars. 10 | 11 | ## 1.2 12 | * More tweaks to the menus. Popup menus can be justified left/right/center. Removed quite a few fudge factors. 13 | * Added a notion of a control (per window) having the 'focus'. Currently listboxes and text edit boxes can have the focus. 14 | 15 | ## 1.1 -- since the release of the original version 16 | * Added support for horizontal scrollbars. 17 | * Added a faster (vectorized) version of an obvious 'cg' function. 18 | * Fixed a problem with mui_timers. Also typed it now. 19 | * Added a bunch of extra comments to mui.h 20 | * Added a *fully justified* text style for text boxes. 21 | 22 | ## 1.0 -- original version 23 | * Initial release. Doh. 24 | -------------------------------------------------------------------------------- /libmui/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Michel Pollet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /libmui/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright (C) 2020 Michel Pollet 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | CC = gcc 8 | 9 | LIBMUI = 10 | MUI_SRC := $(wildcard $(LIBMUI)src/*.c) 11 | 12 | vpath %.c $(LIBMUI)src $(LIBMUI)mui_shell 13 | 14 | # this is just so we compile the tests if not a submodule 15 | IS_SUBMODULE := ${wildcard ../ui_gl/mii_mui.h} 16 | 17 | all : static mui_shell 18 | 19 | include $(LIBMUI)Makefile.common 20 | 21 | ifeq ($(IS_SUBMODULE),) 22 | all : tests 23 | endif 24 | 25 | MUI_OBJ := ${patsubst %, $(OBJ)/%, ${notdir ${MUI_SRC:.c=.o}}} 26 | TARGET_LIB := $(LIB)/libmui.a 27 | 28 | .PHONY : mui_shell static tests 29 | mui_shell : $(BIN)/mui_shell 30 | static : $(TARGET_LIB) 31 | tests : | $(TARGET_LIB) 32 | $(MAKE) -j -C tests 33 | @echo " ** To launch the demo, run " 34 | @echo " $(BIN)/mui_shell -f $(LIB)/mui_widgets_demo.so" 35 | 36 | $(TARGET_LIB) : $(MUI_OBJ) | $(LIB) 37 | @echo " AR $@" 38 | $(Q)$(AR) rcs $@ $^ 39 | 40 | .PHONY : fonts 41 | define font_to_h 42 | src/fonts/$(2).h : $(1) 43 | { echo "// Autogenerated by the Makefile, do not edit"; \ 44 | echo "#pragma once"; \ 45 | xxd -n $(2) -i $$< |\ 46 | sed 's/unsigned/static const unsigned/' ; \ 47 | }>$$@ 48 | $(OBJ)/mii.o : src/fonts/$(2).h 49 | fonts: src/fonts/$(2).h 50 | endef 51 | 52 | $(eval $(call font_to_h,fonts/Charcoal_mui.ttf,mui_main_font)) 53 | $(eval $(call font_to_h,fonts/typicon.ttf,mui_icon_font)) 54 | $(eval $(call font_to_h,fonts/Geneva.ttf,mui_geneva_font)) 55 | 56 | # 57 | # The shell program is used to test the UI library using plugins 58 | # It is made using XCB and XKB libraries to have a minimal dependency 59 | # on X11. Also, allows partial updates to be tested properly 60 | # 61 | $(OBJ)/mui_shell.o : CPPFLAGS += -DUI_HAS_XCB=1 -DUI_HAS_XKB=1 62 | ifeq ($(shell uname),NetBSD) 63 | # NetBSD requirements 64 | $(OBJ)/mui_shell.o : CPPFLAGS += $(shell pkg-config --cflags xorg-server xkbcommon) 65 | $(BIN)/mui_shell : LDLIBS += $(shell pkg-config --libs xorg-server) 66 | endif 67 | 68 | $(BIN)/mui_shell : LDLIBS += $(shell pkg-config --libs \ 69 | xcb xcb-shm xcb-image \ 70 | xkbcommon xkbcommon-x11) 71 | $(BIN)/mui_shell : LDLIBS += -lm 72 | ifeq ($(shell uname),Linux) 73 | $(BIN)/mui_shell : LDLIBS += -ldl 74 | endif 75 | $(BIN)/mui_shell : $(OBJ)/mui_shell.o $(LIB)/libmui.a 76 | 77 | clean : 78 | rm -rf $(O) 79 | 80 | # This is for development purpose. This will recompile the project 81 | # everytime a file is modified. 82 | watch : 83 | while true; do \ 84 | clear; $(MAKE) -j all tests; \ 85 | inotifywait -qre close_write src mui_shell tests/* ../ui_gl; \ 86 | done 87 | 88 | compile_commands.json: lsp 89 | lsp: 90 | { $$(which gmake) CC=gcc V=1 --always-make --dry-run all tests ; } | \ 91 | sh ../utils/clangd_gen.sh >compile_commands.json 92 | 93 | -include $(OBJ)/*.d 94 | 95 | DESTDIR ?= /usr/local 96 | 97 | install: $(TARGET_LIB) 98 | install -d $(DESTDIR)/lib 99 | install -m 644 $(TARGET_LIB) $(DESTDIR)/lib 100 | install -d $(DESTDIR)/include/mui/mui $(DESTDIR)/include/mui/control/ 101 | install -m 644 src/*.h $(DESTDIR)/include/mui 102 | install -m 644 src/mui/*.h $(DESTDIR)/include/mui/mui/ 103 | install -m 644 src/control/*.h $(DESTDIR)/include/mui/control/ 104 | -------------------------------------------------------------------------------- /libmui/Makefile.common: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright (C) 2024 Michel Pollet 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | LIBMUI ?= ../ 8 | 9 | # for bsd_queue.h and incbin.h 10 | #MII ?= $(LIBMUI).. 11 | #CPPFLAGS += -I$(MII)/libmish/src -I$(MII)/contrib 12 | CPPFLAGS += -I$(LIBMUI)contrib 13 | 14 | BUILD_DIR ?= $(LIBMUI) 15 | 16 | O := $(BUILD_DIR)build-$(shell $(CC) -dumpmachine) 17 | BIN := $(O)/bin 18 | OBJ := $(O)/obj/libmui 19 | LIB := $(O)/lib 20 | 21 | CPPFLAGS += -I$(LIBMUI)src 22 | CPPFLAGS += -I$(LIBMUI)mui_shell 23 | CPPFLAGS += ${shell pkg-config --cflags pixman-1} 24 | LDLIBS += ${shell pkg-config --libs pixman-1} 25 | 26 | MUI_VERSION := ${shell \ 27 | echo $$(git describe --tags --abbrev=0 2>/dev/null || \ 28 | echo "(dev)") \ 29 | $$(git log -1 --date=short --pretty="%h %cd")} 30 | #CPPFLAGS += -DMUI_VERSION="\"$(MUI_VERSION)\"" 31 | 32 | OPTIMIZE ?= -O0 -g 33 | CFLAGS += --std=gnu99 -Wall -Wextra 34 | CFLAGS += $(OPTIMIZE) 35 | CFLAGS += -Wno-unused-parameter -Wno-unused-function 36 | # PIC is necessary for the shared library/plugin to work 37 | CFLAGS += -fPIC 38 | 39 | #CFLAGS += -fsanitize=address 40 | #LDFLAGS += -fsanitize=address 41 | #LDLIBS += 42 | 43 | ifeq ($(V),1) 44 | Q := 45 | else 46 | Q := @ 47 | endif 48 | 49 | # use a .temp file, otherwise the mui_shell tries to reload before the file 50 | # is fully written, and it fails. 51 | # the ${filter} are there to make the sure object files are linked before the .a 52 | $(LIB)/%.so : $(OBJ)/%.o $(LIB)/libmui.a | $(O) 53 | ifeq ($(V),) 54 | @echo " LDSO $@" 55 | endif 56 | $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) -shared -fPIC -o $@.temp \ 57 | ${filter %.o, $^} ${filter %.a, $^} $(LDLIBS) && \ 58 | mv $@.temp $@ 59 | 60 | $(OBJ)/%.o : %.c | $(OBJ) 61 | ifeq ($(V),) 62 | @echo " CC" ${filter -O%, $(CPPFLAGS) $(CFLAGS)} "$<" 63 | endif 64 | $(Q)$(CC) -MMD $(CPPFLAGS) $(CFLAGS) -c -o $@ $< 65 | 66 | $(BIN)/% : | $(BIN) 67 | ifeq ($(V),) 68 | @echo " LD $@" 69 | endif 70 | $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) 71 | 72 | $(O) $(OBJ) $(BIN) $(LIB): 73 | @mkdir -p $@ 74 | -------------------------------------------------------------------------------- /libmui/doc/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/doc/alert.png -------------------------------------------------------------------------------- /libmui/doc/control_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/doc/control_demo.png -------------------------------------------------------------------------------- /libmui/doc/standard_get_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/doc/standard_get_file.png -------------------------------------------------------------------------------- /libmui/doc/static_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/doc/static_text.png -------------------------------------------------------------------------------- /libmui/doc/widgets.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/doc/widgets.gif -------------------------------------------------------------------------------- /libmui/fonts/Cairo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/fonts/Cairo.ttf -------------------------------------------------------------------------------- /libmui/fonts/Charcoal_mui.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/fonts/Charcoal_mui.ttf -------------------------------------------------------------------------------- /libmui/fonts/Dingbat.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/fonts/Dingbat.ttf -------------------------------------------------------------------------------- /libmui/fonts/Geneva.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/fonts/Geneva.ttf -------------------------------------------------------------------------------- /libmui/fonts/typicon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buserror/mii_emu/cb27727bb2f8270fcb6545b3514d9f85b99f731c/libmui/fonts/typicon.ttf -------------------------------------------------------------------------------- /libmui/mui_shell/mui_shell_plugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_shell_plugin.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | /* 15 | * Quck and dirty plugin interface for mui_shell, just allows a quick reload 16 | * of a plugin without having to restart the whole shell. 17 | * In combination with 'make watch' you have have 1s turnaround time for 18 | * your plugin development, helps a ton when tweaking a dialog etc. 19 | */ 20 | struct mui_t; 21 | struct mui_drawable_t; 22 | 23 | typedef struct mui_plug_t { 24 | const char * name; // 'Human' name of the plugin 25 | const uint32_t * icon; // optional 26 | // return a 'handle' to some data for this plugin. 27 | void * (*init)( 28 | struct mui_t * ui, 29 | struct mui_plug_t * plug, 30 | struct mui_drawable_t * dr ); 31 | void (*dispose)( 32 | void * plug ); 33 | int (*draw)( 34 | struct mui_t * ui, 35 | void * plug, 36 | struct mui_drawable_t * dr, 37 | uint16_t all ); 38 | int (*event)( 39 | struct mui_t * ui, 40 | void * plug, 41 | struct mui_event_t * event ); 42 | } mui_plug_t; 43 | -------------------------------------------------------------------------------- /libmui/src/c2_arrays.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c2_arrays.c 3 | * 4 | * Created on: 4 Feb 2023 5 | * Author: michel 6 | */ 7 | 8 | #include "c2_arrays.h" 9 | 10 | /*! Simplify array 'a' into 'b', return 1 if it was. 11 | * This takes a list of rectangles 'a', duplicates are skipped. If two rectangles 12 | * overlap, see if the union is 'worth' it. Returns results in array 'b' 13 | * Returns 1 if the b array has been simplified somehow, zero if not 14 | */ 15 | int 16 | c2_rect_array_simplify( 17 | c2_rect_array_p a, 18 | c2_rect_array_p b) 19 | { 20 | c2_rect_array_clear(b); 21 | for (unsigned int i = 0; i < a->count; i++) { 22 | int addit = 1; 23 | c2_rect_p ra = &a->e[i]; 24 | for (unsigned int j = 0; j < b->count && addit; j++) { 25 | c2_rect_p rb = &b->e[j]; 26 | if (c2_rect_equal(rb, ra)) { // already a copy here 27 | addit = 0; 28 | } else if (c2_rect_contains_rect(rb, ra)) { 29 | addit = 0; 30 | } else if (c2_rect_intersect_rect(rb, ra)) { 31 | c2_rect_t o = *ra; 32 | c2_rect_union(&o, rb); 33 | long sa = c2_rect_surface_squared(ra); 34 | long sb = c2_rect_surface_squared(rb); 35 | long so = c2_rect_surface_squared(&o); 36 | if (so <= (sa + sb)) { 37 | *rb = o; 38 | addit = 0; 39 | } 40 | } 41 | } 42 | if (addit) 43 | c2_rect_array_add(b, *ra); 44 | } 45 | return b->count < a->count; 46 | } 47 | -------------------------------------------------------------------------------- /libmui/src/c2_arrays.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c2_arrays.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #ifndef C2_ARRAYS_H_ 10 | #define C2_ARRAYS_H_ 11 | 12 | #include "c2_geometry.h" 13 | #include "c_array.h" 14 | 15 | DECLARE_C_ARRAY(c2_pt_t, c2_pt_array, 16); 16 | DECLARE_C_ARRAY(c2_coord_t, c2_coord_array, 16); 17 | DECLARE_C_ARRAY(c2_rect_t, c2_rect_array, 16); 18 | 19 | IMPLEMENT_C_ARRAY(c2_pt_array); 20 | IMPLEMENT_C_ARRAY(c2_coord_array); 21 | IMPLEMENT_C_ARRAY(c2_rect_array); 22 | 23 | /*! Simplify array 'a' into 'b', return 1 if it was. 24 | * This takes a list of rectangles 'a', duplicates are skipped. If two rectangles 25 | * overlap, see if the union is 'worth' it. Returns results in array 'b' 26 | * Returns 1 if the b array has been simplified somehow, zero if not 27 | */ 28 | int 29 | c2_rect_array_simplify( 30 | c2_rect_array_p a, 31 | c2_rect_array_p b); 32 | 33 | #endif /* C2_ARRAYS_H_ */ 34 | -------------------------------------------------------------------------------- /libmui/src/c2_geometry.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c2_geometry.h 3 | * 4 | * C2DGeometry Implementation 5 | * Created: Monday, March 30, 1998 11:51:47 6 | * Converted back to C99 May 2013 7 | * 8 | * Copyright (C) 1998-2023 Michel Pollet 9 | * 10 | * SPDX-License-Identifier: MIT 11 | */ 12 | 13 | #ifndef __C2_GEOMETRY_H__ 14 | #define __C2_GEOMETRY_H__ 15 | 16 | #include 17 | 18 | #define PMIN(x1,x2) ((x1 < x2) ? x1 : x2) 19 | #define PMAX(x1,x2) ((x1 > x2) ? x1 : x2) 20 | 21 | typedef int32_t c2_coord_t; 22 | 23 | enum { 24 | X = 0, Y, 25 | // L = 0, T, R, B, 26 | }; 27 | 28 | // 29 | // a basic x,y point 30 | // 31 | typedef union c2_pt_t { 32 | struct { 33 | c2_coord_t x, y; 34 | }; 35 | c2_coord_t v[2]; 36 | } c2_pt_t;//, *c2_pt_p; 37 | #define c2_pt_p union c2_pt_t* 38 | 39 | #define C2_PT(_a, _b) (c2_pt_t){ .x = (_a), .y = (_b) } 40 | #define C2_PT_ZERO C2_PT(0,0) 41 | 42 | // 43 | // a basic segment, a pair of points 44 | // 45 | typedef struct c2_segment_t { 46 | c2_pt_t a, b; 47 | } c2_segment_t;//, *c2_segment_p; 48 | #define c2_segment_p struct c2_segment_t* 49 | 50 | #define C2_SEGMENT_ZERO { C2_PT_ZERO, C2_PT_ZERO } 51 | 52 | // 53 | // a rectangle defined by two points 54 | // 55 | typedef union c2_rect_t { 56 | struct { 57 | c2_pt_t tl, br; 58 | }; 59 | c2_coord_t v[4]; 60 | struct { 61 | c2_coord_t l,t,r,b; 62 | }; 63 | } c2_rect_t;//, *c2_rect_p; 64 | #define c2_rect_p union c2_rect_t* 65 | 66 | #define C2_RECT(_l,_t,_r,_b) (c2_rect_t){ .l = _l, .t = _t, .r = _r, .b = _b } 67 | #define C2_RECT_WH(_l,_t,_w,_h) (c2_rect_t){ \ 68 | .l = _l, .t = _t, .r = (_l)+(_w), .b = (_t)+(_h) } 69 | /* Return the squared surface of the rectangle, for area comparison sake */ 70 | #define c2_rect_surface_squared(_r) \ 71 | ((c2_rect_width(_r)*c2_rect_width(_r))+\ 72 | (c2_rect_height(_r)*c2_rect_height(_r))) 73 | 74 | #define C2_RECT_ZERO C2_RECT(0,0,0,0) 75 | 76 | int 77 | c2_rect_contains_rect( 78 | const c2_rect_p r1, 79 | const c2_rect_p r2 ); 80 | 81 | int 82 | c2_rect_clip_segment( 83 | const c2_rect_p r, 84 | c2_segment_p s, 85 | c2_segment_p o, 86 | char * outEdges ); 87 | int 88 | c2_rect_clip_rect( 89 | const c2_rect_p r, 90 | const c2_rect_p s, 91 | c2_rect_p o ); 92 | int 93 | c2_rect_intersect_rect( 94 | const c2_rect_p s, 95 | const c2_rect_p r ); 96 | 97 | enum { 98 | out_Left = (1 << 0), 99 | out_Top = (1 << 1), 100 | out_Right = (1 << 2), 101 | out_Bottom = (1 << 3), 102 | }; 103 | enum { 104 | corner_TopLeft = out_Left, 105 | corner_TopRight = out_Top, 106 | corner_BottomRight = out_Right, 107 | corner_BottomLeft = out_Bottom, 108 | }; 109 | 110 | uint8_t 111 | c2_rect_get_out_code( 112 | const c2_rect_p r, 113 | const c2_pt_p p ); 114 | void 115 | c2_rect_clip_pt( 116 | const c2_rect_p r, 117 | c2_pt_p p ); 118 | 119 | uint8_t 120 | c2_rect_get_next_edge( 121 | uint8_t inEdge, 122 | int inCW ); 123 | uint8_t 124 | c2_rect_is_on_edge( 125 | const c2_rect_p r, 126 | const c2_pt_p p ); 127 | int 128 | c2_rect_get_edge( 129 | const c2_rect_p r, 130 | uint8_t inEdge, 131 | c2_segment_p outEdge ); 132 | int 133 | c2_rect_get_corner( 134 | const c2_rect_p r, 135 | uint8_t inCorner, 136 | c2_pt_p outCorner, 137 | int inCW ); 138 | 139 | const char * 140 | c2_rect_as_str( 141 | const c2_rect_p r ); 142 | 143 | #ifndef C2_GEOMETRY_INLINE_H_ 144 | #ifndef C2_DECL 145 | #include "c2_geometry_inline.h" 146 | #endif 147 | #endif 148 | #endif 149 | -------------------------------------------------------------------------------- /libmui/src/c2_geometry_poly.h: -------------------------------------------------------------------------------- 1 | /* 2 | * c2_geometry_poly.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #ifndef C2_GEOMETRY_POLY_H_ 10 | #define C2_GEOMETRY_POLY_H_ 11 | 12 | #include "c2_arrays.h" 13 | 14 | typedef struct c2_polyline_t { 15 | c2_pt_array_t pt; 16 | c2_rect_t bounds; 17 | } c2_polyline_t, *c2_polyline_p; 18 | 19 | DECLARE_C_ARRAY(c2_polyline_p, c2_polyline_array, 16, 20 | c2_polyline_p current; ); 21 | DECLARE_C_ARRAY(c2_coord_array_t, c2_scanline_array, 16, 22 | c2_coord_t height; ); 23 | 24 | IMPLEMENT_C_ARRAY(c2_scanline_array); 25 | IMPLEMENT_C_ARRAY(c2_polyline_array); 26 | 27 | 28 | void 29 | c2_polyline_clear( 30 | c2_polyline_p pl); 31 | 32 | int 33 | c2_polyline_get_segment( 34 | c2_polyline_p pl, 35 | unsigned long ind, 36 | c2_segment_p o ); 37 | 38 | void 39 | c2_polyline_offset( 40 | c2_polyline_p pl, 41 | c2_coord_t inX, 42 | c2_coord_t inY ); 43 | 44 | void 45 | c2_polyline_scale( 46 | c2_polyline_p pl, 47 | double inFactor, 48 | c2_rect_p inSkip /* = NULL */ ); 49 | 50 | void 51 | c2_polyline_add_pt( 52 | c2_polyline_p pl, 53 | c2_pt_p p ); 54 | 55 | int 56 | c2_polyline_array_clip( 57 | c2_polyline_array_p pa, 58 | c2_rect_p clip, 59 | c2_polyline_array_p outPoly ); 60 | void 61 | c2_polyline_array_break( 62 | c2_polyline_array_p pa); 63 | void 64 | c2_polyline_array_add_pt( 65 | c2_polyline_array_p pa, 66 | c2_pt_p p ); 67 | 68 | void 69 | c2_polyline_array_scale( 70 | c2_polyline_array_p pa, 71 | float inFactor, 72 | c2_rect_p inSkip /* = NULL */ ); 73 | void 74 | c2_polyline_array_offset( 75 | c2_polyline_array_p pa, 76 | c2_coord_t inX, 77 | c2_coord_t inY ); 78 | 79 | 80 | void 81 | c2_scanline_array_proper_alloc( 82 | c2_scanline_array_p a, 83 | c2_coord_t height); 84 | void 85 | c2_scanline_array_proper_clear( 86 | c2_scanline_array_p a); 87 | void 88 | c2_scanline_array_add_coord( 89 | c2_scanline_array_p a, 90 | int inY, 91 | c2_coord_t inX ); 92 | 93 | 94 | typedef c2_polyline_p c2_polygon_p; 95 | 96 | int 97 | c2_polygon_isempty( 98 | c2_polygon_p pl); 99 | 100 | c2_coord_t 101 | c2_polygon_get_heigth( 102 | c2_polygon_p pl); 103 | 104 | 105 | void 106 | c2_polygon_clip( 107 | c2_polygon_p pl, 108 | c2_rect_p clip, 109 | c2_polygon_p outPoly ); 110 | 111 | void 112 | c2_polygon_scanline( 113 | c2_polygon_p pl, 114 | c2_scanline_array_p ioList, 115 | c2_coord_t ymin ); 116 | 117 | 118 | 119 | #endif /* C2_GEOMETRY_POLY_H_ */ 120 | -------------------------------------------------------------------------------- /libmui/src/control/box.h: -------------------------------------------------------------------------------- 1 | /* 2 | * box.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | /* 17 | * Create a static text box. Font is optional (default to the system main font), 18 | * flags corresponds to the MUI_TEXT_ALIGN_* * PLUS the extra(s) listed below. 19 | */ 20 | enum mui_textbox_e { 21 | // draw the frame around the text box 22 | MUI_CONTROL_TEXTBOX_FRAME = (1 << (MUI_TEXT_FLAGS_COUNT+1)), 23 | MUI_CONTROL_TEXTBOX_FLAGS_COUNT = (MUI_TEXT_FLAGS_COUNT+1), 24 | }; 25 | mui_control_t * 26 | mui_textbox_new( 27 | mui_window_t * win, 28 | c2_rect_t frame, 29 | const char * text, 30 | const char * font, 31 | uint32_t flags ); 32 | 33 | mui_control_t * 34 | mui_groupbox_new( 35 | mui_window_t * win, 36 | c2_rect_t frame, 37 | const char * title, 38 | uint32_t flags ); 39 | mui_control_t * 40 | mui_separator_new( 41 | mui_window_t * win, 42 | c2_rect_t frame); 43 | -------------------------------------------------------------------------------- /libmui/src/control/button.h: -------------------------------------------------------------------------------- 1 | /* 2 | * button.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | 15 | enum mui_button_style_e { 16 | MUI_BUTTON_STYLE_NORMAL = 0, 17 | MUI_BUTTON_STYLE_DEFAULT = 1, 18 | MUI_BUTTON_STYLE_RADIO, 19 | MUI_BUTTON_STYLE_CHECKBOX, 20 | }; 21 | 22 | mui_control_t * 23 | mui_button_new( 24 | mui_window_t * win, 25 | c2_rect_t frame, 26 | uint8_t style, // one of mui_button_style_e 27 | const char * title, 28 | uint32_t uid ); 29 | // "align" is not implemented yet 30 | void 31 | mui_button_set_icon( 32 | mui_control_t * c, 33 | const char * icon, 34 | mui_text_e align ); 35 | -------------------------------------------------------------------------------- /libmui/src/control/drawable.h: -------------------------------------------------------------------------------- 1 | /* 2 | * drawable.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | 15 | /* Drawable control is just an offscreen buffer (icon, pixel view) */ 16 | mui_control_t * 17 | mui_drawable_control_new( 18 | mui_window_t * win, 19 | c2_rect_t frame, 20 | mui_drawable_t * dr, 21 | mui_drawable_t * mask, 22 | uint16_t flags); 23 | mui_drawable_t * 24 | mui_drawable_control_get_drawable( 25 | mui_control_t * c); 26 | -------------------------------------------------------------------------------- /libmui/src/control/listbox.h: -------------------------------------------------------------------------------- 1 | /* 2 | * listbox.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | struct mui_control_t; 15 | struct mui_window_t; 16 | struct mui_listbox_elem_t; 17 | 18 | 19 | typedef struct mui_listbox_elem_t { 20 | uint32_t disabled : 1; 21 | // currently this is a UTF8 string using the 'icons' font 22 | char icon[8]; // UTF8 icon 23 | // default 'LDEF' is to draw the 'elem' string using normal font 24 | void * elem; // char * or... ? 25 | } mui_listbox_elem_t; 26 | 27 | DECLARE_C_ARRAY(mui_listbox_elem_t, mui_listbox_elems, 2); 28 | IMPLEMENT_C_ARRAY(mui_listbox_elems); 29 | 30 | enum mui_ldef_op_e { 31 | /* 32 | * Initialize the LDEF, the 'ldef_param' is passed in, and the 33 | */ 34 | MUI_LDEF_OP_INIT, 35 | /* 36 | * Allow the LDEF to dispose of ldef_param, if applicable 37 | */ 38 | MUI_LDEF_OP_DISPOSE, 39 | MUI_LDEF_OP_DRAW, 40 | MUI_LDEF_OP_GET_TEXT, 41 | MUI_LDEF_OP_EVENT, 42 | MUI_LDEF_OP_COUNT, 43 | }; 44 | 45 | /* This is currently unused */ 46 | typedef void (*mui_ldef_p)( 47 | mui_control_t * c, 48 | enum mui_ldef_op_e op, 49 | void * ldef_param, 50 | uint32_t elem_index, 51 | mui_listbox_elem_t * elem, 52 | void * io_result ); 53 | 54 | 55 | mui_control_t * 56 | mui_listbox_new( 57 | mui_window_t * win, 58 | c2_rect_t frame, 59 | uint32_t uid ); 60 | void 61 | mui_listbox_prepare( 62 | mui_control_t * c); 63 | mui_listbox_elems_t * 64 | mui_listbox_get_elems( 65 | mui_control_t * c); 66 | void 67 | mui_listbox_set_ldef( 68 | mui_control_t * c, 69 | mui_ldef_p ldef, 70 | void * ldef_param); 71 | -------------------------------------------------------------------------------- /libmui/src/control/popup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * popup.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | /* 16 | * Popup menu control. 17 | * 18 | * flags are MUI_TEXT_ALIGN_* -- however this corresponds to the margins 19 | * of the popup control itself when placed into it's 'frame' -- the 20 | * popup will be placed left,right,center of the frame rectangle depending 21 | * on these flags. 22 | */ 23 | mui_control_t * 24 | mui_popupmenu_new( 25 | mui_window_t * win, 26 | c2_rect_t frame, 27 | const char * title, 28 | uint32_t uid, 29 | uint32_t flags); 30 | mui_menu_items_t * 31 | mui_popupmenu_get_items( 32 | mui_control_t * c); 33 | void 34 | mui_popupmenu_prepare( 35 | mui_control_t * c); 36 | -------------------------------------------------------------------------------- /libmui/src/control/scrollbar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * scrollbar.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | /* 15 | * Page step and line step are optional, they default to '30' pixels and the 16 | * 'visible' area of the scrollbar, respectively. 17 | * 18 | * If you want to -for example- have a scrollbar that scrolls by 5 when you 19 | * click the arrows, and by 20 when you click the bar, you would set the 20 | * line_step to 5, and the page_step to 20. 21 | */ 22 | mui_control_t * 23 | mui_scrollbar_new( 24 | mui_window_t * win, 25 | c2_rect_t frame, 26 | uint32_t uid, 27 | uint32_t line_step, 28 | uint32_t page_step); 29 | uint32_t 30 | mui_scrollbar_get_max( 31 | mui_control_t * c); 32 | void 33 | mui_scrollbar_set_max( 34 | mui_control_t * c, 35 | uint32_t max); 36 | void 37 | mui_scrollbar_set_page( 38 | mui_control_t * c, 39 | uint32_t page); 40 | 41 | -------------------------------------------------------------------------------- /libmui/src/control/textedit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * textedit.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | 17 | /* 18 | * Text editor control 19 | */ 20 | enum { 21 | // do we handle multi-line text? If zero, we only handle one line 22 | MUI_CONTROL_TEXTEDIT_VERTICAL = 1 << (MUI_CONTROL_TEXTBOX_FLAGS_COUNT+1), 23 | MUI_CONTROL_TEXTEDIT_FLAGS_COUNT = (MUI_CONTROL_TEXTBOX_FLAGS_COUNT+1), 24 | }; 25 | 26 | mui_control_t * 27 | mui_textedit_control_new( 28 | mui_window_t * win, 29 | c2_rect_t frame, 30 | uint32_t flags); 31 | void 32 | mui_textedit_set_text( 33 | mui_control_t * c, 34 | const char * text); 35 | void 36 | mui_textedit_set_selection( 37 | mui_control_t * c, 38 | uint start, 39 | uint end); 40 | /* 41 | * Get current selection 42 | */ 43 | void 44 | mui_textedit_get_selection( 45 | mui_control_t * c, 46 | uint * glyph_start, 47 | uint * glyph_end); 48 | uint 49 | mui_textedit_get_text( 50 | mui_control_t * c, 51 | char * text, 52 | uint len); -------------------------------------------------------------------------------- /libmui/src/mui.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | /* 10 | * This is the main include file for the libmui UI library, it should be 11 | * the only one you need to include. 12 | */ 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | 39 | /* 40 | * This is the head of the mui library, it contains the screen size, 41 | * the color scheme, the list of windows, the list of fonts, and the 42 | * clipboard. 43 | * Basically this is the primary parameter that you keep around. 44 | */ 45 | typedef struct mui_t { 46 | c2_pt_t screen_size; 47 | struct { 48 | mui_color_t clear; 49 | mui_color_t highlight; 50 | } color; 51 | uint16_t modifier_keys; 52 | mui_time_t last_click_stamp[MUI_EVENT_BUTTON_MAX]; 53 | int draw_debug; 54 | int quit_request; 55 | // this is the sum of all the window's dirty regions, inc moved windows etc 56 | mui_region_t inval; 57 | // once the pixels have been refreshed, 'inval' is copied to 'redraw' 58 | // to push the pixels to the screen. 59 | mui_region_t redraw; 60 | 61 | TAILQ_HEAD(, mui_font_t) fonts; 62 | TAILQ_HEAD(windows, mui_window_t) windows; 63 | mui_window_ref_t menubar; 64 | mui_window_ref_t event_capture; 65 | mui_utf8_t clipboard; 66 | mui_timer_group_t timer; 67 | // only used by the text editor, as we can only have one carret 68 | mui_timer_id_t carret_timer; 69 | char * pref_directory; /* optional */ 70 | } mui_t; 71 | 72 | void 73 | mui_init( 74 | mui_t * ui); 75 | void 76 | mui_dispose( 77 | mui_t * ui); 78 | void 79 | mui_draw( 80 | mui_t * ui, 81 | mui_drawable_t *dr, 82 | uint16_t all); 83 | void 84 | mui_resize( 85 | mui_t * ui, 86 | mui_drawable_t *dr, 87 | c2_pt_t size); 88 | void 89 | mui_run( 90 | mui_t * ui); 91 | 92 | /* If you want this notification, attach an action function to the 93 | * menubar */ 94 | enum { 95 | // note this will also be send if the application sets the 96 | // clipboard with the system's clipboard, so watch out for 97 | // recursion problems! 98 | MUI_CLIPBOARD_CHANGED = FCC('c','l','p','b'), 99 | // this is sent when the user type 'control-v', this gives 100 | // a chance to the application to do a mui_clipboard_set() 101 | // with the system's clipboard before it gets pasted. 102 | // the default is of course to use our internal clipboard 103 | MUI_CLIPBOARD_REQUEST = FCC('c','l','p','r'), 104 | }; 105 | // This will send a notification that the clipboard was set, 106 | // the notification is sent to the menubar, and the menubar will 107 | // send it to the application. 108 | void 109 | mui_clipboard_set( 110 | mui_t * ui, 111 | const uint8_t * utf8, 112 | uint len); 113 | const uint8_t * 114 | mui_clipboard_get( 115 | mui_t * ui, 116 | uint * len); 117 | 118 | // Pass an event to libmui. Return true if the event was handled by the ui 119 | bool 120 | mui_handle_event( 121 | mui_t * ui, 122 | mui_event_t * ev); 123 | // Return true if event 'ev' is a key combo matching key_equ 124 | bool 125 | mui_event_match_key( 126 | mui_event_t * ev, 127 | mui_key_equ_t key_equ); 128 | /* Return true if the ui has any active windows, ie, not hidden, zombie; 129 | * This does not include the menubar, but it does include any menus or popups 130 | * 131 | * This is used to decide wether to hide the mouse cursor or not 132 | */ 133 | bool 134 | mui_has_active_windows( 135 | mui_t * ui); 136 | 137 | /* Return a hash value for string inString */ 138 | uint32_t 139 | mui_hash( 140 | const char * inString ); 141 | -------------------------------------------------------------------------------- /libmui/src/mui/mui_action.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_action.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | /*! 14 | * Actions are the provided way to add custom response to events for the 15 | * application; action handlers are called for a variety of things, from clicks 16 | * in controls, to menu selections, to window close etc. 17 | * 18 | * The 'what' parameter is a 4 character code, that can be used to identify 19 | * the action, and the 'param' is a pointer to a structure that depends on 20 | * the 'what' action (hopefully documented with that action constant) 21 | * 22 | * the 'cb_param' is specific to this action function pointer and is passed as 23 | * is to the callback, this is the pointer you pass to mui_window_add_action() 24 | */ 25 | typedef int (*mui_window_action_p)( 26 | struct mui_window_t * win, 27 | void * cb_param, 28 | uint32_t what, 29 | void * param); 30 | typedef int (*mui_control_action_p)( 31 | struct mui_control_t *c, 32 | void * cb_param, 33 | uint32_t what, 34 | void * param); 35 | /* 36 | * This is a standardized way of installing 'action' handlers onto windows 37 | * and controls. The 'current' field is used to prevent re-entrance. This structure 38 | * is opaque and is not accessible by the application, typically. 39 | */ 40 | typedef struct mui_action_t { 41 | STAILQ_ENTRY(mui_action_t) self; 42 | uint32_t current; // prevents re-entrance 43 | union { 44 | mui_window_action_p window_cb; 45 | mui_control_action_p control_cb; 46 | }; 47 | void * cb_param; 48 | } mui_action_t; 49 | 50 | typedef STAILQ_HEAD(, mui_action_t) mui_action_queue_t; -------------------------------------------------------------------------------- /libmui/src/mui/mui_alert.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_alert.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | 15 | /* 16 | * Alert dialog 17 | */ 18 | enum mui_alert_flag_e { 19 | MUI_ALERT_FLAG_OK = (1 << 0), 20 | MUI_ALERT_FLAG_CANCEL = (1 << 1), 21 | 22 | MUI_ALERT_ICON_INFO = (1 << 8), 23 | 24 | MUI_ALERT_INFO = (MUI_ALERT_FLAG_OK | MUI_ALERT_ICON_INFO), 25 | MUI_ALERT_WARN = (MUI_ALERT_FLAG_OK | MUI_ALERT_FLAG_CANCEL), 26 | }; 27 | 28 | enum { 29 | MUI_ALERT_BUTTON_OK = FCC('o','k',' ',' '), 30 | MUI_ALERT_BUTTON_CANCEL = FCC('c','a','n','c'), 31 | }; 32 | 33 | mui_window_t * 34 | mui_alert( 35 | struct mui_t * ui, 36 | c2_pt_t where, // (0,0) will center it 37 | const char * title, 38 | const char * message, 39 | uint16_t flags ); 40 | -------------------------------------------------------------------------------- /libmui/src/mui/mui_control_group.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_control_group.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | struct mui_control_t; 14 | 15 | /* 16 | * The control group is a a list of controls, the subtelety is that 17 | * there is a list of groups as well, so that controls can be grouped 18 | * together, and hidden or shown as a group. 19 | * This is mostly done to allow for 'tab' controls, where the controls 20 | * are grouped together, and only one group is shown at a time. 21 | * For most windows, there is only one group, and all controls are 22 | * in that group. 23 | * The 'hidden' flag is used to hide the whole group, and all the controls 24 | * in it. 25 | * This API allows iterating over all the controls in a group, or all 26 | * the controls in all the groups. The iterator allows to skip hidden 27 | * controls, or to walk all controls. 28 | */ 29 | struct mui_control_t; 30 | typedef struct mui_control_group_t { 31 | struct { 32 | uint hidden : 1; 33 | } flags; 34 | TAILQ_ENTRY(mui_control_group_t) self; 35 | TAILQ_HEAD(controls, mui_control_t) controls; 36 | } mui_control_group_t; 37 | 38 | typedef struct mui_controls_t { 39 | TAILQ_HEAD(list, mui_control_group_t) controls; 40 | } mui_controls_t; 41 | 42 | void 43 | mui_controls_init( 44 | mui_controls_t * group_list); 45 | // Return the 'current' group; this is more of a placeholder as it 46 | // always return the 'last' group in the list. 47 | struct mui_control_group_t * 48 | mui_controls_current_group( 49 | mui_controls_t * group_list); 50 | typedef enum mui_controls_flags_e { 51 | // only return visible controls 52 | MUI_CONTROLS_VISIBLE = 0, 53 | // return all controls, regardless of visibility 54 | MUI_CONTROLS_ALL = 1, 55 | } mui_controls_flags_e; 56 | 57 | // Initializes a control group, optionaly (if 'attach' is not NULL) attach 58 | // it to the 'attach' group. 59 | void 60 | mui_control_group_init( 61 | struct mui_control_group_t * group, 62 | mui_controls_t * attach); 63 | struct mui_control_t * 64 | mui_control_group_first( 65 | struct mui_control_group_t * group, 66 | mui_controls_flags_e flags); 67 | struct mui_control_t * 68 | mui_control_group_last( 69 | struct mui_control_group_t * group, 70 | mui_controls_flags_e flags); 71 | struct mui_control_t * 72 | mui_control_group_next( 73 | struct mui_control_t * c, 74 | mui_controls_flags_e flags); 75 | struct mui_control_t * 76 | mui_control_group_prev( 77 | struct mui_control_t * c, 78 | mui_controls_flags_e flags); 79 | 80 | // return first control in the list of groups. 81 | struct mui_control_t * 82 | mui_controls_first( 83 | mui_controls_t * group_list, 84 | mui_controls_flags_e flags); 85 | // return last control in the list of groups. Only return visible controls 86 | // if 'all' is false 87 | struct mui_control_t * 88 | mui_controls_last( 89 | mui_controls_t * group_list, 90 | mui_controls_flags_e flags); 91 | /* 92 | * Return the next control relative to 'c'; it can be in the same group 93 | * or any following groups. 94 | */ 95 | struct mui_control_t * 96 | mui_controls_next( 97 | struct mui_control_t * c, 98 | mui_controls_flags_e flags); 99 | /* Return the previous control relative to 'c'; it can be in the same group 100 | * or any previous groups. 101 | */ 102 | struct mui_control_t * 103 | mui_controls_prev( 104 | struct mui_control_t * control, 105 | mui_controls_flags_e flags); 106 | -------------------------------------------------------------------------------- /libmui/src/mui/mui_event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_event.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | 15 | /* 16 | * Event description. pretty standard stuff here -- the 'when' field is 17 | * only used really to detect double clicks so far. 18 | * 19 | * Even handlers should return true if the event was handled, (in which case 20 | * even processing stops for that event) or false to continue passing the even 21 | * down the chain. 22 | * 23 | * Events are passed to the top window first, and then down the chain of 24 | * windows, until one of them returns true. 25 | * Implicitely, it means the menubar gets to see the events first, even clicks, 26 | * even if the click wasn't in the menubar. This is also true of key events of 27 | * course, which allows the menu to detect key combos, first. 28 | */ 29 | typedef struct mui_event_t { 30 | mui_event_e type; 31 | mui_time_t when; 32 | mui_modifier_e modifiers; 33 | union { 34 | struct key { 35 | uint32_t key; // ASCII or mui_key_e 36 | bool up; 37 | } key; 38 | struct { 39 | uint32_t button : 4, 40 | count : 2; // click count 41 | c2_pt_t where; 42 | } mouse; 43 | struct { 44 | int32_t delta; 45 | c2_pt_t where; 46 | } wheel; 47 | struct { // MUI_EVENT_TEXT is of variable size! 48 | uint32_t size; 49 | uint8_t text[0]; 50 | } text; 51 | }; 52 | } mui_event_t; 53 | 54 | /* Just a generic buffer for UTF8 text */ 55 | DECLARE_C_ARRAY(uint8_t, mui_utf8, 8); 56 | IMPLEMENT_C_ARRAY(mui_utf8); 57 | 58 | /* 59 | * Key equivalent, used to match key events to menu items 60 | * Might be extended to controls, right now only the 'key' is checked, 61 | * mostly for Return and ESC. 62 | */ 63 | typedef union mui_key_equ_t { 64 | struct { 65 | uint16_t mod; 66 | uint16_t key; 67 | }; 68 | uint32_t value; 69 | } mui_key_equ_t; 70 | 71 | #define MUI_KEY_EQU(_mask, _key) \ 72 | (mui_key_equ_t){ .mod = (_mask), .key = (_key) } 73 | -------------------------------------------------------------------------------- /libmui/src/mui/mui_menu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_menu.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct mui_menu_items_t; 16 | 17 | /* 18 | * This is a menu item descriptor (also used for the titles, bar a few bits). 19 | * This is not a *control* in the window, instead this is used to describe 20 | * the menus and menu item controls that are created when the menu becomes 21 | * visible. 22 | */ 23 | typedef struct mui_menu_item_t { 24 | uint32_t disabled : 1, 25 | hilited : 1, 26 | is_menutitle : 1; 27 | uint32_t index: 9; 28 | uint32_t uid; 29 | char * title; 30 | // currently only supported for menu titles 31 | const uint32_t * color_icon; // optional, ARGB colors 32 | char mark[8]; // UTF8 -- Charcoal 33 | char icon[8]; // UTF8 -- Wider, icon font 34 | char kcombo[16]; // UTF8 -- display only 35 | mui_key_equ_t key_equ; // keystroke to select this item 36 | struct mui_menu_item_t * submenu; 37 | c2_coord_t location; // calculated by menu creation code 38 | c2_coord_t height; 39 | } mui_menu_item_t; 40 | 41 | /* 42 | * The menu item array is atypical as the items ('e' field) are not allocated 43 | * by the array, but by the menu creation code. This is because the menu 44 | * reuses the pointer to the items that is passed when the menu is added to 45 | * the menubar. 46 | * the 'read only' field is used to prevent the array from trying to free the 47 | * items when being disposed. 48 | */ 49 | DECLARE_C_ARRAY(mui_menu_item_t, mui_menu_items, 2, 50 | bool read_only; ); 51 | IMPLEMENT_C_ARRAY(mui_menu_items); 52 | 53 | enum mui_menubar_action_e { 54 | // parameter is a mui_menu_item_t* for the first item of the menu, 55 | // this is exactly the parameter passed to add_simple() 56 | // you can use this to disable/enable menu items etc 57 | MUI_MENUBAR_ACTION_PREPARE = FCC('m','b','p','r'), 58 | // parameter 'target' is a mui_menuitem_t* 59 | MUI_MENUBAR_ACTION_SELECT = FCC('m','b','a','r'), 60 | }; 61 | /* 62 | * Menu related. 63 | * Menubar, and menus/popups are windows as well, in a layer above the 64 | * normal ones. 65 | */ 66 | mui_window_t * 67 | mui_menubar_new( 68 | struct mui_t * ui ); 69 | // return the previously created menubar (or NULL) 70 | mui_window_t * 71 | mui_menubar_get( 72 | struct mui_t * ui ); 73 | 74 | /* 75 | * Add a menu to the menubar. 'items' is an array of mui_menu_item_t 76 | * terminated by an element with a NULL title. 77 | * 78 | * Note: The array is NOT const, it will be tweaked for storing items 79 | * position, it can also be tweaked to set/reset the disabled state, 80 | * check marks etc 81 | * 82 | * Once created, you can do a mui_popupmenu_get_items() to get the array, 83 | * modify it (still make sure there is a NULL item at the end) then 84 | * call mui_popupmenu_prepare() to update the menu. 85 | */ 86 | struct mui_control_t * 87 | mui_menubar_add_simple( 88 | mui_window_t * win, 89 | const char * title, 90 | uint32_t menu_uid, 91 | mui_menu_item_t * items ); 92 | struct mui_control_t * 93 | mui_menubar_add_menu( 94 | mui_window_t * win, 95 | uint32_t menu_uid, 96 | mui_menu_item_t * items, 97 | uint count ); 98 | 99 | /* Turn off any highlighted menu titles */ 100 | mui_window_t * 101 | mui_menubar_highlight( 102 | mui_window_t * win, 103 | bool ignored ); -------------------------------------------------------------------------------- /libmui/src/mui/mui_ref.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_ref.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | struct mui_t; 14 | struct mui_control_t; 15 | struct mui_window_t; 16 | 17 | /*! 18 | * References allows arbitrary code to keep a 'handle' on either 19 | * a window or a control. This is used for example to keep track of 20 | * the currently focused control. 21 | * Client code Must Not keep random pointers on control and windows, 22 | * as they could get deleted and they will end up with a dangling 23 | * pointer. 24 | * Instead, client code create a reference, and use that reference 25 | * to keep track of the object. If an object is deleted, all it's 26 | * current references are reset to NULL, so the client code can 27 | * detect that the object is gone just by checking that its pointer 28 | * is still around. Otherwise, it's just gone. 29 | */ 30 | struct mui_ref_t; 31 | 32 | typedef struct mui_refqueue_t { 33 | TAILQ_HEAD(head, mui_ref_t) head; 34 | } mui_refqueue_t; 35 | 36 | typedef void (*mui_deref_p)( 37 | struct mui_ref_t * ref); 38 | 39 | typedef struct mui_ref_t { 40 | // in refqueue's 'head' 41 | TAILQ_ENTRY(mui_ref_t) self; 42 | mui_refqueue_t * queue; 43 | // OPTIONAL arbitrary kind set when referencing an object. 44 | uint32_t kind; 45 | uint32_t alloc : 1, trace : 1, count : 8; 46 | // OPTIONAL: called if the object win/control get disposed or 47 | // otherwise dereferenced. 48 | mui_deref_p deref; 49 | } _mui_ref_t; // this is not a 'user' type. 50 | 51 | /* 52 | * Window and Control references 53 | * While these two count technically be a union, I've deciced for separate 54 | * types to enforce the type checking. 55 | */ 56 | typedef struct mui_window_ref_t { 57 | _mui_ref_t ref; 58 | struct mui_window_t * window; 59 | } mui_window_ref_t; 60 | 61 | typedef struct mui_control_ref_t { 62 | _mui_ref_t ref; 63 | struct mui_control_t * control; 64 | } mui_control_ref_t; 65 | 66 | /*! 67 | * Initializes a reference to 'control', with the (optional) kind. 68 | * if 'ref' is NULL a new reference is allocated and returned, will be 69 | * freed on deref(). 70 | * 'kind' is an optional arbitrary value that can be used to identify 71 | * the reference, it has no meaning to the library. 72 | */ 73 | mui_control_ref_t * 74 | mui_control_ref( 75 | mui_control_ref_t * ref, 76 | struct mui_control_t * control, 77 | uint32_t kind); 78 | void 79 | mui_control_deref( 80 | mui_control_ref_t * ref); 81 | /*! 82 | * Initializes a reference to 'window', with the (optional) kind. 83 | * if 'ref' is NULL a new reference is allocated and returned, will be 84 | * freed on deref(). 85 | * 'kind' is an optional arbitrary value that can be used to identify 86 | * the reference, it has no meaning to the library. 87 | */ 88 | mui_window_ref_t * 89 | mui_window_ref( 90 | mui_window_ref_t * ref, 91 | struct mui_window_t * win, 92 | uint32_t kind); 93 | void 94 | mui_window_deref( 95 | mui_window_ref_t * ref); 96 | -------------------------------------------------------------------------------- /libmui/src/mui/mui_stdfile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_stdfile.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | 15 | /* 16 | * Standard file dialog 17 | */ 18 | enum mui_std_action_e { 19 | MUI_STDF_ACTION_NONE = 0, 20 | // parameter 'target' is a char * with full pathname of selected file 21 | MUI_STDF_ACTION_SELECT = FCC('s','t','d','s'), 22 | MUI_STDF_ACTION_CANCEL = FCC('s','t','d','c'), 23 | }; 24 | enum mui_std_flags_e { 25 | // 'pattern' is a GNU extended regexp applied to filenames. 26 | MUI_STDF_FLAG_REGEXP = (1 << 0), 27 | // don't use the 'pref_directory', load, or same preference files 28 | MUI_STDF_FLAG_NOPREF = (1 << 1), 29 | }; 30 | 31 | /* 32 | * Standard file dialog related 33 | * 34 | * Presents a standard 'get' file dialog, with optional prompt, regexp and 35 | * start path. The return value is a pointer to a window, you can add your own 36 | * 'action' function to get MUI_STDF_ACTION_* events. 37 | * Once in the action function, you can call mui_stdfile_get_selected_path() 38 | * to get the selected path, and free it when done. 39 | * NOTE: The dialog does not auto-close, your own action function should close 40 | * the dialog using mui_window_dispose(). 41 | * 42 | * The dialog will attempt to remember the last directory used *for this 43 | * particular pattern* and will use it as the default start path when called 44 | * again. This is optional, it requires a mui->pref_directory to be set. 45 | * You can also disable this feature by setting the MUI_STDF_FLAG_NOPREF flag. 46 | * 47 | * + 'pattern' is a regular expression to filter the files, or NULL for no 48 | * filter. 49 | * + if 'start_path' is NULL, the $HOME directory is used. 50 | * + 'where' is the location of the dialog, (0,0) will center it. 51 | */ 52 | mui_window_t * 53 | mui_stdfile_get( 54 | struct mui_t * ui, 55 | c2_pt_t where, 56 | const char * prompt, 57 | const char * pattern, 58 | const char * start_path, 59 | uint16_t flags ); 60 | // return the curently selected pathname -- caller must free() it 61 | char * 62 | mui_stdfile_get_selected_path( 63 | mui_window_t * w ); 64 | 65 | mui_window_t * 66 | mui_stdfile_put( 67 | struct mui_t * ui, 68 | c2_pt_t where, // pass 0,0 to center 69 | const char * prompt, // Window title 70 | const char * pattern, // Enforce any of these suffixes 71 | const char * start_path, // start in this path (optional) 72 | const char * save_filename, // start with this filename 73 | uint16_t flags ); 74 | -------------------------------------------------------------------------------- /libmui/src/mui/mui_timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_timer.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | 14 | typedef uint64_t mui_time_t; 15 | 16 | struct mui_t; 17 | 18 | /*! 19 | * Timer callback definition. Behaves in a pretty standard way; the timer 20 | * returns 0 to be cancelled (for one shot timers for example) or return 21 | * the delay to the next call (that will be added to 'now' to get the next) 22 | */ 23 | typedef mui_time_t (*mui_timer_p)( 24 | struct mui_t * mui, 25 | mui_time_t now, 26 | void * param); 27 | 28 | 29 | enum mui_time_e { 30 | MUI_TIME_RES = 1, 31 | MUI_TIME_SECOND = 1000000, 32 | MUI_TIME_MS = (MUI_TIME_SECOND/1000), 33 | }; 34 | mui_time_t 35 | mui_get_time(); 36 | 37 | #define MUI_TIMER_COUNT 64 38 | #define MUI_TIMER_NONE 0xff 39 | 40 | typedef uint8_t mui_timer_id_t; 41 | 42 | typedef struct mui_timer_group_t { 43 | uint64_t map; 44 | struct { 45 | mui_time_t when; 46 | mui_timer_p cb; 47 | void * param; 48 | } timers[MUI_TIMER_COUNT]; 49 | } mui_timer_group_t; 50 | 51 | /* 52 | * Register 'cb' to be called after 'delay'. Returns a timer id (0 to 63) 53 | * or MUI_TIMER_NONE if no timer is available. 54 | * The timer function cb can return 0 for a one shot timer, or another 55 | * delay that will be added to the current stamp for a further call 56 | * of the timer. 57 | * 'param' will be also passed to the timer callback. 58 | */ 59 | mui_timer_id_t 60 | mui_timer_register( 61 | struct mui_t * ui, 62 | mui_timer_p cb, 63 | void * param, 64 | uint32_t delay); 65 | /* 66 | * Reset timer 'id' if 'cb' matches what was registered. Set a new delay, 67 | * or cancel the timer if delay is 0. 68 | * Returns the time that was left on the timer, or 0 if the timer was 69 | * not found. 70 | */ 71 | mui_time_t 72 | mui_timer_reset( 73 | struct mui_t * ui, 74 | mui_timer_id_t id, 75 | mui_timer_p cb, 76 | mui_time_t delay); 77 | -------------------------------------------------------------------------------- /libmui/src/mui_alert.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_alert.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mui.h" 15 | 16 | 17 | typedef struct mui_alert_t { 18 | mui_window_t win; 19 | mui_control_t * ok, *cancel; 20 | } mui_alert_t; 21 | 22 | static int 23 | _mui_alert_button_cb( 24 | mui_control_t * c, 25 | void * cb_param, 26 | uint32_t what, 27 | void * param) 28 | { 29 | // mui_alert_t * alert = (mui_alert_t *)c->win; 30 | 31 | // notify the window handler of the control action 32 | mui_window_action(c->win, what, c); 33 | mui_window_dispose(c->win); 34 | return 0; 35 | } 36 | 37 | mui_window_t * 38 | mui_alert( 39 | struct mui_t * ui, 40 | c2_pt_t where, 41 | const char * title, 42 | const char * message, 43 | uint16_t flags ) 44 | { 45 | c2_rect_t cf = C2_RECT_WH(0, 0, 540, 200); 46 | 47 | if (where.x && where.y) 48 | c2_rect_offset(&cf, where.x, where.y); 49 | else 50 | c2_rect_offset(&cf, 51 | (ui->screen_size.x / 2) - (c2_rect_width(&cf) / 2), 52 | (ui->screen_size.y * 0.3) - (c2_rect_height(&cf) / 2)); 53 | mui_window_t *w = mui_window_create(ui, cf, 54 | NULL, MUI_WINDOW_LAYER_ALERT, 55 | title, sizeof(mui_alert_t)); 56 | mui_alert_t * alert = (mui_alert_t *)w; 57 | mui_control_t * c = NULL; 58 | 59 | cf = C2_RECT_WH(0, 0, 120, 40); 60 | c2_rect_left_of(&cf, c2_rect_width(&w->content), 20); 61 | c2_rect_top_of(&cf, c2_rect_height(&w->content), 20); 62 | if (flags & MUI_ALERT_FLAG_OK) { 63 | alert->ok = c = mui_button_new(w, cf, MUI_BUTTON_STYLE_DEFAULT, 64 | "OK", MUI_ALERT_BUTTON_OK); 65 | alert->ok->key_equ = MUI_KEY_EQU(0, 13); // return 66 | c2_rect_left_of(&cf, cf.l, 30); 67 | } 68 | if (flags & MUI_ALERT_FLAG_CANCEL) { 69 | alert->cancel = c = mui_button_new(w, cf, MUI_BUTTON_STYLE_NORMAL, 70 | "Cancel", MUI_ALERT_BUTTON_CANCEL); 71 | alert->cancel->key_equ = MUI_KEY_EQU(0, 27); // ESC 72 | } 73 | cf = C2_RECT_WH(0, 10, 540-140, 70); 74 | c2_rect_left_of(&cf, c2_rect_width(&w->content), 20); 75 | c = mui_textbox_new(w, cf, message, NULL, MUI_TEXT_ALIGN_COMPACT); 76 | cf = C2_RECT_WH(10, 10, 80, 85); 77 | c = mui_textbox_new(w, cf, 78 | "", "icon_large", 79 | MUI_TEXT_ALIGN_CENTER | MUI_TEXT_ALIGN_MIDDLE | 80 | MUI_TEXT_ALIGN_COMPACT); 81 | 82 | c = mui_controls_first(&w->controls, MUI_CONTROLS_ALL); 83 | while (c) { 84 | if (mui_control_get_uid(c) != 0) { 85 | mui_control_set_action(c, _mui_alert_button_cb, alert); 86 | } 87 | c = mui_controls_next(c, MUI_CONTROLS_ALL); 88 | } 89 | return w; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /libmui/src/mui_cdef_drawable.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_cdef_drawable.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include "mui.h" 13 | 14 | enum { 15 | MUI_CONTROL_DRAWABLE = FCC('D','R','A','W'), 16 | }; 17 | 18 | typedef struct mui_drawable_control_t { 19 | mui_control_t control; 20 | uint16_t flags; 21 | mui_drawable_t * mask; 22 | mui_drawable_array_t drawables; 23 | } mui_drawable_control_t; 24 | 25 | IMPLEMENT_C_ARRAY(mui_drawable_array); 26 | 27 | static void 28 | mui_drawable_draw( 29 | mui_window_t * win, 30 | mui_control_t * c, 31 | mui_drawable_t *dr ) 32 | { 33 | c2_rect_t f = c->frame; 34 | c2_rect_offset(&f, win->content.l, win->content.t); 35 | 36 | mui_drawable_clip_push(dr, &f); 37 | mui_drawable_control_t *dc = (mui_drawable_control_t *)c; 38 | 39 | for (uint i = 0; i < dc->drawables.count; i++) { 40 | mui_drawable_t *d = dc->drawables.e[i]; 41 | if (!d->pix.pixels) 42 | continue; 43 | c2_rect_t src = C2_RECT_WH(0, 0, 44 | d->pix.size.x, 45 | d->pix.size.y); 46 | c2_rect_offset(&src, d->origin.x, d->origin.y); 47 | 48 | pixman_image_composite32(PIXMAN_OP_OVER, 49 | mui_drawable_get_pixman(d), 50 | mui_drawable_get_pixman(dc->mask), 51 | mui_drawable_get_pixman(dr), 52 | src.l, src.t, 0, 0, 53 | f.l, f.t, 54 | c2_rect_width(&src), c2_rect_height(&src)); 55 | } 56 | mui_drawable_clip_pop(dr); 57 | } 58 | 59 | static bool 60 | mui_cdef_drawable( 61 | struct mui_control_t * c, 62 | uint8_t what, 63 | void * param) 64 | { 65 | switch (what) { 66 | case MUI_CDEF_DRAW: { 67 | mui_drawable_t * dr = param; 68 | switch (c->type) { 69 | case MUI_CONTROL_DRAWABLE: 70 | mui_drawable_draw(c->win, c, dr); 71 | break; 72 | } 73 | } break; 74 | case MUI_CDEF_DISPOSE: { 75 | switch (c->type) { 76 | case MUI_CONTROL_DRAWABLE: { 77 | mui_drawable_control_t *dc = (mui_drawable_control_t *)c; 78 | for (uint i = 0; i < dc->drawables.count; i++) { 79 | mui_drawable_t *d = dc->drawables.e[i]; 80 | mui_drawable_dispose(d); 81 | } 82 | mui_drawable_dispose(dc->mask); 83 | mui_drawable_array_free(&dc->drawables); 84 | } break; 85 | } 86 | } break; 87 | } 88 | return false; 89 | } 90 | 91 | mui_control_t * 92 | mui_drawable_control_new( 93 | mui_window_t * win, 94 | c2_rect_t frame, 95 | mui_drawable_t * dr, 96 | mui_drawable_t * mask, 97 | uint16_t flags) 98 | { 99 | mui_drawable_control_t *dc = (mui_drawable_control_t *)mui_control_new( 100 | win, MUI_CONTROL_DRAWABLE, mui_cdef_drawable, 101 | frame, NULL, 0, sizeof(mui_drawable_control_t)); 102 | dc->mask = mask; 103 | dc->flags = flags; 104 | if (dr) 105 | mui_drawable_array_add(&dc->drawables, dr); 106 | return &dc->control; 107 | } 108 | 109 | mui_drawable_t * 110 | mui_drawable_control_get_drawable( 111 | mui_control_t * c) 112 | { 113 | mui_drawable_control_t *dc = (mui_drawable_control_t *)c; 114 | return dc->drawables.count ? dc->drawables.e[0] : NULL; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /libmui/src/mui_cdef_te_priv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_cdef_te_priv.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "mui.h" 12 | 13 | 14 | /* 15 | * This describes a text edit action, either we insert some text at some position, 16 | * or we delete some text at some position. 17 | * These actions are queued in a TAILQ, so we can undo/redo them. 18 | * The text is UTF8, and the position is a BYTE index in the text (not a glyph). 19 | * 20 | * We preallocate a fixed number of actions, and when we reach the limit, we 21 | * start reusing the oldest ones. This limits the number of undo/redo actions 22 | * to something sensible. 23 | */ 24 | typedef struct mui_te_action_t { 25 | TAILQ_ENTRY(mui_te_action_t) self; 26 | uint insert : 1; // if not insert, its a delete 27 | uint32_t position, length; 28 | mui_utf8_t text; 29 | } mui_te_action_t; 30 | 31 | // action queue 32 | typedef TAILQ_HEAD(mui_te_action_queue_t, mui_te_action_t) mui_te_action_queue_t; 33 | 34 | /* 35 | * This describes the selection in the text-edit, it can either be a carret, 36 | * or a selection of text. The selection is kept as a start and end glyph index, 37 | * and the drawing code calculates the rectangles for the selection. 38 | */ 39 | typedef struct mui_sel_t { 40 | uint carret: 1; // carret is visible (if sel.start == end) 41 | uint start, end; // glyph index in text 42 | // rectangles for the first partial line, the body, 43 | // and the last partial line. All of them can be empty 44 | union { 45 | struct { 46 | c2_rect_t first, body, last; 47 | }; 48 | c2_rect_t e[3]; 49 | }; 50 | } mui_sel_t; 51 | 52 | typedef struct mui_textedit_control_t { 53 | mui_control_t control; 54 | uint trace : 1; // debug trace 55 | uint32_t flags; // display flags 56 | mui_sel_t sel; 57 | mui_font_t * font; 58 | mui_utf8_t text; 59 | mui_glyph_line_array_t measure; 60 | c2_pt_t margin; 61 | c2_rect_t text_content; 62 | struct { 63 | uint start, end; 64 | } click; 65 | uint selecting_mode; 66 | } mui_textedit_control_t; 67 | 68 | 69 | bool 70 | _mui_textedit_key( 71 | struct mui_control_t * c, 72 | mui_event_t * ev); 73 | 74 | /* this 'forces' the carret to be visible, used when typing */ 75 | void 76 | _mui_textedit_show_carret( 77 | mui_textedit_control_t * te); 78 | /* this one allows passing -1 etc, which is handy of cursor movement */ 79 | void 80 | _mui_textedit_select_signed( 81 | mui_textedit_control_t * te, 82 | int glyph_start, 83 | int glyph_end); 84 | /* Refresh the whole selection (or around the carret selection) */ 85 | void 86 | _mui_textedit_refresh_sel( 87 | mui_textedit_control_t * te, 88 | mui_sel_t * sel); 89 | uint 90 | _mui_glyph_to_byte_offset( 91 | mui_glyph_line_array_t * measure, 92 | uint glyph_pos); 93 | /* Return the glyph position in the text for line number and index in line */ 94 | uint 95 | _mui_line_index_to_glyph( 96 | mui_glyph_line_array_t * measure, 97 | uint line, 98 | uint index); 99 | 100 | /* Return the line number, and glyph position in line a glyph index */ 101 | int 102 | _mui_glyph_to_line_index( 103 | mui_glyph_line_array_t * measure, 104 | uint glyph_pos, 105 | uint * out_line, 106 | uint * out_line_index); 107 | 108 | void 109 | _mui_textedit_sel_delete( 110 | mui_textedit_control_t * te, 111 | bool re_measure, 112 | bool reset_sel); 113 | int 114 | _mui_make_sel_rects( 115 | mui_glyph_line_array_t * measure, 116 | mui_font_t * font, 117 | mui_sel_t * sel, 118 | c2_rect_t frame); 119 | /* This scrolls the view following the carret, used when typing. 120 | * This doesn't check for out of bounds, but the clamping should 121 | * have made sure the text is always visible. */ 122 | void 123 | _mui_textedit_ensure_carret_visible( 124 | mui_textedit_control_t * te); 125 | /* this makes sure the text is always visible in the frame */ 126 | void 127 | _mui_textedit_clamp_text_frame( 128 | mui_textedit_control_t * te); 129 | 130 | void 131 | _mui_textedit_refresh_measure( 132 | mui_textedit_control_t * te); 133 | /* 134 | * Rectangles passed here are in TEXT coordinates. 135 | * which means they are already offset by margin.x, margin.y 136 | * and the text_content.tl.x, text_content.tl.y 137 | */ 138 | void 139 | _mui_textedit_inval( 140 | mui_textedit_control_t * te, 141 | c2_rect_t r); 142 | -------------------------------------------------------------------------------- /libmui/src/mui_priv.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_priv.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | /* 10 | * This are functions that are used internally by the MUI library itself, 11 | * but are not part of the public API. 12 | */ 13 | 14 | #pragma once 15 | 16 | #include "mui.h" 17 | 18 | // private, used my mui_draw() -- do not use outside of this context 19 | void 20 | mui_window_draw( 21 | mui_window_t *win, 22 | mui_drawable_t *dr); 23 | bool 24 | mui_window_handle_mouse( 25 | mui_window_t *win, 26 | mui_event_t *event); 27 | bool 28 | mui_window_handle_keyboard( 29 | mui_window_t *win, 30 | mui_event_t *event); 31 | void 32 | mui_window_lock( 33 | mui_window_t *win); 34 | mui_window_t * 35 | mui_window_unlock( 36 | mui_window_t *win); 37 | 38 | void 39 | mui_control_draw( 40 | mui_window_t * win, 41 | mui_control_t * c, 42 | mui_drawable_t *dr ); 43 | void 44 | mui_control_lock( 45 | mui_control_t *c); 46 | mui_control_t * 47 | mui_control_unlock( 48 | mui_control_t *c); 49 | 50 | bool 51 | mui_control_event( 52 | mui_control_t * c, 53 | mui_event_t * ev ); 54 | 55 | 56 | /* This is common to: 57 | * - Menu titles in the menubar 58 | * - Menu items in a popup menu 59 | * - Menu items pointing to a sub-menu 60 | */ 61 | typedef struct mui_menuitem_control_t { 62 | mui_control_t control; 63 | mui_drawable_t * color_icon; // if one had been provided 64 | mui_menu_item_t item; 65 | } mui_menuitem_control_t; 66 | 67 | /* this is for menu title, popup menu labels, and hierarchical menu items */ 68 | typedef struct mui_menu_control_t { 69 | mui_menuitem_control_t item; 70 | mui_menu_items_t menu; 71 | c2_rect_t menu_frame; 72 | mui_window_ref_t menubar; 73 | mui_window_ref_t menu_window; // when open 74 | } mui_menu_control_t; 75 | 76 | // used by mui_wdef_menubar for frame and drawing. 77 | enum mui_mbar_style_e { 78 | MUI_MENU_STYLE_MBAR = 0, 79 | MUI_MENU_STYLE_MENU, 80 | MUI_MENU_STYLE_POPUP, 81 | }; 82 | 83 | void 84 | mui_wdef_menubar_draw( 85 | struct mui_window_t * win, 86 | mui_drawable_t * dr); 87 | void 88 | mui_popuptitle_draw( 89 | mui_window_t * win, 90 | mui_control_t * c, 91 | mui_drawable_t *dr ); 92 | void 93 | mui_menuitem_draw( 94 | mui_window_t * win, 95 | mui_control_t * c, 96 | mui_drawable_t *dr ); 97 | void 98 | mui_menutitle_draw( 99 | mui_window_t * win, 100 | mui_control_t * c, 101 | mui_drawable_t *dr ); 102 | void 103 | mui_popupmark_draw( 104 | mui_window_t * win, 105 | mui_control_t * c, 106 | mui_drawable_t *dr ); 107 | 108 | enum { 109 | MUI_MENUTITLE_PART_ALL = 0, 110 | MUI_MENUTITLE_PART_ICON, 111 | MUI_MENUTITLE_PART_TITLE, 112 | MUI_MENUTITLE_PART_COUNT, 113 | }; 114 | void 115 | mui_menutitle_get_part_locations( 116 | mui_t * ui, 117 | c2_rect_t * frame, // optional! 118 | mui_menu_item_t * item, 119 | c2_rect_t * out); 120 | 121 | // return true if window win is the menubar 122 | bool 123 | mui_menubar_window( 124 | mui_window_t * win); 125 | 126 | 127 | void 128 | mui_refqueue_init( 129 | mui_refqueue_t *queue); 130 | uint 131 | mui_refqueue_dispose( 132 | mui_refqueue_t *queue); 133 | -------------------------------------------------------------------------------- /libmui/src/mui_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_utils.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include "mui.h" 11 | 12 | mui_time_t 13 | mui_get_time() 14 | { 15 | struct timespec tim; 16 | clock_gettime(CLOCK_MONOTONIC, &tim); 17 | uint64_t time = ((uint64_t)tim.tv_sec) * (1000000 / MUI_TIME_RES) + 18 | tim.tv_nsec / (1000 * MUI_TIME_RES); 19 | return time; 20 | } 21 | 22 | 23 | uint32_t 24 | mui_hash( 25 | const char * inString ) 26 | { 27 | if (!inString) 28 | return 0; 29 | /* Described http://papa.bretmulvey.com/post/124027987928/hash-functions */ 30 | const uint32_t p = 16777619; 31 | uint32_t hash = 0x811c9dc5; 32 | while (*inString) 33 | hash = (hash ^ (*inString++)) * p; 34 | hash += hash << 13; 35 | hash ^= hash >> 7; 36 | hash += hash << 3; 37 | hash ^= hash >> 17; 38 | hash += hash << 5; 39 | return hash; 40 | } -------------------------------------------------------------------------------- /libmui/tests/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright (C) 2024 Michel Pollet 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | # Check to see if we are a submodule of the MII emulator 8 | MII_PLUG := ${wildcard ../../ui_gl/*.c} 9 | 10 | ifneq ($(MII_PLUG),) 11 | PLUGS += mii_ui 12 | endif 13 | PLUGS += mui_widgets_demo 14 | 15 | all : 16 | for plug in $(PLUGS); do \ 17 | $(MAKE) -C $$plug; \ 18 | done 19 | 20 | -------------------------------------------------------------------------------- /libmui/tests/mii_ui/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright (C) 2024 Michel Pollet 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | TARGET := mii_ui_tests 8 | 9 | LIBMUI := ../../ 10 | MII := ../../../ 11 | 12 | all : $(TARGET) 13 | 14 | include $(LIBMUI)/Makefile.common 15 | 16 | vpath %.c $(MII)/ui_gl 17 | 18 | CPPFLAGS += -I$(MII)/ui_gl 19 | 20 | .PHONY : $(TARGET) 21 | $(TARGET) : $(LIB)/$(TARGET).so 22 | 23 | MII_UI_OBJ := $(OBJ)/mii_mui_slots.o \ 24 | $(OBJ)/mii_mui_loadbin.o \ 25 | $(OBJ)/mii_mui_1mb.o \ 26 | $(OBJ)/mii_mui_2dsk.o \ 27 | $(OBJ)/mii_mui_about.o \ 28 | $(OBJ)/mii_mui_ssc.o \ 29 | $(OBJ)/mii_mui_utils.o 30 | 31 | $(LIB)/$(TARGET).so : $(OBJ)/$(TARGET).o $(MII_UI_OBJ) 32 | $(LIB)/$(TARGET).so : LDLIBS += $(LIB)/libmui.a 33 | 34 | $(OBJ)/mii_mui_about.o : CPPFLAGS+=-DMII_ICON64_DEFINE 35 | 36 | clean: 37 | rm -rf $(LIB)/$(TARGET).so $(MII_UI_OBJ) 38 | 39 | -include $(OBJ)/*.d 40 | -------------------------------------------------------------------------------- /libmui/tests/mii_ui/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### MII Emulator test plugin 3 | 4 | This is compiled only when libmui is part of the MII build. 5 | -------------------------------------------------------------------------------- /libmui/tests/mui_widgets_demo/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # Copyright (C) 2024 Michel Pollet 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | TARGET := mui_widgets_demo 8 | 9 | LIBMUI := ../../ 10 | 11 | all : $(TARGET) 12 | 13 | include $(LIBMUI)/Makefile.common 14 | 15 | .PHONY : $(TARGET) 16 | $(TARGET) : $(LIB)/$(TARGET).so 17 | 18 | $(LIB)/$(TARGET).so : $(OBJ)/$(TARGET).o 19 | $(LIB)/$(TARGET).so : LDLIBS += $(LIB)/libmui.a 20 | 21 | clean: 22 | rm -rf $(LIB)/$(TARGET).so 23 | 24 | -include $(OBJ)/*.d 25 | -------------------------------------------------------------------------------- /libmui/utils/png2raw.c: -------------------------------------------------------------------------------- 1 | #!/usr/bin/tcc -run 2 | /* 3 | * This tool is made to load a PNG, and convert it to a C array with 4 | * ARGB8888 format, premultiplied alpha. 5 | * It uses stb_image.h to load the PNG, which isn't included in the 6 | * repository. The tool isn't essential to build, but is useful if you 7 | * want icons in your menus. 8 | * The output is a C array, with the width and height of the image 9 | * followed by the pixels in ARGB8888 format. 10 | * The output is written to stdout, unless -o is used. 11 | */ 12 | #include 13 | #define STB_IMAGE_IMPLEMENTATION 14 | #define STBI_NO_SIMD 15 | #define STBI_NO_HDR 16 | #include "/opt/projects/stb/stb_image.h" 17 | 18 | static void usage(const char *argv0) 19 | { 20 | fprintf(stderr, 21 | "Usage: %s -n " 22 | "[-o ] " 23 | "[-t ] " 24 | "\n", 25 | argv0); 26 | } 27 | 28 | int main(int argc, char **argv) 29 | { 30 | const char *array_name = NULL; 31 | const char *fname = "docs/Apple_logo_rainbow_version2_28x28.png"; 32 | const char *int_type = "uint32_t"; 33 | FILE *out = stdout; 34 | 35 | for (int i = 1; i < argc; i++) { 36 | if (!strcmp(argv[i], "-n") && i + 1 < argc) { 37 | array_name = argv[i + 1]; 38 | i++; 39 | } else if (!strcmp(argv[i], "-o") && i + 1 < argc) { 40 | out = fopen(argv[i + 1], "w"); 41 | if (!out) { 42 | fprintf(stderr, "Error opening %s\n", argv[i + 1]); 43 | usage(argv[0]); 44 | return 1; 45 | } 46 | i++; 47 | } else if (!strcmp(argv[i], "-t") && i + 1 < argc) { 48 | int_type = argv[i + 1]; 49 | i++; 50 | } else if (!strcmp(argv[i], "-h")) { 51 | usage(argv[0]); 52 | return 0; 53 | } else { 54 | fname = argv[i]; 55 | } 56 | } 57 | if (!array_name) { 58 | fprintf(stderr, "Missing array name\n"); 59 | usage(argv[0]); 60 | return 1; 61 | } 62 | char define_name[256]; 63 | strcpy(define_name, array_name); 64 | for (int i = 0; define_name[i]; i++) 65 | define_name[i] = toupper(define_name[i]); 66 | int width, height, channels; 67 | unsigned char *data = stbi_load(fname, &width, &height, &channels, 0); 68 | if (!data) { 69 | fprintf(stderr, "Error loading image\n"); 70 | return 1; 71 | } 72 | fprintf(out, "// Autogenerated with:\n//\t"); 73 | for (int i = 0; i < argc; i++) 74 | fprintf(out, " %s", argv[i]); 75 | fprintf(out, "\n"); 76 | fprintf(out, "// Image with a W:%dpx, H:%dpx and %d channels\n" 77 | "// Converted to ARGB8888 and premultiplied alpha\n" 78 | "#pragma once\n", 79 | width, height, channels); 80 | fprintf(out, "#define %s_SIZE %d\n", define_name, 2 + (width * height)); 81 | fprintf(out, "extern const %s %s[%s_SIZE];\n", int_type, array_name, define_name); 82 | fprintf(out, "#ifdef %s_DEFINE\n", define_name); 83 | fprintf(out, "const %s %s[%s_SIZE] = {\n", int_type, array_name, define_name); 84 | fprintf(out, "%d, %d, // width, height\n", width, height); 85 | uint32_t *pixels = (uint32_t *)data; 86 | for (int y = 0; y < height; y++) { 87 | for (int x = 0; x < width; x++) { 88 | uint32_t pixel = pixels[y * width + x]; 89 | // flip R and B channels 90 | pixel = (pixel & 0xff00ff00) | 91 | ((pixel & 0xff) << 16) | ((pixel >> 16) & 0xff); 92 | // also premultiply channels with alpha 93 | uint32_t a = pixel >> 24; 94 | pixel = (pixel & ~(0xff << 0)) | 95 | ((((((pixel >> 0) & 0xff) * a) >> 8) & 0xff) << 0); 96 | pixel = (pixel & ~(0xff << 8)) | 97 | ((((((pixel >> 8) & 0xff) * a) >> 8) & 0xff) << 8); 98 | pixel = (pixel & ~(0xff << 16)) | 99 | ((((((pixel >> 16) & 0xff) * a) >> 8) & 0xff) << 16); 100 | fprintf(out, "0x%08x,", pixel); 101 | if (((y * height) + x) % 8 == 7) 102 | fprintf(out, "\n"); 103 | } 104 | } 105 | fprintf(out, "};\n"); 106 | fprintf(out, "#endif /* %s_DEFINE */\n", define_name); 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /src/drivers/mii_disk2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_disk2.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include "mii.h" 14 | #include "mii_floppy.h" 15 | 16 | enum { 17 | // bits used to address the LSS ROM using lss_mode 18 | Q7_WRITE_BIT = 3, 19 | Q6_LOAD_BIT = 2, 20 | QA_BIT = 1, 21 | RP_BIT = 0, 22 | }; 23 | 24 | typedef struct mii_card_disk2_t { 25 | mii_t * mii; 26 | mii_dd_t drive[2]; 27 | mii_floppy_t floppy[2]; 28 | uint8_t selected; 29 | 30 | uint8_t timer_off; 31 | uint8_t timer_lss; 32 | 33 | uint8_t iwm_mode; // IWM mode register -- fake for now 34 | uint8_t write_register; 35 | uint8_t head : 4; // bits are shifted in there 36 | uint16_t clock; // LSS clock cycles, read a bit when 0 37 | uint8_t lss_state : 4, // Sequence state 38 | lss_mode : 4; // WRITE/LOAD/SHIFT/QA/RP etc 39 | uint8_t lss_prev_state; // for write bit 40 | uint8_t lss_skip; 41 | uint8_t data_register; 42 | 43 | uint64_t debug_last_write, debug_last_duration; 44 | mii_vcd_t *vcd; 45 | mii_signal_t *sig; 46 | } mii_card_disk2_t; 47 | 48 | 49 | void 50 | _mii_disk2_vcd_debug( 51 | mii_card_disk2_t *c, 52 | int on); 53 | -------------------------------------------------------------------------------- /src/drivers/mii_epromcard.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_epromcard.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * This is a driver for these eprom/flash cards from 9 | * Terence J. Boldt and the likes 10 | */ 11 | #define _GNU_SOURCE // for asprintf 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "mii.h" 23 | #include "mii_bank.h" 24 | 25 | typedef struct mii_card_ee_t { 26 | mii_dd_t drive[1]; 27 | uint8_t * file; 28 | uint16_t latch; 29 | } mii_card_ee_t; 30 | 31 | 32 | static int 33 | _mii_ee_init( 34 | mii_t * mii, 35 | struct mii_slot_t *slot ) 36 | { 37 | mii_card_ee_t *c = calloc(1, sizeof(*c)); 38 | 39 | slot->drv_priv = c; 40 | printf("%s loading in slot %d\n", __func__, slot->id + 1); 41 | 42 | for (int i = 0; i < 1; i++) { 43 | mii_dd_t *dd = &c->drive[i]; 44 | dd->slot_id = slot->id + 1; 45 | dd->drive = i + 1; 46 | dd->slot = slot; 47 | dd->ro = 1; dd->wp = 1; 48 | asprintf((char **)&dd->name, "EE1MB S:%d D:%d", 49 | dd->slot_id, dd->drive); 50 | } 51 | mii_dd_register_drives(&mii->dd, c->drive, 1); 52 | 53 | #if 1 54 | mii_rom_t *rom = mii_rom_get("epromcard"); 55 | c->file = (uint8_t*)rom->rom; 56 | #else 57 | const char *fname = "disks/GamesWithFirmware.po"; 58 | 59 | mii_dd_file_t *file = mii_dd_file_load(&mii->dd, fname, 0); 60 | mii_dd_drive_load(&c->drive[0], file); 61 | c->file = file->map; 62 | #endif 63 | if (c->file) { 64 | uint16_t addr = 0xc100 + (slot->id * 0x100); 65 | mii_bank_write( 66 | &mii->bank[MII_BANK_CARD_ROM], 67 | addr, c->file + 0x300, 256); 68 | } 69 | return 0; 70 | } 71 | 72 | static uint8_t 73 | _mii_ee_access( 74 | mii_t * mii, struct mii_slot_t *slot, 75 | uint16_t addr, uint8_t byte, bool write) 76 | { 77 | mii_card_ee_t *c = slot->drv_priv; 78 | 79 | // printf("%s PC:%04x addr %04x %02x wr:%d\n", __func__, 80 | // mii->cpu.PC, addr, byte, write); 81 | int psw = addr & 0x0F; 82 | if (write) { 83 | switch (psw) { 84 | case 0: 85 | c->latch = (c->latch & 0xff00) | byte; 86 | break; 87 | case 1: 88 | c->latch = (c->latch & 0x00ff) | (byte << 8); 89 | break; 90 | } 91 | } else { 92 | return c->file ? c->file[(c->latch << 4) + psw] : 0xff; 93 | } 94 | return 0; 95 | } 96 | 97 | static int 98 | _mii_ee_command( 99 | mii_t * mii, 100 | struct mii_slot_t *slot, 101 | uint32_t cmd, 102 | void * param) 103 | { 104 | mii_card_ee_t *c = slot->drv_priv; 105 | int res = -1; 106 | switch (cmd) { 107 | case MII_SLOT_DRIVE_COUNT: 108 | if (param) { 109 | *(int *)param = 1; 110 | res = 0; 111 | } 112 | break; 113 | case MII_SLOT_DRIVE_LOAD: { 114 | const char *filename = param; 115 | mii_dd_file_t *file = NULL; 116 | if (filename && *filename) { 117 | file = mii_dd_file_load(&mii->dd, filename, 0); 118 | if (!file) 119 | return -1; 120 | } 121 | mii_dd_drive_load(&c->drive[0], file); 122 | mii_rom_t *rom = mii_rom_get("epromcard"); 123 | c->file = file ? file->map : (uint8_t*)rom->rom; 124 | res = 0; 125 | } break; 126 | } 127 | return res; 128 | } 129 | 130 | static mii_slot_drv_t _driver = { 131 | .name = "eecard", 132 | .desc = "EEPROM 1MB card", 133 | .init = _mii_ee_init, 134 | .access = _mii_ee_access, 135 | .command = _mii_ee_command, 136 | }; 137 | MI_DRIVER_REGISTER(_driver); 138 | -------------------------------------------------------------------------------- /src/drivers/mii_mouse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_mouse.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | // mins/max are set by the card, x,y,button are set by the UI 15 | typedef struct mii_mouse_t { 16 | bool enabled; // read only, set by driver 17 | int16_t min_x, max_x, 18 | min_y, max_y; // set by driver when enabled 19 | uint16_t x, y; 20 | bool button; 21 | } mii_mouse_t; 22 | -------------------------------------------------------------------------------- /src/drivers/mii_ssc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_ssc.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | /* 12 | * This is mostly a duplicate from the UI one in mii_mui_settings.h, but it is 13 | * part of the way we decouple the UI from the emulator, so we can test the UI 14 | * without having to link against the emulator. 15 | */ 16 | // this is to be used with MII_SLOT_SSC_SET_TTY and mii_slot_command() 17 | typedef struct mii_ssc_setconf_t { 18 | unsigned int baud, bits : 4, parity : 4, stop : 4, handshake : 4, 19 | is_device : 1, is_socket : 1, is_pty : 1; 20 | unsigned socket_port; 21 | char device[256]; 22 | } mii_ssc_setconf_t; 23 | 24 | -------------------------------------------------------------------------------- /src/drivers/mii_titan_iie.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_titan_iie.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mii.h" 15 | #include "mii_bank.h" 16 | 17 | /* 18 | * This is a mini driver for the Titan Accelerator IIe, not very common, 19 | * but it's a nice card, and it's a good example of a driver that needs 20 | * to use a softswitch ovverride to work. 21 | * Also, I own one of these, and none of the other fancy ones, so this one 22 | * gets the love. 23 | */ 24 | static bool 25 | _mii_titan_access( 26 | struct mii_bank_t *bank, 27 | void *param, 28 | uint16_t addr, 29 | uint8_t * byte, 30 | bool write) 31 | { 32 | mii_t *mii = param; 33 | bool res = false; 34 | mii_bank_t *sw = &mii->bank[MII_BANK_SW]; 35 | if (write) { 36 | printf("titan: write %02x to %04x\n", *byte, addr); 37 | switch (*byte) { 38 | case 5: 39 | mii->speed = MII_SPEED_TITAN; 40 | mii_bank_poke(sw, 0xc086, *byte); 41 | break; 42 | case 1: 43 | mii_bank_poke(sw, 0xc086, *byte); 44 | mii->speed = MII_SPEED_NTSC; 45 | break; 46 | case 0xa: // supposed to lock it too... 47 | mii_bank_poke(sw, 0xc086, *byte); 48 | mii->speed = MII_SPEED_NTSC; 49 | break; 50 | default: 51 | printf("titan: unknown speed %02x\n", *byte); 52 | break; 53 | } 54 | } 55 | return res; 56 | } 57 | 58 | static int 59 | _mii_titan_probe( 60 | mii_t *mii, 61 | uint32_t flags) 62 | { 63 | // printf("%s %s\n", __func__, flags & MII_INIT_TITAN ? "enabled" : "disabled"); 64 | if (!(flags & MII_INIT_TITAN)) 65 | return 0; 66 | // this override a read-only soft switch, but we only handle writes 67 | // so it's fine 68 | mii_set_sw_override(mii, 0xc086, _mii_titan_access, mii); 69 | mii->speed = 3.58; 70 | return 1; 71 | } 72 | 73 | static mii_slot_drv_t _driver = { 74 | .name = "titan", 75 | .desc = "Titan Accelerator IIe", 76 | .enable_flag = MII_INIT_TITAN, 77 | .probe = _mii_titan_probe, 78 | }; 79 | MI_DRIVER_REGISTER(_driver); 80 | -------------------------------------------------------------------------------- /src/format/mii_dd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_dd.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | struct mii_dd_t; 14 | 15 | enum { 16 | // MII_DD_FILE_OVERLAY = 1, 17 | MII_DD_FILE_RAM = 1, 18 | MII_DD_FILE_ROM, 19 | MII_DD_FILE_PO, 20 | MII_DD_FILE_2MG, 21 | MII_DD_FILE_DSK, 22 | MII_DD_FILE_DO, 23 | MII_DD_FILE_NIB, 24 | MII_DD_FILE_WOZ 25 | }; 26 | 27 | // a disk image file (or chunck of ram, if ramdisk is used) 28 | typedef struct mii_dd_file_t { 29 | struct mii_dd_file_t *next; 30 | char * pathname; 31 | uint8_t format; 32 | uint8_t read_only; 33 | uint8_t * start; // start of the file 34 | uint8_t * map; // start of the blocks 35 | 36 | int fd; // if fd >= 0, map is mmaped, otherwise it's malloced 37 | uint32_t size; 38 | struct mii_dd_t * dd; 39 | } mii_dd_file_t; 40 | 41 | /* 42 | * Overlays are made to provide what looks like read/write on files, but 43 | * without commiting any of the changes to the real primary file, instead 44 | * alld the changed blocks are kept into a sparse file and reloaded when 45 | * the 'real' block is read. 46 | * That way you can keep your disk images fresh and clean, while having 47 | * multiple version of them if you like. 48 | */ 49 | typedef union mii_dd_overlay_header_t { 50 | struct { 51 | uint32_t magic; // 'MIOV' 52 | uint32_t version; // 1 for now 53 | uint32_t flags; // unused for now 54 | uint32_t size; // size in blocks of original file 55 | uint8_t src_md5[16]; // md5 of the SOURCE disk 56 | }; 57 | uint32_t raw[16]; 58 | } mii_dd_overlay_header_t; 59 | 60 | typedef struct mii_dd_overlay_t { 61 | mii_dd_overlay_header_t *header; // points to the file mapped in memory 62 | uint64_t * bitmap; // usage bitmap 63 | uint8_t * blocks; // raw block data 64 | mii_dd_file_t * file; // overlay file mapping 65 | } mii_dd_overlay_t; 66 | 67 | struct mii_slot_t; 68 | struct mii_dd_system_t; 69 | struct mii_floppy_t; 70 | 71 | // a disk drive, with a slot, a drive number, and a file 72 | typedef struct mii_dd_t { 73 | struct mii_dd_t * next; 74 | struct mii_dd_system_t *dd; 75 | const char * name; // ie "Disk ][ D:2" 76 | struct mii_floppy_t * floppy; // if it's a floppy drive 77 | uint8_t slot_id : 4, drive : 4; 78 | struct mii_slot_t * slot; 79 | unsigned int ro : 1, wp : 1, can_eject : 1; 80 | mii_dd_file_t * file; 81 | mii_dd_overlay_t overlay; 82 | } mii_dd_t; 83 | 84 | typedef struct mii_dd_system_t { 85 | mii_dd_t * drive; // list of all drives on all slots 86 | mii_dd_file_t * file; // list of all open files (inc overlays) 87 | } mii_dd_system_t; 88 | 89 | struct mii_t; 90 | 91 | void 92 | mii_dd_system_init( 93 | struct mii_t *mii, 94 | mii_dd_system_t *dd ); 95 | void 96 | mii_dd_system_dispose( 97 | mii_dd_system_t *dd ); 98 | /* 99 | * register drives with the system -- these are not allocated, they are 100 | * statically defined in the driver code in their own structures 101 | */ 102 | void 103 | mii_dd_register_drives( 104 | mii_dd_system_t *dd, 105 | mii_dd_t * drives, 106 | uint8_t count ); 107 | int 108 | mii_dd_drive_load( 109 | mii_dd_t *dd, 110 | mii_dd_file_t *file ); 111 | 112 | /* 113 | * unmap, close and dispose of 'file', clear it from the drive, if any 114 | */ 115 | void 116 | mii_dd_file_dispose( 117 | mii_dd_system_t *dd, 118 | mii_dd_file_t *file ); 119 | mii_dd_file_t * 120 | mii_dd_file_load( 121 | mii_dd_system_t *dd, 122 | const char *filename, 123 | uint16_t flags); 124 | 125 | struct mii_bank_t; 126 | // read blocks from blk into bank's address 'addr' 127 | int 128 | mii_dd_read( 129 | mii_dd_t * dd, 130 | struct mii_bank_t *bank, 131 | uint16_t addr, 132 | uint32_t blk, 133 | uint16_t blockcount); 134 | 135 | int 136 | mii_dd_write( 137 | mii_dd_t * dd, 138 | struct mii_bank_t *bank, 139 | uint16_t addr, 140 | uint32_t blk, 141 | uint16_t blockcount); 142 | 143 | -------------------------------------------------------------------------------- /src/format/mii_dsk.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_dsk.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | 10 | #pragma once 11 | 12 | #include "mii_floppy.h" 13 | 14 | int 15 | mii_floppy_dsk_load( 16 | mii_floppy_t *f, 17 | mii_dd_file_t *file ); 18 | void 19 | _mii_floppy_dsk_write_sector( 20 | mii_dd_file_t *file, 21 | uint8_t *track_data, 22 | mii_floppy_track_map_t *map, 23 | uint8_t track_id, 24 | uint8_t sector, 25 | uint8_t data_sector[342 + 1] ); 26 | int 27 | mii_floppy_decode_sector( 28 | uint8_t data_sector[342 + 1], 29 | uint8_t data[256]); 30 | -------------------------------------------------------------------------------- /src/format/mii_nib.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_nib.c 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mii_dsk.h" 15 | 16 | /* 17 | * NIB isn't ideal to use with our bitstream, as it's lacking the sync 18 | * bits. 19 | * Anyway, We can recreate the proper bitstream by finding sectors headers, 20 | * filling up a few 'correct' 10 bits sync uint8_ts, then plonk said sector 21 | * as is. 22 | */ 23 | static void 24 | mii_floppy_nib_render_track( 25 | uint8_t *src_track, 26 | mii_floppy_track_t * dst, 27 | uint8_t * dst_track) 28 | { 29 | dst->bit_count = 0; 30 | dst->virgin = 0; 31 | uint32_t window = 0; 32 | int srci = 0; 33 | int seccount = 0; 34 | int state = 0; // look for address field 35 | int tid = 0, sid; 36 | uint16_t hmap = 0, dmap = 0; 37 | dst->has_map = 0; // we are redoing it all 38 | do { 39 | window = (window << 8) | src_track[srci++]; 40 | switch (state) { 41 | case 0: { 42 | if (window != 0xffd5aa96) 43 | break; 44 | uint32_t pos = dst->bit_count; 45 | for (int i = 0; i < (seccount == 0 ? 40 : 20); i++) 46 | mii_floppy_write_track_bits(dst, dst_track, 0xff << 2, 10); 47 | // mii_floppy_write_track_bits(dst, dst_track, 0xff, 8); 48 | // Points to the last sync 0xff of sync (which is 8 bits) 49 | uint8_t * h = src_track + srci - 4; 50 | tid = DE44(h[6], h[7]); 51 | sid = DE44(h[8], h[9]); 52 | // printf("Track %2d sector %2d pos %5d\n", tid, sid, pos); 53 | hmap |= 1 << sid; 54 | dst->map.sector[sid].hsync = dst->bit_count - pos; 55 | // points to the 0xd5 56 | dst->map.sector[sid].header = dst->bit_count + 8; 57 | memcpy(dst_track + (dst->bit_count >> 3), h, 15); 58 | dst->bit_count += 15 * 8; 59 | srci += 11; 60 | state = 1; 61 | } break; 62 | case 1: { 63 | if (window != 0xffd5aaad) 64 | break; 65 | uint32_t pos = dst->bit_count; 66 | for (int i = 0; i < 4; i++) 67 | mii_floppy_write_track_bits(dst, dst_track, 0xff << 2, 10); 68 | // printf("\tdata at %d\n", dst->bit_count); 69 | dmap |= 1 << sid; 70 | uint8_t *h = src_track + srci - 4; 71 | dst->map.sector[sid].dsync = dst->bit_count - pos; 72 | // keep the position in track to be able to save sectors back 73 | // this points to the first byte of data 74 | dst->map.sector[sid].nib_position = srci; 75 | // points to the 0xd5 76 | dst->map.sector[sid].data = dst->bit_count + 8; 77 | memcpy(dst_track + (dst->bit_count >> 3), h, 4 + 342 + 4); 78 | dst->map.sector[sid].crc = mii_floppy_crc(-1, src_track + srci, 342); 79 | dst->bit_count += (4 + 342 + 4) * 8; 80 | srci += 4 + 342; 81 | seccount++; 82 | state = 0; 83 | } break; 84 | } 85 | } while (srci < 6656); 86 | // printf("%s %d sectors found hmap %04x dmap %04x - %5d bits\n", 87 | // __func__, seccount, hmap, dmap, dst->bit_count); 88 | if (hmap != 0xffff || dmap != 0xffff) 89 | printf("%s: track %2d incomplete? (header 0x%04x data 0x%04x)\n", 90 | __func__, tid, ~hmap, ~dmap); 91 | else 92 | dst->has_map = 1; 93 | } 94 | 95 | /* 96 | * This one is easy, just copy the nibble back where they came from 97 | */ 98 | void 99 | _mii_floppy_nib_write_sector( 100 | mii_dd_file_t *file, 101 | uint8_t *track_data, 102 | mii_floppy_track_map_t *map, 103 | uint8_t track_id, 104 | uint8_t sector, 105 | uint8_t data_sector[342 + 1] ) 106 | { 107 | printf("%s: T %2d S %2d has changed, writing sector\n", 108 | __func__, track_id, sector); 109 | uint8_t *dst = file->map + (track_id * 6656) + 110 | map->sector[sector].nib_position; 111 | memcpy(dst, data_sector, 342 + 1); 112 | } 113 | 114 | int 115 | mii_floppy_nib_load( 116 | mii_floppy_t *f, 117 | mii_dd_file_t *file ) 118 | { 119 | const char *filename = basename(file->pathname); 120 | printf("%s: loading NIB %s\n", __func__, filename); 121 | for (int i = 0; i < 35; i++) { 122 | uint8_t *track = file->map + (i * 6656); 123 | mii_floppy_nib_render_track(track, &f->tracks[i], f->track_data[i]); 124 | if (f->tracks[i].bit_count < 100) { 125 | printf("%s: %s: Invalid track %d has zero bits!\n", __func__, 126 | filename, i); 127 | return -1; 128 | } 129 | // printf("Track %d converted to %d bits\n", i, f->tracks[i].bit_count); 130 | f->tracks[i].dirty = 0; 131 | } 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /src/format/mii_nib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_nib.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "mii_floppy.h" 12 | 13 | int 14 | mii_floppy_nib_load( 15 | mii_floppy_t *f, 16 | mii_dd_file_t *file ); 17 | void 18 | _mii_floppy_nib_write_sector( 19 | mii_dd_file_t *file, 20 | uint8_t *track_data, 21 | mii_floppy_track_map_t *map, 22 | uint8_t track_id, 23 | uint8_t sector, 24 | uint8_t data_sector[342 + 1] ); 25 | -------------------------------------------------------------------------------- /src/format/mii_woz.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_woz.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | /* 14 | * Woz format parser. 'le' fields are little-endian in the file. 15 | * Use the appropriate macros like le32toh() to convert to host-endian as needed. 16 | */ 17 | typedef struct mii_woz_header_t { 18 | uint32_t magic_le; // 'WOZ2' 19 | uint8_t padding[4]; 20 | uint32_t crc_le; 21 | } __attribute__((packed)) mii_woz_header_t; 22 | 23 | typedef struct mii_woz_chunk_t { 24 | uint32_t id_le; 25 | uint32_t size_le; 26 | } __attribute__((packed)) mii_woz_chunk_t; 27 | 28 | // offset 12 in the file, 'version' at offset 20 29 | typedef struct mii_woz2_info_t { 30 | mii_woz_chunk_t chunk; // 'INFO' 31 | uint8_t version; // version 3 32 | uint8_t disk_type; // 1=5.25" 2=3.5" 33 | uint8_t write_protected; 34 | uint8_t sync; // 1=cross track sync was used when imaged 35 | uint8_t cleaned; // 1=MC3470 fake bits have been cleaned 36 | uint8_t creator[32]; 37 | uint8_t sides; // 1 or 2 (3.5") 38 | uint8_t boot_format; // boot sector format 1:16,2:13,3:both 39 | uint8_t optimal_bit_timing; 40 | uint16_t compatible_hardware_le; 41 | uint16_t required_ram_le; // apple II ram required (48, 64 etc) 42 | uint16_t largest_track_le; // in units of 512 bytes 43 | uint16_t flux_block_le; 44 | uint16_t flux_largest_track_le; 45 | } __attribute__((packed)) mii_woz2_info_t; 46 | 47 | // offset 80 in the file -- same for WOZ1 and WOZ2 48 | typedef struct mii_woz_tmap_t { 49 | mii_woz_chunk_t chunk; // 'TMAP' 50 | // 140 bytes for 35*4 tracks; or 160 bytes for 80 tracks*2 sides 51 | uint8_t track_id[160]; // 'TRKS' id for each quarter track 52 | } __attribute__((packed)) mii_woz_tmap_t; 53 | 54 | // offset 248 in the file 55 | typedef struct mii_woz2_trks_t { 56 | mii_woz_chunk_t chunk; // 'TRKS' 57 | // offset 256 in the file 58 | struct { 59 | /* First block of BITS data. This value is relative to the start 60 | of the file, so the first possible starting block is 3. 61 | Multiply this value by 512 (x << 9) to get the starting byte 62 | of the BITS data. */ 63 | uint16_t start_block_le; // starting block number 64 | uint16_t block_count_le; // number of 512-byte blocks 65 | uint32_t bit_count_le; // number of bits in the track 66 | } track[160]; 67 | uint8_t bits[]; // the actual bits 68 | } __attribute__((packed)) mii_woz2_trks_t; 69 | 70 | /* Same info, tmap and trks for WOZ 1 files */ 71 | typedef struct mii_woz1_info_t { 72 | mii_woz_chunk_t chunk; // 'INFO' 73 | uint8_t version; // version 1 74 | uint8_t disk_type; // 1=5.25" 2=3.5" 75 | uint8_t write_protected; 76 | uint8_t sync; // 1=cross track sync was used when imaged 77 | uint8_t cleaned; // 1=MC3470 fake bits have been cleaned 78 | uint8_t creator[32]; 79 | } __attribute__((packed)) mii_woz1_info_t; 80 | 81 | // offset 248 in the file 82 | typedef struct mii_woz1_trks_t { 83 | mii_woz_chunk_t chunk; // 'TRKS' 84 | // offset 256 in the file 85 | struct { 86 | uint8_t bits[6646]; 87 | uint16_t byte_count_le; // size in bytes 88 | uint16_t bit_count_le; 89 | uint16_t splice_point_le; 90 | uint8_t splice_nibble; 91 | uint8_t splice_bit_count; 92 | uint16_t reserved; 93 | } track[35]; 94 | } __attribute__((packed)) mii_woz1_trks_t; 95 | 96 | struct mii_floppy_t; 97 | 98 | int 99 | mii_floppy_woz_write_track( 100 | struct mii_floppy_t *f, 101 | mii_dd_file_t *file, 102 | int track_id ); 103 | int 104 | mii_floppy_woz_load( 105 | struct mii_floppy_t *f, 106 | mii_dd_file_t *file ); 107 | -------------------------------------------------------------------------------- /src/mii_65c02_asm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_65c02_asm.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #pragma once 9 | 10 | #include 11 | 12 | 13 | typedef struct mii_cpu_asm_line_t { 14 | struct mii_cpu_asm_line_t *next; 15 | struct mii_cpu_asm_line_t *sym_next; 16 | uint16_t line_index; 17 | uint8_t symbol : 1, 18 | label_resolved : 1, 19 | addr_set : 1, op_low : 1, op_high : 1; 20 | uint16_t addr; // address of the instruction (if resolved) 21 | uint8_t mode; // mode of the instruction 22 | uint8_t opcode_count; // number of bytes for the opcode 23 | uint8_t opcodes[32]; // or .byte statements 24 | 25 | char label[64]; 26 | char mnemonic[64]; 27 | char operand[64]; 28 | 29 | char op_name[64]; 30 | int op_value; 31 | 32 | char line[]; // actual line read ends up here, untouched 33 | } mii_cpu_asm_line_t; 34 | 35 | typedef struct mii_cpu_asm_program_t { 36 | uint8_t verbose; 37 | uint16_t org; // origin, can be set before, or with .org 38 | mii_cpu_asm_line_t *sym; 39 | mii_cpu_asm_line_t *sym_tail; 40 | 41 | mii_cpu_asm_line_t *prog; 42 | mii_cpu_asm_line_t *prog_tail; 43 | 44 | uint8_t * output; 45 | uint16_t output_len; 46 | } mii_cpu_asm_program_t; 47 | 48 | int 49 | mii_cpu_asm( 50 | mii_cpu_asm_program_t *p, 51 | const char *prog); 52 | void 53 | mii_cpu_asm_free( 54 | mii_cpu_asm_program_t *p); 55 | -------------------------------------------------------------------------------- /src/mii_65c02_disasm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_65c02_disasm.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include "mii_65c02.h" 13 | #include "mii_65c02_ops.h" 14 | #include "mii_65c02_disasm.h" 15 | 16 | int 17 | mii_cpu_disasm_one( 18 | const uint8_t *prog, 19 | uint16_t addr, 20 | char *out, 21 | size_t out_len, 22 | uint16_t flags) 23 | { 24 | uint16_t pc = addr; 25 | int i = 0; 26 | 27 | uint8_t op = prog[i]; 28 | mii_op_desc_t d = mii_cpu_op[op].desc; 29 | if (!d.pc) 30 | d.pc = 1; 31 | // special case for JSR, it is marked as IMPLIED for execution, but is 32 | // in fact ABSOLUTE for PC calculation 33 | if (op == 0x20) 34 | d.mode = ABS; 35 | *out = 0; 36 | int len = out_len; 37 | if (flags & MII_DUMP_DIS_PC) 38 | len -= snprintf(out + strlen(out), len, "%04X: ", pc + i); 39 | if (flags & MII_DUMP_DIS_DUMP_HEX) { 40 | char hex[32] = {0}; 41 | for (int oi = 0; oi < d.pc; oi++) 42 | sprintf(hex + (oi * 3), "%02X ", prog[i + oi]); 43 | len -= snprintf(out + strlen(out), len, "%-9s ", hex); 44 | } 45 | len -= snprintf(out + strlen(out), len, "%.4s ", 46 | mii_cpu_op[op].name[0] ? 47 | mii_cpu_op[op].name : "???"); 48 | i++; 49 | switch (d.mode) { 50 | case IMM: 51 | len -= snprintf(out + strlen(out), len, 52 | "#$%02X", prog[i++]); 53 | break; 54 | case BRANCH: 55 | case ZP_REL: 56 | if ((op & 0xf) == 0xf) { 57 | uint16_t base = pc + i + 1; 58 | uint16_t dest = base + (int8_t)prog[i++]; 59 | len -= snprintf(out + strlen(out), len, 60 | "%d,$%04X", d.s_bit, dest); 61 | } else if (d.branch) { 62 | uint16_t base = pc + i + 1; 63 | uint16_t dest = base + (int8_t)prog[i++]; 64 | len -= snprintf(out + strlen(out), len, 65 | "$%04X", dest); 66 | } else 67 | len -= snprintf(out + strlen(out), len, 68 | "$%02X", prog[i++]); 69 | break; 70 | case ZP_X: 71 | len -= snprintf(out + strlen(out), len, 72 | "$%02X,X", prog[i++]); 73 | break; 74 | case ZP_Y: 75 | len -= snprintf(out + strlen(out), len, 76 | "$%02X,Y", prog[i++]); 77 | break; 78 | case ABS: 79 | len -= snprintf(out + strlen(out), len, 80 | "$%02X%02X", prog[i + 1], prog[i]); 81 | i += 2; 82 | break; 83 | case ABS_X: 84 | len -= snprintf(out + strlen(out), len, 85 | "$%02X%02X,X", prog[i + 1], prog[i]); 86 | i += 2; 87 | break; 88 | case ABS_Y: 89 | len -= snprintf(out + strlen(out), len, 90 | "$%02X%02X,Y", prog[i + 1], prog[i]); 91 | i += 2; 92 | break; 93 | case IND_X: 94 | len -= snprintf(out + strlen(out), len, 95 | "($%02X,X)", prog[i++]); 96 | break; 97 | case IND_AX: 98 | len -= snprintf(out + strlen(out), len, 99 | "($%02X%02X,X)", prog[i + 1], prog[i]); 100 | i += 2; 101 | break; 102 | case IND_Y: 103 | len -= snprintf(out + strlen(out), len, 104 | "($%02X),Y", prog[i++]); 105 | break; 106 | case IND_Z: 107 | len -= snprintf(out + strlen(out), len, 108 | "($%02X)", prog[i++]); 109 | break; 110 | case IND: 111 | len -= snprintf(out + strlen(out), len, 112 | "($%02X%02X)", prog[i + 1], prog[i]); 113 | i += 2; 114 | break; 115 | case IMPLIED: 116 | break; 117 | } 118 | return d.pc; 119 | } 120 | 121 | void 122 | mii_cpu_disasm( 123 | const uint8_t *prog, 124 | uint16_t addr, 125 | uint16_t len) 126 | { 127 | uint16_t pc = addr; 128 | int i = 0; 129 | while (i < len) { 130 | char out[256] = {0}; 131 | i += mii_cpu_disasm_one(prog + i, pc + i, out, sizeof(out), 132 | MII_DUMP_DIS_PC | MII_DUMP_DIS_DUMP_HEX); 133 | printf("%s\n", out); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/mii_65c02_disasm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_65c02_disasm.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | 14 | enum { 15 | MII_DUMP_DIS_PC = (1 << 0), 16 | MII_DUMP_DIS_DUMP_HEX = (1 << 1), 17 | }; 18 | 19 | 20 | int 21 | mii_cpu_disasm_one( 22 | const uint8_t *prog, 23 | uint16_t addr, 24 | char *out, 25 | size_t out_len, 26 | uint16_t flags); 27 | 28 | void 29 | mii_cpu_disasm( 30 | const uint8_t *prog, 31 | uint16_t addr, 32 | uint16_t len); 33 | -------------------------------------------------------------------------------- /src/mii_analog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_analog.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mii.h" 15 | #include "mii_analog.h" 16 | 17 | /* 18 | * Analog joystick 19 | * This is fairly easy, as long as the 65c02 respects the proper cycle 20 | * count for all the instruction involved in reading, as it's very cycle 21 | * sensitive. 22 | * the UI fills up the analog values in mii_t, and here we just simulate 23 | * the capacitor decay. 24 | */ 25 | 26 | void 27 | mii_analog_init( 28 | struct mii_t *mii, 29 | mii_analog_t * a ) 30 | { 31 | memset(a, 0, sizeof(*a)); 32 | } 33 | 34 | /* 35 | * https://retrocomputing.stackexchange.com/questions/15093/how-do-i-read-the-position-of-an-apple-ii-joystick 36 | */ 37 | void 38 | mii_analog_access( 39 | mii_t *mii, 40 | mii_analog_t * a, 41 | uint16_t addr, 42 | uint8_t * byte, 43 | bool write) 44 | { 45 | if (write) 46 | return; 47 | switch (addr) { 48 | case 0xc070: { 49 | /* 50 | * No need starting the cycle timers when nobody cares about 51 | * the analog values aka joysticks 52 | */ 53 | if (!a->enabled) { 54 | a->enabled = true; 55 | /* 56 | * No need for a function pointer here for the timer, the 57 | * decrementing value is just what we need, and we're quite 58 | * happy to stop at ~0 as well. 59 | */ 60 | for (int i = 0; i < 4; i++) 61 | a->v[i].timer_id = mii_timer_register(mii, 62 | NULL, NULL, 0, __func__); 63 | } 64 | /* 65 | * Multiplying by mii->speed allows reading joystick in 66 | * 'fast' emulation mode, this basically simulate slowing down 67 | * just for the joystick reading 68 | */ 69 | for (int i = 0; i < 4; i++) { 70 | mii_timer_set(mii, a->v[i].timer_id, 71 | ((a->v[i].value * 11) * mii->speed)); 72 | // printf("joystick %d: %d\n", i, a->v[i].value); 73 | } 74 | } break; 75 | case 0xc064 ... 0xc067: { 76 | addr -= 0xc064; 77 | *byte = mii_timer_get(mii, a->v[addr].timer_id) > 0 ? 0x80 : 0x00; 78 | } break; 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/mii_analog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_analog.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #pragma once 9 | 10 | #include 11 | 12 | typedef struct mii_analog_t { 13 | struct { 14 | uint8_t value; 15 | // mii_cycles_t decay; 16 | uint8_t timer_id; 17 | } v[4]; 18 | bool enabled; 19 | } mii_analog_t; 20 | 21 | struct mii_t; 22 | 23 | void 24 | mii_analog_init( 25 | struct mii_t *mii, 26 | mii_analog_t * analog ); 27 | 28 | void 29 | mii_analog_access( 30 | struct mii_t *mii, 31 | mii_analog_t * analog, 32 | uint16_t addr, 33 | uint8_t * byte, 34 | bool write); 35 | -------------------------------------------------------------------------------- /src/mii_audio.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_audio.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "mii.h" 14 | 15 | void 16 | mii_audio_init( 17 | struct mii_t *mii, 18 | mii_audio_sink_t *sink) 19 | { 20 | sink->drv = NULL; 21 | sink->mii = mii; 22 | SLIST_INIT(&sink->source); 23 | mii_audio_run(sink); 24 | } 25 | 26 | void 27 | mii_audio_dispose( 28 | mii_audio_sink_t *sink) 29 | { 30 | if (sink->drv && sink->drv->stop) 31 | sink->drv->stop(sink); 32 | while (!SLIST_EMPTY(&sink->source)) { 33 | mii_audio_source_t *source = SLIST_FIRST(&sink->source); 34 | source->sink = NULL; 35 | SLIST_REMOVE_HEAD(&sink->source, self); 36 | } 37 | } 38 | 39 | void 40 | mii_audio_set_driver( 41 | mii_audio_sink_t *sink, 42 | const mii_audio_driver_t *drv) 43 | { 44 | printf("%s: %p\n", __func__, drv); 45 | sink->drv = (mii_audio_driver_t*)drv; 46 | } 47 | 48 | void 49 | mii_audio_start( 50 | mii_audio_sink_t *sink ) 51 | { 52 | if (sink->drv && sink->drv->start) 53 | sink->drv->start(sink); 54 | } 55 | 56 | void 57 | mii_audio_run( 58 | mii_audio_sink_t *s ) 59 | { 60 | if (!s || !s->mii) return; 61 | // if CPU speed has changed, recalculate the number of cycles per sample 62 | if (s->cpu_speed != s->mii->speed) { 63 | s->cpu_speed = s->mii->speed; 64 | s->clk_per_sample = ((1000000.0 * s->mii->speed) / 65 | (float)MII_AUDIO_FREQ) + 0.5f; 66 | printf("%s: %.2f cycles per sample\n", __func__, s->clk_per_sample); 67 | } 68 | } 69 | 70 | // this is here so we dont' have to drag in libm math library. 71 | double fastPow(double a, double b) { 72 | union { double d; int32_t x[2]; } u = { .d = a }; 73 | u.x[1] = (int)(b * (u.x[1] - 1072632447) + 1072632447); 74 | u.x[0] = 0; 75 | return u.d; 76 | } 77 | 78 | // volume from 0 to 10, sets the audio sample multiplier. 79 | void 80 | mii_audio_volume( 81 | mii_audio_source_t *s, 82 | float volume) 83 | { 84 | if (volume < 0) volume = 0; 85 | else if (volume > 10) volume = 10; 86 | double mul = (fastPow(10.0, volume / 10.0) / 10.0) - 0.09; 87 | s->vol_multiplier = mul; 88 | s->volume = volume; 89 | } 90 | 91 | void 92 | mii_audio_add_source( 93 | mii_audio_sink_t *sink, 94 | mii_audio_source_t *source) 95 | { 96 | source->sink = sink; 97 | mii_audio_volume(source, 5); 98 | SLIST_INSERT_HEAD(&sink->source, source, self); 99 | } 100 | 101 | 102 | void 103 | mii_audio_source_push( 104 | mii_audio_source_t *source, 105 | mii_audio_frame_t *frame) 106 | { 107 | if (source->sink->drv && source->sink->drv->write) 108 | source->sink->drv->write(source->sink, source); 109 | } 110 | -------------------------------------------------------------------------------- /src/mii_audio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_audio.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "fifo_declare.h" 13 | #include "bsd_queue.h" 14 | 15 | #define MII_AUDIO_FREQ (44100) 16 | // circular buffer 17 | #define MII_AUDIO_FRAME_SIZE 4096 18 | 19 | typedef float mii_audio_sample_t; 20 | 21 | DECLARE_FIFO(mii_audio_sample_t, mii_audio_frame, MII_AUDIO_FRAME_SIZE); 22 | DEFINE_FIFO(mii_audio_sample_t, mii_audio_frame); 23 | 24 | struct mii_audio_sink_t; 25 | 26 | 27 | enum mii_audio_source_state_e { 28 | MII_AUDIO_IDLE, 29 | MII_AUDIO_STARTING, 30 | MII_AUDIO_PLAYING, 31 | MII_AUDIO_STOPPING, 32 | }; 33 | 34 | /* 35 | * A source of samples. It has a FIFO that source can fill up, and 36 | * it is attached to a sink that will consume the samples. 37 | * The state field is filed by the source itself, the audio sink uses 38 | * it to know when playing starts/stops for padding reasons. 39 | */ 40 | typedef struct mii_audio_source_t { 41 | struct mii_audio_sink_t * sink; 42 | SLIST_ENTRY(mii_audio_source_t) self; 43 | uint state; 44 | // mute (without having to the the volume to zero) 45 | float volume; // volume, 0.0 to 10.0 46 | float vol_multiplier; // 0.0 to 1.0 47 | mii_audio_frame_t fifo; 48 | uint last_read; 49 | } mii_audio_source_t; 50 | 51 | /* 52 | * Audio sink "pulls" samples from the sources, mix them, and send them to the 53 | * audio driver. 54 | */ 55 | typedef struct mii_audio_driver_t { 56 | void (*start)( 57 | struct mii_audio_sink_t *sink); 58 | void (*stop)( 59 | struct mii_audio_sink_t *sink); 60 | void (*write)( 61 | struct mii_audio_sink_t *sink, 62 | mii_audio_source_t *source); 63 | } mii_audio_driver_t; 64 | 65 | 66 | typedef struct mii_audio_sink_t { 67 | struct mii_t * mii; 68 | mii_audio_driver_t * drv; 69 | uint muted : 1, state; 70 | SLIST_HEAD(, mii_audio_source_t) source; 71 | // last CPU speed in MHz, to calculate clk_per_sample 72 | float cpu_speed; 73 | // number of cycles per sample (at current CPU speed) 74 | float clk_per_sample; 75 | } mii_audio_sink_t; 76 | 77 | void 78 | mii_audio_init( 79 | struct mii_t *mii, 80 | mii_audio_sink_t *sink); 81 | 82 | void 83 | mii_audio_dispose( 84 | mii_audio_sink_t *sink); 85 | 86 | void 87 | mii_audio_set_driver( 88 | mii_audio_sink_t *sink, 89 | const mii_audio_driver_t *drv); 90 | 91 | void 92 | mii_audio_add_source( 93 | mii_audio_sink_t *sink, 94 | mii_audio_source_t *source); 95 | 96 | void 97 | mii_audio_start( 98 | mii_audio_sink_t *sink ); 99 | void 100 | mii_audio_run( 101 | mii_audio_sink_t *sink ); 102 | // volume from 0 to 10, sets the audio sample multiplier. 103 | void 104 | mii_audio_volume( 105 | mii_audio_source_t *source, 106 | float volume); 107 | -------------------------------------------------------------------------------- /src/mii_bank.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_bank.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mii.h" 15 | #include "mii_bank.h" 16 | 17 | 18 | void 19 | mii_bank_init( 20 | mii_bank_t *bank) 21 | { 22 | if (bank->mem) 23 | return; 24 | if (bank->mem_offset == 0 && !bank->no_alloc) { 25 | bank->mem = calloc(1, bank->size * 256); 26 | bank->alloc = 1; 27 | } 28 | } 29 | 30 | void 31 | mii_bank_dispose( 32 | mii_bank_t *bank) 33 | { 34 | // printf("%s %s\n", __func__, bank->name); 35 | if (bank->alloc) 36 | free(bank->mem); 37 | bank->mem = NULL; 38 | bank->alloc = 0; 39 | if (bank->access) { 40 | // Allow callback to free anything it wants 41 | for (int i = 0; i < bank->size; i++) 42 | if (bank->access[i].cb) 43 | bank->access[i].cb(NULL, bank->access[i].param, 0, NULL, false); 44 | free(bank->access); 45 | } 46 | bank->access = NULL; 47 | } 48 | 49 | bool 50 | mii_bank_access( 51 | mii_bank_t *bank, 52 | uint16_t addr, 53 | const uint8_t *data, 54 | uint16_t len, 55 | bool write) 56 | { 57 | uint8_t page_index = (addr - bank->base) >> 8; 58 | if (bank->access && bank->access[page_index].cb) { 59 | if (bank->access[page_index].cb(bank, bank->access[page_index].param, 60 | addr, (uint8_t *)data, write)) 61 | return true; 62 | } 63 | return false; 64 | } 65 | 66 | void 67 | mii_bank_write( 68 | mii_bank_t *bank, 69 | uint16_t addr, 70 | const uint8_t *data, 71 | uint16_t len) 72 | { 73 | #if 0 // rather expensive test when profiling! 74 | uint32_t end = bank->base + (bank->size << 8); 75 | if (unlikely(addr < bank->base || (addr + len) > end)) { 76 | printf("%s %s INVALID write addr %04x len %d %04x:%04x\n", 77 | __func__, bank->name, addr, (int)len, 78 | bank->base, end); 79 | abort(); 80 | return; 81 | } 82 | #endif 83 | if (mii_bank_access(bank, addr, data, len, true)) 84 | return; 85 | uint32_t phy = bank->mem_offset + addr - bank->base; 86 | do { 87 | bank->mem[phy++] = *data++; 88 | } while (likely(--len)); 89 | } 90 | 91 | void 92 | mii_bank_read( 93 | mii_bank_t *bank, 94 | uint16_t addr, 95 | uint8_t *data, 96 | uint16_t len) 97 | { 98 | #if 0 // rather expensive test when profiling! 99 | uint32_t end = bank->base + (bank->size << 8); 100 | if (unlikely(addr < bank->base) || unlikely((addr + len) > end)) { 101 | printf("%s %s INVALID read addr %04x len %d %04x-%04x\n", 102 | __func__, bank->name, addr, (int)len, 103 | bank->base, end); 104 | return; 105 | } 106 | #endif 107 | if (mii_bank_access(bank, addr, data, len, false)) 108 | return; 109 | uint32_t phy = bank->mem_offset + addr - bank->base; 110 | do { 111 | *data++ = bank->mem[phy++]; 112 | } while (likely(--len)); 113 | } 114 | 115 | 116 | void 117 | mii_bank_install_access_cb( 118 | mii_bank_t *bank, 119 | mii_bank_access_cb cb, 120 | void *param, 121 | uint8_t page, 122 | uint8_t end) 123 | { 124 | if (!end) 125 | end = page; 126 | if ((page << 8) < bank->base || (end << 8) > (bank->base + bank->size * 256)) { 127 | printf("%s %s INVALID install access cb %p param %p page %02x-%02x\n", 128 | __func__, bank->name, cb, param, page, end); 129 | return; 130 | } 131 | page -= bank->base >> 8; 132 | end -= bank->base >> 8; 133 | if (!bank->access) { 134 | bank->access = calloc(1, bank->size * sizeof(bank->access[0])); 135 | } 136 | printf("%s %s install access cb page %02x:%02x\n", 137 | __func__, bank->name, page, end); 138 | for (int i = page; i <= end; i++) { 139 | if (bank->access[i].cb) 140 | printf("%s %s page %02x already has a callback\n", 141 | __func__, bank->name, i); 142 | bank->access[i].cb = cb; 143 | bank->access[i].param = param; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/mii_bank.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_bank.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include 12 | 13 | struct mii_bank_t; 14 | /* 15 | * Bank access callback can be register with banks and will be called 16 | * when a specific page (or pages) are read/written to. 17 | * Parameter will be the bank specified, the 'real' address beind read/written 18 | * and the 'param' passed when the callback was registered. 19 | * The callback should return true if it handled the access, false otherwise. 20 | * If it return true, the bank will not be accessed for this read/write. 21 | * 22 | * Note: the callback will be called once with bank == NULL when the bank 23 | * is being disposed, this allow any memory allocated by the callback to be 24 | * freed. 25 | */ 26 | typedef bool (*mii_bank_access_cb)( 27 | struct mii_bank_t *bank, 28 | void *param, 29 | uint16_t addr, 30 | uint8_t * byte, 31 | bool write); 32 | 33 | typedef struct mii_bank_access_t { 34 | mii_bank_access_cb cb; 35 | void *param; 36 | } mii_bank_access_t; 37 | 38 | typedef struct mii_bank_t { 39 | uint64_t base : 16, // base address 40 | size : 9, // in pages 41 | no_alloc: 1, // don't allocate memory 42 | alloc : 1, // been malloced() 43 | ro : 1; // read only 44 | char * name; 45 | mii_bank_access_t * access; 46 | uint8_t *mem; 47 | uint32_t mem_offset; 48 | } mii_bank_t; 49 | 50 | void 51 | mii_bank_init( 52 | mii_bank_t *bank); 53 | void 54 | mii_bank_dispose( 55 | mii_bank_t *bank); 56 | void 57 | mii_bank_write( 58 | mii_bank_t *bank, 59 | uint16_t addr, 60 | const uint8_t *data, 61 | uint16_t len); 62 | void 63 | mii_bank_read( 64 | mii_bank_t *bank, 65 | uint16_t addr, 66 | uint8_t *data, 67 | uint16_t len); 68 | bool 69 | mii_bank_access( 70 | mii_bank_t *bank, 71 | uint16_t addr, 72 | const uint8_t *data, 73 | uint16_t len, 74 | bool write); 75 | 76 | /* return the number of pages dirty (written into since last time) between 77 | * addr1 and addr2 (inclusive) */ 78 | uint8_t 79 | mii_bank_is_dirty( 80 | mii_bank_t *bank, 81 | uint16_t addr1, 82 | uint16_t addr2); 83 | void 84 | mii_bank_install_access_cb( 85 | mii_bank_t *bank, 86 | mii_bank_access_cb cb, 87 | void *param, 88 | uint8_t page, 89 | uint8_t end); 90 | 91 | static inline void 92 | mii_bank_poke( 93 | mii_bank_t *bank, 94 | uint16_t addr, 95 | const uint8_t data) 96 | { 97 | mii_bank_write(bank, addr, &data, 1); 98 | } 99 | 100 | static inline uint8_t 101 | mii_bank_peek( 102 | mii_bank_t *bank, 103 | uint16_t addr) 104 | { 105 | uint8_t res = 0; 106 | mii_bank_read(bank, addr, &res, 1); 107 | return res; 108 | } 109 | -------------------------------------------------------------------------------- /src/mii_mish_dd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_mish_dd.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mii.h" 15 | 16 | 17 | static void 18 | _mii_mish_dd( 19 | void * param, 20 | int argc, 21 | const char * argv[]) 22 | { 23 | mii_t * mii = param; 24 | if (!argv[1] || !strcmp(argv[1], "list")) { 25 | mii_dd_t *d = mii->dd.drive; 26 | printf("dd %p %p drives\n", &mii->dd, mii->dd.drive); 27 | printf(" ID %-16s %-20s\n", "Card", "Name"); 28 | while (d) { 29 | printf("%d:%d %-16s %-20s : %s\n", 30 | d->slot_id, d->drive, 31 | d->slot->drv->desc, 32 | d->name, d->file ? d->file->pathname : "*empty*"); 33 | d = d->next; 34 | } 35 | return; 36 | } 37 | } 38 | 39 | #include "mish.h" 40 | 41 | MISH_CMD_NAMES(dd, "dd", "disk"); 42 | MISH_CMD_HELP(dd, 43 | "mii: disk commands", 44 | " |list: list all disk drives" 45 | ); 46 | MII_MISH(dd, _mii_mish_dd); -------------------------------------------------------------------------------- /src/mii_rom.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_rom.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "mii_rom.h" 14 | 15 | 16 | typedef SLIST_HEAD(mii_rom_queue_t, mii_rom_t) mii_rom_queue_t; 17 | 18 | static mii_rom_queue_t mii_rom_queue = SLIST_HEAD_INITIALIZER(mii_rom_queue); 19 | 20 | void 21 | mii_rom_register( 22 | struct mii_rom_t *rom) 23 | { 24 | // insert in the queue, but keep it sorted by class, then name 25 | mii_rom_t *r, *prev = NULL; 26 | SLIST_FOREACH(r, &mii_rom_queue, self) { 27 | int class = strcmp(rom->class, r->class); 28 | if (class < 0 || (class == 0 && strcmp(rom->name, r->name) < 0)) 29 | break; 30 | prev = r; 31 | } 32 | if (prev == NULL) { 33 | SLIST_INSERT_HEAD(&mii_rom_queue, rom, self); 34 | } else { 35 | SLIST_INSERT_AFTER(prev, rom, self); 36 | } 37 | } 38 | 39 | mii_rom_t * 40 | mii_rom_get( 41 | const char *name) 42 | { 43 | mii_rom_t *rom; 44 | // this one is for debug only, so we can pass NULL to get the first one 45 | if (!name) 46 | return SLIST_FIRST(&mii_rom_queue); 47 | 48 | SLIST_FOREACH(rom, &mii_rom_queue, self) { 49 | if (strcmp(rom->name, name) == 0) 50 | return rom; 51 | } 52 | fprintf(stderr, "%s: ROM %s not found\n", __func__, name); 53 | return NULL; 54 | } 55 | 56 | mii_rom_t * 57 | mii_rom_get_class( 58 | mii_rom_t * prev, 59 | const char *class) 60 | { 61 | mii_rom_t *rom; 62 | 63 | // if prev is NULL, we want the first that matches that class 64 | if (!prev) { 65 | SLIST_FOREACH(rom, &mii_rom_queue, self) { 66 | if (strcmp(rom->class, class) == 0) 67 | return rom; 68 | } 69 | fprintf(stderr, "%s: ROM class %s not found\n", __func__, class); 70 | return NULL; 71 | } 72 | // else we want the next one that matches that class. 73 | // list is sorted by class, so we can just check the next one 74 | rom = SLIST_NEXT(prev, self); 75 | if (rom && strcmp(rom->class, class) == 0) 76 | return rom; 77 | return NULL; 78 | } 79 | -------------------------------------------------------------------------------- /src/mii_rom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_rom.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | */ 9 | 10 | #pragma once 11 | 12 | /* 13 | * ROM and rom queue declaration. The ROM queue is just a single list of ROM 14 | * that auto-add themselves to the list. There is a small utility function to 15 | * locate a rom by name, or by class. 16 | */ 17 | 18 | #include 19 | #include "bsd_queue.h" 20 | 21 | typedef struct mii_rom_t { 22 | SLIST_ENTRY(mii_rom_t) self; 23 | char * name; 24 | char * class; 25 | char * description; 26 | const uint8_t * rom; 27 | uint32_t len; 28 | } mii_rom_t; 29 | 30 | 31 | void 32 | mii_rom_register( 33 | struct mii_rom_t *rom); 34 | 35 | mii_rom_t * 36 | mii_rom_get( 37 | const char *name); 38 | 39 | mii_rom_t * 40 | mii_rom_get_class( 41 | mii_rom_t * prev, 42 | const char *class); 43 | 44 | #define MII_ROM(_d) \ 45 | __attribute__((constructor,used)) \ 46 | static void _mii_rom_register_##_d() { \ 47 | mii_rom_register(&_d); \ 48 | } 49 | -------------------------------------------------------------------------------- /src/mii_slot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_slot.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "mii.h" 14 | 15 | mii_slot_drv_t * 16 | mii_slot_drv_find( 17 | mii_t *mii, 18 | const char * name) 19 | { 20 | mii_slot_drv_t * drv = mii_slot_drv_list; 21 | while (drv) { 22 | if (!strcmp(drv->name, name)) 23 | return drv; 24 | drv = drv->next; 25 | } 26 | return NULL; 27 | } 28 | 29 | int 30 | mii_slot_drv_register( 31 | mii_t *mii, 32 | uint8_t slot_id, 33 | const char *driver_name) 34 | { 35 | if (!mii || !driver_name) { 36 | printf("%s invalid args\n", __func__); 37 | return -1; 38 | } 39 | if (slot_id < 1 || slot_id > 7) { 40 | printf("%s invalid slot id %d\n", __func__, slot_id); 41 | return -1; 42 | } 43 | if (mii->slot[slot_id - 1].drv) { 44 | printf("%s slot %d already has a driver (%s)\n", 45 | __func__, slot_id, mii->slot[slot_id - 1].drv->name); 46 | return -1; 47 | } 48 | mii_slot_drv_t * drv = mii_slot_drv_find(mii, driver_name); 49 | if (!drv) { 50 | printf("%s driver %s not found\n", __func__, driver_name); 51 | return -1; 52 | } 53 | if (drv->init) { 54 | if (drv->init(mii, &mii->slot[slot_id - 1]) != 0) { 55 | printf("%s driver %s init failed\n", __func__, driver_name); 56 | return -1; 57 | } 58 | } 59 | mii->slot[slot_id - 1].drv = drv; 60 | return 0; 61 | } 62 | 63 | mii_slot_drv_t * 64 | mii_slot_drv_get( 65 | mii_t *mii, 66 | uint8_t slot_id) 67 | { 68 | if (!mii || slot_id < 1 || slot_id > 7) 69 | return NULL; 70 | return (mii_slot_drv_t *)mii->slot[slot_id - 1].drv; 71 | } 72 | 73 | int 74 | mii_slot_command( 75 | mii_t *mii, 76 | uint8_t slot_id, 77 | uint8_t cmd, 78 | void * param) 79 | { 80 | mii_slot_drv_t * drv = mii_slot_drv_get(mii, slot_id); 81 | if (!drv) 82 | return -1; 83 | if (drv->command) 84 | return drv->command(mii, &mii->slot[slot_id - 1], cmd, param); 85 | return -1; 86 | } 87 | -------------------------------------------------------------------------------- /src/mii_slot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_slot.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #pragma once 9 | 10 | #include 11 | 12 | typedef struct mii_slot_t { 13 | uint8_t aux_rom_selected: 1, id; 14 | void * drv_priv; // for driver use 15 | const struct mii_slot_drv_t * drv; 16 | } mii_slot_t; 17 | 18 | 19 | typedef struct mii_slot_drv_t { 20 | struct mii_slot_drv_t *next; // in global driver queue 21 | uint32_t enable_flag; // if there is a MII_INIT_xxx flag 22 | const char * name; 23 | const char * desc; 24 | int (*probe)( 25 | struct mii_t * mii, 26 | uint32_t flags); 27 | int (*init)( 28 | struct mii_t * mii, 29 | struct mii_slot_t *slot); 30 | /* optional */ 31 | void (*dispose)( 32 | struct mii_t * mii, 33 | struct mii_slot_t *slot); 34 | /* optional */ 35 | void (*reset)( 36 | struct mii_t * mii, 37 | struct mii_slot_t *slot); 38 | // access to the slot's soft switches. 39 | uint8_t (*access)( 40 | struct mii_t * mii, 41 | struct mii_slot_t *slot, 42 | uint16_t addr, uint8_t data, bool write); 43 | // arbitrary command for load/save etc /* optional */ 44 | int (*command)( 45 | struct mii_t * mii, 46 | struct mii_slot_t *slot, 47 | uint32_t cmd, 48 | void * param); 49 | } mii_slot_drv_t; 50 | 51 | // get driver installed in slot_id 52 | mii_slot_drv_t * 53 | mii_slot_drv_get( 54 | struct mii_t *mii, 55 | uint8_t slot_id); 56 | // install driver 'driver_name' in slot_id slot 57 | int 58 | mii_slot_drv_register( 59 | struct mii_t *mii, 60 | uint8_t slot_id, 61 | const char *driver_name); 62 | // find a driver 'name' in the global list 63 | mii_slot_drv_t * 64 | mii_slot_drv_find( 65 | struct mii_t *mii, 66 | const char * name); 67 | 68 | enum { 69 | MII_SLOT_DRIVE_COUNT = 0x01, 70 | MII_SLOT_DRIVE_LOAD = 0x20, // + drive index 0...n 71 | MII_SLOT_DRIVE_WP = 0x30, // + drive index 0...n 72 | 73 | MII_SLOT_SSC_SET_TTY = 0x10, // param is a mii_ssc_setconf_t 74 | MII_SLOT_SSC_GET_TTY = 0x11, // param is a mii_ssc_setconf_t 75 | // + drive index 0..1. Param is a mii_floppy_t ** 76 | MII_SLOT_D2_GET_FLOPPY = 0x40, 77 | }; 78 | 79 | // send a command to a slot/driver. Return >=0 if ok, -1 if error 80 | int 81 | mii_slot_command( 82 | struct mii_t *mii, 83 | uint8_t slot_id, 84 | uint8_t cmd, 85 | void * param); 86 | -------------------------------------------------------------------------------- /src/mii_speaker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_speaker.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | #include 11 | 12 | #include "mii_audio.h" 13 | 14 | 15 | struct mii_t; 16 | 17 | typedef struct mii_speaker_t { 18 | struct mii_t * mii; 19 | uint8_t timer_id; 20 | mii_audio_sample_t sample; // current value for the speaker output 21 | mii_audio_source_t source; 22 | uint64_t last_click_cycle, last_fill_cycle; 23 | } mii_speaker_t; 24 | 25 | // Initialize the speaker with the frame size in samples 26 | void 27 | mii_speaker_init( 28 | struct mii_t * mii, 29 | mii_speaker_t *speaker); 30 | void 31 | mii_speaker_dispose( 32 | mii_speaker_t *speaker); 33 | // Called when $c030 is touched, place a sample at the 'appropriate' time 34 | void 35 | mii_speaker_click( 36 | mii_speaker_t *speaker); 37 | -------------------------------------------------------------------------------- /src/mii_sw.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_sw.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #pragma once 9 | 10 | enum { 11 | SWKBD = 0xc000, 12 | SW80STOREOFF = 0xc000, 13 | SW80STOREON = 0xc001, 14 | SWRAMRDOFF = 0xc002, 15 | SWRAMRDON = 0xc003, 16 | SWRAMWRTOFF = 0xc004, 17 | SWRAMWRTON = 0xc005, 18 | SWINTCXROMOFF = 0xc006, 19 | SWINTCXROMON = 0xc007, 20 | SWALTPZOFF = 0xc008, 21 | SWALTPZON = 0xc009, 22 | SWSLOTC3ROMOFF = 0xc00a, 23 | SWSLOTC3ROMON = 0xc00b, 24 | SW80COLOFF = 0xc00c, 25 | SW80COLON = 0xc00d, 26 | SWALTCHARSETOFF = 0xc00e, 27 | SWALTCHARSETON = 0xc00f, 28 | SWAKD = 0xc010, 29 | SWBSRBANK2 = 0xc011, 30 | SWBSRREADRAM = 0xc012, 31 | SWRAMRD = 0xc013, 32 | SWRAMWRT = 0xc014, 33 | SWINTCXROM = 0xc015, 34 | SWALTPZ = 0xc016, 35 | SWSLOTC3ROM = 0xc017, 36 | SW80STORE = 0xc018, 37 | SWVBL = 0xc019, 38 | SWALTCHARSET = 0xc01e, 39 | SW80COL = 0xc01f, 40 | SWTEXT = 0xc01a, 41 | SWMIXED = 0xc01b, 42 | SWPAGE2 = 0xc01c, 43 | SWHIRES = 0xc01d, 44 | SWSPEAKER = 0xc030, 45 | SWTEXTOFF = 0xc050, // (AKA LORES ON) 46 | SWTEXTON = 0xc051, 47 | SWMIXEDOFF = 0xc052, 48 | SWMIXEDON = 0xc053, 49 | SWPAGE2OFF = 0xc054, 50 | SWPAGE2ON = 0xc055, 51 | SWHIRESOFF = 0xc056, 52 | SWHIRESON = 0xc057, 53 | // this one is inverted, the ON is the even address 54 | SWDHIRESON = 0xc05e, // AN3_OFF 55 | SWDHIRESOFF = 0xc05f, // AN3_ON 56 | SWAN3 = 0xc05e, // AN3 status 57 | SWAN3_REGISTER = 0xc05f, // AN3 register for video mode 58 | SWRAMWORKS_BANK = 0xc073, 59 | // https://retrocomputing.stackexchange.com/questions/13449/apple-iie-auxiliary-ram-bank-select-register-address-c073-or-c07x 60 | SWRAMWORKS_ALT1 = 0xc071, 61 | SWRAMWORKS_ALT5 = 0xc075, 62 | SWRAMWORKS_ALT7 = 0xc077, 63 | SWRDDHIRES = 0xc07f, 64 | }; 65 | 66 | /* 67 | * This is for a bitfield in mii_t that keeps track of the state of the 68 | * softswitches used for memory mapping and video. 69 | */ 70 | enum { 71 | B_SW80STORE = ( 0), 72 | B_SWALTCHARSET = ( 1), 73 | B_SW80COL = ( 2), 74 | B_SWTEXT = ( 3), 75 | B_SWMIXED = ( 4), 76 | B_SWPAGE2 = ( 5), 77 | B_SWHIRES = ( 6), 78 | B_SWRAMRD = ( 7), 79 | B_SWRAMWRT = ( 8), 80 | B_SWINTCXROM = ( 9), 81 | B_SWALTPZ = (10), 82 | B_SWSLOTC3ROM = (11), 83 | B_BSRWRITE = (12), 84 | B_BSRREAD = (13), 85 | B_BSRPAGE2 = (14), 86 | B_BSRPREWRITE = (15), 87 | B_SWDHIRES = (16), 88 | // this is no 'real' softwitch, but a bit to mention a card has 89 | // it's secondary rom online in pages c800-cfff 90 | B_INTC8ROM = (17), 91 | 92 | M_SW80STORE = (1 << B_SW80STORE), 93 | M_SWALTCHARSET = (1 << B_SWALTCHARSET), 94 | M_SW80COL = (1 << B_SW80COL), 95 | M_SWTEXT = (1 << B_SWTEXT), 96 | M_SWMIXED = (1 << B_SWMIXED), 97 | M_SWPAGE2 = (1 << B_SWPAGE2), 98 | M_SWHIRES = (1 << B_SWHIRES), 99 | M_SWRAMRD = (1 << B_SWRAMRD), 100 | M_SWRAMWRT = (1 << B_SWRAMWRT), 101 | M_SWINTCXROM = (1 << B_SWINTCXROM), 102 | M_SWALTPZ = (1 << B_SWALTPZ), 103 | M_SWSLOTC3ROM = (1 << B_SWSLOTC3ROM), 104 | M_BSRWRITE = (1 << B_BSRWRITE), 105 | M_BSRREAD = (1 << B_BSRREAD), 106 | M_BSRPAGE2 = (1 << B_BSRPAGE2), 107 | M_BSRPREWRITE = (1 << B_BSRPREWRITE), 108 | M_SWDHIRES = (1 << B_SWDHIRES), 109 | M_INTC8ROM = (1 << B_INTC8ROM), 110 | }; 111 | 112 | #define __unused__ __attribute__((unused)) 113 | 114 | // unused is to prevent the stupid warnings about unused static stuff 115 | static const char __unused__ *mii_sw_names[] = { 116 | "80STORE", 117 | "ALTCHARSET", 118 | "80COL", 119 | "TEXT", 120 | "MIXED", 121 | "PAGE2", 122 | "HIRES", 123 | "RAMRD", 124 | "RAMWRT", 125 | "INTCXROM", 126 | "ALTPZ", 127 | "SLOTC3ROM", 128 | "BSRWRITE", 129 | "BSRREAD", 130 | "BSRPAGE2", 131 | "BSRPREWRITE", 132 | "DHIRES", 133 | "INTC8ROM", 134 | NULL, 135 | } ; 136 | 137 | #define SWW_SETSTATE(_bits, _sw, _state) \ 138 | (_bits) = ((_bits) & ~(M_##_sw)) | \ 139 | (!!(_state) << B_##_sw) 140 | #define SWW_GETSTATE(_bits, _sw) \ 141 | (!!((_bits) & M_##_sw)) 142 | 143 | #define SW_SETSTATE(_mii, _sw, _state) \ 144 | SWW_SETSTATE((_mii)->sw_state, _sw, _state) 145 | #define SW_GETSTATE(_mii, _sw) \ 146 | SWW_GETSTATE((_mii)->sw_state, _sw) 147 | 148 | // set bit 8 of a byte to the state of a softswitch 149 | #define SWW_READ(_byte, _bits, _sw) \ 150 | (_byte) = ((_byte) & 0x7f) | (SWW_GETSTATE(_bits, _sw) << 7) 151 | #define SW_READ(_byte, _mii, _sw) \ 152 | SWW_READ(_byte, (_mii)->sw_state, _sw) 153 | -------------------------------------------------------------------------------- /src/mii_video.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_video.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include "mii_rom.h" 14 | 15 | /* 16 | * When 1, this will draw a heat map of the dirty lines alongside the video This 17 | * is useful to see if the video rendering is efficient. This has no real use 18 | * apart from debugging the video rendering. 19 | */ 20 | #define MII_VIDEO_DEBUG_HEAPMAP 0 21 | 22 | // TODO move VRAM stuff to somewhere else 23 | #define MII_VIDEO_WIDTH (280 * 2) 24 | #define MII_VIDEO_HEIGHT (192 * 2) 25 | 26 | struct mii_t; 27 | struct mii_video_t; 28 | 29 | typedef void (*mii_video_line_drawing_cb)( 30 | struct mii_video_t *video, 31 | uint32_t sw, 32 | mii_bank_t * main, 33 | mii_bank_t * aux ); 34 | typedef void (*mii_video_line_check_cb)( 35 | struct mii_video_t *video, 36 | uint32_t sw, 37 | uint16_t addr); 38 | 39 | typedef struct mii_video_cb_t { 40 | mii_video_line_drawing_cb render; 41 | mii_video_line_check_cb check; 42 | } mii_video_cb_t; 43 | 44 | typedef uint32_t mii_color_t; 45 | 46 | /* 47 | * Color LookUp Table for all the modes. They default to the color 48 | * version defined in mii_video.c, then get derived from that for 49 | * the green and amber modes. 50 | */ 51 | typedef union mii_video_clut_t { 52 | struct { 53 | mii_color_t lores[2][16]; // lores (main, and aux page DLORES) 54 | mii_color_t dhires[16]; 55 | mii_color_t hires[10]; 56 | mii_color_t text[2]; // text 57 | mii_color_t mono[2]; // DHRES mono mode 58 | }; 59 | mii_color_t colors[(2*16) + 16 + 10 /*+ 8*/ + 2 + 2]; 60 | } mii_video_clut_t; 61 | 62 | typedef struct mii_video_t { 63 | void * state; // protothread state in mii_video.c 64 | mii_rom_t * rom; // video ROM 65 | uint8_t rom_bank; // video ROM bank (for roms that have them) 66 | uint8_t timer_id; // timer id for the video thread 67 | uint8_t line; // current line for cycle timer 68 | uint8_t an3_mode; // current mode 69 | uint16_t base_addr; // current mode base address 70 | uint16_t line_addr; // VRAM address for the line we are on 71 | uint64_t timer_max; // timer start value 72 | uint32_t frame_count; // incremented every frame 73 | uint8_t color_mode; // color palette index 74 | uint8_t monochrome; // monochrome mode 75 | mii_video_clut_t clut; // current color table 76 | mii_video_clut_t clut_low; // low luminance version 77 | // function pointer to the line drawing function 78 | mii_video_cb_t line_cb; 79 | uint8_t frame_dirty; 80 | // increments when pixels have changed for this frame 81 | uint32_t frame_seed; 82 | /* 83 | * Each bits is a dirty line to redraw. Set by memory accesses, cleared 84 | * by the video thread when the line is updated (converted to pixels) 85 | */ 86 | uint64_t lines_dirty[192 / 64]; // 192 lines / 64 bits 87 | 88 | #if MII_VIDEO_DEBUG_HEAPMAP 89 | uint8_t video_hmap[192] 90 | __attribute__((aligned(32))) ; // line dirty heat map 91 | #endif 92 | // alignment is required for vector extensions 93 | uint32_t pixels[MII_VIDEO_WIDTH * MII_VIDEO_HEIGHT] 94 | __attribute__((aligned(32))); 95 | } mii_video_t; 96 | 97 | bool 98 | mii_access_video( 99 | struct mii_t *mii, 100 | uint16_t addr, 101 | uint8_t * byte, 102 | bool write); 103 | void 104 | mii_video_init( 105 | struct mii_t *mii); 106 | 107 | void 108 | mii_video_set_mode( 109 | struct mii_t *mii, 110 | uint8_t mode); 111 | /* 112 | * Out of bounds write check. This allow SmartPort DMA drive to pass down the 113 | * range it writes buffers to, so the video gets a chance to check if the 114 | * addresses are in RAM, in case the Prodos call is loading an image into VRAM 115 | * proper (Smartport driver "DMA" does that) 116 | */ 117 | void 118 | mii_video_OOB_write_check( 119 | struct mii_t *mii, 120 | uint16_t addr, 121 | uint16_t size); 122 | uint8_t 123 | mii_video_get_vapor( 124 | struct mii_t *mii); 125 | -------------------------------------------------------------------------------- /src/roms/Makefile.inc: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ROMS: This bits convert the binary roms with the product number in the name 4 | # to a C header file that can be included in the project. I used to use 5 | # incbin.h for this, but there were issues with some of the linker used, for 6 | # example, webassembly, so we now use xxd to generate the .h file. 7 | # You'd NEED the xxd tool if you want to re-generate these files, but well, 8 | # they've been constant for over 40 years, my guess is that they ain't going 9 | # anywhere. 10 | # 11 | # The reason these roms are used like this here is that I always found that it 12 | # was a massive pain to have to deal with the roms in *any* apple II (or Mac) 13 | # emulator. You had to find the roms, put them in the right place, name them 14 | # correctly, and then, if you wanted to use a different version, you had to 15 | # rename them, and so on. Dreadful. 16 | # I think it prevents a lot of people learning about the Apple II, because it's 17 | # just too much hassle to get started. So, I've included the roms in the source 18 | # code, and they are compiled in the binary. For the user. Not for convenience, 19 | # Not for 'stealing' from apple, but for the user. For the user to have a 20 | # seamless experience. To be able to just run the emulator, and have it work. 21 | # And be amazed *at the brand*. 22 | # 23 | # Now, I understand that strictly speaking these are copyrighted material, but 24 | # they are so old, and so widely available, and are used here for educational 25 | # purposes, and with the upmost respect for all the original authors, and for 26 | # what 'the brand' represented for us at the time. With that in mind, I think 27 | # that there shouldn't be an issue. But if you, Mr&Mrs Apple Lawyer think 28 | # otherwise, please let me know, I'll remove them. Reluctantly. I'll cry&scream! 29 | .PHONY : roms 30 | 31 | define rom_to_c 32 | $(1) : 33 | @if [ ! -f $$@ ]; then \ 34 | echo "ROM file $$@ not found, relying on the exiting .h"; \ 35 | touch $$@; \ 36 | fi 37 | src/roms/mii_rom_$(2).c : $(1) utils/convert-rom-tcc.c 38 | if [ ! -s "$$<" ]; then \ 39 | touch $$@; \ 40 | else { \ 41 | tcc -run utils/convert-rom-tcc.c -o "$$@" -i "$$<" \ 42 | -n $(2) -c $(3) -d $(4); \ 43 | echo "ROM $$@ Generated"; \ 44 | } fi 45 | roms:: src/roms/mii_rom_$(2).c 46 | endef 47 | 48 | 49 | # 38063e08c778503fc03ecebb979769e9 contrib/mii_rom_iiee_3420349b.bin 50 | $(eval $(call rom_to_c,contrib/mii_rom_iiee_3420349b.bin,iiee,main,\ 51 | "Apple IIe ROM")) 52 | # 9123fff3442c0e688cc6816be88dd4ab contrib/mii_rom_iiee_video_3420265a.bin 53 | $(eval $(call rom_to_c,contrib/mii_rom_iiee_video_3420265a.bin,iiee_video,video,\ 54 | "Apple IIe Video ROM")) 55 | # e0d67bb1aabe2030547b4cbdf3905b60 contrib/mii_rom_iic_3420033a.bin 56 | $(eval $(call rom_to_c,contrib/mii_rom_iic_3420033a.bin,iic,main,\ 57 | "Apple IIc ROM")) 58 | # 67c0d61ab0911183faf05270f881a97e contrib/mii_rom_ssc_3410065a.bin 59 | $(eval $(call rom_to_c,contrib/mii_rom_ssc_3410065a.bin,ssc,ssc,\ 60 | "Super Serial Card ROM")) 61 | # 9123fff3442c0e688cc6816be88dd4ab contrib/mii_rom_iic_video_3410265a.bin 62 | $(eval $(call rom_to_c,contrib/mii_rom_iic_video_3410265a.bin,iic_video,video,\ 63 | "Apple IIc Video ROM")) 64 | # 2020aa1413ff77fe29353f3ee72dc295 contrib/mii_rom_disk2_p5_3410037.bin 65 | $(eval $(call rom_to_c,contrib/mii_rom_disk2_p5_3410037.bin,disk2_p5,disk2_p5,\ 66 | "Disk II Card ROM")) 67 | # The original of this file online is 16KB and is marked as IIe/IIc. The first 68 | # 8KB are empty (0xff) so I trimmed that off. 69 | # a7d723b93d09f776a433fee4d31e1d0d contrib/mii_rom_iiee_video_fr_342274a.bin 70 | $(eval $(call rom_to_c,contrib/mii_rom_iiee_video_fr_342274a.bin,iiee_video_fr,video,\ 71 | "Apple IIe Video ROM FR")) 72 | # 45a0fe6d800273ec9cef4fc23da7f25f contrib/mii_rom_iiee_video_uk_3420273a.bin 73 | $(eval $(call rom_to_c,contrib/mii_rom_iiee_video_uk_3420273a.bin,iiee_video_uk,video,\ 74 | "Apple IIe Video ROM UK")) 75 | # This is the ROM file for the EEPROM card, with some games too... 76 | $(eval $(call rom_to_c,disks/GamesWithFirmware.po,epromcard,epromcard,\ 77 | "EEPROM Card ROM")) 78 | # And the smartport driver 79 | $(eval $(call rom_to_c,test/asm/mii_smartport_driver.bin,smartport,smartport,\ 80 | "Smartport Driver ROM")) 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/roms/mii_rom_disk2_p5.c: -------------------------------------------------------------------------------- 1 | /* this file is auto-generated by convert-rom-tcc.c */ 2 | 3 | #include "mii_rom.h" 4 | 5 | static const uint8_t mii_rom_disk2_p5[] = { 6 | 0xa2,0x20,0xa0,0x00,0xa2,0x03,0x86,0x3c,0x8a,0x0a,0x24,0x3c,0xf0,0x10,0x05,0x3c, 7 | 0x49,0xff,0x29,0x7e,0xb0,0x08,0x4a,0xd0,0xfb,0x98,0x9d,0x56,0x03,0xc8,0xe8,0x10, 8 | 0xe5,0x20,0x58,0xff,0xba,0xbd,0x00,0x01,0x0a,0x0a,0x0a,0x0a,0x85,0x2b,0xaa,0xbd, 9 | 0x8e,0xc0,0xbd,0x8c,0xc0,0xbd,0x8a,0xc0,0xbd,0x89,0xc0,0xa0,0x50,0xbd,0x80,0xc0, 10 | 0x98,0x29,0x03,0x0a,0x05,0x2b,0xaa,0xbd,0x81,0xc0,0xa9,0x56,0x20,0xa8,0xfc,0x88, 11 | 0x10,0xeb,0x85,0x26,0x85,0x3d,0x85,0x41,0xa9,0x08,0x85,0x27,0x18,0x08,0xbd,0x8c, 12 | 0xc0,0x10,0xfb,0x49,0xd5,0xd0,0xf7,0xbd,0x8c,0xc0,0x10,0xfb,0xc9,0xaa,0xd0,0xf3, 13 | 0xea,0xbd,0x8c,0xc0,0x10,0xfb,0xc9,0x96,0xf0,0x09,0x28,0x90,0xdf,0x49,0xad,0xf0, 14 | 0x25,0xd0,0xd9,0xa0,0x03,0x85,0x40,0xbd,0x8c,0xc0,0x10,0xfb,0x2a,0x85,0x3c,0xbd, 15 | 0x8c,0xc0,0x10,0xfb,0x25,0x3c,0x88,0xd0,0xec,0x28,0xc5,0x3d,0xd0,0xbe,0xa5,0x40, 16 | 0xc5,0x41,0xd0,0xb8,0xb0,0xb7,0xa0,0x56,0x84,0x3c,0xbc,0x8c,0xc0,0x10,0xfb,0x59, 17 | 0xd6,0x02,0xa4,0x3c,0x88,0x99,0x00,0x03,0xd0,0xee,0x84,0x3c,0xbc,0x8c,0xc0,0x10, 18 | 0xfb,0x59,0xd6,0x02,0xa4,0x3c,0x91,0x26,0xc8,0xd0,0xef,0xbc,0x8c,0xc0,0x10,0xfb, 19 | 0x59,0xd6,0x02,0xd0,0x87,0xa0,0x00,0xa2,0x56,0xca,0x30,0xfb,0xb1,0x26,0x5e,0x00, 20 | 0x03,0x2a,0x5e,0x00,0x03,0x2a,0x91,0x26,0xc8,0xd0,0xee,0xe6,0x27,0xe6,0x3d,0xa5, 21 | 0x3d,0xcd,0x00,0x08,0xa6,0x2b,0x90,0xdb,0x4c,0x01,0x08,0x00,0x00,0x00,0x00,0x00, 22 | }; 23 | static mii_rom_t disk2_p5_rom = { 24 | .name = "disk2_p5", 25 | .class = "disk2_p5", 26 | .description = "Disk II Card ROM", 27 | .rom = mii_rom_disk2_p5, 28 | .len = 256, 29 | }; 30 | MII_ROM(disk2_p5_rom); 31 | -------------------------------------------------------------------------------- /src/roms/mii_rom_smartport.c: -------------------------------------------------------------------------------- 1 | /* this file is auto-generated by convert-rom-tcc.c */ 2 | 3 | #include "mii_rom.h" 4 | 5 | static const uint8_t mii_rom_smartport[] = { 6 | 0xa2,0x20,0xa9,0x00,0xa2,0x03,0xa9,0x00,0x2c,0xff,0xcf,0xa0,0x00,0x84,0x44,0x84, 7 | 0x46,0x84,0x47,0xc8,0x84,0x42,0xa9,0x4c,0x8d,0xfd,0x07,0xa9,0xc0,0x8d,0xfe,0x07, 8 | 0x20,0x58,0xff,0xba,0xbd,0x00,0x01,0x8d,0xff,0x07,0x0a,0x0a,0x0a,0x0a,0x85,0x43, 9 | 0xa9,0x08,0x85,0x45,0x64,0x44,0x64,0x46,0x64,0x47,0x20,0xfd,0x07,0xb0,0x1e,0xa9, 10 | 0x0a,0x85,0x45,0xa9,0x01,0x85,0x46,0x20,0xfd,0x07,0xb0,0x11,0xad,0x01,0x08,0xf0, 11 | 0x0c,0xa9,0x01,0xcd,0x00,0x08,0xd0,0x05,0xa6,0x43,0x4c,0x01,0x08,0xad,0xff,0x07, 12 | 0xc9,0xc1,0xf0,0x08,0xc5,0x01,0xd0,0x04,0xa5,0x00,0xf0,0x03,0x4c,0x00,0xe0,0xa9, 13 | 0x92,0x85,0x44,0xad,0xff,0x07,0x85,0x45,0xa0,0x00,0xb1,0x44,0xf0,0x06,0x99,0x55, 14 | 0x07,0xc8,0x80,0xf6,0xad,0xff,0x07,0x29,0x0f,0x3a,0x09,0xb0,0x99,0x55,0x07,0x4c, 15 | 0xba,0xfa,0x8e,0xef,0xa0,0x93,0xed,0xe1,0xf2,0xf4,0x90,0xef,0xf2,0xf4,0xa0,0x84, 16 | 0xe9,0xf3,0xe3,0xac,0xa0,0x82,0xef,0xef,0xf4,0xe9,0xee,0xe7,0xa0,0x93,0x00,0x00, 17 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 18 | 0xea,0x80,0x0d,0x80,0x1b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 19 | 0xeb,0xfb,0x00,0x80,0x1b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 20 | 0xeb,0xfb,0x00,0x80,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 21 | 0xb0,0x03,0xa9,0x00,0x60,0xa9,0x27,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xc0, 22 | }; 23 | static mii_rom_t smartport_rom = { 24 | .name = "smartport", 25 | .class = "smartport", 26 | .description = "Smartport Driver ROM", 27 | .rom = mii_rom_smartport, 28 | .len = 256, 29 | }; 30 | MII_ROM(smartport_rom); 31 | -------------------------------------------------------------------------------- /test/asm/001_jmp_indirect.asm: -------------------------------------------------------------------------------- 1 | ; 65c02 Indirect Indexed JMP ($xxx,X) 2 | .org $2000 3 | jump_ad = $3000 4 | ;.verbose 5 | JMP skip 6 | ind LDA #$99 ; $2003 7 | BRA pass 8 | skip: LDA #ind 13 | STA jump_ad,Y 14 | LDX #$02 15 | JMP ($3000,X) ; JMP -> $2003 16 | fail BRK ; JMP Failed! 17 | pass BRK 18 | -------------------------------------------------------------------------------- /test/asm/002_jsr_rts.asm: -------------------------------------------------------------------------------- 1 | ; 65c02 JSR/RTS/SBC/BEQ 2 | .org $2000 3 | ;.verbose 4 | CLC 5 | LDA #$00 6 | JSR skip 7 | SEC 8 | SBC #$03 9 | BEQ back 10 | JMP fail 11 | skip LDA #$03 12 | RTS 13 | fail BRK ; JSR Failed! 14 | ; now test setting some address on the stack and call RTS 15 | jump LDA #>pass 16 | PHA 17 | LDA # 5 | * 6 | * SPDX-License-Identifier: MIT 7 | * 8 | * Small command line assembler for our own drivers 9 | * 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "mii_65c02.c" 18 | #include "mii_65c02_asm.c" 19 | #include "mii_65c02_disasm.c" 20 | #include "mii_65c02_ops.h" 21 | 22 | int 23 | main( 24 | int argc, 25 | const char * argv[]) 26 | { 27 | const char *infile = NULL; 28 | const char *outfile = NULL; 29 | int verbose = 0; 30 | // parse arguments 31 | for (int i = 1; i < argc; i++) { 32 | if (!strcmp(argv[i], "-o")) { 33 | if (i + 1 >= argc) { 34 | fprintf(stderr, "%s Missing argument for -o\n", argv[0]); 35 | exit(1); 36 | } 37 | outfile = argv[++i]; 38 | } else if (!strcmp(argv[i], "-v")) { 39 | verbose = 1; 40 | } else if (!infile) { 41 | infile = argv[i]; 42 | } else { 43 | fprintf(stderr, "%s Unknown argument %s\n", argv[0], argv[i]); 44 | exit(1); 45 | } 46 | } 47 | if (!infile || !outfile) { 48 | fprintf(stderr, "Usage: mii_asm [-v] -o outfile infile\n"); 49 | exit(1); 50 | } 51 | char *prog = calloc(1, 65536); 52 | if (verbose) 53 | printf("%s Loading %s\n", argv[0], infile); 54 | FILE *f = fopen(infile, "r"); 55 | if (!f) { 56 | perror(infile); 57 | exit(1); 58 | 59 | } 60 | fread(prog, 1, 65536, f); 61 | fclose(f); 62 | 63 | mii_cpu_asm_program_t p = {}; 64 | p.verbose = verbose; 65 | if (mii_cpu_asm(&p, prog) == 0) { 66 | if (verbose) 67 | mii_cpu_disasm(p.output, p.org, p.output_len); 68 | if (verbose) 69 | printf("%s Writing %s\n", argv[0], outfile); 70 | f = fopen(outfile, "w"); 71 | if (!f) { 72 | perror(outfile); 73 | exit(1); 74 | } 75 | fwrite(p.output, 1, p.output_len, f); 76 | fclose(f); 77 | } else { 78 | fprintf(stderr, "%s: %s error, no output\n", argv[0], infile); 79 | exit(1); 80 | } 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /test/mii_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_test.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | /* 9 | * This is just a small example on how to use mii as a library 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "mii.h" 18 | #include "mish.h" 19 | 20 | // so mii_mish_cmd can access the global mii_t 21 | mii_t g_mii; 22 | 23 | void 24 | _mii_mish_bp( 25 | void * param, 26 | int argc, 27 | const char * argv[]); 28 | 29 | int main( 30 | int argc, 31 | const char * argv[]) 32 | { 33 | mii_t *mii = &g_mii; 34 | mii_init(mii); 35 | int idx = 1; 36 | uint32_t flags = MII_INIT_DEFAULT; 37 | int r = mii_argv_parse(&g_mii, argc, argv, &idx, &flags); 38 | if (r == 0) { 39 | printf("mii: Invalid argument %s, skipped\n", argv[idx]); 40 | } else if (r == -1) 41 | exit(1); 42 | mii_prepare(mii, MII_INIT_DEFAULT); 43 | int count = 500000; 44 | 45 | mish_prepare(0); 46 | mish_set_command_parameter(MII_MISH_KIND, &g_mii); 47 | // add a breakpoint 48 | const char *av[] = {"bp", "+d5fdr", NULL}; 49 | _mii_mish_bp(mii, 2, av); 50 | do { 51 | if (mii->state != MII_STOPPED) 52 | mii_run(mii); 53 | if (mii->state == MII_STEP) { 54 | // if (mii->trace_cpu) 55 | mii_dump_trace_state(mii); 56 | if (mii->trace.step_inst) 57 | mii->trace.step_inst--; 58 | if (mii->trace.step_inst == 0) 59 | mii->state = MII_STOPPED; 60 | } 61 | if (mii->state != MII_RUNNING) { 62 | // if (!mii->trace_cpu) { 63 | // printf("mii: stopped\n"); 64 | // mii->trace_cpu = 1; 65 | // } 66 | usleep(1000); 67 | } 68 | } while (mii->state != MII_TERMINATE && count--); 69 | mii_dispose(mii); 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /ui_gl/mii_loadbin.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_loadbin.c 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "mii.h" 18 | #include "mii_bank.h" 19 | #include "mii_mui_settings.h" 20 | 21 | 22 | typedef struct mii_loadbin_t { 23 | mii_t * mii; 24 | pthread_t thread; 25 | 26 | mii_loadbin_conf_t conf; 27 | } mii_loadbin_t; 28 | 29 | static mii_loadbin_t * _mii_loadbin = NULL; 30 | 31 | static void * 32 | mii_thread_loadbin( 33 | void *arg) 34 | { 35 | 36 | return NULL; 37 | } 38 | 39 | mii_loadbin_t * 40 | mii_loadbin_start( 41 | struct mii_t *mii, 42 | struct mii_loadbin_conf_t *conf) 43 | { 44 | mii_loadbin_t * res = NULL; 45 | if (_mii_loadbin) { 46 | return _mii_loadbin; 47 | } 48 | _mii_loadbin = res = calloc(1, sizeof(*res)); 49 | res->mii = mii; 50 | res->conf = *conf; 51 | 52 | pthread_create(&res->thread, NULL, mii_thread_loadbin, res); 53 | 54 | return res; 55 | } 56 | -------------------------------------------------------------------------------- /ui_gl/mii_mui.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_mui.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #pragma once 9 | /* 10 | This tries to contains a structure that is the MUI interface over the MII 11 | video, but without any attachment to x11 or opengl. Basically hopefully 12 | segregating the relevant logic without tying it to a specific windowing 13 | system. 14 | Hopefully with a bit more work this OUGHT to allow Windows/macOS port 15 | with a native frontend. 16 | */ 17 | 18 | #include 19 | #include 20 | #include "mii.h" 21 | #include "mui.h" 22 | #include "mii_mui_settings.h" 23 | #include "mii_floppy.h" 24 | 25 | enum mii_mui_transition_e { 26 | MII_MUI_TRANSITION_NONE, 27 | MII_MUI_TRANSITION_HIDE_UI, 28 | MII_MUI_TRANSITION_SHOW_UI, 29 | }; 30 | 31 | #define MII_PIXEL_LAYERS 9 32 | 33 | typedef struct mii_mui_v_t { 34 | float x, y, u, v; 35 | } mii_mui_v_t; 36 | 37 | DECLARE_C_ARRAY(mii_mui_v_t, mii_mui_v_array, 16, 38 | unsigned int vao, vbo, kind;); 39 | IMPLEMENT_C_ARRAY(mii_mui_v_array); 40 | 41 | DECLARE_C_ARRAY(int, int_array, 16, unsigned int ebo;); 42 | IMPLEMENT_C_ARRAY(int_array); 43 | 44 | 45 | typedef struct mii_mui_t { 46 | mui_t mui; // mui interface 47 | mii_t mii; // apple II emulator 48 | c2_pt_t window_size; 49 | long last_button_click; 50 | struct { 51 | int ungrab, grab, grabbed, down; 52 | c2_pt_t pos; 53 | } mouse; 54 | mui_event_t key; 55 | // This is the list of vertices and coordinates for the screen, including 56 | // the pincushion distortion. This is inset from video_frame 57 | mii_mui_v_array_t video_mesh; 58 | int_array_t video_indices; 59 | c2_rect_t video_frame; // current video frame 60 | uint32_t video_drawn_seed; 61 | float mui_alpha; 62 | bool mui_visible; 63 | void * transision_state; 64 | struct { 65 | uint8_t state; 66 | mui_time_t start, end; 67 | c2_rect_t from, to; 68 | } transition; 69 | unsigned int tex_id[MII_PIXEL_LAYERS]; 70 | union { 71 | struct { 72 | mui_drawable_t mii; 73 | mui_drawable_t mui; 74 | struct { 75 | mui_drawable_t bits; 76 | mui_drawable_t hm_read; 77 | mui_drawable_t hm_write; 78 | } floppy[2]; 79 | // this is debug only! 80 | mui_drawable_t video_heapmap; 81 | }; 82 | mui_drawable_t v[MII_PIXEL_LAYERS]; 83 | } pixels; 84 | struct { 85 | mii_floppy_t * floppy; 86 | uint32_t seed_load; 87 | float max_width; 88 | /* Despite having the same size, they can (will) have different 89 | * texture coordinates, so each floppy have their own */ 90 | mii_mui_v_array_t vtx; 91 | } floppy[2]; 92 | 93 | mii_machine_config_t config; 94 | mii_loadbin_conf_t loadbin_conf; 95 | 96 | mii_config_file_t cf; 97 | } mii_mui_t; 98 | 99 | void 100 | mii_mui_init( 101 | mii_mui_t * ui, 102 | c2_pt_t window_size); 103 | 104 | void 105 | mii_mui_menus_init( 106 | mii_mui_t * ui); 107 | void 108 | mii_mui_menu_slot_menu_update( 109 | mii_mui_t * ui); 110 | 111 | void 112 | mii_mui_update_mouse_card( 113 | mii_mui_t * ui); 114 | void 115 | mii_mui_showhide_ui_machine( 116 | mii_mui_t * ui ); 117 | c2_rect_t 118 | mii_mui_get_video_position( 119 | mii_mui_t * ui); 120 | 121 | #define MII_GL_FLOPPY_SEGMENT_COUNT 48 122 | #define MII_GL_FLOPPY_DISC_RADIUS_IN 1.8 123 | #define MII_GL_FLOPPY_DISC_RADIUS_OUT 10 124 | #define MII_GL_FLOPPY_FLUX_RADIUS_IN 2.0 125 | #define MII_GL_FLOPPY_FLUX_RADIUS_OUT 9.8 126 | 127 | void 128 | mii_generate_floppy_mesh( 129 | mii_mui_v_array_t * vtx, 130 | float tex_width ); 131 | 132 | // slot can be <= 0 to open the machine dialog instead 133 | void 134 | mii_config_open_slots_dialog( 135 | mii_mui_t * ui); 136 | 137 | 138 | -------------------------------------------------------------------------------- /ui_gl/mii_mui_gl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_mui_gl.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | #pragma once 9 | 10 | #include "mii_mui.h" 11 | 12 | void 13 | mii_mui_gl_init( 14 | mii_mui_t *ui); 15 | void 16 | mii_mui_gl_prepare_textures( 17 | mii_mui_t *ui); 18 | void 19 | mui_mui_gl_regenerate_ui_texture( 20 | mii_mui_t *ui); 21 | void 22 | mii_mui_gl_render( 23 | mii_mui_t *ui); 24 | bool 25 | mii_mui_gl_run( 26 | mii_mui_t *ui); 27 | -------------------------------------------------------------------------------- /ui_gl/mii_mui_utils.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "mii_mui_utils.h" 4 | 5 | int 6 | mii_mui_fileselect_widget( // 7 | mii_mui_file_select_t *out, 8 | mui_window_t *w, 9 | c2_rect_t *where, 10 | const char *enclosing_box_title, 11 | const char *button_title, 12 | const char *checkbox_title) 13 | { 14 | mui_t *ui = w->ui; 15 | float base_size = mui_font_find(ui, "main")->size; 16 | float icons_size = mui_font_find(ui, "icon_small")->size; 17 | float margin = base_size * 0.7; 18 | 19 | mui_control_t * c = NULL; 20 | c2_rect_t cf; 21 | c2_rect_t cp = *where; 22 | cp.b = cp.t + base_size * 3; 23 | where->b = cp.b; 24 | const int but_width = 100; 25 | cp.r = where->r - margin * 2 - but_width; 26 | 27 | const int label_size = base_size * 1.85; 28 | c2_rect_set(&cf, margin, (margin / 2), 29 | c2_rect_width(&w->frame) - margin - but_width, 30 | (margin/2) + label_size); 31 | out->box = c = mui_groupbox_new(w, cp, 32 | enclosing_box_title, MUI_CONTROL_TEXTBOX_FRAME); 33 | c2_rect_bottom_of(&cf, cp.t, 34 | (c2_rect_height(&cf) / 2)); 35 | c2_rect_right_of(&cf, cp.l, margin * 0.5); 36 | //cf.b = cf.t + icons_size; 37 | cf.r = cf.l + icons_size; 38 | out->icon = c = mui_textbox_new(w, cf, 39 | MUI_ICON_FILE, "icon_small", 40 | MUI_TEXT_ALIGN_MIDDLE | MUI_TEXT_ALIGN_CENTER | 0); 41 | c->state = MUI_CONTROL_STATE_DISABLED; 42 | cf.l = cf.r + 5; 43 | cf.r = cp.r - margin * 0.5; 44 | cf.b = cf.t + label_size; 45 | out->fname = c = mui_textbox_new(w, cf, 46 | "Click \"Select\" to pick a file", NULL, 47 | MUI_TEXT_ALIGN_MIDDLE|0); 48 | c->state = MUI_CONTROL_STATE_DISABLED; 49 | 50 | c2_rect_right_of(&cf, cp.r, margin); 51 | cf.r = where->r - margin * 0.5; 52 | cf.b = cf.t + base_size * 1.1; 53 | c2_rect_offset(&cf, 0, (c2_rect_height(&cf) / 2.5)); 54 | c2_rect_inset(&cf, -4,-4); 55 | out->button = c = mui_button_new(w, 56 | cf, MUI_BUTTON_STYLE_NORMAL, 57 | button_title , 0); 58 | if (checkbox_title) { 59 | c2_rect_bottom_of(&cf, cp.b, margin * 0.4); 60 | cf.l = cp.l + (margin * 0.7); 61 | cf.r = cf.l + 200; 62 | cf.b = cf.t + base_size; 63 | where->b = cf.b; 64 | out->checkbox = c = mui_button_new(w, 65 | cf, MUI_BUTTON_STYLE_CHECKBOX, 66 | checkbox_title, 0); 67 | c2_rect_right_of(&cf, cf.r, margin * 0.5); 68 | cf.r = c2_rect_width(&w->frame) - margin * 1.2; 69 | out->warning = c = mui_textbox_new(w, cf, 70 | "", NULL, 71 | MUI_TEXT_ALIGN_MIDDLE|MUI_TEXT_ALIGN_RIGHT); 72 | } 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /ui_gl/mii_mui_utils.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "mui.h" 5 | 6 | 7 | typedef struct mii_mui_file_select_t { 8 | mui_control_t * box; 9 | mui_control_t * icon; 10 | mui_control_t * fname; 11 | mui_control_t * button; 12 | mui_control_t * checkbox; 13 | mui_control_t * warning; 14 | } mii_mui_file_select_t; 15 | 16 | int 17 | mii_mui_fileselect_widget( // 18 | mii_mui_file_select_t *out, 19 | mui_window_t *w, 20 | c2_rect_t *where, 21 | const char *enclosing_box_title, 22 | const char *button_title, 23 | const char *checkbox_title); 24 | -------------------------------------------------------------------------------- /ui_gl/mii_sokol_audio.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mui_sokol_audio.h 3 | * 4 | * Copyright (C) 2024 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | #include "mii.h" 11 | 12 | void 13 | mii_sokol_audio_init( 14 | mii_t *mii); -------------------------------------------------------------------------------- /ui_gl/mii_thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mii_thread.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "fifo_declare.h" 13 | 14 | enum mii_th_state_e { 15 | SIGNAL_RESET, 16 | SIGNAL_STOP, 17 | SIGNAL_STEP, 18 | SIGNAL_NEXT, 19 | SIGNAL_RUN, 20 | SIGNAL_PASTE, 21 | SIGNAL_LOADBIN, 22 | }; 23 | 24 | typedef struct mii_th_signal_t { 25 | uint8_t cmd; 26 | union { 27 | uint32_t data; 28 | void * ptr; 29 | }; 30 | } mii_th_signal_t; 31 | 32 | DECLARE_FIFO(mii_th_signal_t, mii_th_fifo, 16); 33 | DEFINE_FIFO(mii_th_signal_t, mii_th_fifo); 34 | 35 | DECLARE_FIFO(char*, mii_th_msg_fifo, 16); 36 | DEFINE_FIFO(char*, mii_th_msg_fifo); 37 | 38 | typedef struct mii_thread_t { 39 | pthread_t thread; 40 | uint8_t state; 41 | struct mii_t * mii; 42 | 43 | mii_th_msg_fifo_t msg; 44 | } mii_thread_t; 45 | 46 | struct mii_t; 47 | 48 | pthread_t 49 | mii_threads_start( 50 | struct mii_t *mii); 51 | struct mii_th_fifo_t* 52 | mii_thread_get_fifo( 53 | struct mii_t *mii); 54 | int 55 | mii_thread_set_fps( 56 | int timerfd, 57 | float fps); 58 | -------------------------------------------------------------------------------- /ui_gl/miigl_counter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * miigl_counter.h 3 | * 4 | * Copyright (C) 2023 Michel Pollet 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include "fifo_declare.h" 14 | 15 | /* 16 | * Cheapish way of counting how many time 'stuff' happends in a second, 17 | * Can be used to count FPS or other things as long as the frequency is less 18 | * than 1024hz. 19 | */ 20 | DECLARE_FIFO(uint64_t, miigl_counter, 1024); 21 | DEFINE_FIFO(uint64_t, miigl_counter); 22 | static uint64_t 23 | miigl_get_time() 24 | { 25 | struct timespec tim; 26 | clock_gettime(CLOCK_MONOTONIC_RAW, &tim); 27 | uint64_t time = ((uint64_t)tim.tv_sec) * (1000000 / 1) + 28 | tim.tv_nsec / (1000 * 1); 29 | return time; 30 | } 31 | static int 32 | miigl_counter_tick( 33 | miigl_counter_t *c, 34 | uint64_t time) 35 | { 36 | // = miigl_get_time(); 37 | // delete stamps that are older than a second 38 | while (!miigl_counter_isempty(c) && 39 | (time - miigl_counter_read_at(c, 0)) > 1000000) { 40 | miigl_counter_read(c); 41 | } 42 | long freq = miigl_counter_get_read_size(c); 43 | if (!miigl_counter_isfull(c)) 44 | miigl_counter_write(c, time); 45 | return freq; 46 | } 47 | -------------------------------------------------------------------------------- /utils/clangd_gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Generate a JSON file compatible with clangd for indexing. Parses 3 | # the output of the make process, extract gcc lines, and convert to JSON 4 | # using (gnu) AWK 5 | # Join lines that finish by \ -- then split the large command lines around 6 | # && and ||, then look for gcc, then split and look for a .c argument. 7 | # when found, populate the compile_commands.json file 8 | gawk -v dir="$(pwd)" ' 9 | BEGIN { 10 | acc="";line=0; 11 | print "["; 12 | } 13 | END { 14 | print "]"; 15 | } 16 | /\\ *$/ { 17 | $0 = gensub(/\\ *$/, "", "g", $0); 18 | acc = acc $0 19 | next; 20 | } 21 | /\|\| *$/ { 22 | acc = acc $0 23 | next; 24 | } 25 | /^g?make\[([0-9]+)]/ { 26 | if ($2 != "Entering") 27 | next; 28 | dir = gensub(/'\''/, "", "g", $4) 29 | next; 30 | } 31 | { 32 | acc = acc $0 33 | acc = gensub(/([^\\])"/, "\\1\\\\\"", "g", acc); 34 | acc = gensub(/[ \t]+/, " ", "g", acc); 35 | cnt = split(acc, e, / *\|\| *| *&& */); 36 | acc = ""; 37 | for (cmd in e) { 38 | e[cmd] = gensub(/[ |\t]+$/, "", "g", e[cmd]); 39 | e[cmd] = gensub(/\\"\\"/, "\\\\\"", "g", e[cmd]); 40 | split(e[cmd], arg, / +/); 41 | if (!match(arg[1], /g?cc$/)) 42 | continue; 43 | c_file="" 44 | for (ai in arg) { 45 | if (match(arg[ai], /\.c$/)) { 46 | c_file = arg[ai]; 47 | break; 48 | } 49 | } 50 | if (c_file != "") { 51 | if (line > 0) printf ","; 52 | line++; 53 | printf "{\"directory\":\"%s\",\"file\":\"%s\",\"command\":\"%s\"}\n", 54 | dir, c_file, e[cmd]; 55 | } 56 | } 57 | } 58 | ' 59 | -------------------------------------------------------------------------------- /utils/convert-rom-tcc.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // this take these parameters. 12 | /* 13 | -o -- .c file with rom data 14 | -i -- binary file to load 15 | -c -- class of the rom 16 | -n -- internal name of the rom 17 | -d -- Human description of the rom 18 | */ 19 | int 20 | main(int argc, const char * argv[]) { 21 | const char * output = NULL; 22 | const char * class = NULL; 23 | const char * name = NULL; 24 | const char * desc = NULL; 25 | const char * input = NULL; 26 | uint8_t * data = NULL; 27 | size_t size = 0; 28 | 29 | for (int i = 1; i < argc; i++) { 30 | if (strcmp(argv[i], "-o") == 0 && i < argc-1) { 31 | output = argv[++i]; 32 | } else if (strcmp(argv[i], "-i") == 0 && i < argc-1) { 33 | input = argv[++i]; 34 | } else if (strcmp(argv[i], "-c") == 0 && i < argc-1) { 35 | class = argv[++i]; 36 | } else if (strcmp(argv[i], "-n") == 0 && i < argc-1) { 37 | name = argv[++i]; 38 | } else if (strcmp(argv[i], "-d") == 0 && i < argc-1) { 39 | desc = argv[++i]; 40 | } else { 41 | fprintf(stderr, "Unknown option %s\n", argv[i]); 42 | exit(1); 43 | } 44 | } 45 | // stat the file to get the size, load it in data 46 | { 47 | struct stat st; 48 | if (stat(input, &st) < 0) { 49 | perror(input); 50 | exit(1); 51 | } 52 | size = st.st_size; 53 | if (size == 0) { 54 | fprintf(stderr, "File %s is empty, ignored\n", input); 55 | exit(1); 56 | } 57 | data = malloc(size); 58 | if (!data) { 59 | perror("malloc"); 60 | exit(1); 61 | } 62 | FILE * f = fopen(input, "rb"); 63 | if (!f) { 64 | perror(input); 65 | exit(1); 66 | } 67 | if (fread(data, 1, size, f) != size) { 68 | perror(input); 69 | exit(1); 70 | } 71 | fclose(f); 72 | } 73 | // open the output file, add a header, the data, and the mii_rom_t struct 74 | { 75 | char newoutput[1024]; 76 | snprintf(newoutput, sizeof(newoutput), "%s.tmp", output); 77 | FILE * f = fopen(newoutput, "w"); 78 | if (!f) { 79 | perror(output); 80 | exit(1); 81 | } 82 | fprintf(f, "/* this file is auto-generated by convert-rom-tcc.c */\n\n"); 83 | fprintf(f, "#include \"mii_rom.h\"\n\n"); 84 | fprintf(f, "static const uint8_t mii_rom_%s[] = {\n", name); 85 | for (size_t i = 0; i < size; i++) { 86 | fprintf(f, "0x%02x,", data[i]); 87 | if ((i % 16) == 15) 88 | fprintf(f, "\n"); 89 | } 90 | fprintf(f, "};\n"); 91 | fprintf(f, "static mii_rom_t %s_rom = {\n", name); 92 | fprintf(f, "\t.name = \"%s\",\n", name); 93 | fprintf(f, "\t.class = \"%s\",\n", class); 94 | fprintf(f, "\t.description = \"%s\",\n", desc); 95 | fprintf(f, "\t.rom = mii_rom_%s,\n", name); 96 | fprintf(f, "\t.len = %ld,\n", size); 97 | fprintf(f, "};\n"); 98 | fprintf(f, "MII_ROM(%s_rom);\n", name); 99 | fclose(f); 100 | // rename the .tmp file to the output file 101 | rename(newoutput, output); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /utils/icon-convert-tcc.c: -------------------------------------------------------------------------------- 1 | /* 2 | first convert the .png to binary files in contrib using imagemagik: 3 | $ convert your_image.png -depth 8 rgba:your_image.bin 4 | run this with 5 | $ tcc -run icon-convert-tcc.c 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | /* this loads each contrib/mii-icon-*.bin file which is raw RGBA and 16 | add the header width/height, and convert the pixels to ARGB */ 17 | 18 | int main() { 19 | glob_t g = {}; 20 | glob("contrib/mii-icon-*.bin", 0, NULL, &g); 21 | for (int i = 0; i < g.gl_pathc; i++) { 22 | char * path = strdup(g.gl_pathv[i]); 23 | char * name = basename(path); 24 | char * dot = strrchr(name, '.'); 25 | if (dot) 26 | *dot = 0; 27 | char * dup = strdup(name); 28 | char * size = strrchr(dup, '-'); 29 | if (size) 30 | size++; 31 | int w = 0, h = 0; 32 | if (size) 33 | w = h = atoi(size); 34 | char *orig = g.gl_pathv[i]; 35 | if (!w || !h) { 36 | printf("bad size %s\n", orig); 37 | continue; 38 | } 39 | FILE * f = fopen(orig, "rb"); 40 | if (!f) { 41 | perror(orig); 42 | printf("bad file %s\n", orig); 43 | continue; 44 | } 45 | fseek(f, 0, SEEK_END); 46 | int size_ = ftell(f); 47 | fseek(f, 0, SEEK_SET); 48 | unsigned char * pixels = malloc(size_); 49 | fread(pixels, 1, size_, f); 50 | fclose(f); 51 | char out[1024]; 52 | snprintf(out, sizeof(out), "contrib/%s.h", name); 53 | f = fopen(out, "wb"); 54 | if (!f) { 55 | printf("bad file %s\n", out); 56 | continue; 57 | } 58 | fprintf(f, "/* this file is auto-generated by icon-convert-tcc.c */\n"); 59 | // fprintf(f, "#include \n"); 60 | fprintf(f, "#define MII_ICON%d_SIZE %d\n", w, (w * h + 2)); 61 | fprintf(f, "extern const unsigned long mii_icon%d[MII_ICON%d_SIZE];\n", w, w); 62 | fprintf(f, "#ifdef MII_ICON%d_DEFINE\n", w); 63 | fprintf(f, "const unsigned long mii_icon%d[MII_ICON%d_SIZE] = {\n", 64 | w, w); 65 | fprintf(f, "\t%d,%d,\n", w, h); 66 | for (int y = 0; y < h; y++) { 67 | fprintf(f, "\t"); 68 | for (int x = 0; x < w; x++) { 69 | int i = (y * w + x) * 4; 70 | int r = pixels[i + 0]; 71 | int g = pixels[i + 1]; 72 | int b = pixels[i + 2]; 73 | int a = pixels[i + 3]; 74 | fprintf(f, "0x%02x%02x%02x%02x, ", a, r, g, b); 75 | if (x % 8 == 7) 76 | fprintf(f, "\n\t"); 77 | } 78 | fprintf(f, "\n"); 79 | } 80 | fprintf(f, "};\n"); 81 | fprintf(f, "#endif\n"); 82 | fclose(f); 83 | free(pixels); 84 | } 85 | } --------------------------------------------------------------------------------