The officially official Devuan Forum!

You are not logged in.

#1 Re: Packaging for Devuan » [SOLVED] Where is Veracrypt? » 2025-11-01 01:20:58

Are you trying to invoke tcplay as a regular user, but don't have /sbin/ in your path? I just ran apt-file list tcplay, and it says the package installs the executable to /usr/sbin/tcplay.

#2 Re: Desktop and Multimedia » A tale of two laptops » 2025-10-27 04:16:00

Are you talking about too small text in the grub menu specifically, or just small text in general? Changing the console font size is pretty easy; you can do this with dpkg-reconfigure console-setup. I'm not sure it affects the grub menu, though. You actually got me curious about how to change the grub menu text, because mine is really small as well. A quick search turned up this askubuntu post, which goes into a lot of detail about how to use the GRUB_FONT setting in /etc/default/grub (along with grub-mkfont) to get bigger text in the menu. I may give it a try later if I get some free time.

#3 Re: Other Issues » [SOLVED] Growing file bloat in ~/.dbus/session-bus and /root/.dbus/session-bus » 2025-10-18 12:56:28

I don't even have that folder on my Daedalus installs, just ~/.dbus/session-bus/

Oops, that's it, I just misspelled it.

If you switch to console without stopping the desktop, log in and then start a second desktop on :1, you'll get another file in ~/.dbus/session-bus with the same number as /var/lib/dbus/machine-id except with -1 at the end

That makes sense. I have a vague memory of doing something like that once.

on my system, the machine-id is defined in /var/lib/dbus/machine-id.

I have that file too, which would explain why I'm not seeing the accumulation of randomly named files.

#4 Re: Installation » actuallization from Ceres impossible » 2025-10-05 03:29:14

The content of /etc/apt is changed: the item sources.list appears in "grey" I would say in inactive color.

The repository configuration has been moved to the file /etc/apt/sources.list.d/devuan.sources, which uses a new format. It contains basically the same information as the old sources.list, it's just broken out into separate lines with descriptive headings.

#5 Re: Other Issues » [SOLVED] Growing file bloat in ~/.dbus/session-bus and /root/.dbus/session-bus » 2025-09-24 06:11:08

This is odd: although my Excalibur system is showing the same behavior that everyone everyone describes here, my Ceres system completely lacks the file at /etc/default/dbus. Nonetheless, dbus is definitely installed and running. I'm also missing /etc/machine-id.

Another puzzle: there are exactly two files in ~/.dbus/session-id/. The files have almost the exact same names, except that one ends in -0, and the other ends in -1. The -0 file gets overwritten on each reboot, but with the exact same name. The -1 file was created back on 8-24, and hasn't been touched since.

#6 Re: Installation » Cant do upgrade Dadedalus to Ceres » 2025-09-24 05:06:03

I don't recall S3 being problematic (unless you wanted 16-bit color, and had more than 16MB RAM, and were still running a VLB card from the '90s... but I digress) but 3DFX sure was tongue

I had a Savage3D, which was known for having hardware issues. The card seemed to have a tendency to overheat, even on Windows. It was also pretty new at the time, so Linux/XFree86 driver support may not have been entirely solid yet.

#7 Re: Installation » Cant do upgrade Dadedalus to Ceres » 2025-09-21 20:23:20

Did you have problems with sound and sound quality in 2000? Or it worked out of the box?

Wow, that's a great question! I actually don't even remember what sound card I was using at the time. I think I had basic functionality working. It's hard to remember anything about sound because I was so focused on video issues. The main thing I remember was trouble getting X11 to start on my S3 video card. My first attempt at Linux was with RedHat, but I just could not get it working. I ended up on Debian because I could actually get the desktop to appear, but only with twm at first.

#8 Re: Hardware & System Configuration » [SOLVED] Disabling firefox auto=update » 2025-09-21 20:06:53

Unless something has changed very recently, The ESR version of Firefox included with Debian/Devuan never "auto-updates" the way mainline Firefox does. It used to display a message that said updates were being "managed by your organization," but this confused people, so they removed it.

Putting a hold on the package with apt-mark means it will never be updated, not even manually. For an application exposed to the Internet such as a web browser, this can be very dangerous. You won't get patches even for serious vulnerabilities.

#9 Re: Installation » Cant do upgrade Dadedalus to Ceres » 2025-09-21 18:51:13

apt just invokes apt-get or apt-cache on the back-end. There's no difference in the underlying functionality. When I first started using Debian in 2000, apt didn't even exist yet, you had to run apt-get. apt was added (around 2014, I think) to provide a more convenient high-level interface for interactive use.

The main advantage of using apt-get or apt-cache directly is that you can access a wider variety of options, such as --no-recommends or --names-only. apt is fine to use when you don't need to do anything complicated. It formats output in color and pipes to a pager.

#10 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-09-19 03:22:06

Well of course there is, i'm an idiot, lol, didn't scroll down far enough.

I probably added that screenshot after your post! smile It wasn't there when I first uploaded.

Sorry I haven't checked back here in a while, I've been super busy. As for EFL, I considered it, but I was already familiar with the X Toolkit from working with the Athena widgets. I also kind of just wanted to see if it was still possible to use Motif for anything. Believe it or not, I also wrote an ALSA mixer for GNUStep! I was exploring a lot of obscure stuff.

greenjeans, I added some stuff to your code so it would update the UI on external mixer changes. I didn't do it for the EQ controls, as I'm not familiar with the ALSA EQ plugin. But I would guess the principle is probably similar. I called out the changes with comments. There are a couple of added functions, and an added section in main(). I think that's it.

#include <gtk/gtk.h>
#include <alsa/asoundlib.h>
#include <stdio.h>
#include <string.h>
#include <glib/gstdio.h>

typedef struct {
    snd_mixer_t *mixer;
    snd_mixer_elem_t *elem;
    GtkWidget *scale;
    const char *channel_name;
} MixerChannel;

typedef struct {
    snd_mixer_t *mixer;
    MixerChannel *channels;
    int num_channels;
} MixerData;

