Anyone know how to recreate Archcraft volume notifications

I think Archcraft uses dunst to display a pop-up when you adjust your computer’s volume wheel. I was wondering what configuration files to take from Archcraft to do this in Mabox.

Removed previous post by @muzqs because suggestions where useless to @ArchcraftHater.

I think it must be some script, not only configuration file. Take a look at Openbox’s config file ~/.config/openbox/rc.xml in Archcraft. Probably keybinds/mousebinds and commands are defined there.

ob-volume (83 lines) script needs dunst + icons. Not a big deal but copyright protected.
by Aditya Shakya :confused:

ob-volume --dec
ob-volume --inc
ob-volume --toggle

Thanks for the help. Adding this to rc.xml works but creates a new notification box below the previous one for every volume increment.

@ArchcraftHater
Maybe insering -t switch for dunstify in ob-volume. Find an optimal value.
notify_cmd='dunstify -u low -t 100 -h string:x-dunst-stack-tag:obvolume'

I forked pa-notify

@ArchcraftHater … Is this kinda what you where looking for.

volnotify

:bird:

edit: simplified notification.

1 Like

@muzqs Tested and have some problems.
The ‘TITEL version’ I could fix and use it.
I’m not a C coder but defined TITEL as char[0] and could compile.
Due to external HDMI monitor I use pulseaudio and pulsemixer and
the night version as of 24 May does not work for me. pa-notify works fine(ly).

@zolw

Updated version:

Can you try it again. :wink: Thanks for testing.

volnotify

:bird:

Update:

Added another option to build. Instructions available.

tinyvol

:bird:

With manjaro-pulse (if it matters) it blocks audio.
In case I start volnotify after boot:
Translate ID error: '-1' is not a valid ID (returned by default-nodes-api)
otherwise it shows 74% and does not reflect the actual value.
the Satuday early version worked for me. It has been overwritten here.
I’m not part of the target audience at the moment.

@zolw thanks for testing.

i will have a look at pulse and alsa support how they behave.
need some time to look at that.

Not sure if it is this version you mentioned.

Summary
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libnotify/notify.h>

typedef enum { BACKEND_NONE, BACKEND_WPCTL, BACKEND_PACTL, BACKEND_AMIXER } AudioBackend;

// Detect which audio backend command is available
AudioBackend detect_backend() {
    if (system("which wpctl > /dev/null 2>&1") == 0)
        return BACKEND_WPCTL;
    else if (system("which pactl > /dev/null 2>&1") == 0)
        return BACKEND_PACTL;
    else if (system("which amixer > /dev/null 2>&1") == 0)
        return BACKEND_AMIXER;
    return BACKEND_NONE;
}

// Get volume level and mute status depending on backend
int get_volume(AudioBackend backend, float *volume, int *muted) {
    FILE *fp;
    char output[256];

    switch (backend) {
        case BACKEND_WPCTL:
            fp = popen("wpctl get-volume @DEFAULT_SINK@", "r");
            if (!fp) return -1;
            if (fgets(output, sizeof(output), fp)) {
                sscanf(output, "Volume: %f", volume);
                *muted = strstr(output, "[MUTED]") != NULL;
            }
            pclose(fp);
            break;

        case BACKEND_PACTL:
            fp = popen("pactl get-sink-mute @DEFAULT_SINK@ && pactl get-sink-volume @DEFAULT_SINK@", "r");
            if (!fp) return -1;
            *muted = 0;
            *volume = 0;
            while (fgets(output, sizeof(output), fp)) {
                if (strstr(output, "Mute:")) {
                    *muted = strstr(output, "yes") != NULL;
                } else if (strstr(output, "Volume:")) {
                    char *percent = strrchr(output, '%');
                    if (percent) {
                        int v = 0;
                        sscanf(percent - 3, "%d", &v); // read 3 chars before %
                        *volume = v / 100.0f;
                    }
                }
            }
            pclose(fp);
            break;

        case BACKEND_AMIXER:
            fp = popen("amixer get Master", "r");
            if (!fp) return -1;
            *volume = 0;
            *muted = 0;
            while (fgets(output, sizeof(output), fp)) {
                if (strstr(output, "%")) {
                    int v = 0;
                    sscanf(output, "%*[^[][%d", &v);  // get number between first [ and %
                    *volume = v / 100.0f;
                    *muted = strstr(output, "[off]") != NULL;
                    break;
                }
            }
            pclose(fp);
            break;

        default:
            return -1;
    }

    return 0;
}

// Create a volume bar made of full and empty blocks
void make_volume_bar(int percent, char *bar, size_t size) {
    const char *full = "█";
    const char *empty = "░";
    int total_blocks = 10;
    int blocks = (percent * total_blocks) / 100;

    bar[0] = '\0';
    for (int i = 0; i < total_blocks && strlen(bar) + strlen(full) < size - 1; i++) {
        strcat(bar, i < blocks ? full : empty);
    }
}

// Return an appropriate volume icon based on volume and mute status
const char* get_volume_icon(int percent, int muted) {
    if (muted) return "🔇";
    if (percent <= 0) return "🔈";
    if (percent <= 30) return "🔉";
    if (percent <= 75) return "🔉";
    return "🔊";
}

int main(void) {
    float volume = -1.0, prev_volume = -1.0;
    int muted = -1, prev_muted = -1;

    AudioBackend backend = detect_backend();
    if (backend == BACKEND_NONE) {
        fprintf(stderr, "No supported audio backend found (wpctl, pactl, amixer).\n");
        return 1;
    }

    notify_init("pa-notify");
    NotifyNotification *n = notify_notification_new("Volume", "", NULL);
    notify_notification_set_hint(n, "transient", g_variant_new_boolean(FALSE)); // Persistent notification

    while (1) {
        if (get_volume(backend, &volume, &muted) == 0) {
            if ((int)(volume * 100) != (int)(prev_volume * 100) || muted != prev_muted) {
                prev_volume = volume;
                prev_muted = muted;

                char icon[10];
                char bar[32];
                char message[128];

                int percent = (int)(volume * 100);
                strcpy(icon, get_volume_icon(percent, muted));

                if (muted) {
                    snprintf(message, sizeof(message), "%s  %-10s  %3s", icon, "Muted", "");
                } else {
                    make_volume_bar(percent, bar, sizeof(bar));
                    snprintf(message, sizeof(message), "%s  %-10s  %3d%%", icon, bar, percent);
                }

                notify_notification_update(n, "Volume", message, NULL);
                notify_notification_show(n, NULL);
            }
        }

        usleep(200000);  // Sleep 200ms before updating again
    }

    notify_uninit();
    return 0;
}

:bird:

Updated version.

:bird:

volnotify-next version.

modernvol

I read your manual. Yes it works on Pipewire. If my TV worked with
pipewire I would have been on it.
yay manjaro-pulse
makes the ‘migration’ and these 2 lines back to pipewire

yay -Rns pulseaudio-bluetooth manjaro-pulse pulseaudio
yay manjaro-pipewire
1 Like

This works after removing dunst. However, the padding on top of the bar is too high. I don’t understand how the install script chooses which C file to compile.

Example Install latest .C file of the README.

The name is volnotify-next

1: Create backup of original .c file.

cp volnotify.c volnotify.c.bak2

2: after that do

cp volnotify-next.c  volnotify.c 

3: Finaly

./Install

modernvol

I can’t change the padding at the top.

This is what i can do. I don’t know that mutch about C myself. (expirimental)

:BIRD