From 8a2e5f58358c7f0372d313eb2308efb03fb051ed Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Wed, 26 Feb 2025 03:13:13 +1030 Subject: [PATCH 01/18] Add RTFM advice to FAQ.md --- FAQ.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/FAQ.md b/FAQ.md index 33e6417..c99c0b1 100644 --- a/FAQ.md +++ b/FAQ.md @@ -92,6 +92,16 @@ to the user manual of your interface for more information. ## Help?! +Have you read the User Guide for your interface? It’s available +online: https://downloads.focusrite.com/focusrite and contains a lot +of helpful/useful/important information about your device. + +You can skip the “Easy Start” and “Setting up your DAW” sections, but +the rest is well worth reading. Even the information about Focusrite +Control is useful, although not directly applicable, because it will +help you understand more about the possibilities of what you can do +with your device. + For help with the Scarlett2 and FCP kernel drivers: https://github.com/geoffreybennett/linux-fcp/issues From 460b03c6682b0834ad71ebfa45135a78d660fae2 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Wed, 26 Feb 2025 03:15:38 +1030 Subject: [PATCH 02/18] =?UTF-8?q?Replace=20'/"=20with=20=E2=80=99/?= =?UTF-8?q?=E2=80=9C/=E2=80=9D=20in=20*.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FAQ.md | 12 ++++++------ docs/INSTALL.md | 2 +- docs/iface-1st-gen.md | 14 +++++++------- docs/iface-4th-gen-big.md | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/FAQ.md b/FAQ.md index c99c0b1..ecca702 100644 --- a/FAQ.md +++ b/FAQ.md @@ -24,7 +24,7 @@ deactivate MSD mode by holding down the 48V button while powering it on). However, to access the mixer, routing, and hardware-specific features, -you'll need the appropriate driver for your interface model. +you’ll need the appropriate driver for your interface model. ## MSD Mode? @@ -34,7 +34,7 @@ you'll need the appropriate driver for your interface model. If MSD Mode is enabled, you need to disable it and restart your interface to get access to its full functionality. -When you plug the interface in, there'll be a tiny read-only virtual +When you plug the interface in, there’ll be a tiny read-only virtual disk that has a link to the Focusrite product registration page; until you turn off MSD Mode not all features of the interface will be available. @@ -46,7 +46,7 @@ powering on the interface, or by clicking the button in If you do the recommended/required (depending on the model) firmware update, MSD Mode will automatically be turned off. -## What is the purpose of these drivers if they're not needed for basic audio? +## What is the purpose of these drivers if they’re not needed for basic audio? These drivers are for users who want more control over their interface. They allow for detailed manipulation of: @@ -72,13 +72,13 @@ The ALSA Scarlett Control Panel supports: - **Vocaster**: One, Two Note: The Scarlett 1st and 2nd Gen small interfaces (Solo, 2i2, 2i4) -don't have any software controls. All the controls are available from -the front panel, so they don't require the specialised drivers or this +don’t have any software controls. All the controls are available from +the front panel, so they don’t require the specialised drivers or this GUI. ## Where are the options to set the sample rate and buffer size? -The ALSA Scarlett Control Panel doesn't handle audio input/output +The ALSA Scarlett Control Panel doesn’t handle audio input/output settings like sample rate and buffer size. These settings are managed by the application using the soundcard, typically a sound server such as PulseAudio, JACK, or PipeWire. diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 7d014f0..f831297 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -30,7 +30,7 @@ is not needed, useful, or supported for these models. If your distribution doesn’t include a recent-enough kernel for your interface, you can get the latest driver from here and build it for -your current kernel if it's not too old (the Scarlett2 and FCP drivers +your current kernel if it’s not too old (the Scarlett2 and FCP drivers are both maintained in the same tree here): https://github.com/geoffreybennett/linux-fcp/releases diff --git a/docs/iface-1st-gen.md b/docs/iface-1st-gen.md index 57d265c..dfc7343 100644 --- a/docs/iface-1st-gen.md +++ b/docs/iface-1st-gen.md @@ -26,7 +26,7 @@ ALSA driver implementation that you should be aware of: 2. **State Update Issues**: The driver only updates the hardware state when it thinks a setting needs to be changed. If the driver incorrectly believes a control is already in the desired state, it - won't actually update the control. + won’t actually update the control. 3. **Level Meters**: The driver does not support reading the level meters from the hardware. @@ -37,7 +37,7 @@ ALSA driver implementation that you should be aware of: To ensure your settings are properly applied: -1. Apply a "zero" configuration that sets all controls to values that +1. Apply a “zero” configuration that sets all controls to values that are *not* what you desire. 2. Then apply your desired configuration @@ -70,12 +70,12 @@ Global controls relate to the operation of the interface as a whole. #### Clock Source Clock Source selects where the interface receives its digital clock -from. If you aren't using S/PDIF or ADAT inputs, set this to Internal. +from. If you aren’t using S/PDIF or ADAT inputs, set this to Internal. #### Sync Status Sync Status indicates if the interface is locked to a valid digital -clock. If you aren't using S/PDIF or ADAT inputs and the status is +clock. If you aren’t using S/PDIF or ADAT inputs and the status is Unlocked, change the Clock Source to Internal. ### Analogue Input Controls @@ -136,16 +136,16 @@ from more than one source, use the mixer inputs and outputs: The Presets menu can be used to clear all connections, or to set up common configurations: -- The "Direct" preset sets up the usual configuration using the +- The “Direct” preset sets up the usual configuration using the interface as a regular audio interface by connecting: - all Hardware Inputs to PCM Inputs - all PCM Outputs to Hardware Outputs -- The "Preamp" preset connects all Hardware Inputs to Hardware +- The “Preamp” preset connects all Hardware Inputs to Hardware Outputs. -- The "Stereo Out" preset connects PCM 1 and 2 Outputs to pairs of +- The “Stereo Out” preset connects PCM 1 and 2 Outputs to pairs of Hardware Outputs. ## Mixer diff --git a/docs/iface-4th-gen-big.md b/docs/iface-4th-gen-big.md index cf900cb..9a622db 100644 --- a/docs/iface-4th-gen-big.md +++ b/docs/iface-4th-gen-big.md @@ -10,7 +10,7 @@ with the big Scarlett 4th Gen interfaces: ### FCP Driver The big 4th Gen interfaces are supported by a new “FCP” (Focusrite -Control Protocol) driver introduced in Linux 6.14. If you haven't +Control Protocol) driver introduced in Linux 6.14. If you haven’t installed [fcp-support](https://github.com/geoffreybennett/fcp-support) yet, you need to do that (and update the firmware) before you can use From 91fc3bbb035d876a70397d8ab3c556c8e9dbe954 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Wed, 26 Feb 2025 03:26:48 +1030 Subject: [PATCH 03/18] Add information about alsa-state and alsa-restore to FAQ.md --- FAQ.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/FAQ.md b/FAQ.md index ecca702..40f9758 100644 --- a/FAQ.md +++ b/FAQ.md @@ -90,6 +90,28 @@ displays the current rate being used by applications. If it shows Note that not all features are available at higher sample rates; refer to the user manual of your interface for more information. +## Why do my settings keep resetting? + +The settings in the ALSA Scarlett Control Panel are automatically +saved in the interface itself (all series except 1st Gen), so they +“should” persist across reboots and even across different computers. + +If you find that the opposite is happening — that every time you plug +your interface in, the settings get reverted, try disabling the +`alsa-state` and `alsa-restore` systemd services: + +```sh +sudo systemctl mask alsa-state +sudo systemctl mask alsa-restore +``` + +These services save the state of ALSA controls on system shutdown to +`/var/lib/alsa/asound.state` and then restore it each time the device +is plugged in. It can be rather annoying, wondering why your device is +unusable every time you plug it in, only to find that it’s because you +once shut down your computer with the Clock Source set to “ADAT” or +some such thing. + ## Help?! Have you read the User Guide for your interface? It’s available From 6677e5c87dc6505b609396250d95b5fcd15a77be Mon Sep 17 00:00:00 2001 From: Pro-pra Date: Thu, 6 Mar 2025 13:43:33 +0900 Subject: [PATCH 04/18] Use template spec with macros --- alsa-scarlett-gui.spec.template | 40 ++++++++++++++------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/alsa-scarlett-gui.spec.template b/alsa-scarlett-gui.spec.template index 3ca92d5..3d3300c 100644 --- a/alsa-scarlett-gui.spec.template +++ b/alsa-scarlett-gui.spec.template @@ -1,35 +1,29 @@ -Summary: ALSA Scarlett Control Panel -Name: alsa-scarlett-gui -Version: VERSION -Release: 1%{?dist} -License: GPLv3+ LGPLv3+ -Url: https://github.com/geoffreybennett/alsa-scarlett-gui -Source: %{name}-%{version}.tar.gz +Summary: ALSA Scarlett Control Panel +Name: alsa-scarlett-gui +Version: VERSION +Release: 1%{?dist} +License: GPLv3+ LGPLv3+ +Url: https://github.com/geoffreybennett/alsa-scarlett-gui +Source0: https://github.com/geoffreybennett/alsa-scarlett-gui/archive/refs/tags/%{version}.tar.gz?/%{name}-%{version}.tar.gz +BuildRequires: pkgconfig(alsa) +BuildRequires: pkgconfig(gtk4) +BuildRequires: pkgconfig(openssl) %description - alsa-scarlett-gui is a Gtk4 GUI for the ALSA controls presented by the Linux kernel Focusrite USB drivers. %prep -%setup +%setup -q -n %{name}-%{version}/src %build -make -C src %{?_smp_mflags} VERSION=%{version} PREFIX=/usr +%make_build VERSION=%{version} PREFIX=%{_prefix} %install -%make_install -C src PREFIX=/usr -DOCDIR=%{buildroot}/usr/share/doc/%{name}-%{version} -mkdir -p $DOCDIR/img -mkdir $DOCDIR/demo -mkdir $DOCDIR/docs -cp *.md $DOCDIR -cp img/* $DOCDIR/img -cp demo/* $DOCDIR/demo -cp docs/* $DOCDIR/docs +%make_install PREFIX=%{_prefix} %files -%doc /usr/share/doc/%{name}-%{version} -/usr/bin/alsa-scarlett-gui -/usr/share/applications/vu.b4.alsa-scarlett-gui.desktop -/usr/share/icons/hicolor/256x256/apps/vu.b4.alsa-scarlett-gui.png +%doc ../img ../demo ../docs ../*.md +%{_bindir}/alsa-scarlett-gui +%{_datadir}/applications/vu.b4.alsa-scarlett-gui.desktop +%{_iconsdir}/hicolor/256x256/apps/vu.b4.alsa-scarlett-gui.png From a34df84dfa354c29eff67d92ee452b9e5b2e7df9 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 15 Mar 2025 10:16:13 +1030 Subject: [PATCH 05/18] Improve "settings keep resetting" FAQ entry --- FAQ.md | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/FAQ.md b/FAQ.md index 40f9758..1e7f955 100644 --- a/FAQ.md +++ b/FAQ.md @@ -94,23 +94,41 @@ to the user manual of your interface for more information. The settings in the ALSA Scarlett Control Panel are automatically saved in the interface itself (all series except 1st Gen), so they -“should” persist across reboots and even across different computers. +should persist across reboots, power cycles, USB disconnect/reconnect, +and even across different computers. This includes all routing, +mixing, and other control panel settings. -If you find that the opposite is happening — that every time you plug -your interface in, the settings get reverted, try disabling the -`alsa-state` and `alsa-restore` systemd services: +If you find that your settings are reverting whenever you plug your +interface in or power it back on, the most likely cause is the +`alsa-state` and `alsa-restore` systemd services. These services save +the state of ALSA controls on system shutdown to +`/var/lib/alsa/asound.state` and then restore it each time the device +is plugged in, potentially overwriting your interface’s stored +settings. + +It can be rather annoying, wondering why your device is unusable or +needs to be reconfigured every time you plug it in or turn it on. + +To fix this issue, disable these services: ```sh sudo systemctl mask alsa-state sudo systemctl mask alsa-restore ``` -These services save the state of ALSA controls on system shutdown to -`/var/lib/alsa/asound.state` and then restore it each time the device -is plugged in. It can be rather annoying, wondering why your device is -unusable every time you plug it in, only to find that it’s because you -once shut down your computer with the Clock Source set to “ADAT” or -some such thing. +You can verify if this is the cause of your issues by: + +1. Change some setting that is indicated on the device (the “Inst” + setting is a good). +2. Disconnect USB and notice the state of the setting on the device + has not changed. +3. Power cycle the device and notice the state of the setting on the + device has not changed. +4. Reconnect USB and notice the state of the setting on the device has + changed. + +If the setting on the device changes at step 4, then the `alsa-state` +and `alsa-restore` services are the likely cause of your issues. ## Help?! From b6117a501f87dd06df8d61432b106f1a0645afe5 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sun, 16 Mar 2025 18:08:38 +1030 Subject: [PATCH 06/18] Replace 1st Gen Startup Controls info with Startup Configuration The Startup Controls information wasn't very useful, and the Startup Configuration information is actually important. --- docs/iface-1st-gen.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/iface-1st-gen.md b/docs/iface-1st-gen.md index dfc7343..8973c55 100644 --- a/docs/iface-1st-gen.md +++ b/docs/iface-1st-gen.md @@ -31,7 +31,11 @@ ALSA driver implementation that you should be aware of: 3. **Level Meters**: The driver does not support reading the level meters from the hardware. -4. **Startup Controls**: The driver has no startup controls. +4. **Startup Configuration**: The driver is not able to save the + current configuration to the non-volatile memory of the device, so + you’ll need to reapply the desired configuration each time you + restart it (or write your preferred configuration using MixControl + on Windows or Mac). ### Recommended Workaround From 0b5b47ae666689f85dbc22a3ff1d2a53298f23e5 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sun, 16 Mar 2025 18:14:23 +1030 Subject: [PATCH 07/18] Disable the startup menu option for 1st Gen devices --- src/window-iface.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window-iface.c b/src/window-iface.c index 100752c..04a6cd5 100644 --- a/src/window-iface.c +++ b/src/window-iface.c @@ -54,6 +54,7 @@ void create_card_window(struct alsa_card *card) { // Scarlett Gen 1 } else if (get_elem_by_prefix(card->elems, "Matrix")) { card->window_main_contents = create_iface_mixer_main(card); + has_startup = false; // Scarlett Gen 2, Gen 3 4i4+, Gen 4, Clarett, or Vocaster } else if (get_elem_by_prefix(card->elems, "Mixer")) { From c88f7796f4d1c86f25c4ee0245d633b2ccde5a14 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Fri, 21 Feb 2025 18:28:32 +1030 Subject: [PATCH 08/18] Move card init from alsa_scan_cards() to new card_init() function --- src/alsa.c | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/alsa.c b/src/alsa.c index 9d3a3b1..3022f92 100644 --- a/src/alsa.c +++ b/src/alsa.c @@ -1067,6 +1067,32 @@ static void alsa_get_serial_number(struct alsa_card *card) { card->serial = strdup(serial); } +static void card_init(struct alsa_card *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); + + 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); + alsa_add_card_callback(card); +} + static void alsa_scan_cards(void) { snd_ctl_card_info_t *info; snd_ctl_t *ctl; @@ -1110,30 +1136,7 @@ static void alsa_scan_cards(void) { card->name = strdup(snd_ctl_card_info_get_name(info)); card->handle = ctl; - 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); - - 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); - alsa_add_card_callback(card); + card_init(card); continue; From 6f0ab1890d8778616653d09447ba55709cd07089 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 22 Feb 2025 23:43:26 +1030 Subject: [PATCH 09/18] Add driver type detection --- src/alsa.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/alsa.h | 14 ++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/alsa.c b/src/alsa.c index 3022f92..becfd3b 100644 --- a/src/alsa.c +++ b/src/alsa.c @@ -5,10 +5,15 @@ #include #include "alsa.h" +#include "scarlett2.h" #include "scarlett2-firmware.h" +#include "scarlett2-ioctls.h" #include "stringhelper.h" #include "window-iface.h" +#define MAJOR_HWDEP_VERSION_SCARLETT2 1 +#define MAJOR_HWDEP_VERSION_FCP 2 + #define MAX_TLV_RANGE_SIZE 1024 // TLV type for channel labels @@ -1067,7 +1072,81 @@ static void alsa_get_serial_number(struct alsa_card *card) { card->serial = strdup(serial); } +// return true if the Firmware Version control exists and is writable +// and locked (i.e. the FCP server is running) +static int check_firmware_version_locked(struct alsa_card *card) { + snd_ctl_elem_id_t *id; + snd_ctl_elem_info_t *info; + + snd_ctl_elem_id_alloca(&id); + snd_ctl_elem_info_alloca(&info); + + // look for the Firmware Version control + snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD); + snd_ctl_elem_id_set_name(id, "Firmware Version"); + snd_ctl_elem_info_set_id(info, id); + + // no Firmware Version control found + int err = snd_ctl_elem_info(card->handle, info); + if (err < 0) + return 0; + + return snd_ctl_elem_info_is_writable(info) && + snd_ctl_elem_info_is_locked(info); +} + +// return the driver type for this card +// DRIVER_TYPE_NONE: no driver +// DRIVER_TYPE_HWDEP: Scarlett2 driver +// DRIVER_TYPE_SOCKET: FCP driver +// DRIVER_TYPE_SOCKET_UNINIT: FCP driver, but not initialised +static int get_driver_type(struct alsa_card *card) { + snd_hwdep_t *hwdep; + + int err = scarlett2_open_card(card->device, &hwdep); + + // no hwdep for this card - driver type none + if (err == -ENOENT) + return DRIVER_TYPE_NONE; + + // if we get EPERM, it's FCP but no server running + if (err == -EPERM) + return DRIVER_TYPE_SOCKET_UNINIT; + + // if we get EBUSY, it's FCP + if (err == -EBUSY) + // fcp-server locks the Firmware Version control when it has + // finished starting up + return check_firmware_version_locked(card) ? + DRIVER_TYPE_SOCKET : DRIVER_TYPE_SOCKET_UNINIT; + + // failed to open hwdep + if (err < 0) + return DRIVER_TYPE_NONE; + + // we can open hwdep, so now check the protocol version + int ver = scarlett2_get_protocol_version(hwdep); + scarlett2_close(hwdep); + + // failed to get protocol version + if (ver < 0) + return DRIVER_TYPE_NONE; + + // hwdep protocol version 1.x.x is Scarlett2 driver + if (SCARLETT2_HWDEP_VERSION_MAJOR(ver) == MAJOR_HWDEP_VERSION_SCARLETT2) + return DRIVER_TYPE_HWDEP; + + // hwdep protocol version 2.x.x is FCP driver (but not initialised, + // because we were able to open the hwdep) + if (SCARLETT2_HWDEP_VERSION_MAJOR(ver) == MAJOR_HWDEP_VERSION_FCP) + return DRIVER_TYPE_SOCKET_UNINIT; + + return DRIVER_TYPE_NONE; +} + 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); diff --git a/src/alsa.h b/src/alsa.h index 9693e39..31fbcfc 100644 --- a/src/alsa.h +++ b/src/alsa.h @@ -41,6 +41,19 @@ enum { HW_TYPE_COUNT }; +// driver types +// NONE is 1st Gen or Scarlett2 before hwdep support was added +// (no erase config or firmware update support) +// HWDEP is the Scarlett2 driver after hwdep support was added +// SOCKET is the FCP driver +enum { + DRIVER_TYPE_NONE, + DRIVER_TYPE_HWDEP, + DRIVER_TYPE_SOCKET, + DRIVER_TYPE_SOCKET_UNINIT, + DRIVER_TYPE_COUNT +}; + // names for the hardware types extern const char *hw_type_names[HW_TYPE_COUNT]; @@ -162,6 +175,7 @@ struct alsa_card { uint32_t pid; char *serial; char *name; + int driver_type; char *fcp_socket; int best_firmware_version; snd_ctl_t *handle; From 97f993db7ba78497f4cb1ca17c8a83d9edc57357 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 8 Mar 2025 04:38:58 +1030 Subject: [PATCH 10/18] 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. --- src/alsa.c | 108 ++++++++++++++++++++++++++++++++-------- src/iface-waiting.c | 117 ++++++++++++++++++++++++++++++++++++++++++++ src/iface-waiting.h | 8 +++ src/window-iface.c | 26 +++++++++- 4 files changed, 237 insertions(+), 22 deletions(-) create mode 100644 src/iface-waiting.c create mode 100644 src/iface-waiting.h 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); From 9a33b92392c386e2eb0f4a33edb6313eeaa1323f Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 8 Mar 2025 04:49:43 +1030 Subject: [PATCH 11/18] Don't attempt to attach unused routing_mixer_in_grid --- src/window-routing.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/window-routing.c b/src/window-routing.c index 34f6785..b4e4a6b 100644 --- a/src/window-routing.c +++ b/src/window-routing.c @@ -279,9 +279,10 @@ static void create_routing_grid(struct alsa_card *card) { routing_grid, card->routing_dsp_out_grid, dsp_col_num, 3, 1, 1 ); } - gtk_grid_attach( - routing_grid, card->routing_mixer_in_grid, mix_col_num, 0, 1, 1 - ); + if (!card->has_fixed_mixer_inputs) + gtk_grid_attach( + routing_grid, card->routing_mixer_in_grid, mix_col_num, 0, 1, 1 + ); gtk_grid_attach( routing_grid, card->routing_mixer_out_grid, mix_col_num, 3, 1, 1 ); From a5676eeb5af91efd433c67cc59bc4d05051b133a Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 8 Mar 2025 04:57:40 +1030 Subject: [PATCH 12/18] Replace hwdep check in window-startup.c with driver_type check Since alsa.c already checks the hwdep version to determine the driver type, window-startup.c doesn't need to do the same. --- src/window-startup.c | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/src/window-startup.c b/src/window-startup.c index 38940c7..acf063e 100644 --- a/src/window-startup.c +++ b/src/window-startup.c @@ -10,8 +10,6 @@ #include "widget-drop-down.h" #include "window-startup.h" -#define REQUIRED_HWDEP_VERSION_MAJOR 1 - static GtkWidget *small_label(const char *text) { GtkWidget *w = gtk_label_new(NULL); @@ -261,38 +259,9 @@ static void add_reset_actions( int *grid_y, int show_reboot_option ) { - // simulated cards don't support hwdep - if (!card->device) + if (card->driver_type != DRIVER_TYPE_HWDEP) 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); - // Add reboot action if there is a control that requires a reboot // to take effect if (show_reboot_option) { From b8420ba31cc836210074d3c732b3959d57008367 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 8 Mar 2025 06:12:20 +1030 Subject: [PATCH 13/18] Add support for rebooting devices using the FCP socket interface --- src/fcp-shared.c | 19 ++++ src/fcp-shared.h | 80 ++++++++++++++++ src/fcp-socket.c | 220 +++++++++++++++++++++++++++++++++++++++++++ src/fcp-socket.h | 27 ++++++ src/window-startup.c | 41 +++++--- 5 files changed, 373 insertions(+), 14 deletions(-) create mode 100644 src/fcp-shared.c create mode 100644 src/fcp-shared.h create mode 100644 src/fcp-socket.c create mode 100644 src/fcp-socket.h diff --git a/src/fcp-shared.c b/src/fcp-shared.c new file mode 100644 index 0000000..09c44f6 --- /dev/null +++ b/src/fcp-shared.c @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +// Error messages +const char *fcp_socket_error_messages[] = { + "Success", + "Invalid magic", + "Invalid command", + "Invalid length", + "Invalid hash", + "Firmware PID does not match USB PID", + "Configuration error (check fcp-server log)", + "FCP communication error", + "Timeout", + "Read error", + "Write error", + "Not running leapfrog firmware", + "Invalid state" +}; diff --git a/src/fcp-shared.h b/src/fcp-shared.h new file mode 100644 index 0000000..1849bd0 --- /dev/null +++ b/src/fcp-shared.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +// Error codes +#define FCP_SOCKET_ERR_INVALID_MAGIC 1 +#define FCP_SOCKET_ERR_INVALID_COMMAND 2 +#define FCP_SOCKET_ERR_INVALID_LENGTH 3 +#define FCP_SOCKET_ERR_INVALID_HASH 4 +#define FCP_SOCKET_ERR_INVALID_USB_ID 5 +#define FCP_SOCKET_ERR_CONFIG 6 +#define FCP_SOCKET_ERR_FCP 7 +#define FCP_SOCKET_ERR_TIMEOUT 8 +#define FCP_SOCKET_ERR_READ 9 +#define FCP_SOCKET_ERR_WRITE 10 +#define FCP_SOCKET_ERR_NOT_LEAPFROG 11 +#define FCP_SOCKET_ERR_INVALID_STATE 12 +#define FCP_SOCKET_ERR_MAX 12 + +// Protocol constants +#define FCP_SOCKET_PROTOCOL_VERSION 1 +#define FCP_SOCKET_MAGIC_REQUEST 0x53 +#define FCP_SOCKET_MAGIC_RESPONSE 0x73 + +// Maximum payload length (2MB) +#define MAX_PAYLOAD_LENGTH 2 * 1024 * 1024 + +// Request types +#define FCP_SOCKET_REQUEST_REBOOT 0x0001 +#define FCP_SOCKET_REQUEST_CONFIG_ERASE 0x0002 +#define FCP_SOCKET_REQUEST_APP_FIRMWARE_ERASE 0x0003 +#define FCP_SOCKET_REQUEST_APP_FIRMWARE_UPDATE 0x0004 +#define FCP_SOCKET_REQUEST_ESP_FIRMWARE_UPDATE 0x0005 + +// Response types +#define FCP_SOCKET_RESPONSE_VERSION 0x00 +#define FCP_SOCKET_RESPONSE_SUCCESS 0x01 +#define FCP_SOCKET_RESPONSE_ERROR 0x02 +#define FCP_SOCKET_RESPONSE_PROGRESS 0x03 + +extern const char *fcp_socket_error_messages[]; + +// Message structures +#pragma pack(push, 1) + +struct fcp_socket_msg_header { + uint8_t magic; + uint8_t msg_type; + uint32_t payload_length; +}; + +struct firmware_payload { + uint32_t size; + uint16_t usb_vid; + uint16_t usb_pid; + uint8_t sha256[32]; + uint8_t md5[16]; + uint8_t data[]; +}; + +struct version_msg { + struct fcp_socket_msg_header header; + uint8_t version; +}; + +struct progress_msg { + struct fcp_socket_msg_header header; + uint8_t percent; +}; + +struct error_msg { + struct fcp_socket_msg_header header; + int16_t error_code; +}; + +#pragma pack(pop) + diff --git a/src/fcp-socket.c b/src/fcp-socket.c new file mode 100644 index 0000000..2de1201 --- /dev/null +++ b/src/fcp-socket.c @@ -0,0 +1,220 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fcp-shared.h" +#include "fcp-socket.h" +#include "error.h" + +// Connect to the FCP socket server for the given card +int fcp_socket_connect(struct alsa_card *card) { + if (!card || !card->fcp_socket) { + fprintf(stderr, "FCP socket path is not available"); + return -1; + } + + int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock_fd < 0) { + fprintf(stderr, "Cannot create socket: %s", strerror(errno)); + return -1; + } + + struct sockaddr_un addr = { + .sun_family = AF_UNIX + }; + strncpy(addr.sun_path, card->fcp_socket, sizeof(addr.sun_path) - 1); + + if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + fprintf(stderr, "Cannot connect to server at %s: %s", + addr.sun_path, strerror(errno)); + close(sock_fd); + return -1; + } + + return sock_fd; +} + +// Send a simple command with no payload to the server +int fcp_socket_send_command(int sock_fd, uint8_t command) { + struct fcp_socket_msg_header header = { + .magic = FCP_SOCKET_MAGIC_REQUEST, + .msg_type = command, + .payload_length = 0 + }; + + if (write(sock_fd, &header, sizeof(header)) != sizeof(header)) { + fprintf(stderr, "Error sending command: %s", strerror(errno)); + return -1; + } + + return 0; +} + +// Handle server responses from a command +int fcp_socket_handle_response(int sock_fd, bool show_progress) { + struct fcp_socket_msg_header header; + ssize_t bytes_read; + + // Read response header + bytes_read = read(sock_fd, &header, sizeof(header)); + if (bytes_read != sizeof(header)) { + if (bytes_read == 0) { + // Server closed the connection + return 0; + } + fprintf(stderr, "Error reading response header: %s", strerror(errno)); + return -1; + } + + // Verify the magic value + if (header.magic != FCP_SOCKET_MAGIC_RESPONSE) { + fprintf(stderr, "Invalid response magic: 0x%02x", header.magic); + return -1; + } + + // Handle different response types + switch (header.msg_type) { + case FCP_SOCKET_RESPONSE_VERSION: { + // Protocol version response + uint8_t version; + bytes_read = read(sock_fd, &version, sizeof(version)); + if (bytes_read != sizeof(version)) { + fprintf(stderr, "Error reading version: %s", strerror(errno)); + return -1; + } + // Protocol version mismatch? + if (version != FCP_SOCKET_PROTOCOL_VERSION) { + fprintf(stderr, "Protocol version mismatch: expected %d, got %d", + FCP_SOCKET_PROTOCOL_VERSION, version); + return -1; + } + break; + } + + case FCP_SOCKET_RESPONSE_SUCCESS: + // Command completed successfully + return 0; + + case FCP_SOCKET_RESPONSE_ERROR: { + // Error response + int16_t error_code; + bytes_read = read(sock_fd, &error_code, sizeof(error_code)); + if (bytes_read != sizeof(error_code)) { + fprintf(stderr, "Error reading error code: %s", strerror(errno)); + return -1; + } + + if (error_code > 0 && error_code <= FCP_SOCKET_ERR_MAX) { + fprintf(stderr, "Server error: %s", fcp_socket_error_messages[error_code]); + } else { + fprintf(stderr, "Unknown server error code: %d", error_code); + } + return -1; + } + + case FCP_SOCKET_RESPONSE_PROGRESS: { + // Progress update + if (show_progress) { + uint8_t percent; + bytes_read = read(sock_fd, &percent, sizeof(percent)); + if (bytes_read != sizeof(percent)) { + fprintf(stderr, "Error reading progress: %s", strerror(errno)); + return -1; + } + fprintf(stderr, "\rProgress: %d%%", percent); + if (percent == 100) + fprintf(stderr, "\n"); + } else { + // Skip the progress byte + uint8_t dummy; + if (read(sock_fd, &dummy, sizeof(dummy)) < 0) { + fprintf(stderr, "Error reading progress: %s", strerror(errno)); + return -1; + } + } + + // Continue reading responses + return fcp_socket_handle_response(sock_fd, show_progress); + } + + default: + fprintf(stderr, "Unknown response type: 0x%02x", header.msg_type); + return -1; + } + + return 0; +} + +// Wait for server to disconnect (used after reboot command) +int fcp_socket_wait_for_disconnect(int sock_fd) { + fd_set rfds; + struct timeval tv, start_time, now; + char buf[1]; + const int TIMEOUT_SECS = 2; + + gettimeofday(&start_time, NULL); + + while (1) { + FD_ZERO(&rfds); + FD_SET(sock_fd, &rfds); + + gettimeofday(&now, NULL); + int elapsed = now.tv_sec - start_time.tv_sec; + if (elapsed >= TIMEOUT_SECS) { + fprintf(stderr, "Timeout waiting for server disconnect\n"); + return -1; + } + + tv.tv_sec = TIMEOUT_SECS - elapsed; + tv.tv_usec = 0; + + int ret = select(sock_fd + 1, &rfds, NULL, NULL, &tv); + if (ret < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "Select error: %s\n", strerror(errno)); + return -1; + } + + if (ret > 0) { + // Try to read one byte + ssize_t n = read(sock_fd, buf, 1); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + fprintf(stderr, "Read error: %s\n", strerror(errno)); + return -1; + } + if (n == 0) { + // EOF received - server has disconnected + return 0; + } + // Ignore any data received, just keep waiting for EOF + } + } +} + +// Reboot a device using the FCP socket interface +int fcp_socket_reboot_device(struct alsa_card *card) { + int sock_fd, ret = -1; + + sock_fd = fcp_socket_connect(card); + if (sock_fd < 0) + return -1; + + // Send reboot command and wait for server to disconnect + if (fcp_socket_send_command(sock_fd, FCP_SOCKET_REQUEST_REBOOT) == 0) + ret = fcp_socket_wait_for_disconnect(sock_fd); + + close(sock_fd); + return ret; +} diff --git a/src/fcp-socket.h b/src/fcp-socket.h new file mode 100644 index 0000000..2e63dc3 --- /dev/null +++ b/src/fcp-socket.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "alsa.h" + +// Connect to the FCP socket server for the given card +// Returns socket file descriptor on success, -1 on error +int fcp_socket_connect(struct alsa_card *card); + +// Send a simple command with no payload to the server +// Returns 0 on success, -1 on error +int fcp_socket_send_command(int sock_fd, uint8_t command); + +// Handle server responses from a command +// Returns 0 on success, -1 on error +int fcp_socket_handle_response(int sock_fd, bool show_progress); + +// Wait for server to disconnect (used after reboot command) +// Returns 0 if disconnected, -1 on timeout or error +int fcp_socket_wait_for_disconnect(int sock_fd); + +// Reboot a device using the FCP socket interface +// Returns 0 on success, -1 on error +int fcp_socket_reboot_device(struct alsa_card *card); \ No newline at end of file diff --git a/src/window-startup.c b/src/window-startup.c index acf063e..968b3f5 100644 --- a/src/window-startup.c +++ b/src/window-startup.c @@ -3,6 +3,7 @@ #include "device-reset-config.h" #include "device-update-firmware.h" +#include "fcp-socket.h" #include "gtkhelper.h" #include "scarlett2.h" #include "scarlett2-ioctls.h" @@ -236,21 +237,32 @@ static void add_reset_action( } static void reboot_device(GtkWidget *button, struct alsa_card *card) { - snd_hwdep_t *hwdep; + int err = 0; - int err = scarlett2_open_card(card->device, &hwdep); - if (err < 0) { - fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err)); - return; + // HWDEP (Scarlett2) driver type + if (card->driver_type == DRIVER_TYPE_HWDEP) { + snd_hwdep_t *hwdep; + + err = scarlett2_open_card(card->device, &hwdep); + if (err < 0) { + fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err)); + return; + } + + err = scarlett2_reboot(hwdep); + if (err < 0) { + fprintf(stderr, "unable to reboot device: %s\n", snd_strerror(err)); + return; + } + + scarlett2_close(hwdep); + + // Socket (FCP) driver type + } else if (card->driver_type == DRIVER_TYPE_SOCKET) { + err = fcp_socket_reboot_device(card); + if (err < 0) + fprintf(stderr, "unable to reboot device via socket\n"); } - - err = scarlett2_reboot(hwdep); - if (err < 0) { - fprintf(stderr, "unable to reboot device: %s\n", snd_strerror(err)); - return; - } - - scarlett2_close(hwdep); } static void add_reset_actions( @@ -259,7 +271,8 @@ static void add_reset_actions( int *grid_y, int show_reboot_option ) { - if (card->driver_type != DRIVER_TYPE_HWDEP) + if (card->driver_type != DRIVER_TYPE_HWDEP && + card->driver_type != DRIVER_TYPE_SOCKET) return; // Add reboot action if there is a control that requires a reboot From 1f7bafbfc3f841bed9acabd94482b3be45c24e50 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 8 Mar 2025 06:46:09 +1030 Subject: [PATCH 14/18] Update window-hardware with big 4th Gen and Vocaster models --- src/window-hardware.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/window-hardware.c b/src/window-hardware.c index 52bfd3f..303e872 100644 --- a/src/window-hardware.c +++ b/src/window-hardware.c @@ -30,13 +30,9 @@ struct hw_info gen_2_info[] = { { } }; -struct hw_info gen_3_small_info[] = { +struct hw_info gen_3_info[] = { { "Scarlett Solo 3rd Gen" }, { "Scarlett 2i2 3rd Gen" }, - { } -}; - -struct hw_info gen_3_big_info[] = { { "Scarlett 4i4 3rd Gen" }, { "Scarlett 8i6 3rd Gen" }, { "Scarlett 18i8 3rd Gen" }, @@ -48,6 +44,9 @@ struct hw_info gen_4_info[] = { { "Scarlett Solo 4th Gen" }, { "Scarlett 2i2 4th Gen" }, { "Scarlett 4i4 4th Gen" }, + { "Scarlett 16i16 4th Gen" }, + { "Scarlett 18i16 4th Gen" }, + { "Scarlett 18i20 4th Gen" }, { } }; @@ -65,6 +64,12 @@ struct hw_info clarett_plus_info[] = { { } }; +struct hw_info vocaster_info[] = { + { "Vocaster One" }, + { "Vocaster Two" }, + { } +}; + struct hw_cat hw_cat[] = { { "1st Gen", gen_1_info @@ -72,11 +77,8 @@ struct hw_cat hw_cat[] = { { "2nd Gen", gen_2_info }, - { "Small 3rd Gen", - gen_3_small_info - }, - { "Big 3rd Gen", - gen_3_big_info + { "3rd Gen", + gen_3_info }, { "4th Gen", gen_4_info @@ -87,6 +89,9 @@ struct hw_cat hw_cat[] = { { "Clarett+", clarett_plus_info }, + { "Vocaster", + vocaster_info + }, { } }; From adeea461fd66423a64fdde19a414cafc15dbdaae Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 15 Mar 2025 10:06:23 +1030 Subject: [PATCH 15/18] Change alsa_get_elem_int_values() to return longs rather than ints --- src/alsa.c | 4 ++-- src/alsa.h | 2 +- src/window-levels.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/alsa.c b/src/alsa.c index 1adc4bd..0b719a9 100644 --- a/src/alsa.c +++ b/src/alsa.c @@ -220,8 +220,8 @@ long alsa_get_elem_value(struct alsa_elem *elem) { // for elements with multiple int values, return all the values // the int array returned needs to be freed by the caller -int *alsa_get_elem_int_values(struct alsa_elem *elem) { - int *values = calloc(elem->count, sizeof(int)); +long *alsa_get_elem_int_values(struct alsa_elem *elem) { + long *values = calloc(elem->count, sizeof(long)); if (elem->card->num == SIMULATED_CARD_NUM) { for (int i = 0; i < elem->count; i++) diff --git a/src/alsa.h b/src/alsa.h index 31fbcfc..ab099a5 100644 --- a/src/alsa.h +++ b/src/alsa.h @@ -240,7 +240,7 @@ void alsa_elem_add_callback( int alsa_get_elem_type(struct alsa_elem *elem); char *alsa_get_elem_name(struct alsa_elem *elem); long alsa_get_elem_value(struct alsa_elem *elem); -int *alsa_get_elem_int_values(struct alsa_elem *elem); +long *alsa_get_elem_int_values(struct alsa_elem *elem); void alsa_set_elem_value(struct alsa_elem *elem, long value); int alsa_get_elem_writable(struct alsa_elem *elem); int alsa_get_elem_volatile(struct alsa_elem *elem); diff --git a/src/window-levels.c b/src/window-levels.c index c75abd2..01af25e 100644 --- a/src/window-levels.c +++ b/src/window-levels.c @@ -38,7 +38,7 @@ static int update_levels_controls(void *user_data) { struct alsa_elem *level_meter_elem = data->level_meter_elem; - int *values = alsa_get_elem_int_values(level_meter_elem); + long *values = alsa_get_elem_int_values(level_meter_elem); gtk_dial_peak_tick(); From 87ee0ed66b75925b554545e40b63be40392334a4 Mon Sep 17 00:00:00 2001 From: runiq Date: Sat, 15 Mar 2025 23:52:38 +0100 Subject: [PATCH 16/18] Add alsactl utility Allows saving and loading device state with the Flatpak version. The Gnome 47 SDK uses alsa-lib 1.2.12 [1] via the Freedesktop.org SDK [2], so we use that here as well. [1] https://gitlab.gnome.org/search?search=alsa&nav_source=navbar&project_id=456&group_id=8&search_code=true&repository_ref=47.4 [2] https://gitlab.com/freedesktop-sdk/freedesktop-sdk/-/blob/release/24.08/elements/components/alsa-lib.bst?ref_type=heads --- vu.b4.alsa-scarlett-gui.yml | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/vu.b4.alsa-scarlett-gui.yml b/vu.b4.alsa-scarlett-gui.yml index 786523c..d7ff40c 100644 --- a/vu.b4.alsa-scarlett-gui.yml +++ b/vu.b4.alsa-scarlett-gui.yml @@ -17,6 +17,44 @@ finish-args: # Point to the firmware directory - --env=SCARLETT2_FIRMWARE_DIR=/app/lib/firmware/scarlett2 modules: + - name: alsa-utils + sources: + - type: archive + url: https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.12.tar.bz2 + sha256: 4868cd908627279da5a634f468701625be8cc251d84262c7e5b6a218391ad0d2 + dest: .deps/alsa-lib + - type: archive + url: https://www.alsa-project.org/files/pub/utils/alsa-utils-1.2.12.tar.bz2 + sha256: 98bc6677d0c0074006679051822324a0ab0879aea558a8f68b511780d30cd924 + buildsystem: autotools + config-opts: + # We are only interested in alsactl + - --bindir=/app/null + - --with-udev-rules-dir=/app/null + - --with-systemdsystemunitdir=/app/null + # https://github.com/alsa-project/alsa-utils/issues/33 + - --enable-alsa-topology + - --disable-alsaconf + - --disable-alsatest + - --disable-alsabat-backend-tiny + - --disable-alsamixer + - --disable-alsaloop + - --disable-nhlt + - --disable-xmlto + - --disable-rst2man + - --with-alsa-inc-prefix=.deps/alsa-lib/include + post-install: + - install -Dm755 /app/sbin/alsactl /app/bin/alsactl + cleanup: + - /lib/debug + - /lib/alsa-topology + - /null + - /sbin + - /share/alsa + - /share/locale + - /share/man + - /share/runtime + - /share/sounds - name: alsa-scarlett-gui buildsystem: simple build-commands: From e4dc8054221aa8e1f9e64137a5dc3356df3225fa Mon Sep 17 00:00:00 2001 From: runiq Date: Sun, 16 Mar 2025 00:05:08 +0100 Subject: [PATCH 17/18] Remove superfluous files from Flatpak --- vu.b4.alsa-scarlett-gui.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vu.b4.alsa-scarlett-gui.yml b/vu.b4.alsa-scarlett-gui.yml index d7ff40c..e78e2f1 100644 --- a/vu.b4.alsa-scarlett-gui.yml +++ b/vu.b4.alsa-scarlett-gui.yml @@ -66,7 +66,9 @@ modules: # - type: git # url: https://github.com/geoffreybennett/alsa-scarlett-gui.git # tag: "0.2" - + cleanup: + - /lib/debug + - /lib/source - name: scarlett2-firmware buildsystem: simple build-commands: From e6fbb4f146103ae70ac592ea73b46cf182fd655c Mon Sep 17 00:00:00 2001 From: runiq Date: Sun, 16 Mar 2025 00:05:57 +0100 Subject: [PATCH 18/18] More Flatpak manifest cleanup The flow should be clearer if every module is structured this way: 1. name 2. sources 3. buildsystem 4. config-opts 5. build-commands 6. post-install 7. cleanup --- vu.b4.alsa-scarlett-gui.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vu.b4.alsa-scarlett-gui.yml b/vu.b4.alsa-scarlett-gui.yml index e78e2f1..4b9b5de 100644 --- a/vu.b4.alsa-scarlett-gui.yml +++ b/vu.b4.alsa-scarlett-gui.yml @@ -56,9 +56,6 @@ modules: - /share/runtime - /share/sounds - name: alsa-scarlett-gui - buildsystem: simple - build-commands: - - make -j8 install PREFIX=$FLATPAK_DEST sources: - type: dir path: ./src @@ -66,15 +63,18 @@ modules: # - type: git # url: https://github.com/geoffreybennett/alsa-scarlett-gui.git # tag: "0.2" + buildsystem: simple + build-commands: + - make -j8 install PREFIX=$FLATPAK_DEST cleanup: - /lib/debug - /lib/source - name: scarlett2-firmware - buildsystem: simple - build-commands: - - mkdir -p $FLATPAK_DEST/lib/firmware/scarlett2 - - cp -a LICENSE.Focusrite firmware/* $FLATPAK_DEST/lib/firmware/scarlett2 sources: - type: archive url: https://github.com/geoffreybennett/scarlett2-firmware/archive/refs/tags/2128b.tar.gz sha256: 4a17fdbe2110855c2f7f6cfc5ea1894943a6e58770f3dff5ef283961f8ae2a03 + buildsystem: simple + build-commands: + - mkdir -p $FLATPAK_DEST/lib/firmware/scarlett2 + - cp -a LICENSE.Focusrite firmware/* $FLATPAK_DEST/lib/firmware/scarlett2