typedef struct {
    snd_ctl_t *ctl;
    snd_ctl_elem_value_t *val;
    GtkWidget *scale;
    const char *band_name;
} EQBand;

typedef struct {
    snd_ctl_t *ctl;
    EQBand *bands;
    int num_bands;
} EQData;

/* ADDED SECTION: mixer event handling callbacks. */
static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask) {
    if (mask == SND_CTL_EVENT_MASK_REMOVE)
        return 0;
    long current, min, max;
    snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &current);
    snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
    double value = (double)current / (double)(max - min);
    gtk_range_set_value(GTK_RANGE(snd_mixer_elem_get_callback_private(elem)),
                        value);
    return 0;
}
static gboolean fd_dispatch(G_GNUC_UNUSED GSource *source,
                               G_GNUC_UNUSED GSourceFunc callback,
                               gpointer user_data) {
    snd_mixer_handle_events(user_data);
    return G_SOURCE_CONTINUE;
}
/* END OF ADDED SECTION */

// Callback to save preset to ~/.local/share/mxeq/presets.csv
static void save_preset(GtkWidget *button, gpointer user_data) {
    EQData *eq_data = (EQData *)user_data;
    GtkWidget *entry = g_object_get_data(G_OBJECT(button), "preset_entry");
    GtkWidget *combo = g_object_get_data(G_OBJECT(button), "preset_combo");
    GtkWidget *window = g_object_get_data(G_OBJECT(button), "window");
    const char *preset_name = gtk_entry_get_text(GTK_ENTRY(entry));

    if (!preset_name || strlen(preset_name) == 0) {
        GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                                  GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                                  "Please enter a preset name.");
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        return;
    }

    // Check for duplicate preset name
    const char *home = g_get_home_dir();
    char *preset_path = g_build_filename(home, ".local", "share", "mxeq", "presets.csv", NULL);
    FILE *fp_check = fopen(preset_path, "r");
    if (fp_check) {
        char line[256];
        while (fgets(line, sizeof(line), fp_check)) {
            char *name = strtok(line, ":");
            if (name && strcmp(name, preset_name) == 0) {
                GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                                          GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                                          "Preset name already exists. Choose a different name.");
                gtk_dialog_run(GTK_DIALOG(dialog));
                gtk_widget_destroy(dialog);
                fclose(fp_check);
                g_free(preset_path);
                return;
            }
        }
        fclose(fp_check);
    }

    // Create directory if it doesn't exist
    char *dir_path = g_build_filename(home, ".local", "share", "mxeq", NULL);
    g_mkdir_with_parents(dir_path, 0755);
    g_free(dir_path);

    // Open file in append mode
    FILE *fp = fopen(preset_path, "a");
    if (!fp) {
        GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                                  GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                                  "Failed to save preset: Could not open file.");
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        g_free(preset_path);
        return;
    }

    // Collect EQ values
    GString *values = g_string_new("");
    for (int i = 0; i < eq_data->num_bands; i++) {
        gdouble value = gtk_range_get_value(GTK_RANGE(eq_data->bands[i].scale));
        g_string_append_printf(values, "%.2f", value);
        if (i < eq_data->num_bands - 1) {
            g_string_append(values, ",");
        }
    }

    // Write preset to file
    fprintf(fp, "%s:%s\n", preset_name, values->str);
    fclose(fp);
    g_string_free(values, TRUE);

    // Add to combo box and set as active
    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), preset_name);
    gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo), preset_name);

    // Show confirmation dialog
    GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                              GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
                                              "Preset Saved!");
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);

    // Clear entry
    gtk_entry_set_text(GTK_ENTRY(entry), "");
    g_free(preset_path);
}

// Callback to apply selected preset
static void apply_preset(GtkComboBox *combo, gpointer user_data) {
    EQData *eq_data = (EQData *)user_data;
    char *preset_name = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
    if (!preset_name || g_strcmp0(preset_name, "Choose EQ Pre-set") == 0) {
        g_free(preset_name);
        return;
    }

    // Build preset file path
    const char *home = g_get_home_dir();
    char *preset_path = g_build_filename(home, ".local", "share", "mxeq", "presets.csv", NULL);

    // Open preset file
    FILE *fp = fopen(preset_path, "r");
    if (!fp) {
        fprintf(stderr, "Failed to open presets.csv\n");
        g_free(preset_name);
        g_free(preset_path);
        return;
    }

    // Read file to find matching preset
    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        char *name = strtok(line, ":");
        if (name && strcmp(name, preset_name) == 0) {
            char *values = strtok(NULL, "\n");
            if (values) {
                char *token = strtok(values, ",");
                int i = 0;
                while (token && i < eq_data->num_bands) {
                    gdouble value = g_ascii_strtod(token, NULL);
                    gtk_range_set_value(GTK_RANGE(eq_data->bands[i].scale), value);
                    long alsa_value = (long)(value * 100.0);
                    snd_ctl_elem_value_set_integer(eq_data->bands[i].val, 0, alsa_value);
                    snd_ctl_elem_value_set_integer(eq_data->bands[i].val, 1, alsa_value);
                    snd_ctl_elem_write(eq_data->bands[i].ctl, eq_data->bands[i].val);
                    token = strtok(NULL, ",");
                    i++;
                }
            }
            break;
        }
    }
    fclose(fp);
    g_free(preset_name);
    g_free(preset_path);
}

// Load presets into combo box at startup
static void load_presets(GtkComboBoxText *combo) {
    gtk_combo_box_text_append_text(combo, "Choose EQ Pre-set");
    const char *home = g_get_home_dir();
    char *preset_path = g_build_filename(home, ".local", "share", "mxeq", "presets.csv", NULL);
    FILE *fp = fopen(preset_path, "r");
    if (fp) {
        char line[256];
        while (fgets(line, sizeof(line), fp)) {
            char *name = strtok(line, ":");
            if (name) {
                gtk_combo_box_text_append_text(combo, name);
            }
        }
        fclose(fp);
    }
    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); // Default to "Choose EQ Pre-set"
    g_free(preset_path);
}

