Add support for config reset

This commit is contained in:
Geoffrey D. Bennett
2023-12-14 13:03:51 +10:30
parent e95cbff3d5
commit 9544635e30
14 changed files with 675 additions and 3 deletions

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);

87
src/device-reset-config.c Normal file
View File

@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtk/gtk.h>
#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
);
}

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <gtk/gtk.h>
#include "alsa.h"
void create_reset_config_window(GtkWidget *w, struct alsa_card *card);

View File

@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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;
}

74
src/scarlett2-ioctls.c Normal file
View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <stdio.h>
#include <alsa/asoundlib.h>
#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;
}

26
src/scarlett2-ioctls.h Normal file
View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2023 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef SCARLETT2_IOCTLS_H
#define SCARLETT2_IOCTLS_H
#include <alsa/hwdep.h>
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

54
src/scarlett2.h Normal file
View File

@@ -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 <g at b4.vu>
*/
#ifndef __UAPI_SOUND_SCARLETT2_H
#define __UAPI_SOUND_SCARLETT2_H
#include <linux/types.h>
#include <linux/ioctl.h>
#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 */

View File

@@ -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);
}

View File

@@ -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);

216
src/window-modal.c Normal file
View File

@@ -0,0 +1,216 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtk/gtk.h>
#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;
}

49
src/window-modal.h Normal file
View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <gtk/gtk.h>
#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);

View File

@@ -1,11 +1,16 @@
// SPDX-FileCopyrightText: 2022-2024 Geoffrey D. Bennett <g@b4.vu>
// 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("<b>%s</b>", 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);