The officially official Devuan Forum!

You are not logged in.

#26 Re: Off-topic » Hard Rust requirements for APT from may next year » 2025-11-11 11:44:20

Likely well after every other BSD has, and one little bite at a time.

He has already made his position on this issue clear.

In OpenBSD there is a strict requirement that base builds base.

So we cannot replace any base utility, unless the toolchain to build
it is in the base.  Adding such a toolchain would take make build time
from 40 minutes to hours.  I don't see how that would happen.

#27 Re: Hardware & System Configuration » [SOLVED] Excalibur 6 - cannot shut down from terminal » 2025-11-11 10:30:31

I remember being confused by halt very early on and switching to shutdown --poweroff now and eventually just poweroff. (Confusingly, according to the manual, shutdown --halt -h or shutdown -H -h will not power down!)

#28 Re: Desktop and Multimedia » [SOLVED] Can't suspend, reboot, or shut down in a WM » 2025-11-11 09:31:39

sudo isn't always easy, but this particular task isn't too difficult. You can give all members of a particular group privileges to run specific commands without a password. Then, you add your regular user account to that group. Generally, people use the existing sudo group for this purpose, although you can create a special group if you want, such as a shutdown group. For example, add this line to your /etc/sudoers file (remember to use visudo to edit!)

%sudo    ALL=NOPASSWD:    /sbin/shutdown,/sbin/poweroff,/sbin/reboot

Then add yourself to the sudo group:

usermod -aG sudo your_username

Now you can reboot without a password prompt:

sudo reboot

This is my preferred method, as a member of the nonsensical hair-shirt brigade! (I don't even use a display manager, just good old xinit.)

#29 Re: Documentation » [SOLVED] dwww Install » 2025-11-11 09:09:09

I recall having similar issues with this system a few years ago; specifically, search would not work. Unfortunately, I wasn't able to find a solution. There is a README file at the source repository which says you need to enable CGI in your local web server, so maybe that is worth a try.

#30 Re: News & Announcements » Announcing Devuan 6.0 Excalibur! » 2025-11-11 08:21:16

I can't even imagine being responsible for a project of this magnitude. I watched as all these big issues came up, usrmerge, 64-bit time_t, and more, and I wondered how it would ever be completely finished. But you folks did it! Thank you so much for everything. After all these years, it still amazes me when I set up a new system, and everything just works.

I assume you folks discussing size differences are using the standard installer? I didn't really notice a huge difference between the sizes of Daedalus and Excalibur, but I install manually, and my systems are pretty minimal. My kid was laughing at me last night because I don't even have a graphical file manager!

#31 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.

#32 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.

#33 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.

#34 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.

#35 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.

#36 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.

#37 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.

#38 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.

#39 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.

#40 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;
}

#41 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!

#42 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.

#43 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.

#44 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.

#45 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.

#46 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.

#47 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

#48 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.

#49 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.

#50 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.

Board footer

Forum Software