The officially official Devuan Forum!

You are not logged in.

#601 Re: Devuan » Can't use MAKE command driver for installation » 2025-07-10 14:11:40

@comborico1611

You have used the inline code tag ("c" with square brackets) whereas you should have used the large code block tag ("code" with square brackets) for that lengthy piece of code. It's a simple matter to fix. wink

#602 Re: DIY » Working on a new app, the learning curve continues... » 2025-07-09 20:31:13

Version 4 uploaded.

No bugs, just moved some files around to comply with Debian's FHS policy,  added a readme in /usr/share/vai with some info,
and cleaned up some superfluous code in the postisnt script in the .deb package. Looking great!

#603 Re: Off-topic » The need for cooperation as central motivation in Unix and GNUproject » 2025-07-09 16:35:17

the marketshare loss is mostly due to the continuing increase in smartphone usage which ultimately drives users to the devices default/included browser which is _not_ firefox

I don't believe so, if you look at the chart going back over the years, it shows google Chrome and Safari steadily growing, while FF declines.

#604 Re: Other Issues » [SOLVED] Best practice/procedure question » 2025-07-09 16:31:22

Solved. The $PATH won't expand to sub-directories if I made one in /usr/local/bin. I would have to use full paths. And you probably shouldn't put sub-directories in there anyway.

So likely best practice is I move the wrapper script to /usr/bin, and move the other two scripts to /usr/local/bin. That should satisfy the FHS. I'll still have a folder in usr/share with a copy of the source code, and probably a readme about the app and some compile instructions for anyone who'd like to do any modding.

#605 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-09 16:14:23

Yep, going forward i'll make a git account, just taking baby steps
right now as much as I can, this is a lot of stuff to learn. Here's what's
going on currently for anyone who might like to test:

So this app assumes that users will be alsa-purists who already have a working
alsa-only system with all the goodies including packages alsamixergui, libasound2-plugin-equal,
alsa-utils etc. And already have working terminal versions of the mixer and EQ and a
properly configured ~/.asoundrc and /etc/.asound.conf.

You must not have any sound server daemons or controls active whatsoever, no Pulse
or Pipe etc. Best not to have them installed at all. VLC drags in libpulse0, but that's
just a library and doesn't affect function.

To compile the source, you will need : GCC, libgtk-3-dev, and libasound2-dev and all their
dependencies.

