From 9544635e30f510232c84fca79e38aa669a4f7907 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Thu, 14 Dec 2023 13:03:51 +1030 Subject: [PATCH] Add support for config reset --- src/alsa-scarlett-gui.css | 9 ++ src/alsa.c | 43 ++++++++ src/alsa.h | 11 ++ src/device-reset-config.c | 87 +++++++++++++++ src/device-reset-config.h | 9 ++ src/iface-none.c | 5 +- src/scarlett2-ioctls.c | 74 +++++++++++++ src/scarlett2-ioctls.h | 26 +++++ src/scarlett2.h | 54 ++++++++++ src/window-iface.c | 8 ++ src/window-iface.h | 1 + src/window-modal.c | 216 ++++++++++++++++++++++++++++++++++++++ src/window-modal.h | 49 +++++++++ src/window-startup.c | 86 ++++++++++++++- 14 files changed, 675 insertions(+), 3 deletions(-) create mode 100644 src/device-reset-config.c create mode 100644 src/device-reset-config.h create mode 100644 src/scarlett2-ioctls.c create mode 100644 src/scarlett2-ioctls.h create mode 100644 src/scarlett2.h create mode 100644 src/window-modal.c create mode 100644 src/window-modal.h diff --git a/src/alsa-scarlett-gui.css b/src/alsa-scarlett-gui.css index 15fbcbb..9b26eeb 100644 --- a/src/alsa-scarlett-gui.css +++ b/src/alsa-scarlett-gui.css @@ -272,3 +272,12 @@ button.toggle { .window-frame separator { background: #800000; } + +.window-frame .big-padding { + padding: 50px; +} + +/* Bigger buttons in confirmation dialogs */ +.window-frame .big-padding button { + padding: 5px 30px; +} diff --git a/src/alsa.c b/src/alsa.c index fa06281..612eda8 100644 --- a/src/alsa.c +++ b/src/alsa.c @@ -23,6 +23,14 @@ static GArray *alsa_cards; // static fd and wd for ALSA inotify static int inotify_fd, inotify_wd; +struct reopen_callback { + ReOpenCallback *callback; + void *data; +}; + +// hash table for cards being rebooted +GHashTable *reopen_callbacks; + // forward declaration static void alsa_elem_change(struct alsa_elem *elem); @@ -742,6 +750,18 @@ static void alsa_scan_cards(void) { alsa_subscribe(card); alsa_get_serial_number(card); + if (card->serial) { + + // call the callbacks for this card + struct reopen_callback *rc = g_hash_table_lookup( + reopen_callbacks, card->serial + ); + if (rc) + rc->callback(rc->data); + + g_hash_table_remove(reopen_callbacks, card->serial); + } + create_card_window(card); alsa_add_card_callback(card); @@ -802,6 +822,29 @@ static void alsa_inotify_init(void) { void alsa_init(void) { alsa_cards = g_array_new(FALSE, TRUE, sizeof(struct alsa_card *)); + reopen_callbacks = g_hash_table_new_full( + g_str_hash, g_str_equal, g_free, g_free + ); alsa_inotify_init(); alsa_scan_cards(); } + +void alsa_register_reopen_callback( + const char *serial, + ReOpenCallback *callback, + void *data +) { + struct reopen_callback *rc = g_new0(struct reopen_callback, 1); + rc->callback = callback; + rc->data = data; + + g_hash_table_insert(reopen_callbacks, g_strdup(serial), rc); +} + +void alsa_unregister_reopen_callback(const char *serial) { + g_hash_table_remove(reopen_callbacks, serial); +} + +int alsa_has_reopen_callbacks(void) { + return g_hash_table_size(reopen_callbacks); +} diff --git a/src/alsa.h b/src/alsa.h index 0c24e06..3aaf92e 100644 --- a/src/alsa.h +++ b/src/alsa.h @@ -168,6 +168,7 @@ struct alsa_card { GtkWidget *window_mixer; GtkWidget *window_levels; GtkWidget *window_startup; + GtkWidget *window_modal; GtkWidget *window_main_contents; GtkWidget *routing_grid; GtkWidget *routing_lines; @@ -225,3 +226,13 @@ struct alsa_card *card_create(int card_num); // init void alsa_init(void); + +// register re-open callback +typedef void (ReOpenCallback)(void *); +void alsa_register_reopen_callback( + const char *serial, + ReOpenCallback *callback, + void *data +); +void alsa_unregister_reopen_callback(const char *serial); +int alsa_has_reopen_callbacks(void); diff --git a/src/device-reset-config.c b/src/device-reset-config.c new file mode 100644 index 0000000..089eb67 --- /dev/null +++ b/src/device-reset-config.c @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "device-reset-config.h" +#include "scarlett2.h" +#include "scarlett2-ioctls.h" +#include "window-modal.h" + +static gpointer update_progress( + struct modal_data *modal_data, + char *text, + int progress +) { + struct progress_data *progress_data = g_new0(struct progress_data, 1); + progress_data->modal_data = modal_data; + progress_data->text = text; + progress_data->progress = progress; + + g_main_context_invoke(NULL, modal_update_progress, progress_data); + return NULL; +} + +#define fail(msg) { \ + if (hwdep) \ + scarlett2_close(hwdep); \ + return update_progress(modal_data, msg, -1); \ +} + +#define failsndmsg(msg) g_strdup_printf(msg, snd_strerror(err)) + +gpointer reset_config_thread(gpointer user_data) { + struct modal_data *modal_data = user_data; + + update_progress(modal_data, g_strdup("Resetting configuration..."), 0); + + snd_hwdep_t *hwdep; + + int err = scarlett2_open_card(modal_data->card->device, &hwdep); + if (err < 0) + fail(failsndmsg("Unable to open hwdep interface: %s")); + + err = scarlett2_erase_config(hwdep); + if (err < 0) + fail(failsndmsg("Unable to reset configuration: %s")); + + while (1) { + g_usleep(50000); + + err = scarlett2_get_erase_progress(hwdep); + if (err < 0) + fail(failsndmsg("Unable to get erase progress: %s")); + if (err == 255) + break; + + update_progress(modal_data, NULL, err); + } + + g_main_context_invoke(NULL, modal_start_reboot_progress, modal_data); + scarlett2_reboot(hwdep); + scarlett2_close(hwdep); + + return NULL; +} + +static void join_thread(gpointer thread) { + g_thread_join(thread); +} + +static void reset_config_yes_callback(struct modal_data *modal_data) { + GThread *thread = g_thread_new( + "reset_config_thread", reset_config_thread, modal_data + ); + g_object_set_data_full( + G_OBJECT(modal_data->button_box), "thread", thread, join_thread + ); +} + +void create_reset_config_window(GtkWidget *w, struct alsa_card *card) { + create_modal_window( + w, card, + "Confirm Reset Configuration", + "Resetting Configuration", + "Are you sure you want to reset the configuration?", + reset_config_yes_callback + ); +} diff --git a/src/device-reset-config.h b/src/device-reset-config.h new file mode 100644 index 0000000..c406306 --- /dev/null +++ b/src/device-reset-config.h @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "alsa.h" + +void create_reset_config_window(GtkWidget *w, struct alsa_card *card); diff --git a/src/iface-none.c b/src/iface-none.c index 655fbbe..41b0dc7 100644 --- a/src/iface-none.c +++ b/src/iface-none.c @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later +#include "alsa.h" #include "iface-none.h" #include "gtkhelper.h" #include "menu.h" @@ -24,7 +25,9 @@ GtkWidget *create_window_iface_none(GtkApplication *app) { GTK_APPLICATION_WINDOW(w), TRUE ); add_window_action_map(GTK_WINDOW(w)); - gtk_widget_set_visible(w, TRUE); + if (!alsa_has_reopen_callbacks()) { + gtk_widget_set_visible(w, TRUE); + } return w; } diff --git a/src/scarlett2-ioctls.c b/src/scarlett2-ioctls.c new file mode 100644 index 0000000..c5cdd6b --- /dev/null +++ b/src/scarlett2-ioctls.c @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "scarlett2.h" + +#include "scarlett2-ioctls.h" + +int scarlett2_open_card(char *alsa_name, snd_hwdep_t **hwdep) { + return snd_hwdep_open(hwdep, alsa_name, SND_HWDEP_OPEN_DUPLEX); +} + +int scarlett2_get_protocol_version(snd_hwdep_t *hwdep) { + int version = 0; + int err = snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_PVERSION, &version); + + if (err < 0) + return err; + return version; +} + +int scarlett2_close(snd_hwdep_t *hwdep) { + return snd_hwdep_close(hwdep); +} + +int scarlett2_reboot(snd_hwdep_t *hwdep) { + return snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_REBOOT, 0); +} + +static int scarlett2_select_flash_segment(snd_hwdep_t *hwdep, int segment) { + return snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT, &segment); +} + +static int scarlett2_erase_flash_segment(snd_hwdep_t *hwdep) { + return snd_hwdep_ioctl(hwdep, SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT, 0); +} + +int scarlett2_erase_config(snd_hwdep_t *hwdep) { + int err; + + err = scarlett2_select_flash_segment(hwdep, SCARLETT2_SEGMENT_ID_SETTINGS); + if (err < 0) + return err; + return scarlett2_erase_flash_segment(hwdep); +} + +int scarlett2_erase_firmware(snd_hwdep_t *hwdep) { + int err; + + err = scarlett2_select_flash_segment(hwdep, SCARLETT2_SEGMENT_ID_FIRMWARE); + if (err < 0) + return err; + return scarlett2_erase_flash_segment(hwdep); +} + +int scarlett2_get_erase_progress(snd_hwdep_t *hwdep) { + struct scarlett2_flash_segment_erase_progress progress; + + int err = snd_hwdep_ioctl( + hwdep, SCARLETT2_IOCTL_GET_ERASE_PROGRESS, &progress + ); + if (err < 0) + return err; + + // translate progress from [1..num_blocks, 255] to [[0..100), 255]] + if (progress.num_blocks == 0 || + progress.progress == 0 || + progress.progress == 255) + return progress.progress; + + return (progress.progress - 1) * 100 / progress.num_blocks; +} diff --git a/src/scarlett2-ioctls.h b/src/scarlett2-ioctls.h new file mode 100644 index 0000000..0a25721 --- /dev/null +++ b/src/scarlett2-ioctls.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SCARLETT2_IOCTLS_H +#define SCARLETT2_IOCTLS_H + +#include + +int scarlett2_open_card(char *alsa_name, snd_hwdep_t **hwdep); +int scarlett2_get_protocol_version(snd_hwdep_t *hwdep); +int scarlett2_lock(snd_hwdep_t *hwdep); +int scarlett2_unlock(snd_hwdep_t *hwdep); +int scarlett2_close(snd_hwdep_t *hwdep); + +int scarlett2_reboot(snd_hwdep_t *hwdep); +int scarlett2_erase_config(snd_hwdep_t *hwdep); +int scarlett2_erase_firmware(snd_hwdep_t *hwdep); +int scarlett2_get_erase_progress(snd_hwdep_t *hwdep); +int scarlett2_write_firmware( + snd_hwdep_t *hwdep, + off_t offset, + unsigned char *buf, + size_t buf_len +); + +#endif // SCARLETT2_IOCTLS_H diff --git a/src/scarlett2.h b/src/scarlett2.h new file mode 100644 index 0000000..d0ff38f --- /dev/null +++ b/src/scarlett2.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Focusrite Scarlett 2 Protocol Driver for ALSA + * (including Scarlett 2nd Gen, 3rd Gen, Clarett USB, and Clarett+ + * series products) + * + * Copyright (c) 2023 by Geoffrey D. Bennett + */ +#ifndef __UAPI_SOUND_SCARLETT2_H +#define __UAPI_SOUND_SCARLETT2_H + +#include +#include + +#define SCARLETT2_HWDEP_MAJOR 1 +#define SCARLETT2_HWDEP_MINOR 0 +#define SCARLETT2_HWDEP_SUBMINOR 0 + +#define SCARLETT2_HWDEP_VERSION \ + ((SCARLETT2_HWDEP_MAJOR << 16) | \ + (SCARLETT2_HWDEP_MINOR << 8) | \ + SCARLETT2_HWDEP_SUBMINOR) + +#define SCARLETT2_HWDEP_VERSION_MAJOR(v) (((v) >> 16) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_MINOR(v) (((v) >> 8) & 0xFF) +#define SCARLETT2_HWDEP_VERSION_SUBMINOR(v) ((v) & 0xFF) + +/* Get protocol version */ +#define SCARLETT2_IOCTL_PVERSION _IOR('S', 0x60, int) + +/* Reboot */ +#define SCARLETT2_IOCTL_REBOOT _IO('S', 0x61) + +/* Select flash segment */ +#define SCARLETT2_SEGMENT_ID_SETTINGS 0 +#define SCARLETT2_SEGMENT_ID_FIRMWARE 1 +#define SCARLETT2_SEGMENT_ID_COUNT 2 + +#define SCARLETT2_IOCTL_SELECT_FLASH_SEGMENT _IOW('S', 0x62, int) + +/* Erase selected flash segment */ +#define SCARLETT2_IOCTL_ERASE_FLASH_SEGMENT _IO('S', 0x63) + +/* Get selected flash segment erase progress + * 1 through to num_blocks, or 255 for complete + */ +struct scarlett2_flash_segment_erase_progress { + unsigned char progress; + unsigned char num_blocks; +}; +#define SCARLETT2_IOCTL_GET_ERASE_PROGRESS \ + _IOR('S', 0x64, struct scarlett2_flash_segment_erase_progress) + +#endif /* __UAPI_SOUND_SCARLETT2_H */ diff --git a/src/window-iface.c b/src/window-iface.c index a9cffb2..2afba4f 100644 --- a/src/window-iface.c +++ b/src/window-iface.c @@ -86,6 +86,9 @@ void destroy_card_window(struct alsa_card *card) { gtk_window_destroy(GTK_WINDOW(card->window_levels)); if (card->window_startup) gtk_window_destroy(GTK_WINDOW(card->window_startup)); + if (card->window_modal) { + gtk_window_destroy(GTK_WINDOW(card->window_modal)); + } // disable the level meter timer source if (card->meter_gsource_timer) @@ -95,3 +98,8 @@ void destroy_card_window(struct alsa_card *card) { window_count--; create_no_card_window(); } + +void check_modal_window_closed(void) { + if (!window_count) + gtk_widget_set_visible(no_cards_window, TRUE); +} diff --git a/src/window-iface.h b/src/window-iface.h index 4f544c6..2cb20b2 100644 --- a/src/window-iface.h +++ b/src/window-iface.h @@ -8,3 +8,4 @@ void create_card_window(struct alsa_card *card); void create_no_card_window(void); void destroy_card_window(struct alsa_card *card); +void check_modal_window_closed(void); diff --git a/src/window-modal.c b/src/window-modal.c new file mode 100644 index 0000000..a2c769b --- /dev/null +++ b/src/window-modal.c @@ -0,0 +1,216 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "gtkhelper.h" +#include "window-iface.h" +#include "window-modal.h" + +static void modal_no_callback(GtkWidget *w, struct modal_data *modal_data) { + GtkWidget *dialog = modal_data->dialog; + + alsa_unregister_reopen_callback(modal_data->serial); + + gtk_window_destroy(GTK_WINDOW(dialog)); + check_modal_window_closed(); +} + +static void modal_yes_callback(GtkWidget *w, struct modal_data *modal_data) { + // remove the buttons + GtkWidget *child; + while ((child = gtk_widget_get_first_child(modal_data->button_box))) + gtk_box_remove(GTK_BOX(modal_data->button_box), child); + + // add a progress bar + modal_data->progress_bar = gtk_progress_bar_new(); + gtk_box_append(GTK_BOX(modal_data->button_box), modal_data->progress_bar); + + // change the title + gtk_window_set_title( + GTK_WINDOW(modal_data->dialog), modal_data->title_active + ); + + // if the card goes away, don't close this window + modal_data->card->window_modal = NULL; + + modal_data->callback(modal_data); +} + +static void free_modal_data(gpointer user_data) { + struct modal_data *modal_data = user_data; + + g_free(modal_data->serial); + g_free(modal_data); +} + +void create_modal_window( + GtkWidget *w, + struct alsa_card *card, + const char *title, + const char *title_active, + const char *message, + modal_callback callback +) { + if (card->window_modal) { + fprintf(stderr, "Error: Modal window already open\n"); + return; + } + + GtkWidget *parent = gtk_widget_get_ancestor(GTK_WIDGET(w), GTK_TYPE_WINDOW); + GtkWidget *dialog = gtk_window_new(); + + struct modal_data *modal_data = g_new0(struct modal_data, 1); + modal_data->card = card; + modal_data->serial = g_strdup(card->serial); + modal_data->title_active = title_active; + modal_data->dialog = dialog; + modal_data->callback = callback; + + gtk_window_set_title(GTK_WINDOW(dialog), title); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent)); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + gtk_widget_add_css_class(dialog, "window-frame"); + + GtkWidget *content_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 50); + gtk_window_set_child(GTK_WINDOW(dialog), content_box); + gtk_widget_add_css_class(content_box, "window-content"); + gtk_widget_add_css_class(content_box, "top-level-content"); + gtk_widget_add_css_class(content_box, "big-padding"); + + modal_data->label = gtk_label_new(message); + gtk_box_append(GTK_BOX(content_box), modal_data->label); + + GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_widget_set_margin(sep, 0); + gtk_box_append(GTK_BOX(content_box), sep); + + modal_data->button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 50); + gtk_widget_set_halign(modal_data->button_box, GTK_ALIGN_CENTER); + gtk_box_append(GTK_BOX(content_box), modal_data->button_box); + + g_object_set_data_full( + G_OBJECT(dialog), "modal_data", modal_data, free_modal_data + ); + + GtkWidget *no_button = gtk_button_new_with_label("No"); + g_signal_connect( + no_button, "clicked", G_CALLBACK(modal_no_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), no_button); + + GtkWidget *yes_button = gtk_button_new_with_label("Yes"); + g_signal_connect( + yes_button, "clicked", G_CALLBACK(modal_yes_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), yes_button); + + gtk_widget_set_visible(dialog, TRUE); + + card->window_modal = dialog; +} + +gboolean modal_update_progress(gpointer user_data) { + struct progress_data *progress_data = user_data; + struct modal_data *modal_data = progress_data->modal_data; + + // Done? Replace the progress bar with an Ok button. + if (progress_data->progress < 0) { + GtkWidget *child; + while ((child = gtk_widget_get_first_child(modal_data->button_box))) + gtk_box_remove(GTK_BOX(modal_data->button_box), child); + + GtkWidget *ok_button = gtk_button_new_with_label("Ok"); + g_signal_connect( + ok_button, "clicked", G_CALLBACK(modal_no_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), ok_button); + } else { + gtk_progress_bar_set_fraction( + GTK_PROGRESS_BAR(modal_data->progress_bar), + progress_data->progress / 100.0 + ); + } + + // Update the label text if we have a new message. + if (progress_data->text) + gtk_label_set_text(GTK_LABEL(modal_data->label), progress_data->text); + + g_free(progress_data->text); + g_free(progress_data); + return G_SOURCE_REMOVE; +} + +// make the progress bar move along +// if it gets to the end twice, something probably went wrong +static gboolean update_progress_bar_reboot(gpointer user_data) { + struct progress_data *progress_data = user_data; + struct modal_data *modal_data = progress_data->modal_data; + + if (progress_data->progress >= 200) { + // Done? + gtk_label_set_text( + GTK_LABEL(modal_data->label), + "Reboot failed? Try unplugging/replugging/power-cycling the device." + ); + + GtkWidget *child; + while ((child = gtk_widget_get_first_child(modal_data->button_box))) + gtk_box_remove(GTK_BOX(modal_data->button_box), child); + + GtkWidget *ok_button = gtk_button_new_with_label("Ok"); + g_signal_connect( + ok_button, "clicked", G_CALLBACK(modal_no_callback), modal_data + ); + gtk_box_append(GTK_BOX(modal_data->button_box), ok_button); + + modal_data->timeout_id = 0; + + return G_SOURCE_REMOVE; + } + + progress_data->progress++; + gtk_progress_bar_set_fraction( + GTK_PROGRESS_BAR(modal_data->progress_bar), + (progress_data->progress % 100) / 100.0 + ); + + return G_SOURCE_CONTINUE; +} + +// this is called when the card is seen again so we can close the +// modal window +void modal_reopen_callback(void *user_data) { + struct modal_data *modal_data = user_data; + + // stop the progress bar + if (modal_data->timeout_id) + g_source_remove(modal_data->timeout_id); + + // close the window + gtk_window_destroy(GTK_WINDOW(modal_data->dialog)); +} + +// make a progress bar that moves while the device is rebooting +gboolean modal_start_reboot_progress(gpointer user_data) { + struct modal_data *modal_data = user_data; + + gtk_label_set_text(GTK_LABEL(modal_data->label), "Rebooting..."); + + struct progress_data *progress_data = g_new0(struct progress_data, 1); + progress_data->modal_data = modal_data; + progress_data->progress = 0; + + g_object_set_data_full( + G_OBJECT(modal_data->progress_bar), "progress_data", progress_data, g_free + ); + + modal_data->timeout_id = g_timeout_add( + 55, update_progress_bar_reboot, progress_data + ); + + alsa_register_reopen_callback( + modal_data->card->serial, modal_reopen_callback, modal_data + ); + + return G_SOURCE_REMOVE; +} diff --git a/src/window-modal.h b/src/window-modal.h new file mode 100644 index 0000000..12fde03 --- /dev/null +++ b/src/window-modal.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "alsa.h" + +// create a modal window with a message and yes/no buttons +// the callback is called with the modal_data when yes is clicked + +struct modal_data; + +typedef void (*modal_callback)(struct modal_data *data); + +struct modal_data { + struct alsa_card *card; + char *serial; + const char *title_active; + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *button_box; + GtkWidget *progress_bar; + guint timeout_id; + modal_callback callback; +}; + +void create_modal_window( + GtkWidget *w, + struct alsa_card *card, + const char *title, + const char *title_active, + const char *message, + modal_callback callback +); + +// update the progress bar in a modal window + +struct progress_data { + struct modal_data *modal_data; + char *text; + int progress; +}; + +gboolean modal_update_progress(gpointer user_data); + +// start a progress bar for a reboot + +gboolean modal_start_reboot_progress(gpointer user_data); diff --git a/src/window-startup.c b/src/window-startup.c index 4bc6572..67105c8 100644 --- a/src/window-startup.c +++ b/src/window-startup.c @@ -1,11 +1,16 @@ // SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett // SPDX-License-Identifier: GPL-3.0-or-later +#include "device-reset-config.h" #include "gtkhelper.h" +#include "scarlett2.h" +#include "scarlett2-ioctls.h" #include "widget-boolean.h" #include "window-startup.h" -static GtkWidget *small_label(char *text) { +#define REQUIRED_HWDEP_VERSION_MAJOR 1 + +static GtkWidget *small_label(const char *text) { GtkWidget *w = gtk_label_new(NULL); char *s = g_strdup_printf("%s", text); @@ -17,7 +22,7 @@ static GtkWidget *small_label(char *text) { return w; } -static GtkWidget *big_label(char *text) { +static GtkWidget *big_label(const char *text) { GtkWidget *view = gtk_text_view_new (); GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); @@ -148,6 +153,82 @@ static void add_msd_control( *grid_y += 2; } +static void add_reset_action( + struct alsa_card *card, + GtkWidget *grid, + int *grid_y, + const char *label, + const char *button_label, + const char *description, + GCallback callback +) { + add_sep(grid, grid_y); + + GtkWidget *w; + + w = small_label(label); + gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y, 1, 1); + + w = gtk_button_new_with_label(button_label); + gtk_grid_attach(GTK_GRID(grid), w, 0, *grid_y + 1, 1, 1); + g_signal_connect(w, "clicked", callback, card); + + w = big_label(description); + gtk_grid_attach(GTK_GRID(grid), w, 1, *grid_y, 1, 2); + + *grid_y += 2; +} + +static void add_reset_actions( + struct alsa_card *card, + GtkWidget *grid, + int *grid_y +) { + // simulated cards don't support hwdep + if (!card->device) + return; + + snd_hwdep_t *hwdep; + + int err = scarlett2_open_card(card->device, &hwdep); + if (err < 0) { + fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err)); + return; + } + + int ver = scarlett2_get_protocol_version(hwdep); + if (ver < 0) { + fprintf(stderr, "unable to get protocol version: %s\n", snd_strerror(ver)); + return; + } + + if (SCARLETT2_HWDEP_VERSION_MAJOR(ver) != REQUIRED_HWDEP_VERSION_MAJOR) { + fprintf( + stderr, + "Unsupported hwdep protocol version %d.%d.%d on card %s\n", + SCARLETT2_HWDEP_VERSION_MAJOR(ver), + SCARLETT2_HWDEP_VERSION_MINOR(ver), + SCARLETT2_HWDEP_VERSION_SUBMINOR(ver), + card->device + ); + return; + } + + scarlett2_close(hwdep); + + // Reset Configuration + add_reset_action( + card, + grid, + grid_y, + "Reset Configuration", + "Reset", + "Resetting the configuration will reset the interface to its " + "factory default settings. The firmware will be left unchanged.", + G_CALLBACK(create_reset_config_window) + ); +} + static void add_no_startup_controls_msg(GtkWidget *grid) { GtkWidget *w = big_label( "It appears that there are no startup controls. You probably " @@ -175,6 +256,7 @@ GtkWidget *create_startup_controls(struct alsa_card *card) { add_standalone_control(elems, grid, &grid_y); add_phantom_persistence_control(elems, grid, &grid_y); add_msd_control(elems, grid, &grid_y); + add_reset_actions(card, grid, &grid_y); if (!grid_y) add_no_startup_controls_msg(grid);