Add support for config reset
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
43
src/alsa.c
43
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);
|
||||
}
|
||||
|
||||
11
src/alsa.h
11
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);
|
||||
|
||||
87
src/device-reset-config.c
Normal file
87
src/device-reset-config.c
Normal 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
|
||||
);
|
||||
}
|
||||
9
src/device-reset-config.h
Normal file
9
src/device-reset-config.h
Normal 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);
|
||||
@@ -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
74
src/scarlett2-ioctls.c
Normal 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
26
src/scarlett2-ioctls.h
Normal 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
54
src/scarlett2.h
Normal 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 */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
216
src/window-modal.c
Normal 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
49
src/window-modal.h
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user