static void slider_changed(GtkRange *range, MixerChannel *channel) {
    gdouble value = gtk_range_get_value(range);
    long min, max;
    snd_mixer_selem_get_playback_volume_range(channel->elem, &min, &max);
    long alsa_value = (long)(value * (max - min) + min);
    snd_mixer_selem_set_playback_volume_all(channel->elem, alsa_value);
}

static void eq_slider_changed(GtkRange *range, EQBand *band) {
    gdouble value = gtk_range_get_value(range);
    long alsa_value = (long)(value * 100.0);
    snd_ctl_elem_value_set_integer(band->val, 0, alsa_value); // Left channel
    snd_ctl_elem_value_set_integer(band->val, 1, alsa_value); // Right channel
    snd_ctl_elem_write(band->ctl, band->val);
}

static void init_alsa_mixer(MixerData *data) {
    if (snd_mixer_open(&data->mixer, 0) < 0) {
        fprintf(stderr, "Failed to open ALSA mixer\n");
        return;
    }
    if (snd_mixer_attach(data->mixer, "default") < 0) {
        fprintf(stderr, "Failed to attach mixer to default device\n");
        snd_mixer_close(data->mixer);
        return;
    }
    if (snd_mixer_selem_register(data->mixer, NULL, NULL) < 0) {
        fprintf(stderr, "Failed to register mixer\n");
        snd_mixer_close(data->mixer);
        return;
    }
    if (snd_mixer_load(data->mixer) < 0) {
        fprintf(stderr, "Failed to load mixer\n");
        snd_mixer_close(data->mixer);
        return;
    }

    const char *channel_names[] = {"Master", "Speaker", "PCM", "Headphone", "Mic", "Mic Boost", "Beep"};
    int max_channels = G_N_ELEMENTS(channel_names);
    data->channels = g_new(MixerChannel, max_channels);
    data->num_channels = 0;

    for (int i = 0; i < max_channels; i++) {
        snd_mixer_selem_id_t *sid;
        snd_mixer_selem_id_alloca(&sid);
        snd_mixer_selem_id_set_name(sid, channel_names[i]);
        snd_mixer_elem_t *elem = snd_mixer_find_selem(data->mixer, sid);
        if (elem) {
            data->channels[data->num_channels].elem = elem;
            data->channels[data->num_channels].channel_name = channel_names[i];
            data->num_channels++;
        }
    }
}

static void init_alsa_eq(EQData *data) {
    int err;
    if ((err = snd_ctl_open(&data->ctl, "equal", 0)) < 0) {
        fprintf(stderr, "Failed to open ALSA control 'equal': %s\n", snd_strerror(err));
        return;
    }

    snd_ctl_elem_list_t *list;
    snd_ctl_elem_list_alloca(&list);
    if ((err = snd_ctl_elem_list(data->ctl, list)) < 0) {
        fprintf(stderr, "Failed to get initial control list: %s\n", snd_strerror(err));
        snd_ctl_close(data->ctl);
        return;
    }

    int num_controls = snd_ctl_elem_list_get_count(list);
    if (num_controls <= 0) {
        fprintf(stderr, "No controls found for 'equal' device\n");
        snd_ctl_close(data->ctl);
        return;
    }

    if ((err = snd_ctl_elem_list_alloc_space(list, num_controls)) < 0) {
        fprintf(stderr, "Failed to allocate control list space: %s\n", snd_strerror(err));
        snd_ctl_close(data->ctl);
        return;
    }
    if ((err = snd_ctl_elem_list(data->ctl, list)) < 0) {
        fprintf(stderr, "Failed to list controls: %s\n", snd_strerror(err));
        snd_ctl_close(data->ctl);
        return;
    }

    num_controls = snd_ctl_elem_list_get_used(list);
    data->bands = g_new(EQBand, num_controls);
    data->num_bands = 0;

    for (int i = 0; i < num_controls; i++) {
        snd_ctl_elem_id_t *id;
        snd_ctl_elem_id_alloca(&id);
        snd_ctl_elem_list_get_id(list, i, id);

        const char *name = snd_ctl_elem_id_get_name(id);
        unsigned int numid = snd_ctl_elem_id_get_numid(id);
        snd_ctl_elem_iface_t iface = snd_ctl_elem_id_get_interface(id);

        if (iface != SND_CTL_ELEM_IFACE_MIXER) {
            fprintf(stderr, "Skipping non-mixer control: %s (interface=%d)\n", name, iface);
            continue;
        }

        snd_ctl_elem_value_t *val;
        if ((err = snd_ctl_elem_value_malloc(&val)) < 0) {
            fprintf(stderr, "Failed to allocate value for control '%s': %s\n", name, snd_strerror(err));
            continue;
        }
        snd_ctl_elem_value_set_id(val, id);

        if ((err = snd_ctl_elem_read(data->ctl, val)) < 0) {
            fprintf(stderr, "Failed to read control '%s' (numid %u): %s\n", name, numid, snd_strerror(err));
            snd_ctl_elem_value_free(val);
            continue;
        }

        data->bands[data->num_bands].val = val;
        data->bands[data->num_bands].band_name = g_strdup(name);
        data->bands[data->num_bands].ctl = data->ctl;
        data->num_bands++;
    }

    if (data->num_bands == 0) {
        fprintf(stderr, "No EQ bands found\n");
    }
    snd_ctl_elem_list_free_space(list);
}

