diff --git a/src/alsa.c b/src/alsa.c index becfd3b..1adc4bd 100644 --- a/src/alsa.c +++ b/src/alsa.c @@ -792,6 +792,77 @@ static void card_destroy_callback(void *data) { } } +// Complete card initialisation after the driver is ready +static void complete_card_init(struct alsa_card *card) { + + // Get full element list and create main window + alsa_get_elem_list(card); + alsa_set_lr_nums(card); + alsa_get_routing_controls(card); + card->best_firmware_version = scarlett2_get_best_firmware_version(card->pid); + + if (card->serial) { + // Call the reopen 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); +} + +// Check if the Firmware Version control has a TLV and is locked, +// indicating the driver is ready +static int check_driver_ready(snd_ctl_elem_info_t *info) { + return snd_ctl_elem_info_is_tlv_readable(info) && + snd_ctl_elem_info_is_locked(info); +} + +// Check if the FCP driver is initialised +static void check_driver_init( + struct alsa_card *card, int numid, unsigned int mask +) { + + // Ignore controls going away + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return; + + // Get the control's info + snd_ctl_elem_id_t *id; + snd_ctl_elem_info_t *info; + + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_info_alloca(&info); + + snd_ctl_elem_id_set_numid(id, numid); + snd_ctl_elem_info_set_id(info, id); + + if (snd_ctl_elem_info(card->handle, info) < 0) { + fprintf(stderr, "error getting elem info %d\n", numid); + return; + } + + const char *name = snd_ctl_elem_info_get_name(info); + + // Check if it's the Firmware Version control being updated + if (strcmp(name, "Firmware Version")) + return; + + // Check if the driver is ready + if (!check_driver_ready(info)) + return; + + // The driver is initialised; update the card's driver type + card->driver_type = DRIVER_TYPE_SOCKET; + + // Complete the card initialisation + complete_card_init(card); +} + static gboolean alsa_card_callback( GIOChannel *source, GIOCondition condition, @@ -823,6 +894,13 @@ static gboolean alsa_card_callback( int numid = snd_ctl_event_elem_get_numid(event); unsigned int mask = snd_ctl_event_elem_get_mask(event); + // Check if we're waiting for FCP driver to initialise and check if + // it's now ready + if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) { + check_driver_init(card, numid, mask); + return 1; + } + if (mask == SND_CTL_EVENT_MASK_REMOVE) { card_destroy_callback(card); return 0; @@ -1091,8 +1169,7 @@ static int check_firmware_version_locked(struct alsa_card *card) { if (err < 0) return 0; - return snd_ctl_elem_info_is_writable(info) && - snd_ctl_elem_info_is_locked(info); + return check_driver_ready(info); } // return the driver type for this card @@ -1145,31 +1222,20 @@ static int get_driver_type(struct alsa_card *card) { } static void card_init(struct alsa_card *card) { - card->driver_type = get_driver_type(card); - - alsa_get_elem_list(card); - alsa_set_lr_nums(card); - alsa_get_routing_controls(card); - - alsa_subscribe(card); alsa_get_usbid(card); alsa_get_serial_number(card); - card->best_firmware_version = scarlett2_get_best_firmware_version(card->pid); + alsa_subscribe(card); + alsa_add_card_callback(card); - if (card->serial) { + card->driver_type = get_driver_type(card); - // call the reopen 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); + // Driver not ready? Create the iface-waiting window + if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) { + create_card_window(card); + return; } - create_card_window(card); - alsa_add_card_callback(card); + complete_card_init(card); } static void alsa_scan_cards(void) { diff --git a/src/iface-waiting.c b/src/iface-waiting.c new file mode 100644 index 0000000..c77dc98 --- /dev/null +++ b/src/iface-waiting.c @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "alsa.h" +#include "iface-waiting.h" +#include "scarlett2-ioctls.h" +#include "window-iface.h" + +// Structure to hold timeout-related widgets +struct timeout_data { + GtkWidget *box; + GtkWidget *spinner; + GtkWidget *message_label; + guint timeout_id; +}; + +// Timeout callback function +static gboolean on_timeout(gpointer user_data) { + struct timeout_data *data = (struct timeout_data *)user_data; + + // Remove spinner + gtk_box_remove(GTK_BOX(data->box), data->spinner); + + // Update message with clickable link + if (data->message_label && GTK_IS_WIDGET(data->message_label)) + gtk_label_set_markup( + GTK_LABEL(data->message_label), + "Driver not detected. Please ensure " + "fcp-server from " + "" + "https://github.com/geoffreybennett/fcp-support " + "has been installed." + ); + + // Reset the timeout ID since it won't be called again + data->timeout_id = 0; + + // Return FALSE to prevent the timeout from repeating + return FALSE; +} + +// Weak reference callback for cleanup +static void on_widget_dispose(gpointer data, GObject *where_the_object_was) { + struct timeout_data *timeout_data = (struct timeout_data *)data; + + // Cancel the timeout if it's still active + if (timeout_data->timeout_id > 0) + g_source_remove(timeout_data->timeout_id); + + // Free the data structure + g_free(timeout_data); +} + +GtkWidget *create_iface_waiting_main(struct alsa_card *card) { + struct timeout_data *data; + + // Main vertical box + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); + gtk_widget_set_margin_start(box, 40); + gtk_widget_set_margin_end(box, 40); + gtk_widget_set_margin_top(box, 40); + gtk_widget_set_margin_bottom(box, 40); + + // Heading + GtkWidget *label = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(label), + "Waiting for FCP Server"); + gtk_box_append(GTK_BOX(box), label); + + // Add picture (scaled down properly) + GtkWidget *picture_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_hexpand(picture_box, TRUE); + gtk_widget_set_halign(picture_box, GTK_ALIGN_CENTER); + + GtkWidget *picture = gtk_picture_new_for_resource( + "/vu/b4/alsa-scarlett-gui/icons/vu.b4.alsa-scarlett-gui.png" + ); + gtk_picture_set_can_shrink(GTK_PICTURE(picture), TRUE); + gtk_widget_set_size_request(picture, 128, 128); + + gtk_box_append(GTK_BOX(picture_box), picture); + gtk_box_append(GTK_BOX(box), picture_box); + + // Add spinner + GtkWidget *spinner = gtk_spinner_new(); + gtk_spinner_start(GTK_SPINNER(spinner)); + gtk_widget_set_size_request(spinner, 48, 48); + gtk_box_append(GTK_BOX(box), spinner); + + // Description + label = gtk_label_new( + "Waiting for the user-space FCP driver to initialise..." + ); + gtk_label_set_wrap(GTK_LABEL(label), TRUE); + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); + gtk_label_set_max_width_chars(GTK_LABEL(label), 1); + gtk_widget_set_hexpand(label, TRUE); + gtk_widget_set_halign(label, GTK_ALIGN_FILL); + + gtk_box_append(GTK_BOX(box), label); + + // Setup timeout + data = g_new(struct timeout_data, 1); + data->box = box; + data->spinner = spinner; + data->message_label = label; + + // Set timeout + data->timeout_id = g_timeout_add_seconds(5, on_timeout, data); + + // Ensure data is freed when the box is destroyed + g_object_weak_ref(G_OBJECT(box), on_widget_dispose, data); + + return box; +} diff --git a/src/iface-waiting.h b/src/iface-waiting.h new file mode 100644 index 0000000..e154cdc --- /dev/null +++ b/src/iface-waiting.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "alsa.h" + +GtkWidget *create_iface_waiting_main(struct alsa_card *card); diff --git a/src/window-iface.c b/src/window-iface.c index 04a6cd5..2c23957 100644 --- a/src/window-iface.c +++ b/src/window-iface.c @@ -8,6 +8,7 @@ #include "iface-none.h" #include "iface-unknown.h" #include "iface-update.h" +#include "iface-waiting.h" #include "main.h" #include "menu.h" #include "window-iface.h" @@ -21,11 +22,34 @@ void create_card_window(struct alsa_card *card) { gtk_window_destroy(GTK_WINDOW(no_cards_window)); no_cards_window = NULL; } - window_count++; + + // Replacing an existing window + if (card->window_main) + gtk_window_destroy(GTK_WINDOW(card->window_main)); + + // New window + else + window_count++; int has_startup = true; int has_mixer = true; + // Check if the FCP driver is not initialised yet + if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) { + card->window_main_contents = create_iface_waiting_main(card); + has_startup = false; + has_mixer = false; + + // Create minimal window with only the waiting interface + card->window_main = gtk_application_window_new(app); + gtk_window_set_resizable(GTK_WINDOW(card->window_main), FALSE); + gtk_window_set_title(GTK_WINDOW(card->window_main), card->name); + gtk_window_set_child(GTK_WINDOW(card->window_main), card->window_main_contents); + gtk_widget_set_visible(card->window_main, TRUE); + + return; + } + struct alsa_elem *msd_elem = get_elem_by_name(card->elems, "MSD Mode Switch"); int in_msd_mode = msd_elem && alsa_get_elem_value(msd_elem);