User:Compyx: Difference between revisions

From vice-emu
Jump to navigation Jump to search
 
(26 intermediate revisions by the same user not shown)
Line 1: Line 1:
= Who am I =
= Personal notes, observations and rants =


I'm an old C64 scener, look me up at csdk.dk if you don't believe me ;)
== SDL UI code ==


= Personal devlog =
Since I'm tasked with updating the SDL code to use the generic UI actions and hotkeys code and use those UI actions from the menu items, hotkeys and custom joystick mappings, and the code is poorly documented, I'll be writing down some observations on how the SDL UI code works here, and some notes on how to move the code to using the shared UI actions and hotkeys code.


=== Menu items ===


== TODO list ==
==== Current API ====


* 2016-09-xx: <s>Try to build VICE on Minix 3.2.x</s> (2016-10-05: Minix' own ports systems fails miserably building subversion)
Menu items are defined with the type <b><tt>ui_menu_entry_t</tt></b> (in <tt>uimenu.h</tt>):
* 2016-09-29: <s>Test Android SDL2 build</s>
<syntaxhighlight lang="C">
* 2016-09-29: <s>Fix build system for *BSD ports</s> (2016-10-03: Done for FreeBSD, NetBSD, DragonflyBSD)
typedef struct ui_menu_entry_s {
* 2016-10-02: <s>Gtk2 crashes NetBSD VirtualBox VM, why? Logging doesn't help, empty log</s>
    char *string;
* 2016-10-08: Clean up resource/cmdline code, using some kind of memory management/free list.
    ui_menu_entry_type_t type;
* 2016-10-09: Clean up c1541 and update man page.
    ui_callback_t callback;
* 2016-10-12: <s>Update build systems with checks for minimum version required for autoconf and automake</s>
    ui_callback_data_t data;
* 2016-10-15: Update build system to allow building shared-ffmpeg without requiring root (liblame tries to install itself in `/usr/local/lib` during VICE's configure stage)
    ui_menu_status_type_t status;
* 2016-10-25: <s>Update palettes: rename vice.vpl -> pepto.vpl, default.vpl -> vice.vpl</s> DONE, renamed vice.vpl to pepto-pal.vpl
} ui_menu_entry_t;
* 2016-11-05: BeOS/Haiku doesn't log anything to LOG_DEFAULT except HardSID messages at startup.
</syntaxhighlight>
* 2016-11-05: VSID: add HVSC Songlengths.txt support, maybe STIL as well. (For 3.1 or later)
* 2017-02-13: *nix make install: fix DOS2UNIX check, doesn't seem to work very well now


== BSD build system adventures ==
===== String =====


The <i><tt>string</tt></i> is used to display the item text in the menus. It is used by <tt>sdl_ui_display_item()</tt> to render a menu item.


=== FreeBSD/PC-BSD ===
===== Type =====


Version(s) tested: PC-BSD 10.3 amd64 (PC-BSD which is a preconfigured X11 install of FreeBSD)
The <i><tt>type</tt></i> determines what to do when the user activates the item, types include radio buttons, toggle buttons, submenus, and dialogs.
The various menu item types are defined as:
<syntaxhighlight lang="C">
typedef enum {
    /* Text item (no operation): if data == 1 text colors are inverted */
    MENU_ENTRY_TEXT = 0,


Use gmake when building VICE! Use normal make when installing from the ports system.
    /* Resource toggle: no UI needed, callback is used */
    MENU_ENTRY_RESOURCE_TOGGLE,


===== Ports =====
    /* Resource radio: no UI needed, callback is used, data is the resource value */
    MENU_ENTRY_RESOURCE_RADIO,


* Xaw: builds and runs
    /* Resource int: needs UI, callback is used */
* Gtk2: builds and runs
    MENU_ENTRY_RESOURCE_INT,
* Gtk3: builds and runs
* SDL1: builds and runs
* SDL2: builds and runs (very well even, best performance of the ports I've tested, most likely thanks to VirtualBox using HW rendering on my host)


(Tested in VirtualBox VM with guest additions).
    /* Resource string: needs UI, callback is used */
Still need to do a write-up on which libraries should be installed for building and for each specific port.
    MENU_ENTRY_RESOURCE_STRING,


=== NetBSD ===
    /* Submenu: needs UI, data points to the submenu */
    MENU_ENTRY_SUBMENU,


Version(s) tested: 7.0.1 amd64 (on VirtualBox 5.1.6)
    /* Dynamic submenu: needs UI, data points to the submenu, hotkeys disabled */
    MENU_ENTRY_DYNAMIC_SUBMENU,


Use gmake!
    /* Custom dialog: needs UI */
    MENU_ENTRY_DIALOG,


Some notes/observations:
    /* Other: no UI needed */
* It has few weird conflicts with library interdependencies: a lot of foo.so.x needed but foo.so.y found, etc. May need to test the pkgsrc system, see if that behave any better, and perhaps run the system on bare metal.
    MENU_ENTRY_OTHER,
* /dev/joy0 and /dev/joy1 can't be opened, <machine/joystick.h> is there and compiling against it works, same goes for USB joysticks, after installing libusb, usbhidapi and some others, configure sees the USB joystick support, but it doesn't work. Most likely a VirtualBox problem.
* HW rendering failed to build (failed at libdrm) while installing MesaLib7 through pkgsrc. So some ports had horrible performance.
* Sounds seems to work, but VBox doesn't output anything, most likely again a VBox issue, there are no guest-additions for NetBSD, there is an effort started in 2013 to port FreeBSD's guest additions to NetBSD, but that was abandoned.


All issues are related to properly configuring NetBSD, not something for VICE to worry about.
    /* Other: no UI needed */
    MENU_ENTRY_OTHER_TOGGLE
} ui_menu_entry_type_t;
</syntaxhighlight>


===== Ports =====
===== Callback and Data =====


* Xaw: builds and runs
When an item is activated the <i><tt>callback</tt></i> is called with <i><tt>data</i></tt> as its argument. The return value of the <i><tt>callback</i></tt> (a <tt>const char*</tt>) is used to update the item's state or to exit the UI (more on that later).
* Gtk2: builds and runs
* Gtk3: builds, starts, but hangs on initializing the Gtk3 widget for displaying the actual screen ("Gtk-WARNING **: Allocating size to GtkWindow 0x7f7ff493cd20 without calling gtk_widget_get_preferred_width/height(). How does the code know the size to allocate?")
* SDL1: builds and runs, no HW acceleration yet, needs Mesa, which fails to build
* SDL2: builds and 'runs' (5fps, most likely due to Gallium using software rendering)


===== In-tree libraries =====
Callbacks are prototyped in <tt>uimenu.h</tt>:
<syntaxhighlight lang="C">typedef const char *(*ui_callback_t)(int activated, ui_callback_data_t param);</syntaxhighlight>


--enable-static-ffmpeg fails when building src/lib/libx264 => configure script needs bash, standard shell fails.
These callbacks are triggered both by displaying an item (<tt>activated == 0</tt>) and by activating an item (<tt>activated == 1</tt>). The return value is used to either display something in front of the item, like <tt>sdl_menu_text_tick</tt> (a tick mark), nothing (<tt>NULL</tt>) or the special <tt>sdl_menu_text_exit_ui</tt> ("\1") which means the activation succeeded and the UI must be exited (for example when triggering soft/hard reset). The <i><tt>param</tt></i> argument is the <tt>data</tt> field of the menu item definition.


=== OpenBSD ===
Some helper macros and functions exit for the callbacks in <tt>menu_common.{c,h}</tt>.
<syntaxhighlight lang="C">
#define UI_MENU_CALLBACK(name) \
    const char *name(int activated, ui_callback_data_t param)


Version(s) tested: 6.0 amd64
#define UI_MENU_DEFINE_TOGGLE(resource)                             \
    static UI_MENU_CALLBACK(toggle_##resource##_callback)            \
    {                                                                \
        return sdl_ui_menu_toggle_helper(activated, #resource);      \
    }


<s>Can't get the bloody X server to behave :) (Again in VBox VM)</s>
#define UI_MENU_DEFINE_RADIO(resource)                                \
    static UI_MENU_CALLBACK(radio_##resource##_callback)             \
    {                                                                \
        return sdl_ui_menu_radio_helper(activated, param, #resource); \
    }


OpenBSD doesn't provide guest additions for VirtualBox, so for me sound output failed.
#define UI_MENU_DEFINE_STRING(resource)                                \
    static UI_MENU_CALLBACK(string_##resource##_callback)              \
    {                                                                  \
        return sdl_ui_menu_string_helper(activated, param, #resource); \
    }


==== Ports ====
#define UI_MENU_DEFINE_INT(resource)                                \
    static UI_MENU_CALLBACK(int_##resource##_callback)              \
    {                                                              \
        return sdl_ui_menu_int_helper(activated, param, #resource); \
    }


* Xaw: builds and runs
#define UI_MENU_DEFINE_FILE_STRING(resource)                                \
* Gtk2: builds and runs
    static UI_MENU_CALLBACK(file_string_##resource##_callback)              \
* Gtk3: builds and runs
    {                                                                      \
* SDL1: builds and runs
        return sdl_ui_menu_file_string_helper(activated, param, #resource); \
* SDL2: doesn't build, problem with uncompilable SDL2 headers
    }


For some reason the SDL2 configure fails on the SDL2 headers. Need to figure out why?
#define UI_MENU_DEFINE_SLIDER(resource, min, max)                                  \
    static UI_MENU_CALLBACK(slider_##resource##_callback)                          \
    {                                                                              \
        return sdl_ui_menu_slider_helper(activated, param, #resource, min, max);  \
    }
</syntaxhighlight>


=== Dragonfly BSD ===
These are used with a resource name as their first argument and expand to the beginning of a function definition of a callback. For example:
<syntaxhighlight lang="C">
UI_MENU_DEFINE_RADIO(VICIIBorderMode)


Version(s) tested: 4.6 amd64 (inside a VirtualBox 5.1.6 VM, configured as FreeBSD)
static const ui_menu_entry_t vicii_border_menu[] = {
    { "Normal",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_NORMAL_BORDERS },
    { "Full",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_FULL_BORDERS },
    /* Debug and None snipped */
    SDL_MENU_LIST_END
};
</syntaxhighlight>


Use gmake when building VICE.
The macro at the top expands to:
<syntaxhighlight lang="C">
static const char *radio_VICIIBorderMode_callback(int activated, ui_callback_data_t param)
{
    return sdl_ui_menu_radio_helper(activated, param, "VICIIBorderMode"); 
}
</syntaxhighlight>


==== Sound in VirtualBox ====
The helper function is defined (in <tt>menu_common.c</tt>as:
<syntaxhighlight lang="C">
const char *sdl_ui_menu_radio_helper(int activated, ui_callback_data_t param, const char *resource_name)
{
    if (activated) {
        if (resources_query_type(resource_name) == RES_INTEGER) {
            resources_set_int(resource_name, vice_ptr_to_int(param));
        } else {
            resources_set_string(resource_name, (char *)param);
        }
    } else {
        int v;
        const char *w;
        if (resources_query_type(resource_name) == RES_INTEGER) {
            if (resources_get_int(resource_name, &v) == 0) {
                if (v == vice_ptr_to_int(param)) {
                    return sdl_menu_text_tick;
                }
            }
        } else {
            if (resources_get_string(resource_name, &w) == 0) {
                if (!strcmp(w, (char *)param)) {
                    return sdl_menu_text_tick;
                }
            }
        }
    }
    return NULL;
}
</syntaxhighlight>


VB's default emulated audio device 'ich' causes some problems, so I set it to 'Intel HD Audio'.
So if activated the resource is set to <tt>param</tt> and <tt>NULL</tt> is returned: The <i><tt>callback</tt></i> and its <i><tt>data</i></tt> are used in <tt>sdl_ui_menu_item_activate()</tt> as follows:
I had to add <b><tt>snd_hda_load="YES"</tt></b> to <tt>/boot/loader.conf</tt> to tell the kernel to load the Intel HD Audio driver to get access to dev/dsp for sound output. I then installed the alsa-utils package to get access to alsamixer to set proper volume. alsamixer needed to be called with its full path `/usr/local/bin/alsamixer` since Dragonfly BSD doesn't include /usr/local/bin in PATH.
<syntaxhighlight lang="C">
const char *p;


==== GNU gettext ====
p = item->callback(1, item->data);
if (p == sdl_menu_text_exit_ui) {
    return MENU_RETVAL_EXIT_UI;
}
</syntaxhighlight>


Make in po/ will output an error: <tt>sed: ./vice.pot: No such file or directory</tt>, when GNU gettext isn't installed. Install <tt>gettext</tt> and <tt>gettext-tools</tt> to avoid this.
If not activated (when rendering the item's text), <tt>sdl_menu_text_tick</tt> is returned if the <tt>param</tt> matches the current resource value, otherwise <tt>NULL</tt> is returned. The <i><tt>callback</tt></i> and its <i><tt>data</i></tt> are used in <tt>sdl_ui_display_item()</tt> as follows:
<syntaxhighlight lang="C">
int istoggle = 0, status = 0;
char string[3] = {0x20, 0x20, 0};


==== Ports ====  
istoggle = (item->type == MENU_ENTRY_RESOURCE_TOGGLE) ||
          (item->type == MENU_ENTRY_RESOURCE_RADIO) ||
          (item->type == MENU_ENTRY_OTHER_TOGGLE);


* Xaw: builds and runs
itemdata = item->callback(0, item->data);
* Gtk2: builds and runs
* Gtk3: builds and runs
* SDL1: builds and runs
* SDL2: builds and runs


== Minix build system ==
if ((itemdata != NULL) && !strcmp(itemdata, MENU_NOT_AVAILABLE_STRING)) {
    /* menu is not available */
    status = 2;
} else {
    /* print tick-mark for toggles and radio buttons at the start of the line */
    if (istoggle) {
        status = (itemdata == NULL) ? 0 : 1;
        string[0] = (itemdata == NULL) ? MENU_CHECKMARK_UNCHECKED_CHAR : itemdata[0];
    }
}
</syntaxhighlight>


Minix 3.3.0 doesn't provide X11 out of the box (eg binary packages), so I'll start with 3.2.1. I have no experience with Minx, except for installing it once about ten years ago, and deciding it sucked.
In the above example when clicking on "Full" the integer resource "VICIIBorderMode" is set to the <i></tt>data</tt></i> field's value <tt>VICII_FULL_BORDERS</tt> (<tt><b>(void*)1</b></tt>). When rendering the menu the callback for "Full" will return <tt>sdl_menu_text_tick</tt> and a tick mark (green filled circle) is rendered in front of the item and the other menu items' callbacks return <tt>NULL</tt> since their <i><tt>data</tt></i> doesn't match the value of "VICIIBorderMode"; which the UI code then renders as an empty red circle (<tt>MENU_CHECKMARK_UNCHECKED_CHAR</tt>).


=== Minix 3.2.1 ===
===== Status =====


Useless. Subversion must be built from source through pksrc, but building fails with circular dependencies with libtool. GCC is 2.95(!), which is ancient.
The <tt><i>status</tt></i> field is used for radio buttons to display the selection's state.
<syntaxhighlight lang="C">
typedef enum {
    MENU_STATUS_ACTIVE = 0,
    MENU_STATUS_INACTIVE = 1,
    MENU_STATUS_NA = 2
} ui_menu_status_type_t;
</syntaxhighlight>


=== Minix 3.3.0 ===
==== Moving to UI Actions ====


Perhaps I'll look a this when I have absolutely nothing better to do, or am very drunk.
Work is done on moving to using UI actions in the [https://sourceforge.net/p/vice-emu/code/HEAD/tree/branches/compyx/sdl-uiactions/ compyx/sdl-uiactions] branch. Currently UI actions for menu items work and are being added.


=== Haiku nightly build ===
===== Initialization of UI actions =====


Compiling and running (inside a VBox VM) works. No sound yet. Need to issue `make CFLAGS=-O0` to avoid gcc barfing on scpu. Building with --enable-ethernet fails since there is no `src/arch/beos/rawnetarch.{c,h}`.
# <tt>ui_actions_init()</tt>, <tt>ui_hotkeys_resource_init()</tt> and <tt>ui_hotkeys_cmdline_init()</tt> are called in <tt>main_program()</tt> (in <tt>src/main.c</tt>), setting up the UI actions and registering hotkeys resources and command line options
# <tt>ui_hotkeys_init()</tt> is called in <tt>kbd_arch_init()</tt> (in <tt>src/arch/sdl/kbd.c</tt>), initializing the new hotkeys system, but not used further currently
# <tt>hotkeys_iterate_menu()</tt> is called in <tt>ui_init_finalize()</tt> to store pointers to menu items that have UI action IDs so the action handlers can use the callbacks (see below).
# The UI action handlers are registered in <tt>ui_init_finalize()</tt>


===== Extending the menu code =====
Fields are added to the <tt><b>ui_menu_entry_t</b></tt> struct:
<syntaxhighlight lang="C">
/* extra members for the UI actions */
int        action;      /**< UI action ID */
const char *activated;    /**< text to return when the item is activated */
</syntaxhighlight>


The <tt>sdl_ui_menu_item_activate()</tt> function has been updated to check the <tt><i>action</i></tt> field, and if &gt; 0, call <tt>ui_action_trigger()</tt> and return the <tt><i>activated</i></tt> field to the UI code so the UI can respond accordingly (keep going, show toggle/radio indicator, or exit UI).


== Preparing for removing Gtk2 in favour of Gtk3 ==
The current menu callbacks are left in place for the current hotkeys/joymapping implementation. Some of these callbacks can be removed once the UI actions are also supported for the hotkeys/joymappings, others need to remain (perhaps in a slightly altered form) since they contain the implementation of dialogs such as file choosers.


Since most modern operating systems used for VICE have support for Gtk3 and Gtk2 is no longer maintained, it makes sense to prepare VICE for using Gtk3 in stead of Gtk2. This means, for now, using a gtk2legacy wrapper module Groepaz started. This provided wrappers around Gtk2 code using the Gtk3 API.
In order to be able to show such a dialog and pass the correct data back to the UI code the function <tt>sdl_ui_menu_item_activate_by_action(int action)</tt> has been added. It checks whether the UI is currently active, and if so, calls sdl_ui_menu_item_activate(), otherwise it sets a CPU trap to activate the UI and trigger the menu item's callback.


The idea is to use the Gtk3 API in all Gtk/Gnome code while providing wrappers in gtk2legacy that translate those Gtk3 API calls into equivalent Gtk2 API calls. Once the Gtk2 API has finally been deprecated, we can simply remove gtk2legacy and use pure Gtk3.
Current (2023-06-22) implementation with a lot of debugging noise:
<syntaxhighlight lang="C">
void sdl_ui_menu_item_activate_by_action(int action)
{
    ui_action_map_t *map = ui_action_map_get(action);
    printf("%s(): map = %p\n", __func__, (const void *)map);
    if (map != NULL) {
        ui_menu_entry_t *item = map->menu_item[0];
        printf("%s(): item = %p\n", __func__, (const void *)item);
        if (item != NULL) {
            if (sdl_menu_state) {
                /* menu is already active */
                printf("%s(): menu is already active, activating item\n", __func__);
                /* we can call sdl_ui_menu_item_activate() because that would trigger
                * the action again */
                if (item->callback != NULL) {
                    item->callback(1 /*activated*/, item->data);
                } else {
                    fprintf(stderr, "%s(): error: no callback to trigger!\n", __func__);
                }
            } else {
                printf("%s(): menu is not active, calling interrupt_maincpu_trigger_trap()\n", __func__);
                interrupt_maincpu_trigger_trap(sdl_ui_trap, item);
            }
        }
    }
}
</syntaxhighlight>


The minimum Gtk3 version supported should be 3.16, since that introduces GtkGlArea, which should replace GtkGlExt.
===== TODO =====


* <s>Handling of <tt>MENU_ENTRY_RESOURCE_RADIO</tt> items with string resources, currently only integer resources are handled in the actions code for radio items.</s>
* <s>Handling of <tt>MENU_ENTRY_RESOURCE_INT<tt> items.</s>
* <s>Handling of <tt>MENU_ENTRY_RESOURCE_STRING<tt> items.</s>


{| class="wikitable sortable"
=== Hotkeys ===
|+ GTk3 support for various operating systems
! scope="col" | OS
! scope="col" | distro/version
! scope="col" | arch
! scope="col" | Gtk2 version
! scope="col" | Gtk3 version
! scope="col" | date checked
! scope="col" | notes
|- style="background-color: red;"
| Linux || Debian Jessie 8.6 || amd64 || 2.24.25 || 3.14.5 || 2016-10-09 ||
|- style="background-color: green;"
| Linux || Fedora 24 || amd64 || 2.24.31 || 3.20.9 || 2016-10-05 ||
|- style="background-color: red;"
| Linux || Mint 17.3 || amd64 || 2.24.3 || 3.10.18 || 2016-10-05 ||
|- style="background-color: green;"
| Linux || Mint 18.0 || amd64 || 2.24.30 || 3.18.9 || 2016-10-05 ||
|-
| Linux || Ubuntu 16.10 || amd64 || 2.24.30 || 3.20.9 || 2016-10-26 ||
|-
| BSD || FreeBSD 10.3 || amd64 || 2.24.29 || 3.18.8 || 2016-10-05 || used the ports collection
|-
| BSD || FreeBSD 11.0 || amd64 || 2.24.29 || 3.18.8 || 2016-10-12 ||
|-
| BSD || NetBSD 7.0.1 || amd64 || 2.24.30 || 3.20.6 || 2016-10-05 || used binary (pkgin) packages
|-
| BSD || NetBSD 7.0.2 || amd64 || 2.24.31 || 3.20.9 || 2016-10-30 || used pkgsrc in the absence of binary packages
|-
| BSD || OpenBSD 6.0 || amd64 || 2.24.30 || 3.20.6 || 2016-10-12 || used binary packages
|-
| BSD ||DragonflyBSD 4.6.0 || amd64 || 2.24.29 || 3.18.8 || 2016-10-13 ||
|-
| BSD ||DragonflyBSD 4.6.1 || amd64 || 2.24.29 || 3.18.8 || 2016-10-26 ||
|- style="background-color: red;"
| Minix || 3.2.1 || i386 || n/a || n/a || 2016-10-05 || the whole Minix build system is broken
|- style="background-color: red;"
| BEOS || Haiku hrev50610 || i386 || n/a || n/a || 2016-10-26 || Haiku doesn't have Gtk at all, uses its own C++ InterfaceKit
|}


 
=== Joystick mappings ===
=== Porting Gtk2 container types to Gtk3 container types ===
 
The current advise of Gtk3 is to use GtkGrid for any kind of layout container. Even GtkBox will someday be deprecated in favour of GtkGrid. For now, I'll use GtkBox untill the Gtk2 port has been completely removed, then we can tackle this.
 
==== GtkHBox and GtkVBox ====
 
GtkHBox and GtkVBox have been deprecated in favour of GtkBox.
 
I wrote a wrapper in gtk2legacy.c so Gtk2 code can use GtkBox while using the underlying GtkHBox/GtkVBox widgets. Naturally these calls aren't fully compatible, gtk_hbox_new() and gtk_vbox_new() use a `homogeneous` argument which determines if all widgets should be the same size. Since all VICE code use FALSE for this, gtk_box_new() works without having to set the homogeneous property afterwards.
 
==== GtkTable ====
 
GtkTable has been deprecated in favour of GtkGrid, porting this should be too much hassle, but lets see.
 
== C1541 and VDrive ==
 
Both c1541 and vdrive need a lot of updating. Many commands are poorly or not implemented, Code uses confusing variable names and values. Documentation (C1541) is very much out of sync.
 
=== Documentation ===
 
Documentation, both user and API, is poor and out of sync. I'm updating the c1541 man page to reflect the commands actually in c1541, for all commands and related function I'm adding Doxygen documentation so that perhaps we can one day generate the man page from the Doxygen docs.
 
=== Code cleanup ===
 
The c1541 and vdrive code needs cleaning up
 
* Names like `unit`, `drive` and `device` are used interchangeably: sometimes `unit` is an actual unit number (8-11), sometimes it's an index into a vdrive array, values are arbitrarily AND'ed, OR'ed or subtracted to convert between 'valid' values for unit numbers and drive indici.
 
* Often user input for commands (c1541) is assumed to be correct. Leading to either weird error messages or even segfaults.
 
* CBM DOS command support needs work. The VDrive command handling is lacking some functionality as opposed to the actual CBM DOS.
 
=== BUGS ===
 
* The copy command doesn't play nice with wild cards. See bug #803.

Latest revision as of 18:49, 19 July 2023

Personal notes, observations and rants

SDL UI code

Since I'm tasked with updating the SDL code to use the generic UI actions and hotkeys code and use those UI actions from the menu items, hotkeys and custom joystick mappings, and the code is poorly documented, I'll be writing down some observations on how the SDL UI code works here, and some notes on how to move the code to using the shared UI actions and hotkeys code.

Menu items

Current API

Menu items are defined with the type ui_menu_entry_t (in uimenu.h):

typedef struct ui_menu_entry_s {
    char *string;
    ui_menu_entry_type_t type;
    ui_callback_t callback;
    ui_callback_data_t data;
    ui_menu_status_type_t status;
} ui_menu_entry_t;
String

The string is used to display the item text in the menus. It is used by sdl_ui_display_item() to render a menu item.

Type

The type determines what to do when the user activates the item, types include radio buttons, toggle buttons, submenus, and dialogs. The various menu item types are defined as:

typedef enum {
    /* Text item (no operation): if data == 1 text colors are inverted */
    MENU_ENTRY_TEXT = 0,

    /* Resource toggle: no UI needed, callback is used */
    MENU_ENTRY_RESOURCE_TOGGLE,

    /* Resource radio: no UI needed, callback is used, data is the resource value */
    MENU_ENTRY_RESOURCE_RADIO,

    /* Resource int: needs UI, callback is used */
    MENU_ENTRY_RESOURCE_INT,

    /* Resource string: needs UI, callback is used */
    MENU_ENTRY_RESOURCE_STRING,

    /* Submenu: needs UI, data points to the submenu */
    MENU_ENTRY_SUBMENU,

    /* Dynamic submenu: needs UI, data points to the submenu, hotkeys disabled */
    MENU_ENTRY_DYNAMIC_SUBMENU,

    /* Custom dialog: needs UI */
    MENU_ENTRY_DIALOG,

    /* Other: no UI needed */
    MENU_ENTRY_OTHER,

    /* Other: no UI needed */
    MENU_ENTRY_OTHER_TOGGLE
} ui_menu_entry_type_t;
Callback and Data

When an item is activated the callback is called with data as its argument. The return value of the callback (a const char*) is used to update the item's state or to exit the UI (more on that later).

Callbacks are prototyped in uimenu.h:

typedef const char *(*ui_callback_t)(int activated, ui_callback_data_t param);

These callbacks are triggered both by displaying an item (activated == 0) and by activating an item (activated == 1). The return value is used to either display something in front of the item, like sdl_menu_text_tick (a tick mark), nothing (NULL) or the special sdl_menu_text_exit_ui ("\1") which means the activation succeeded and the UI must be exited (for example when triggering soft/hard reset). The param argument is the data field of the menu item definition.

Some helper macros and functions exit for the callbacks in menu_common.{c,h}.

#define UI_MENU_CALLBACK(name) \
    const char *name(int activated, ui_callback_data_t param)

#define UI_MENU_DEFINE_TOGGLE(resource)                              \
    static UI_MENU_CALLBACK(toggle_##resource##_callback)            \
    {                                                                \
        return sdl_ui_menu_toggle_helper(activated, #resource);      \
    }

#define UI_MENU_DEFINE_RADIO(resource)                                \
    static UI_MENU_CALLBACK(radio_##resource##_callback)              \
    {                                                                 \
        return sdl_ui_menu_radio_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_STRING(resource)                                \
    static UI_MENU_CALLBACK(string_##resource##_callback)              \
    {                                                                  \
        return sdl_ui_menu_string_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_INT(resource)                                \
    static UI_MENU_CALLBACK(int_##resource##_callback)              \
    {                                                               \
        return sdl_ui_menu_int_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_FILE_STRING(resource)                                \
    static UI_MENU_CALLBACK(file_string_##resource##_callback)              \
    {                                                                       \
        return sdl_ui_menu_file_string_helper(activated, param, #resource); \
    }

#define UI_MENU_DEFINE_SLIDER(resource, min, max)                                  \
    static UI_MENU_CALLBACK(slider_##resource##_callback)                          \
    {                                                                              \
        return sdl_ui_menu_slider_helper(activated, param, #resource, min, max);   \
    }

These are used with a resource name as their first argument and expand to the beginning of a function definition of a callback. For example:

UI_MENU_DEFINE_RADIO(VICIIBorderMode)

static const ui_menu_entry_t vicii_border_menu[] = {
    { "Normal",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_NORMAL_BORDERS },
    { "Full",
      MENU_ENTRY_RESOURCE_RADIO,
      radio_VICIIBorderMode_callback,
      (ui_callback_data_t)VICII_FULL_BORDERS },
    /* Debug and None snipped */
    SDL_MENU_LIST_END
};

The macro at the top expands to:

static const char *radio_VICIIBorderMode_callback(int activated, ui_callback_data_t param)
{
    return sdl_ui_menu_radio_helper(activated, param, "VICIIBorderMode");  
}

The helper function is defined (in menu_common.cas:

const char *sdl_ui_menu_radio_helper(int activated, ui_callback_data_t param, const char *resource_name)
{
    if (activated) {
        if (resources_query_type(resource_name) == RES_INTEGER) {
            resources_set_int(resource_name, vice_ptr_to_int(param));
        } else {
            resources_set_string(resource_name, (char *)param);
        }
    } else {
        int v;
        const char *w;
        if (resources_query_type(resource_name) == RES_INTEGER) {
            if (resources_get_int(resource_name, &v) == 0) {
                if (v == vice_ptr_to_int(param)) {
                    return sdl_menu_text_tick;
                }
            }
        } else {
            if (resources_get_string(resource_name, &w) == 0) {
                if (!strcmp(w, (char *)param)) {
                    return sdl_menu_text_tick;
                }
            }
        }
    }
    return NULL;
}

So if activated the resource is set to param and NULL is returned: The callback and its data are used in sdl_ui_menu_item_activate() as follows:

const char *p;

p = item->callback(1, item->data);
if (p == sdl_menu_text_exit_ui) {
    return MENU_RETVAL_EXIT_UI;
}
If not activated (when rendering the item's text), sdl_menu_text_tick is returned if the param matches the current resource value, otherwise NULL is returned. The callback and its data are used in sdl_ui_display_item() as follows:
int istoggle = 0, status = 0;
char string[3] = {0x20, 0x20, 0};

istoggle = (item->type == MENU_ENTRY_RESOURCE_TOGGLE) ||
           (item->type == MENU_ENTRY_RESOURCE_RADIO) ||
           (item->type == MENU_ENTRY_OTHER_TOGGLE);

itemdata = item->callback(0, item->data);

if ((itemdata != NULL) && !strcmp(itemdata, MENU_NOT_AVAILABLE_STRING)) {
    /* menu is not available */
    status = 2;
} else {
    /* print tick-mark for toggles and radio buttons at the start of the line */
    if (istoggle) {
        status = (itemdata == NULL) ? 0 : 1;
        string[0] = (itemdata == NULL) ? MENU_CHECKMARK_UNCHECKED_CHAR : itemdata[0];
    }
}

In the above example when clicking on "Full" the integer resource "VICIIBorderMode" is set to the data field's value VICII_FULL_BORDERS ((void*)1). When rendering the menu the callback for "Full" will return sdl_menu_text_tick and a tick mark (green filled circle) is rendered in front of the item and the other menu items' callbacks return NULL since their data doesn't match the value of "VICIIBorderMode"; which the UI code then renders as an empty red circle (MENU_CHECKMARK_UNCHECKED_CHAR).

Status

The status field is used for radio buttons to display the selection's state.

typedef enum {
    MENU_STATUS_ACTIVE = 0,
    MENU_STATUS_INACTIVE = 1,
    MENU_STATUS_NA = 2
} ui_menu_status_type_t;

Moving to UI Actions

Work is done on moving to using UI actions in the compyx/sdl-uiactions branch. Currently UI actions for menu items work and are being added.

Initialization of UI actions
  1. ui_actions_init(), ui_hotkeys_resource_init() and ui_hotkeys_cmdline_init() are called in main_program() (in src/main.c), setting up the UI actions and registering hotkeys resources and command line options
  2. ui_hotkeys_init() is called in kbd_arch_init() (in src/arch/sdl/kbd.c), initializing the new hotkeys system, but not used further currently
  3. hotkeys_iterate_menu() is called in ui_init_finalize() to store pointers to menu items that have UI action IDs so the action handlers can use the callbacks (see below).
  4. The UI action handlers are registered in ui_init_finalize()
Extending the menu code

Fields are added to the ui_menu_entry_t struct:

/* extra members for the UI actions */
int         action;       /**< UI action ID */
const char *activated;    /**< text to return when the item is activated */

The sdl_ui_menu_item_activate() function has been updated to check the action field, and if > 0, call ui_action_trigger() and return the activated field to the UI code so the UI can respond accordingly (keep going, show toggle/radio indicator, or exit UI).

The current menu callbacks are left in place for the current hotkeys/joymapping implementation. Some of these callbacks can be removed once the UI actions are also supported for the hotkeys/joymappings, others need to remain (perhaps in a slightly altered form) since they contain the implementation of dialogs such as file choosers.

In order to be able to show such a dialog and pass the correct data back to the UI code the function sdl_ui_menu_item_activate_by_action(int action) has been added. It checks whether the UI is currently active, and if so, calls sdl_ui_menu_item_activate(), otherwise it sets a CPU trap to activate the UI and trigger the menu item's callback.

Current (2023-06-22) implementation with a lot of debugging noise:

void sdl_ui_menu_item_activate_by_action(int action)
{
    ui_action_map_t *map = ui_action_map_get(action);
    printf("%s(): map = %p\n", __func__, (const void *)map);
    if (map != NULL) {
        ui_menu_entry_t *item = map->menu_item[0];
        printf("%s(): item = %p\n", __func__, (const void *)item);
        if (item != NULL) {
            if (sdl_menu_state) {
                /* menu is already active */
                printf("%s(): menu is already active, activating item\n", __func__);
                /* we can call sdl_ui_menu_item_activate() because that would trigger
                 * the action again */
                if (item->callback != NULL) {
                    item->callback(1 /*activated*/, item->data);
                } else {
                    fprintf(stderr, "%s(): error: no callback to trigger!\n", __func__);
                }
            } else {
                printf("%s(): menu is not active, calling interrupt_maincpu_trigger_trap()\n", __func__);
                interrupt_maincpu_trigger_trap(sdl_ui_trap, item);
            }
        }
    }
}
TODO
  • Handling of MENU_ENTRY_RESOURCE_RADIO items with string resources, currently only integer resources are handled in the actions code for radio items.
  • Handling of MENU_ENTRY_RESOURCE_INT items.
  • Handling of MENU_ENTRY_RESOURCE_STRING items.

Hotkeys

Joystick mappings