Add support for waiting for FCP driver initialisation
When a card using the FCP driver is added at runtime, we need to wait for fcp-server to finish creating all the controls before we attempt to enumerate them.
This commit is contained in:
108
src/alsa.c
108
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) {
|
||||
|
||||
117
src/iface-waiting.c
Normal file
117
src/iface-waiting.c
Normal file
@@ -0,0 +1,117 @@
|
||||
// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#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 "
|
||||
"<span font='monospace'>fcp-server</span> from "
|
||||
"<a href=\"https://github.com/geoffreybennett/fcp-support\">"
|
||||
"https://github.com/geoffreybennett/fcp-support</a> "
|
||||
"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),
|
||||
"<span weight='bold' size='large'>Waiting for FCP Server</span>");
|
||||
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;
|
||||
}
|
||||
8
src/iface-waiting.h
Normal file
8
src/iface-waiting.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett <g@b4.vu>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "alsa.h"
|
||||
|
||||
GtkWidget *create_iface_waiting_main(struct alsa_card *card);
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user