static void cleanup_alsa(MixerData *mixer_data, EQData *eq_data) {
    if (mixer_data->mixer) {
        snd_mixer_close(mixer_data->mixer);
        g_free(mixer_data->channels);
    }
    if (eq_data->ctl) {
        for (int i = 0; i < eq_data->num_bands; i++) {
            if (eq_data->bands[i].val) {
                snd_ctl_elem_value_free(eq_data->bands[i].val);
            }
            g_free((char *)eq_data->bands[i].band_name);
        }
        snd_ctl_close(eq_data->ctl);
        g_free(eq_data->bands);
    }
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    // Set the default window icon
    gtk_window_set_default_icon_name("audio-speakers");

    MixerData mixer_data = {0};
    EQData eq_data = {0};
    init_alsa_mixer(&mixer_data);
    init_alsa_eq(&eq_data);

    /* ADDED SECTION: Set up mixer event polling. */
    int fd_ct = snd_mixer_poll_descriptors_count(mixer_data.mixer);
    if(fd_ct > 0) {
        GSource *source = g_source_new(&(GSourceFuncs){.dispatch = fd_dispatch},
                                       sizeof(GSource));
        struct pollfd *pollfds = alloca(fd_ct * sizeof(struct pollfd));
        snd_mixer_poll_descriptors(mixer_data.mixer, pollfds, fd_ct);
        for(int i = 0; i < fd_ct; i++) {
            g_source_add_unix_fd(source, pollfds[i].fd, G_IO_IN);
        }
        g_source_set_callback(source, 0, mixer_data.mixer, NULL);
        g_source_attach(source, g_main_context_default());
    }
    /* END OF ADDED SECTION */

    // Create CSS provider for custom styling
    GtkCssProvider *css_provider = gtk_css_provider_new();
    gtk_css_provider_load_from_data(css_provider,
        "frame {"
        "  border-width: 2px;"
        "  border-style: solid;"
        "  border-color: #333333;"
        "  box-shadow: none;"
        "  border-radius: 4px;"
        "}",
        -1, NULL);
    gtk_style_context_add_provider_for_screen(
        gdk_screen_get_default(),
        GTK_STYLE_PROVIDER(css_provider),
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    g_object_unref(css_provider);

    // Create window
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "AlsaTune");
    gtk_window_set_default_size(GTK_WINDOW(window), 640, 510);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // Main vertical box to hold both mixer and EQ sections
    GtkWidget *main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_container_set_border_width(GTK_CONTAINER(main_box), 5);
    gtk_container_add(GTK_CONTAINER(window), main_box);

    // Mixer frame with border
    GtkWidget *mixer_frame = gtk_frame_new(NULL);
    gtk_box_pack_start(GTK_BOX(main_box), mixer_frame, FALSE, FALSE, 0);

    // Mixer horizontal box
    GtkWidget *mixer_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
    gtk_container_set_border_width(GTK_CONTAINER(mixer_box), 5);
    gtk_container_add(GTK_CONTAINER(mixer_frame), mixer_box);

    // Add sliders for each mixer channel
    for (int i = 0; i < mixer_data.num_channels; i++) {
        GtkWidget *channel_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
        gtk_box_pack_start(GTK_BOX(mixer_box), channel_box, TRUE, TRUE, 5);

        GtkWidget *label = gtk_label_new(mixer_data.channels[i].channel_name);
        gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
        gtk_box_pack_start(GTK_BOX(channel_box), label, FALSE, FALSE, 5);

        mixer_data.channels[i].scale = gtk_scale_new_with_range(GTK_ORIENTATION_VERTICAL, 0, 1, 0.01);
        gtk_range_set_inverted(GTK_RANGE(mixer_data.channels[i].scale), TRUE);
        gtk_scale_set_draw_value(GTK_SCALE(mixer_data.channels[i].scale), TRUE);
        gtk_scale_set_value_pos(GTK_SCALE(mixer_data.channels[i].scale), GTK_POS_BOTTOM);
        gtk_widget_set_size_request(mixer_data.channels[i].scale, -1, 150);
        gtk_box_pack_start(GTK_BOX(channel_box), mixer_data.channels[i].scale, TRUE, TRUE, 0);
        g_signal_connect(mixer_data.channels[i].scale, "value-changed", G_CALLBACK(slider_changed), &mixer_data.channels[i]);

        long min, max, value;
        snd_mixer_selem_get_playback_volume_range(mixer_data.channels[i].elem, &min, &max);
        snd_mixer_selem_get_playback_volume(mixer_data.channels[i].elem, 0, &value);
        gtk_range_set_value(GTK_RANGE(mixer_data.channels[i].scale), (double)(value - min) / (max - min));

        GtkWidget *padding = gtk_label_new("");
        gtk_box_pack_start(GTK_BOX(channel_box), padding, FALSE, FALSE, 5);

        /* ADDED SECTION: Set element callbacks & closures for mixer events. */
        snd_mixer_elem_set_callback(mixer_data.channels[i].elem, elem_callback);
        snd_mixer_elem_set_callback_private(mixer_data.channels[i].elem,
                                            mixer_data.channels[i].scale);
        /* END OF ADDED SECTION */
    }

    // EQ frame with border
    GtkWidget *eq_frame = gtk_frame_new(NULL);
    gtk_box_pack_start(GTK_BOX(main_box), eq_frame, FALSE, FALSE, 0);

    // EQ horizontal box
    GtkWidget *eq_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
    gtk_container_set_border_width(GTK_CONTAINER(eq_box), 2);
    gtk_widget_set_size_request(eq_box, -1, 200);
    gtk_container_add(GTK_CONTAINER(eq_frame), eq_box);

    // Add sliders for each EQ band
    for (int i = 0; i < eq_data.num_bands; i++) {
        if (!eq_data.bands[i].val) {
            fprintf(stderr, "Warning: NULL val for band %d: %s\n", i, eq_data.bands[i].band_name);
            continue;
        }

        GtkWidget *band_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
        gtk_box_pack_start(GTK_BOX(eq_box), band_box, TRUE, TRUE, 5);

        // Use shorter labels (e.g., "31 Hz" or "1 kHz")
        char short_label[16];
        const char *freq_start = eq_data.bands[i].band_name + 4;
        const char *end = strstr(freq_start, " Playback Volume");
        if (end) {
            strncpy(short_label, freq_start, end - freq_start);
            short_label[end - freq_start] = '\0';
        } else {
            strncpy(short_label, freq_start, sizeof(short_label) - 1);
            short_label[sizeof(short_label) - 1] = '\0';
        }
        // Strip existing Hz or kHz
        char *unit = strstr(short_label, " Hz");
        if (!unit) unit = strstr(short_label, " kHz");
        if (unit) *unit = '\0';
        // Append correct unit based on frequency
        if (strcmp(short_label, "1") == 0 || strcmp(short_label, "2") == 0 || strcmp(short_label, "4") == 0 || strcmp(short_label, "8") == 0 || strcmp(short_label, "16") == 0) {
            strcat(short_label, " kHz");
        } else {
            strcat(short_label, " Hz");
        }
        GtkWidget *label = gtk_label_new(short_label);
        gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
        gtk_box_pack_start(GTK_BOX(band_box), label, FALSE, FALSE, 5);

        eq_data.bands[i].scale = gtk_scale_new_with_range(GTK_ORIENTATION_VERTICAL, 0, 1, 0.01);
        gtk_range_set_inverted(GTK_RANGE(eq_data.bands[i].scale), TRUE);
        gtk_scale_set_draw_value(GTK_SCALE(eq_data.bands[i].scale), TRUE);
        gtk_scale_set_value_pos(GTK_SCALE(eq_data.bands[i].scale), GTK_POS_BOTTOM);
        gtk_widget_set_size_request(eq_data.bands[i].scale, -1, 150);
        gtk_box_pack_start(GTK_BOX(band_box), eq_data.bands[i].scale, TRUE, TRUE, 0);
        g_signal_connect(eq_data.bands[i].scale, "value-changed", G_CALLBACK(eq_slider_changed), &eq_data.bands[i]);

        long value = snd_ctl_elem_value_get_integer(eq_data.bands[i].val, 0);
        gtk_range_set_value(GTK_RANGE(eq_data.bands[i].scale), (double)value / 100.0);

        GtkWidget *padding = gtk_label_new("");
        gtk_box_pack_start(GTK_BOX(band_box), padding, FALSE, FALSE, 5);
    }

    // Preset controls: entry, save button, and combo box
    GtkWidget *preset_box = gtk_grid_new();
    gtk_grid_set_column_spacing(GTK_GRID(preset_box), 5);
    gtk_widget_set_halign(preset_box, GTK_ALIGN_CENTER);
    gtk_box_pack_start(GTK_BOX(main_box), preset_box, FALSE, FALSE, 1);

    GtkWidget *preset_entry = gtk_entry_new();
    gtk_entry_set_placeholder_text(GTK_ENTRY(preset_entry), "Enter name for new pre-set");
    gtk_widget_set_size_request(preset_entry, 250, -1);
    gtk_grid_attach(GTK_GRID(preset_box), preset_entry, 1, 0, 1, 1);

    GtkWidget *save_button = gtk_button_new_with_label("Save Pre-set");
    gtk_grid_attach(GTK_GRID(preset_box), save_button, 2, 0, 1, 1);

    GtkWidget *preset_combo = gtk_combo_box_text_new();
    gtk_widget_set_size_request(preset_combo, 250, -1);
    gtk_grid_attach(GTK_GRID(preset_box), preset_combo, 3, 0, 1, 1);

    // Add spacers for equal left/right margins
    GtkWidget *spacer_left = gtk_label_new("");
    gtk_grid_attach(GTK_GRID(preset_box), spacer_left, 0, 0, 1, 1);
    gtk_widget_set_hexpand(spacer_left, TRUE);
    GtkWidget *spacer_right = gtk_label_new("");
    gtk_grid_attach(GTK_GRID(preset_box), spacer_right, 4, 0, 1, 1);
    gtk_widget_set_hexpand(spacer_right, TRUE);

    // Store references for callbacks
    g_object_set_data(G_OBJECT(save_button), "preset_entry", preset_entry);
    g_object_set_data(G_OBJECT(save_button), "preset_combo", preset_combo);
    g_object_set_data(G_OBJECT(save_button), "window", window);

    // Connect signals
    g_signal_connect(save_button, "clicked", G_CALLBACK(save_preset), &eq_data);
    g_signal_connect(preset_combo, "changed", G_CALLBACK(apply_preset), &eq_data);

    // Load existing presets into combo box
    load_presets(GTK_COMBO_BOX_TEXT(preset_combo));

    gtk_widget_show_all(window);
    gtk_main();

    cleanup_alsa(&mixer_data, &eq_data);
    return 0;
}