So copy the code here into your text-editor, save it as "mxeq.c" in any folder you
like. The open a terminal in that same folder to compile (you don't need to be root):

gcc -o mxeq mxeq.c $(pkg-config --cflags --libs gtk+-3.0 alsa glib-2.0) -Wall

Then make the resulting binary executable, then run it, if everything is configged
properly it will be there in all it's glory! If you have Pulse running you will
get a gui with no content but a volume slider maybe, won't hurt anything, just won't
work right. Here's the source:

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

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

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

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

#606 Re: Other Issues » [SOLVED] Best practice/procedure question » 2025-07-08 23:19:03

Thanks for that @fsmithred.

So in reading both, it would seem a couple of things:

1. It is basically okay now to put architecture-independent stuff in other places other than /usr/share, /usr/lib was specifically mentioned.

2. But as far as the contents of /usr/share are concerned, it is not okay to put architecture-specific files in there, only architecture-independent files.

So based on that it seems putting the compiled binary (vai12) in the /usr/share/vai folder is not okay, as it is compiled for amd64 only.

I guess I could put it in /usr/local/bin with the wrapper script and that would be okay. But given my need to try and organize/group things, now i'm wondering something else:

/usr/local/bin is in the $PATH so in /usr/local/bin , vai12 and the wrapper script (vai.sh) would work in there together. But does the $PATH expand to directories inside /usr/local/bin ? Like say I made a folder in that directory for vai and put the wrapper script and the binary gui script in there, would it still work to call the scripts generically i.e. EXEC=vai.sh, or would I have to use the full path to them i.e. EXEC=/usr/local/bin/vai/vai.sh ? Are sub-directories even allowed in /usr/local/bin ?

Sorry for all the questions, i'm just now learning how to package and I know it needs to be done in a specific manner, so I want to be precise.

#607 Re: Off-topic » The need for cooperation as central motivation in Unix and GNUproject » 2025-07-08 21:30:53

^^^Great example fanderal! And to expand on that:

Global marketshare of Firefox web browser:

January 2014 = 14.88%

ten years later...

February 2024 = 2.82%

Source: https://beautifulchart.com/global-brows … 9-to-2024/

#608 Re: Other Issues » [SOLVED] Best practice/procedure question » 2025-07-08 19:54:14

^^^Yep, app-info app. Vuu-do App Info, but I use "vai" as the package name and for the scripts.

The database that's created (simple text file) gets created by the scraper script that's in the /usr/share/vai directory created when you install it, the GUI script is also in that directory.  The database once created is stored in ~/.local/share/vai (scraped_apps.txt). I put it there just in case of a multi-user system, so each user could store their own pre-sets.

The app does not automatically update the database file as apps are installed, that would require re-running the scraper, but a user can just re-run it by running the scraper script (scrape12.sh), but in general, philosohy of use is that this is mainly useful to someone new to linux when they start using a system with a lot of new apps/names they haven't used before, after a time they'll get used to it and the app will no longer be necessary, and in the meantime if they install new apps in Synaptic they will have already seen the description.

Mainly my only question is: Is it okay putting those two executable scripts in /usr/share/vai? I know they work fine from there, but is there any general policy that discourages putting executable scripts in /usr/share for some reason?

#609 Other Issues » [SOLVED] Best practice/procedure question » 2025-07-08 17:22:10

greenjeans
Replies: 6

Hi folks!

Question: In a recent app I made, it consists of two main scripts, one that creates a database and the other the main GUI, and a wrapper script that activates the app. The wrapper checks to see if the database already exists, and if it does it just runs the GUI, if not it runs the database-creator first then runs the GUI. The wrapper is in my $PATH in /usr/local/bin, the other two scripts are not in the $PATH but instead in the folder for the app that's in /usr/share/name-of-app. It works fine as the wrapper specifically calls the full path to the scripts.

But is this okay, or would it be better to store the two scripts elsewhere? It's a little thing I know, but I do want to try and stick with Devuan protocol as much as possible when making these things.

#610 Re: DIY » drpm - a simple tool to handle deb packages for people used to rpm » 2025-07-08 16:35:36

but rather as an opportunity to learn how to make deb packages

.

Nice, pretty much what i've been doing for the last week or so, been meaning for a long time to learn to package, so i'm easing into it with easy stuff, just building the structure manually and using dpkg-deb --build.

#611 Re: DIY » drpm - a simple tool to handle deb packages for people used to rpm » 2025-07-08 15:19:55

Cool script! I really like little utilities that help folks ease into a new system and get up and running quickly, nice work!

#612 Re: Other Issues » Can I Run Refractasnapshot on Devuan Daedalus with runit? [SOLVED] » 2025-07-08 01:19:39

^^ Can't speak for fsmithred, but in my experience if you install Refracta-Snapshot from the repo, it will also install all needed depends to run it on a sysvinit system.

It's no exaggeration to say i've probably done it 1000 times. Worked perfectly every time. Possibly my favorite linux program EVER.

#613 Re: DIY » Working on a new app, the learning curve continues... » 2025-07-07 19:18:06

Well it seems like a whole lotta work....begets more work, lol!

Version 1.0-3 uploaded, this fixes a bug I found that was throwing some leftover de-bugging content into ~/.xsession-errors, bloating it up with some content
that didn't need to be in there, and also confusing my text-editor (Pluma) into thinking the file was binary due to some special characters that got thrown in there. I also added a window icon to the GUI to match the menu entry and other dialogs, and changed the formatting of the description to make it read and render better. Also removed imagemagick as a dependency as it doesn't need it, was going to use it if the app had a hard time with svg icons, but libsrvg and gdk-pixbuf are handling that just fine. Been testing hard all day and can't find any more bugs, so this may finally be the last version.

Thanks to the folks that downloaded and tried it! If you have it and like it, you should un-install the previous version and you'll also need to manually delete the vai folder in ~/.local/share, then install the new .deb.

https://sourceforge.net/projects/vuu-do … -App-Info/

#614 Re: Hardware & System Configuration » How are 'live' disks that can boot on many systems created? » 2025-07-06 15:37:45

It's odd how you can ask the same question multiple times and never be understood. Is it so impossible to create an HDD that can boot on multiple systems like a "live" CD, that no one can even comprehend the question? I would think it would be possible to tell your OS to install all available drivers and use a slightly modified boot process similar to what a live CD uses.

That's not really the same question you asked in the original post, you kinda moved the goal-post my friend.

I guess what you're asking might be possible, you should try it.

But I don't see the point when it's easy to make a much more portable USB stick with your system on it that will boot up any machine. Use Refracta2usb and you can get persistence, multi-boot if you want more than one system on the USB, and also additional partitions just for data.

It's also possible to make a USB system that's NOT a live-session, you can do a conventional install on a USB stick too, i've done it with Refracta Installer. Perhaps that would be more suitable to your purpose.

Either way, good luck! smile

#615 Re: Hardware & System Configuration » How are 'live' disks that can boot on many systems created? » 2025-07-05 23:57:00

^^^What he said is exactly how I would do it.

I started work on a script a while back that's now on the back-burner, that would query your system to find out what all firmware was actually being used and what was superfluous and could be un-installed and do so. But that kinda stuff is fraught with peril, even if I got it working for me I doubt I would make it public as there's too much that can go wrong.

Seems like I remember PclinuxOS had such a script way back in the day, don't know if they still do.

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

Tested earlier in a live session with Vuu-do Mate mini that's also alsa-only, worked great! The mini doesn't have a music app, so I installed Strawberry from the repo for testing (had previously tested with Exaile). Really cool, the pre-set functions are working perfectly. Few more little things to do and it will be ready for packaging.

EDIT: Forgot to mention I also tried it with some different themes in that session, looks fine! Looked really cool in fact in the BlackMate theme.

EDIT2: Just added a window icon to the script, looks good and adds a tiny bit of color to it. Decided on a name for the app, going to call it "AlsaTune" which seems good, pretty much describes what the app does in just a few letters! Now just need to do some more testing, and then i'm ready to package which should be a breeze, though I do need to add a fair amount of depends to the control, and also a good size readme.txt with more info and sample .asoundrc and .asound.conf files to help folks with the process. I'm excited going forward, in the end i'd like to help create more add-ons that extend the functionality of alsa, and remove completely the need for things like Pulse and Pipewire, and make configuration a breeze with simple gui apps like this.

kb4vrq.png

#617 Re: DIY » Working on a new app, the learning curve continues... » 2025-07-05 23:29:05

Fixed and uploaded! Also added installed size to the control file so that info will be available now in Synaptic. And changed the whole thing from a per-user app to system-wide for all users.

#618 Re: Forum Feedback » FluxBB » 2025-07-05 19:41:33

^^ And you're a hero for doing it @golinux, love this place, thank you!!!

#619 Re: DIY » Working on a new app, the learning curve continues... » 2025-07-05 19:04:37

Okay. finally found the hidden flaw(s) in this one, a bug that didn't hurt anything or affect function at all, just the postinst script created some superfluous folders/files.
Also the package has the compiled binary for the gui, and that's done in 64-bit, so I need to change the control file and re-name it as it won't run on other architectures.

Other than that it's running great. So with these changes i'm also going to convert it to a more conventional system-wide thing as opposed to a user-specific app, the files generated by the scraper will still go into userspace, but the other scripts other than the wrapper will go into /usr/share/vai and the .desktop into /usr/share/applications.

#620 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-05 14:59:03

^^^ shouldn't be much longer, I have some more testing to do yet, have only tested in Openbox so far, going to fire up a liveUSB today with a Mate version of Vuu-do that's pure alsa-only and test there. I also need to do some testing with other themes, so far the testing (and those screenshots) have only been tried using my own theme I made for Vuu-do (Ice-Breaker), so I don't know how it might look in other themes. Plus I need to attend to some little things, a name for the app, what icon to use etc.

#621 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-05 14:44:21

Hi tux_99, that's basically what i'm doing. In the .deb package for the VAI app I previously did, I included the source code for the one C script in it, the other stuff is all bash/dash scripts so those are already readable in a text editor. In some of my build threads here I actually post the source code in the thread.

The scripts are so tiny and only do some small specific things, not sure any patches are needed. And as with everything I do, it's true open-source code that I claim no ownership over, if you check my scripts they all say "Copyleft greenjeans 2025. Use as you see fit." People are free to take it and make whatever they want with it. I actively encourage them to do so. This is part of the philosophy I adopted from the epic and legendary Miyolinux that Vuu-do is originally based on : MIYO, Make it your own.

Mostly any scripts I write are intended to be used in Vuu-do, it's only lately that i've expanded to trying to make actual apps that can be used in other systems.

As i'm sure you know, you don't have to install a .deb to look at it's contents, I understand that when using compiled binaries that people want to look at the source code, that's why I include the raw C code in the .deb. And we're talking tiny here, the source for this script is only 21 kb or so, the source for the binary in the VAI app is only 13.5 kb, so it's no big deal to include them.

This is just my workflow for now, again i'm just getting started with this stuff, progressing bit by bit to more complex things.

#622 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-05 13:13:32

^^^Hi! I'm sure I haven't been clear enough about functions here even though my posts are usually a bit wordy anyway, let me clarify with use-case examples and such.

1. When you start the GUI it polls the backend for current settings of the EQ, which if you've previously used it to set them, will be exactly as you left it. If you then open the terminal version (regardless if you close the GUI or leave it open), the terminal version will also be at those values.

2. If you open the terminal version of the EQ first and adjust it, it will change sound obviously, then if you open the GUI (regardless if you close the term version or not) the GUI will reflect the changes you just made in the term version.

3. 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. But as always, I could be very wrong about, any enlightenment in that area would be appreciated. But really it's an unlikely edge-case scenario, no reason to have both open at the same time in the first place so I don't see a need to add a bunch of extra code and complexity to this simple app.

As for git, I have no git, "GIT" is what I yell at the cats when I want them to leave me alone, lol!

I guess I should probably have a git account somewhere going forward, maybe. But right now I don't use any of that and don't have all the mega-dev packages that go with it installed, just GCC, lbgtk3-dev and libasound2-dev and the packages that go with them to be able to compile, and dpkg-deb makes the .deb package for me after i've made all the components.

From what I understand github is now owned by microsoft, and gitlab apparently has some kind of stink on it too, leaving gitea which would make sense to use since Devuan uses it I guess, but any links to it hit me with that accursed Anubis crap, which takes a literal 10 minutes to run at least for me, and then when done still doesn't always let me in, so whenever I hit a link and I get Anubis I just close that tab and move on. That and the Clownflare challenge are the bane of my online existence.

Again you have to remember Aitor, I have always made it clear that i'm not a dev by training, schooling, or experience...just a VUU (veteran unix user) trying to get better and contribute something back to the community that's done so much for me. Previous to now, it actually seemed almost a little pretentious for me to set up some kind of git account, still does really. These are just small niche apps i'm working on, no more than a dozen of my last one has been downloaded, probably get a few more with this one. Small potatoes my friend, that's all my stuff is. I just got tired of waiting and hoping other people would make the things I want and decided to learn how to do it myself, same reason I started using Linux 16 years ago.

#623 Re: Desktop and Multimedia » XLibre: The New Xorg Fork » 2025-07-05 01:37:55

^^^ Nice! Devuan leading the push back for 10 years now, and now it seems our list of allies increases!

#624 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-04 20:09:47

Oh it absolutely saves the settings, it's using the alsa backend to do that as well, the same way running either the mixer or EQ in terminal does, no outboard save files needed for that. But since as per your usual you urged me to do better, lol, here's an updated working draft with user-pre-set functions added, this uses a small outboard text DB file in CSV format for these functions, you set the EQ, type in your new name for the pre-set, hit save pre-set and it saves it to that file, and instantly adds it to the pre-set drop-down list on the right. And you can hit that drop-down and choose a pre-set and it instantly applies it and your sound changes in real-time based on it. wink

dm6m5f.png

#625 Re: DIY » ALSA-only purists: Question, new GUI app for the mixer and EQ? » 2025-07-04 14:31:36

Kinda stuck for the moment, having a hard time thinking of a reason not to just leave it like it is, it does everything now that I intended basically and does it well and fast. Needs some cleaning up for margins and such. I don't like feature creep, just doing things because they can be done. I try to think about philosophy of use and just not sure any extra features are needed.

Really could use some input from y'all on proposed additions, the only one i'm seriously considering at the moment, is implementing fsmithred's suggestion. Adding a text-entry field-button to save/name the current EQ setting as a pre-set, then another little field/button that's also a drop-down menu that shows current pre-set applied (if any) and let's you use the drop-down to select one of your other pre-sets.

Board footer

Forum Software