Revision: 45662
http://sourceforge.net/p/vice-emu/code/45662
Author: compyx
Date: 2025-05-05 20:05:53 +0000 (Mon, 05 May 2025)
Log Message:
-----------
Joystick: implement host joystick polling from UI
Add basic setup for polling host joysticks from the UI thread.
Functions `joystick_ui_poll_setup()`, `joystick_ui_poll_teardown()` and
`joystick_ui_poll()` can be used to request host joystick events to be passed
to the UI thread. The function `joystick_ui_event()` is expected to be
implemented by the UIs (non-SDL/non-headless) to receive said events.
Only a single callback is used for axes, buttons and hats, so the UI logic for
"move input to assign to something" can be kept simple (hopefully).
Currently only a basic "playground" is added to the Gtk3 UI that shows axis
and buttons events using sliders and LEDs.
Modified Paths:
--------------
branches/compyx/joymap-003/vice/src/arch/gtk3/uisettings.c
branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/Makefile.am
branches/compyx/joymap-003/vice/src/joyport/joystick.c
branches/compyx/joymap-003/vice/src/joyport/joystick.h
Added Paths:
-----------
branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.c
branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.h
Modified: branches/compyx/joymap-003/vice/src/arch/gtk3/uisettings.c
===================================================================
--- branches/compyx/joymap-003/vice/src/arch/gtk3/uisettings.c 2025-05-04 13:18:20 UTC (rev 45661)
+++ branches/compyx/joymap-003/vice/src/arch/gtk3/uisettings.c 2025-05-05 20:05:53 UTC (rev 45662)
@@ -106,6 +106,7 @@
#include "settings_io.h"
#include "settings_isepic.h"
#include "settings_jam.h"
+#include "settings_joymap.h"
#include "settings_joystick.h"
#include "settings_keyboard.h"
#include "settings_ltkernal.h"
@@ -951,6 +952,9 @@
{ "Joystick",
"joystick",
settings_joystick_widget_create, NULL },
+ { "Joystick mappings",
+ "joystick-mappings",
+ settings_joymap_widget_create, NULL },
{ "Control port",
"control-port",
settings_controlport_widget_create, NULL },
Modified: branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/Makefile.am
===================================================================
--- branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/Makefile.am 2025-05-04 13:18:20 UTC (rev 45661)
+++ branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/Makefile.am 2025-05-05 20:05:53 UTC (rev 45662)
@@ -140,6 +140,7 @@
settings_io.c \
settings_isepic.c \
settings_jam.c \
+ settings_joymap.c \
settings_joystick.c \
settings_keyboard.c \
settings_ltkernal.c \
@@ -311,6 +312,7 @@
settings_io.h \
settings_isepic.h \
settings_jam.h \
+ settings_joymap.h \
settings_joystick.h \
settings_keyboard.h \
settings_ltkernal.h \
Added: branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.c
===================================================================
--- branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.c (rev 0)
+++ branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.c 2025-05-05 20:05:53 UTC (rev 45662)
@@ -0,0 +1,309 @@
+/** \file settings_joymap.c
+ * \brief Widget to edit joystick mappings
+ *
+ * \author Bas Wassink <b.w...@zi...>
+ */
+
+#include "vice.h"
+#include <gtk/gtk.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "mainlock.h"
+#include "joyport.h"
+#include "joystick.h"
+#include "resources.h"
+#include "statusbarledwidget.h"
+#include "vice_gtk3.h"
+
+#include "settings_joymap.h"
+
+
+static GtkWidget *event_widget_new(joystick_device_t *joydev);
+
+static GtkWidget *layout;
+static GtkWidget *device_combo;
+static GtkWidget *event_widget;
+
+static guint poll_timeout_id;
+
+
+static gboolean poll_callback(gpointer joydev)
+{
+ mainlock_assert_is_not_vice_thread();
+ joystick_ui_poll();
+ return G_SOURCE_CONTINUE;
+}
+
+static void start_polling(joystick_device_t *joydev)
+{
+ mainlock_assert_is_not_vice_thread();
+ if (joystick_ui_poll_setup(joydev)) {
+ poll_timeout_id = g_timeout_add(50, poll_callback, (gpointer)joydev);
+ }
+}
+
+static void stop_polling(void)
+{
+ if (poll_timeout_id > 0) {
+ g_source_remove(poll_timeout_id);
+ poll_timeout_id = 0;
+ }
+}
+
+
+/** \brief Create left-aligned label
+ *
+ * \param[in] markup label text using Pango markup
+ *
+ * \return new GtkLabel
+ */
+static GtkWidget *label_helper(const char *markup)
+{
+ GtkWidget *label = gtk_label_new(NULL);
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+ gtk_label_set_markup(GTK_LABEL(label), markup);
+ return label;
+}
+
+static GtkWidget *grid_helper(const char *markup, int colspacing, int rowspacing, int colspan)
+{
+ GtkWidget *grid = gtk_grid_new();
+ GtkWidget *label = label_helper(markup);
+
+ gtk_grid_set_column_spacing(GTK_GRID(grid), colspacing);
+ gtk_grid_set_row_spacing(GTK_GRID(grid), rowspacing);
+ gtk_grid_attach(GTK_GRID(grid), label, 0, 0, colspan, 1);
+
+ return grid;
+}
+
+
+static void on_device_changed(GtkComboBox *self, gpointer data)
+{
+ GtkTreeIter iter;
+
+ stop_polling();
+ if (gtk_combo_box_get_active_iter(self, &iter)) {
+ GtkTreeModel *model = gtk_combo_box_get_model(self);
+ int index = -1;
+
+ gtk_tree_model_get(model, &iter, 0, &index, -1);
+ debug_gtk3("Got index %d for device", index);
+
+ if (event_widget != NULL) {
+ gtk_widget_destroy(event_widget);
+ }
+ event_widget = event_widget_new(joystick_device_by_index(index));
+ gtk_grid_attach(GTK_GRID(layout), event_widget, 0, 2, 2, 1);
+ gtk_widget_show_all(event_widget);
+ start_polling(joystick_device_by_index(index));
+ }
+}
+
+static GtkListStore *device_combo_model_new(void)
+{
+ GtkListStore *model;
+ int index;
+
+ model = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
+ for (index = 0; index < joystick_device_count(); index++) {
+ GtkTreeIter iter;
+ joystick_device_t *joydev = joystick_device_by_index(index);
+ char buffer[256];
+
+ g_snprintf(buffer, sizeof buffer, "%s (%d %s, %d %s, %d %s)",
+ joydev->name,
+ joydev->num_axes, joydev->num_axes == 1 ? "axis" : "axes",
+ joydev->num_buttons, joydev->num_buttons == 1 ? "button" : "buttons",
+ joydev->num_hats, joydev->num_hats == 1 ? "hat" : "hats");
+
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter, 0, index, 1, buffer, -1);
+ }
+ return model;
+}
+
+
+static GtkWidget *device_combo_new(void)
+{
+ GtkWidget *combo;
+ GtkListStore *model;
+ GtkCellRenderer *renderer;
+
+ combo = gtk_combo_box_new();
+ model = device_combo_model_new();
+ renderer = gtk_cell_renderer_text_new();
+
+ gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(model));
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", 1, NULL);
+ g_signal_connect(G_OBJECT(combo),
+ "changed",
+ G_CALLBACK(on_device_changed),
+ NULL);
+ return combo;
+}
+
+
+/* TODO: Repaint widgets once after all events have been processed, with
+ * Gtk fram tick or so?
+ */
+static GtkWidget *event_widget_new(joystick_device_t *joydev)
+{
+ GtkWidget *grid;
+ GtkWidget *label;
+ GtkWidget *agrid;
+ GtkWidget *bgrid;
+ GtkWidget *hgrid;
+ int i;
+
+ grid = grid_helper("<b>Device events</b>", 16, 8, 3);
+
+ agrid = grid_helper("<b>Axes</b>", 8, 8, 2);
+ bgrid = grid_helper("<b>Buttons</b>", 8, 8, 2);
+ hgrid = grid_helper("<b>Hatss</b>", 8, 8, 2);
+ gtk_grid_attach(GTK_GRID(grid), agrid, 0, 1, 1, 1);
+ gtk_grid_attach(GTK_GRID(grid), bgrid, 1, 1, 1, 1);
+ gtk_grid_attach(GTK_GRID(grid), hgrid, 2, 1, 1, 1);
+
+ for (i = 0; i < joydev->num_axes; i++) {
+ joystick_axis_t *axis = joydev->axes[i];
+ GtkWidget *scale;
+
+ label = gtk_label_new(axis->name);
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+
+ scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL,
+ (gdouble)(axis->minimum),
+ (gdouble)(axis->maximum),
+ 1.0);
+ gtk_scale_set_digits(GTK_SCALE(scale), 0);
+ gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_RIGHT);
+ gtk_range_set_value(GTK_RANGE(scale), axis->minimum + ((axis->maximum - axis->minimum) / 2));
+ gtk_widget_set_size_request(scale, 200, -1);
+
+ gtk_grid_attach(GTK_GRID(agrid), label, 0, i + 1, 1, 1);
+ gtk_grid_attach(GTK_GRID(agrid), scale, 1, i + 1, 1, 1);
+
+ }
+
+ for (i = 0; i < joydev->num_buttons; i++) {
+ joystick_button_t *button = joydev->buttons[i];
+ GtkWidget *led;
+
+ label = gtk_label_new(button->name);
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+
+ led = statusbar_led_widget_create(NULL, "#00ff00", "#000000");
+ statusbar_led_widget_set_active(led, FALSE);
+
+ gtk_grid_attach(GTK_GRID(bgrid), label, 0, i + 1, 1 ,1);
+ gtk_grid_attach(GTK_GRID(bgrid), led, 1, i + 1, 1 ,1);
+ }
+
+ return grid;
+}
+
+
+static void on_joymap_widget_destroy(GtkWidget *self, gpointer unused)
+{
+ stop_polling();
+ /* TODO: reopen previously closed host devices */
+}
+
+
+GtkWidget *settings_joymap_widget_create(GtkWidget *parent)
+{
+ GtkWidget *label;
+ int row = 0;
+
+
+ /* TODO: Remember all opened host devices
+ * Close all host devices
+ * Reopen host devices on widget destruction
+ */
+
+ poll_timeout_id = 0;
+ event_widget = NULL;
+
+ layout = gtk_grid_new();
+ gtk_grid_set_column_spacing(GTK_GRID(layout), 16);
+ gtk_grid_set_row_spacing(GTK_GRID(layout), 8);
+
+ label = label_helper("<b>Joystick mappings playground</b>");
+ gtk_widget_set_margin_bottom(label, 8);
+ gtk_grid_attach(GTK_GRID(layout), label, 0, row, 2, 1);
+ row++;
+
+ device_combo = device_combo_new();
+ gtk_widget_set_hexpand(device_combo, TRUE);
+ label = gtk_label_new("Device");
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+ gtk_grid_attach(GTK_GRID(layout), label, 0, row, 1, 1);
+ gtk_grid_attach(GTK_GRID(layout), device_combo, 1, row, 1, 1);
+
+ g_signal_connect(G_OBJECT(layout),
+ "destroy",
+ G_CALLBACK(on_joymap_widget_destroy),
+ NULL);
+
+ gtk_widget_show_all(layout);
+ return layout;
+}
+
+
+void joystick_ui_event(void *input, joystick_input_t type, int32_t value)
+{
+ GtkWidget *grid;
+ joystick_axis_t *axis;
+ joystick_button_t *button;
+ GtkWidget *led;
+ GtkWidget *scale;
+ const char *name;
+
+ mainlock_assert_is_not_vice_thread();
+
+ switch (type) {
+ case JOY_INPUT_AXIS:
+ axis = input;
+ name = axis->name;
+ if (event_widget != NULL) {
+ grid = gtk_grid_get_child_at(GTK_GRID(event_widget), 0, 1);
+ if (grid != NULL && GTK_IS_GRID(grid)) {
+ scale = gtk_grid_get_child_at(GTK_GRID(grid), 1, 1 + axis->index);
+ if (scale != NULL) {
+ gtk_range_set_value(GTK_RANGE(scale), (gdouble)value);
+ }
+ }
+ }
+ break;
+ case JOY_INPUT_BUTTON:
+ button = input;
+ name = button->name;
+ //debug_gtk3("Button %s, Index %d, Value %d", button->name, button->index, value);
+ if (event_widget != NULL) {
+ /* button grid */
+ grid = gtk_grid_get_child_at(GTK_GRID(event_widget), 1, 1);
+ if (grid != NULL && GTK_IS_GRID(grid)) {
+ led = gtk_grid_get_child_at(GTK_GRID(grid), 1, 1 + button->index);
+ if (led != NULL) {
+ gboolean pressed = value ? TRUE: FALSE;
+
+ if (statusbar_led_widget_get_active(led) != pressed) {
+ statusbar_led_widget_set_active(led, value ? TRUE : FALSE);
+ }
+ }
+ }
+ }
+ break;
+ case JOY_INPUT_HAT:
+ name = ((joystick_hat_t *)input)->name;
+ break;
+ default:
+ name = "<unknown input>";
+ break;
+ }
+
+// debug_gtk3("event: %s: type %u, value %d", name, type, value);
+}
Added: branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.h
===================================================================
--- branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.h (rev 0)
+++ branches/compyx/joymap-003/vice/src/arch/gtk3/widgets/settings_joymap.h 2025-05-05 20:05:53 UTC (rev 45662)
@@ -0,0 +1,14 @@
+/** \file settings_joymap.h
+ * \brief Widget to edit joystick mappings - header
+ *
+ * \author Bas Wassink <b.w...@zi...>
+ */
+
+#ifndef VICE_SETTINGS_JOYMAP_H
+#define VICE_SETTINGS_JOYMAP_H
+
+#include <gtk/gtk.h>
+
+GtkWidget *settings_joymap_widget_create(GtkWidget *parent);
+
+#endif
Modified: branches/compyx/joymap-003/vice/src/joyport/joystick.c
===================================================================
--- branches/compyx/joymap-003/vice/src/joyport/joystick.c 2025-05-04 13:18:20 UTC (rev 45661)
+++ branches/compyx/joymap-003/vice/src/joyport/joystick.c 2025-05-05 20:05:53 UTC (rev 45662)
@@ -3046,15 +3046,20 @@
*/
void joy_axis_event(joystick_axis_t *axis, int32_t value)
{
- joystick_axis_value_t direction = JOY_AXIS_MIDDLE;
- joystick_axis_value_t prev = axis->prev;
- int joyport = axis->device->joyport;
+ joystick_axis_value_t direction = JOY_AXIS_MIDDLE;
+ joystick_axis_value_t prev = axis->prev;
+ int joyport = axis->device->joyport;
+#if !(defined(USE_SDLUI) || defined(USE_SDL2UI) || defined(USE_HEADLESSUI))
+ unsigned int poll_state = axis->device->status & JOY_POLL_MASK;
- if ((axis->device->status & JOY_POLL_MASK) == JOY_POLL_NONE) {
+ if (poll_state == JOY_POLL_NONE) {
return;
+ } else if (poll_state == JOY_POLL_UI) {
+ joystick_ui_event(axis, JOY_INPUT_AXIS, value);
+ return;
}
-
+#endif
/* digital axes don't require calibration: */
if (axis->digital) {
/* calibration: invert value? */
@@ -3143,9 +3148,16 @@
}
#endif
- if ((button->device->status & JOY_POLL_MASK) == JOY_POLL_NONE) {
+#if !(defined(USE_SDLUI) || defined(USE_SDL2UI) || defined(USE_HEADLESSUI))
+ unsigned int poll_state = button->device->status & JOY_POLL_MASK;
+
+ if (poll_state == JOY_POLL_NONE) {
return;
+ } else if (poll_state == JOY_POLL_UI) {
+ joystick_ui_event(button, JOY_INPUT_BUTTON, value);
+ return;
}
+#endif
if (value != button->prev) {
DBG(("joy_button_event: joy: %s, button: %d (%s) pressed: %d\n",
@@ -3166,9 +3178,16 @@
int joyport = hat->device->joyport;
int32_t prev = hat->prev;
- if ((hat->device->status & JOY_POLL_MASK) == JOY_POLL_NONE) {
+#if !(defined(USE_SDLUI) || defined(USE_SDL2UI) || defined(USE_HEADLESSUI))
+ unsigned int poll_state = hat->device->status & JOY_POLL_MASK;
+
+ if (poll_state == JOY_POLL_NONE) {
return;
+ } else if (poll_state == JOY_POLL_UI) {
+ joystick_ui_event(hat, JOY_INPUT_HAT, value);
+ return;
}
+#endif
if (value == prev) {
return;
@@ -3323,7 +3342,7 @@
for (i = 0; i < num_joystick_devices; i++) {
joystick_device_t *joydev = joystick_devices[i];
- if ((joydev->status & JOY_POLL_MASK) != JOY_POLL_NONE) {
+ if ((joydev->status & JOY_POLL_MASK) == JOY_POLL_MAIN) {
joy_driver.poll(joydev);
}
}
@@ -4291,3 +4310,39 @@
joy_driver.close(joydev);
}
}
+
+
+/*
+ * UI joystick polling
+ */
+
+static joystick_device_t *ui_joydev = NULL;
+
+
+bool joystick_ui_poll_setup(joystick_device_t *joydev)
+{
+ printf("%s(): starting polling of %s\n", __func__, joydev->name);
+ if (ui_joydev != NULL) {
+ joystick_ui_poll_teardown();
+ }
+ ui_joydev = joydev;
+ return joystick_device_open(ui_joydev, JOY_POLL_UI);
+}
+
+
+void joystick_ui_poll_teardown(void)
+{
+ if (ui_joydev != NULL) {
+ printf("%s(): stopping polling of %s\n", __func__, ui_joydev->name);
+ joystick_device_close(ui_joydev);
+ ui_joydev = NULL;
+ }
+}
+
+
+void joystick_ui_poll(void)
+{
+ if (ui_joydev != NULL && joy_driver.poll != NULL) {
+ joy_driver.poll(ui_joydev);
+ }
+}
Modified: branches/compyx/joymap-003/vice/src/joyport/joystick.h
===================================================================
--- branches/compyx/joymap-003/vice/src/joyport/joystick.h 2025-05-04 13:18:20 UTC (rev 45661)
+++ branches/compyx/joymap-003/vice/src/joyport/joystick.h 2025-05-05 20:05:53 UTC (rev 45662)
@@ -524,4 +524,11 @@
void joystick_hat_free (joystick_hat_t *hat);
void joystick_hat_clear_mappings(joystick_hat_t *hat);
+bool joystick_ui_poll_setup(joystick_device_t *joydev);
+void joystick_ui_poll(void);
+void joystick_ui_poll_teardown(void);
+
+/* To be implemented by UIs other than SDL or HEADLESS */
+void joystick_ui_event(void *input, joystick_input_t type, int32_t value);
+
#endif
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|