#11 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-21 23:37:53

^^ Cool! Let me know if it works. wink

It definitely works, but it took me a fair amount of trial and error to get it all hooked up properly. Working with ALSA is definitely fiddly, and the documentation is practically nonexistent. I wrote a fully functional ALSA system mixer last year using the Motif toolkit, for the very same reasons you started working on your mixer app. (It only covers input/outputs, though, I didn't try to implement EQ.) I set it aside at the time because I assumed there wouldn't be much interest from the general public. I'll see about getting it up somewhere that other people can take a look at it.

*EDIT: I pushed the ALSA mixer app I wrote to github: nxmixer. Maybe you will find it helpful. You will need meson to build it. The bottom of the README includes basic building instructions.

I know Motif is a strange choice, but I wanted to see what would be involved with writing a pure C GUI app without touching anything GNOME related. It was an interesting experience!

#12 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-21 07:46:55

If you have both open at the same time, adjusting either one will change the sound, but they will not update each other while both are open. It seems to me that the logic needed to do that would be difficult, involving some kind of constant polling of each other while in operation, additional code would probably have to be written for both programs.

If you register a mixer element and a callback procedure with snd_mixer_elem_set_callback(), ALSA will invoke your callback whenever a control value on that element changes.

#13 Re: Installation » How to Install XLibre on Devuan Daedalus (MATE)? » 2025-07-20 04:12:41

The XLibre project provides a tool named xorg-testing which builds and installs X in a chroot jail. This method allows you to test the distribution on an existing Debian system without overwriting the base X installation. Presumably, it would also work with Devuan, although I haven't found the time to to try it yet. I'll update the thread if I do.

#14 Re: Documentation » [HowTo]Install latests community NvidiaGraphicsDrivers on Daedalus » 2025-04-01 01:21:46

From what you describe, it sounds as if you may be trying to log in via a display manager, but the full X server is failing to start. You could try logging into a text console instead. (Press Ctrl+Alt+F1 to bring up a console.) From there, you may be able to troubleshoot the issue.

I'm not sure if persistenced can cause the kind of problem you're having, but removing it definitely won't prevent you from using the graphics driver. Other users (including myself) have experienced the same error. It was discussed here in the Beowulf beta thread. It seems the init script may try to start the persistenced service without checking first to see if it's already running. One user in the above thread modified the script to address this issue, while another fixed it by removing and reinstalling the service. I chose to simply purge it from my system.

#15 Re: Hardware & System Configuration » X server - signal 4 (illegal instruction) loading glamoregl » 2024-08-27 03:27:34

So, many Excalibur updates later, and this is still occurring. I tried manually changing the driver for both video cards to modesetting, and while X no longer loaded the nouveau driver, it still crashed with a backtrace pointing to /usr/lib/x86_66-linux-gnu/dri/nouveau_dri.so. As far as I can tell, then, the nouveau kernel module is causing X to crash even when I'm not using the nouveau display driver.

There doesn't seem to be a way to get around this problem as long as I'm using this hardware. I'm OK using Daedalus for now, but I'm not sure how close Excalibur is to a stable release. If this bug survives to it, I'll have to abandon the distro entirely.

#16 Re: Other Issues » Question on today's update » 2024-08-03 02:11:26

I've noticed that Excalibur updates sometime remove packages, then install newer versions of the same package. I'm not sure why it does that, rather than just updating the package, but I'm sure there's a good reason.

The libc6 package is marked with priority "required," and contains the GNU version of the standard C library, along with some other core support libraries. I don't think it's even possible to install a usable system without it. The other three libraries are part of the LLVM toolchain, though. I've had to install them to compile C++ projects. Did you install clang at some point? If you never selected them manually, they might have been included with a meta-package.

#17 Re: Hardware & System Configuration » merged /usr » 2024-08-03 00:50:20

There are also a couple of directories for 32-bit libraries that become links in a default merged system. Here is a full listing from a system I created by running debootstrap with the --merged-usr option:

$ find / -maxdepth 1 -type l -xtype d -printf '%p -> %l\n'
/libx32 -> usr/libx32
/sbin -> usr/sbin
/bin -> usr/bin
/lib32 -> usr/lib32
/lib64 -> usr/lib64
/lib -> usr/lib

#18 Off-topic » Interesting comment about systemd in schroot script » 2024-08-01 18:02:56

stultumanto
Replies: 1

I was looking over the setup scripts for schroot to get a better sense for how the system works, when I stumbled across this comment in the file /etc/schroot/setup.d/10mount:

# Work around systemd insanity.
#
# Mark this mountpoint as private; some systems have / as a shared mountpoint.
# As an example, assume /home/m/ch is the chroot directory.
# schroot will mount -o bind /home/m/ch to /var/lib/schroot/mount/ch-123
# Afterwards, it will bind-mount /dev to /var/lib/schroot/mount/ch-123.
# With shared mountpoints, that mount will also show up in the original
# /home/m/ch. This is a problem once schroot mounted /home: the following
# mount of /tmp will show up in /var/lib/schroot/mount/ch-123/tmp,
# /home/m/ch/tmp and /home/m/ch/home/m/ch/tmp (!), which leads to failure
# on unmounting.
if [ "$(uname -s)" = "Linux" ]; then
  mount --make-private "$3"
fi

I had no idea systemd made everything a shared mount by default, but now I'm wondering if it could have been the cause of some weird issues I had in the past. I guess it's another reason to be grateful I'm not using systemd anymore.

#19 Re: Installation » [SOLVED] How to avoid installing GRUB » 2024-08-01 17:16:54

The line you may need to un-comment in /etc/default/grub to enable the OS prober is this one:

GRUB_DISABLE_OS_PROBER=false

The default setting is true, which disables the prober. Un-commenting the line sets it to false, which enables the prober. If you install grub manually, the line is commented out by default. I'm not sure what happens when you use the installer, though.

#20 Re: Hardware & System Configuration » X server - signal 4 (illegal instruction) loading glamoregl » 2024-07-31 04:27:22

I booted the system off another partition with Daedalus and X starts fine. If I'm understanding the backtrace, it appears that the nouveau kernel module (nouveau_dri.so) is causing a hardware exception (SIGILL) which would make sense, as this issue started to occur after a kernel upgrade. I'm not sure if I'm reading it right, though. I would like to make a proper bug report, but I don't where it should go, as this problem involves the X server, display driver, and kernel.

Update: Apparently it wasn't the kernel after all. I rebooted with multiple kernel versions, but the X server always fails to start. I guess I'll stick with Daedalus for now. Expecting this old nvidia card to work reliably on the testing branch may be asking too much.

#21 Re: Hardware & System Configuration » X server - signal 4 (illegal instruction) loading glamoregl » 2024-07-25 08:55:16

It seems the seatd issue is unrelated to the problem of X not starting on this system. The same message shows up on one of my other systems, where X starts successfully:

[    20.893] (II) seat-libseat: libseat integration requires -keeptty and -keeptty was not provided, disabling libseat integration

seatd has been installed on both systems for many months, and the seatd daemon is currently running on both. `dpkg-reconfigure seatd` didn't make the message go away. Removing and reinstalling seatd would require reinstalling the X server itself. I don't know whether this message represents a genuine problem on either system, but it doesn't seem to be related to the signal 4 issue on the nvidia system.

#22 Hardware & System Configuration » X server - signal 4 (illegal instruction) loading glamoregl » 2024-07-24 21:05:41

stultumanto
Replies: 5

Intel i7-4770MQ CPU + NVIDIA GeForce GTX 765 M GPU. I'm running Excalibur and just did an update today. X now fails to start, and I'm seeing the message below in the log. Searching turned up nothing helpful. I thought it might have something to do with the nouveau firmware I installed a few weeks ago, but removing the firmware made no difference.

I next thought it might be a problem with the nouveau driver, but looking at the log, the crash seemed to happen when loading the modesetting driver. At any rate, trying to disable the nouveau driver (by forcing the modesetting driver) had the exact same result.

The crash appears to happen specifically when glamoregl is loaded, so I tried forcing the server to load without glamoregl by putting the lines below into the file /etc/X11/xorg.conf.d/00-noglamoregl.conf. The log did initially say that glamoregl would not be loaded by default, however, glamorgl did load when the fbdev driver was loaded. At that point, it crashed just as before.

Section "Device"
	Identifier "nogpu"
	Driver "modesetting"
	Option "Accelmethod" "none"
EndSection

Section "Module"
	Disable "glamoregl"
EndSection

It appears glamoregl is the culprit, but I don't know how to disable it universally, or whether the server will even be usable without it. Any suggestions would be appreciated. At this point, I'm afraid I may just have to wait for an update. Below is the full text of the initial log.

[   165.321] 
X.Org X Server 1.21.1.11
X Protocol Version 11, Revision 0
[   165.321] Current Operating System: Linux alien 6.9.9-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.9.9-1 (2024-07-13) x86_64
[   165.322] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-6.9.9-amd64 root=UUID=3eff8d67-d669-4093-816d-08f3ff918faa ro quiet
[   165.322] xorg-server 2:21.1.12-1devuan1 (https://www.devuan.org/os/community) 
[   165.322] Current version of pixman: 0.42.2
[   165.322] 	Before reporting problems, check http://wiki.x.org
	to make sure that you have the latest version.
[   165.322] Markers: (--) probed, (**) from config file, (==) default setting,
	(++) from command line, (!!) notice, (II) informational,
	(WW) warning, (EE) error, (NI) not implemented, (??) unknown.
[   165.322] (==) Log file: "/var/log/Xorg.0.log", Time: Wed Jul 24 12:31:21 2024
[   165.322] (==) Using config directory: "/etc/X11/xorg.conf.d"
[   165.322] (==) Using system config directory "/usr/share/X11/xorg.conf.d"
[   165.322] (**) Option "defaultserverlayout" "ServerLayout0"
[   165.322] (**) ServerLayout "ServerLayout0"
[   165.322] (==) No screen section available. Using defaults.
[   165.322] (**) |-->Screen "Default Screen Section" (0)
[   165.322] (**) |   |-->Monitor "<default monitor>"
[   165.322] (==) No monitor specified for screen "Default Screen Section".
	Using a default monitor configuration.
[   165.322] (**) Option "BlankTime" "0"
[   165.322] (**) Option "StandbyTime" "0"
[   165.322] (**) Option "SuspendTime" "0"
[   165.322] (**) Option "OffTime" "0"
[   165.322] (**) Allowing byte-swapped clients
[   165.322] (==) Automatically adding devices
[   165.322] (==) Automatically enabling devices
[   165.322] (==) Automatically adding GPU devices
[   165.322] (==) Automatically binding GPU devices
[   165.322] (==) Max clients allowed: 256, resource mask: 0x1fffff
[   165.322] (WW) The directory "/usr/share/fonts/X11/cyrillic" does not exist.
[   165.322] 	Entry deleted from font path.
[   165.322] (==) FontPath set to:
	/usr/share/fonts/X11/misc,
	/usr/share/fonts/X11/100dpi/:unscaled,
	/usr/share/fonts/X11/75dpi/:unscaled,
	/usr/share/fonts/X11/Type1,
	/usr/share/fonts/X11/100dpi,
	/usr/share/fonts/X11/75dpi,
	built-ins
[   165.322] (==) ModulePath set to "/usr/lib/xorg/modules"
[   165.322] (II) The server relies on udev to provide the list of input devices.
	If no devices become available, reconfigure udev or disable AutoAddDevices.
[   165.322] (II) Loader magic: 0x564b0d38eea0
[   165.322] (II) Module ABI versions:
[   165.322] 	X.Org ANSI C Emulation: 0.4
[   165.322] 	X.Org Video Driver: 25.2
[   165.322] 	X.Org XInput driver : 24.4
[   165.322] 	X.Org Server Extension : 10.0
[   165.322] (++) using VT number 7

[   165.322] (II) seat-libseat: libseat integration requires -keeptty and -keeptty was not provided, disabling libseat integration
[   165.322] (II) xfree86: Adding drm device (/dev/dri/card0)
[   165.322] (II) Platform probe for /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/drm/card0
[   165.323] (II) xfree86: Adding drm device (/dev/dri/card1)
[   165.323] (II) Platform probe for /sys/devices/pci0000:00/0000:00:01.1/0000:07:00.0/drm/card1
[   165.325] (--) PCI:*(1@0:0:0) 10de:11e1:1028:05ab rev 161, Mem @ 0xd4000000/16777216, 0xa0000000/268435456, 0xb0000000/33554432, I/O @ 0x00005000/128, BIOS @ 0x????????/131072
[   165.325] (--) PCI: (7@0:0:0) 10de:11e1:1028:05ab rev 161, Mem @ 0xd2000000/16777216, 0xc0000000/268435456, 0xd0000000/33554432, I/O @ 0x00004000/128, BIOS @ 0x????????/524288
[   165.325] (II) LoadModule: "glx"
[   165.325] (II) Loading /usr/lib/xorg/modules/extensions/libglx.so
[   165.326] (II) Module glx: vendor="X.Org Foundation"
[   165.326] 	compiled for 1.21.1.11, module version = 1.0.0
[   165.326] 	ABI class: X.Org Server Extension, version 10.0
[   166.042] (==) Matched modesetting as autoconfigured driver 0
[   166.042] (==) Matched fbdev as autoconfigured driver 1
[   166.042] (==) Matched vesa as autoconfigured driver 2
[   166.042] (==) Assigned the driver to the xf86ConfigLayout
[   166.042] (II) LoadModule: "modesetting"
[   166.042] (II) Loading /usr/lib/xorg/modules/drivers/modesetting_drv.so
[   166.042] (II) Module modesetting: vendor="X.Org Foundation"
[   166.042] 	compiled for 1.21.1.11, module version = 1.21.1
[   166.042] 	Module class: X.Org Video Driver
[   166.042] 	ABI class: X.Org Video Driver, version 25.2
[   166.042] (II) LoadModule: "fbdev"
[   166.042] (II) Loading /usr/lib/xorg/modules/drivers/fbdev_drv.so
[   166.042] (II) Module fbdev: vendor="X.Org Foundation"
[   166.042] 	compiled for 1.21.1.3, module version = 0.5.0
[   166.042] 	Module class: X.Org Video Driver
[   166.042] 	ABI class: X.Org Video Driver, version 25.2
[   166.042] (II) LoadModule: "vesa"
[   166.042] (II) Loading /usr/lib/xorg/modules/drivers/vesa_drv.so
[   166.042] (II) Module vesa: vendor="X.Org Foundation"
[   166.042] 	compiled for 1.21.1.9, module version = 2.6.0
[   166.042] 	Module class: X.Org Video Driver
[   166.042] 	ABI class: X.Org Video Driver, version 25.2
[   166.042] (II) modesetting: Driver for Modesetting Kernel Drivers: kms
[   166.042] (II) FBDEV: driver for framebuffer: fbdev
[   166.042] (II) VESA: driver for VESA chipsets: vesa
[   166.042] (WW) xf86OpenConsole: setpgid failed: Operation not permitted
[   166.042] (WW) xf86OpenConsole: setsid failed: Operation not permitted
[   166.051] (II) modeset(0): using drv /dev/dri/card0
[   166.051] (WW) Falling back to old probe method for fbdev
[   166.051] (II) Loading sub module "fbdevhw"
[   166.051] (II) LoadModule: "fbdevhw"
[   166.051] (II) Loading /usr/lib/xorg/modules/libfbdevhw.so
[   166.051] (II) Module fbdevhw: vendor="X.Org Foundation"
[   166.051] 	compiled for 1.21.1.11, module version = 0.0.2
[   166.051] 	ABI class: X.Org Video Driver, version 25.2
[   166.051] (II) modeset(G0): using drv /dev/dri/card1
[   166.052] (II) modeset(0): Creating default Display subsection in Screen section
	"Default Screen Section" for depth/fbbpp 24/32
[   166.052] (==) modeset(0): Depth 24, (==) framebuffer bpp 32
[   166.052] (==) modeset(0): RGB weight 888
[   166.052] (==) modeset(0): Default visual is TrueColor
[   166.052] (II) Loading sub module "glamoregl"
[   166.052] (II) LoadModule: "glamoregl"
[   166.052] (II) Loading /usr/lib/xorg/modules/libglamoregl.so
[   166.055] (II) Module glamoregl: vendor="X.Org Foundation"
[   166.055] 	compiled for 1.21.1.11, module version = 1.0.1
[   166.055] 	ABI class: X.Org ANSI C Emulation, version 0.4
[   166.079] (EE) 
[   166.079] (EE) Backtrace:
[   166.079] (EE) 0: /usr/lib/xorg/Xorg (OsLookupColor+0x14d) [0x564b0d30243d]
[   166.080] (EE) 1: /lib/x86_64-linux-gnu/libc.so.6 (__sigaction+0x40) [0x7fce28b8e590]
[   166.080] (EE) 2: /usr/lib/x86_64-linux-gnu/dri/nouveau_dri.so (nouveau_drm_screen_create+0x1326) [0x7fce267cf4a6]
[   166.080] (EE) 3: ? (?+0x0) [0xb90]
[   166.080] (EE) 4: ? (?+0x0) [0x564b14bc4aa0]
[   166.080] (EE) 
[   166.080] (EE) Illegal instruction at address 0x7fce267cf4a6
[   166.080] (EE) 
Fatal server error:
[   166.080] (EE) Caught signal 4 (Illegal instruction). Server aborting
[   166.080] (EE) 
[   166.080] (EE) 
Please consult the The X.Org Foundation support 
	 at http://wiki.x.org
 for help. 
[   166.080] (EE) Please also check the log file at "/var/log/Xorg.0.log" for additional information.
[   166.080] (EE) 
[   166.090] (EE) Server terminated with error (1). Closing log file.

EDIT - I just noticed there's an error in there about seatd, I'm not sure why I would suddenly be having problems with that, or what to do about it. There's virtually nothing online about seatd. Someone else mentioned getting the same error on this forum:
https://dev1galaxy.org/viewtopic.php?id=6385
But the thread wasn't very helpful. I've had seatd installed and working on this system for many months.

#23 Re: Installation » Upgrade custom Devuan. » 2024-07-15 06:08:25

Yes, debsums is excelllent, thank you! Running 'debsums -ec' printed all of my edited configuration files to stdout. It found a few I had completely forgotten about. It won't report newly created config files, of course, such as those under /etc/network/interfaces.d/. I doubt there would be a simple way to automate finding those, unfortunately. It would probably be wise to make a note somewhere whenever they are created.

#24 Re: Hardware & System Configuration » [SOLVED] Huge upgrade this morning ... » 2024-07-13 13:19:38

I started seeing errors about missing nvidia firmware recently as well. It's confusing, because the nouveau driver didn't report this error in the past, but the official site says nouveau has always required the nvidia firmware.

Fortunately, it is relatively easy to extract and install the nvidia firmware yourself. The whole issue is explained here, in the Firmware section: https://nouveau.freedesktop.org/VideoAcceleration.html

#25 Re: Other Issues » Grub is broken! » 2024-05-05 18:20:31

That appears to be corrrect. Perhaps your system does not have EFI selected as the default boot option. Some PCs support both EFI and legacy BIOS boot. That's the only other thing I can think of, as grub seems to be set up properly.

Board footer

Forum Software