PulseAudio Volume Meter. Would like to add VU ticks. - Page 1
Forum section: electronics / general

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: July 21, 2020, 12:31:44 pm
content: Only applies to you if you run Linux and PulseAudio.Many years ago, Lennart offered up a volume meter program for PA.http://0pointer.de/lennart/projects/pavumeter/My gui/GTK1.2! skills are stuck in a timewarp just as the aforementioned src.I use the program to check if my mic is not maxxing out when participating in certain occasional d-live streams.I'd like to add VU marks to the meters. I'd like to be able to read the live meter and then push the fader on the desk to 0dB to -3dB, on the fly.I am interested to know you think this src software a dead duck. The thing is, it suits me 85%. For the last 15% for it to be perfect would be to add the VU ticks and possibly rotate it horizontal.Or do I commit the Cardinal sin and re-invent the wheel?

author: gnif
author_rank: Unknown
timestamp: January 02, 2021, 01:30:05 am
content: While not a solution to your question, if you're using audio for professional work and need such things, why not use JACK and something like KxStudio's Carla for a virtual rack?. There are tons of modules (LDASPA, and LV2) you can load which will do not just DSP, but also metering, etc.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 02, 2021, 03:12:03 am
content: Quote from: gnif on January 02, 2021, 01:30:05 amShort answer: too fiddly and I don't want to dick around with low-latency kernels, prefer vanilla ones.I just wanted something a bit betterer than the terrible stock (mono) meter in pavucontrol. I also asked a dev overseas to quote me a price to quickly add what is needed to the src, only to be told that Pulse is a dead duck. Pipewire is in my future.It's only been 15 years since I last had Jack running fully. Got fed up with ordinary software always requiring oss/alsa shims to connect to the server. Maybe it's time to look at it again.https://wiki.archlinux.org/index.php/PipeWireedit: typo

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 02, 2021, 09:37:42 pm
content: Quote from: Ed.Kloonk on July 21, 2020, 12:31:44 pmWhy use that shit whenpavucontrolexists?Quote from: Ed.Kloonk on July 21, 2020, 12:31:44 pmIt's developer for sure is.Also, we use GTK+3 nowadays.Quote from: Ed.Kloonk on July 21, 2020, 12:31:44 pmPulseAudio volume controls are pretty darn simple things, so it's more like customizing your wheel than reinventing one.See here.The only tricky thing is to remember that the audio samples are linear and correspond to amplitude; so 3 dB corresponds to a ratio of sqrt(2) in amplitude. You usually want a peak detect (difference between minimum and maximum amplitudes within a small set of samples, say 50ms worth), but you can use e.g.fftw3to do a real spectrum analysis too.What I've thought about, is to make one that dynamically adjusts the mic volume within given bounds, using the specified time constant, but only when there is input that exceeds a given "silence" level; at "silence", there is no adjustment.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 02, 2021, 10:12:26 pm
content: Quote from: Nominal Animal on January 02, 2021, 09:37:42 pmQuote from: Ed.Kloonk on January 02, 2021, 03:12:03 amWhat wasn't mentioned is the other need for the OP. We had a number of analog recordings to bring into the computer. Many of them were a bit weak on one channel, yet we needed a few dBs of headroom for EQ before the mp3 encoder. I used a terminal PCM recorder and I just wanted a spot on a stereo scale to aim for a certain level at a glance. Do you know what I ended up doing? I used a white board marker right on the monitor screen.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 02, 2021, 10:20:36 pm
content: Quote from: Nominal Animal on January 02, 2021, 09:37:42 pmNo argument here. I avoid anything with systemd and I pay the penalty for it.QuoteI'm not into GUI, just like the command line.I read the GTK1 API at one stage, played around a little bit at the time. Now 4.0 is out.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 02, 2021, 10:34:48 pm
content: Quote from: Ed.Kloonk on January 02, 2021, 10:12:26 pmThis is exactly why I harp so often about how important it is to understand the real world use cases first, before designing/redesigning/upgrading a tool.Most VU meters show linear scale amplitude (so a dB scale ticks are at logarithmic intervals). But that's not at all necessary, it just means an additional (logarithmic) conversion at display time (so, what, max. 60 times a second; completely neglible in the scheme of things).For your use case, I wonder if a undecorated transparent window at left or right edge of the display (i.e., heads-up display style) with just the channel volume bars would work; maybe a tiny bar per dB? Or a continuous (pair of) bar(s) with tick marks at dB intervals? The actual controls could be in a separate window that could be minimized without affecting the display.So, what is itexactlythat you need? Not as a difference to that awful thing, but as a full description of a small applet? Maybe even draw (freehand, Paint-style) an example picture?

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 02, 2021, 11:20:15 pm
content: Quote from: Nominal Animal on January 02, 2021, 10:34:48 pmedit: I think everyone on here knows what I really need...What I thought I needed was just a few tics on the scale so I could get the L+R balance closer. An indication, not so much a actual measurement. But yeah, a 0dB downwards log scale would be nice.I remember in way back in Visual Basic 3(?) you could set ticks on progress bars and sliders and I was hoping someone who knows GTK could add it to the code without breaking the very easy compile.The only other wish was that it was orientated vertically not horizontal. Transparency apps, I've tended to stay away from, opting for the always-on-top available in lxde, lxqt, mate. But a transparent rectangle with no window widgets is compelling, the more I think about it. I've since realized that the reason these apps don't exist is because they are included in the DAW interface yet I prefer to use command line tools mostly.My dilemma was that if I had to get my hands dirty, I'd want slider/fader controls on it as well. But it's just to much C++ for this ole dog, as I won't go beyond C. So I took the lazy way out and asked on here if a few little changes could be made without going overboard.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 03, 2021, 01:46:58 am
content: Quote from: Ed.Kloonk on January 02, 2021, 11:20:15 pmBoth GTK+ and PulseAudio are C, so why not write one in C?Heck, if you only need it for your default sound source, it'll be a very simple little program, as the "simple" interface suffices. Lemme cobble together something...

author: PKTKS
author_rank: Super Contributor
timestamp: January 03, 2021, 01:17:35 pm
content: Quote from: Ed.Kloonk on January 02, 2021, 11:20:15 pmALSA comes (since almost very first releases) withwhat you want already.. toolkit independent suitedto work with any sort of audio stream you can think ..try it a bit by just :aplay -vv -V stereo -i -B -N -M -D plug:ameterameter like any alsa device must already be compiledwith your release... if not you need to enable that in compile timeLater you define you alsa.conf carefully with those requiredbindings and data paths to your audio..Pulse has nothing to do with that.. nor JACK they all willjust attach the data streamscreenshot providedPaul

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 03, 2021, 01:51:41 pm
content: Something like the following.vu.h: A simple thread-safe interface for VU meteringCode:#ifndef VU_H#define VU_H/*** Initialize VU measurements** @param server PulseAudio server; NULL for default* @param appname Application name* @param devname Source name; NULL for default* @param stream Descriptive stream name* @param channels Number of channels* @param rate Samples per second per channel* @param samples Samples per update* @return Zero if success, nonzero if error.*/int vu_start(const char *server,const char *appname,const char *devname,const char *stream,int channels,int rate,int samples);/*** Convert vu_start() return value to a string*/const char *vu_error(int);/*** Stop VU measurements*/void vu_stop(void);/*** Wait for the next VU update*/void vu_wait(void);/*** Get latest VU peaks per channel; thread-safe** @param peak Array of floats to be populated* @param channels Number of channels in peak array* @return Zero if no new data available,*number of channels available if updated,* negative if an error occurred.*/int vu_peak(float *to, int channels);/*** Check if new VU peaks are available; thread-safe*/int vu_peak_available(void);#endif /* VU_H */vu.c: Implementing the aboveCode:#define _POSIX_C_SOURCE 200809L#include <stdlib.h>#include <stdint.h>#include <pthread.h>#include <limits.h>#include <pulse/simple.h>#include <pulse/error.h>#include <string.h>#include <stdio.h>#include <errno.h>static volatile int done = 0;static pa_simple *audio = NULL;static size_t audio_channels = 0;static size_t audio_samples = 0;static int32_t *audio_buffer = NULL; /* audio_buffer */static int32_t *audio_min = NULL; /* audio_min */static int32_t *audio_max = NULL; /* audio_max */static pthread_t audio_thread;static pthread_mutex_t peak_lock = PTHREAD_MUTEX_INITIALIZER;static pthread_cond_t peak_update = PTHREAD_COND_INITIALIZER;static float *peak_amplitude = NULL;static volatile int peak_available = 0;int vu_peak_available(void){return peak_available;}static void *worker(void *unused){(void)unused; /* Silence warning about unused parameter. */while (!done) {int err = 0;if (pa_simple_read(audio, audio_buffer, audio_channels * audio_samples * sizeof audio_buffer, &err) < 0) {done = -EIO;break;}for (size_t c = 0; c < audio_channels; c++) {audio_min = (int32_t)( 2147483647);audio_max = (int32_t)(-2147483648);}int32_t *const end = audio_buffer + audio_channels * audio_samples;int32_t *ptr = audio_buffer;/* Min-max peak detect. */while (ptr < end) {for (size_t c = 0; c < audio_channels; c++) {const int32_t s = *(ptr++);audio_min = (audio_min < s) ? audio_min : s;audio_max = (audio_max > s) ? audio_max : s;}}/* absolute values. */for (size_t c = 0; c < audio_channels; c++) {if (audio_min == (int32_t)(-2147483648))audio_min = (int32_t)( 2147483647);elseif (audio_min < 0)audio_min = -audio_min;elseaudio_min = 0;if (audio_max < 0)audio_max = 0;}/* Update peak amplitudes. */pthread_mutex_lock(&peak_lock);if (peak_available++) {for (size_t c = 0; c < audio_channels; c++) {const float amplitude = (audio_max > audio_min) ? audio_max / 2147483647.0f : audio_min / 2147483647.0f;peak_amplitude = (peak_amplitude > amplitude) ? peak_amplitude : amplitude;}} else {for (size_t c = 0; c < audio_channels; c++) {const float amplitude = (audio_max > audio_min) ? audio_max / 2147483647.0f : audio_min / 2147483647.0f;peak_amplitude = amplitude;}}pthread_cond_broadcast(&peak_update);pthread_mutex_unlock(&peak_lock);}/* Wake up all waiters on the peak update, too. */pthread_cond_broadcast(&peak_update);return NULL;}const char *vu_error(int err){if (err < 0)return strerror(-err);elseif (err > 0)return pa_strerror(err);elsereturn "OK";}void vu_stop(void){if (audio) {if (!done)done = 1;pthread_join(audio_thread, NULL);pa_simple_free(audio);audio_thread = pthread_self();audio = NULL;}free(audio_buffer); /* Note: free(NULL) is OK. */free(audio_min);free(audio_max);audio_buffer = NULL;audio_min = NULL;audio_max = NULL;audio_channels = 0;audio_samples = 0;pthread_mutex_lock(&peak_lock);free(peak_amplitude);peak_amplitude = NULL;peak_available = 0;pthread_mutex_unlock(&peak_lock);}void vu_wait(void){pthread_mutex_lock(&peak_lock);if (audio && !done)pthread_cond_wait(&peak_update, &peak_lock);pthread_mutex_unlock(&peak_lock);}int vu_peak(float *to, int num){pthread_mutex_lock(&peak_lock);if (!peak_amplitude || !peak_available || audio_channels < 1) {pthread_mutex_unlock(&peak_lock);return 0;}const int have = (int)audio_channels;if (num > 0) {const int cmax = (num < have) ? num : have;for (int c = 0; c < cmax; c++)to = peak_amplitude;}peak_available = 0;pthread_mutex_unlock(&peak_lock);return have;}int vu_start(const char *server,const char *appname,const char *devname,const char *stream,int channels,int rate,int samples){pa_sample_spec samplespec;pa_buffer_attr bufferspec;pthread_attr_t attrs;int err;if (!appname || !*appname || !stream || !*stream ||channels < 1 || channels > 128 || rate < 1 || rate > 1000000 || samples < 1 || samples > 1000000) {return -EINVAL;}/* Empty or "default" server maps to NULL. */if (server && (!*server || !strcmp(server, "default")))server = NULL;/* Empty or "default" devname maps to NULL. */if (devname && (!*devname || !strcmp(devname, "default")))devname = NULL;/* If already running, stop. */if (audio) {vu_stop();}done = 0;pthread_mutex_lock(&peak_lock);samplespec.format = PA_SAMPLE_S32NE;samplespec.rate = rate;samplespec.channels = channels;bufferspec.maxlength = (uint32_t)(-1);bufferspec.tlength = (uint32_t)(-1);bufferspec.prebuf = (uint32_t)(-1);bufferspec.minreq = (uint32_t)(-1);bufferspec.fragsize = (uint32_t)channels * (uint32_t)samples * (uint32_t)sizeof audio_buffer;err = PA_OK;audio = pa_simple_new(server, appname, PA_STREAM_RECORD, devname, stream, &samplespec, NULL, &bufferspec, &err);if (!audio) {pthread_mutex_unlock(&peak_lock);return err;}/* Allocate memory for the various buffers. */audio_buffer = calloc((size_t)channels * sizeof audio_buffer, (size_t)samples);audio_min = malloc((size_t)channels * sizeof audio_min);audio_max = malloc((size_t)channels * sizeof audio_max);peak_amplitude = malloc((size_t)channels * sizeof peak_amplitude);if (!audio_buffer || !audio_min || !audio_max || !peak_amplitude) {free(peak_amplitude);free(audio_max);free(audio_min);free(audio_buffer);pa_simple_free(audio);audio = NULL;audio_buffer = NULL;audio_min = NULL;audio_max = NULL;peak_amplitude = NULL;pthread_mutex_unlock(&peak_lock);return -ENOMEM;}for (int c = 0; c < channels; c++)peak_amplitude = 0.0f;peak_available = 0;audio_channels = channels;audio_samples = samples;pthread_attr_init(&attrs);pthread_attr_setstacksize(&attrs, 2 * PTHREAD_STACK_MIN);err = pthread_create(&audio_thread, &attrs, worker, NULL);if (err) {pa_simple_free(audio);free(peak_amplitude);free(audio_max);free(audio_min);free(audio_buffer);audio = NULL;audio_buffer = NULL;audio_min = NULL;audio_max = NULL;peak_amplitude = NULL;audio_channels = 0;audio_samples = 0;pthread_mutex_unlock(&peak_lock);return -err;}pthread_attr_destroy(&attrs);pthread_mutex_unlock(&peak_lock);return 0;}gui.c: A simple Gtk+ UI for the VU metersCode:#define _POSIX_C_SOURCE 200809L#include <stdlib.h>#include <unistd.h>#include <locale.h>#include <signal.h>#include <gtk/gtk.h>#include <ctype.h>#include <string.h>#include <strings.h>#include <errno.h>#include "vu.h"#ifndef MAX_CHANNELS#define MAX_CHANNELS 32#endif#ifndef MAX_RATE#define MAX_RATE 250000#endifstatic volatile sig_atomic_t done = 0;static void handle_done(int signum){if (!done)done = signum;}static int install_done(int signum){struct sigaction act;memset(&act, 0, sizeof act);sigemptyset(&act.sa_mask);act.sa_handler = handle_done;act.sa_flags = SA_RESTART;if (sigaction(signum, &act, NULL) == -1)return errno;return 0;}struct tickmark {float amplitude;float red;float green;float blue;};enum placement {PLACEMENT_LEFT = 1,PLACEMENT_RIGHT = 2,PLACEMENT_TOP = 3,PLACEMENT_BOTTOM = 4};static const char *server = NULL;static const char *device = NULL;static int channels = 2;static int rate = 48000;static int updates = 60;static int bar_size = 4;static int bar_space = 3;static int display_monitor = -1;static enum placement display_placement = PLACEMENT_RIGHT;static float *peak = NULL;static float decay = 0.95f;static float red_limit = 0.891251f; /* Amplitude within 1 dB of clipping */static float green_limit = 0.707946f; /* Amplitude within 3 dB of clipping */static struct tickmark tickmarks[] = {{ .amplitude = 0.891251f, .red = 1.0f, .green = 0.00f, .blue = 0.0f }, /* 1 dB */{ .amplitude = 0.794328f, .red = 1.0f, .green = 1.00f, .blue = 0.0f }, /* 2 dB */{ .amplitude = 0.707946f, .red = 0.0f, .green = 1.00f, .blue = 0.0f }, /* 3 dB */{ .amplitude = 0.630957f, .red = 0.0f, .green = 0.95f, .blue = 0.0f }, /* 4 dB */{ .amplitude = 0.562341f, .red = 0.0f, .green = 0.90f, .blue = 0.0f }, /* 5 dB */{ .amplitude = 0.501187f, .red = 0.0f, .green = 0.85f, .blue = 0.0f }, /* 6 dB */{ .amplitude = 0.446684f, .red = 0.0f, .green = 0.80f, .blue = 0.0f }, /* 7 dB */{ .amplitude = 0.398107f, .red = 0.0f, .green = 0.75f, .blue = 0.0f }, /* 8 dB */{ .amplitude = 0.354813f, .red = 0.0f, .green = 0.70f, .blue = 0.0f }, /* 9 dB */{ .amplitude = 0.316228f, .red = 0.0f, .green = 0.65f, .blue = 0.0f }, /* 10 dB */{ .amplitude = -1.0f }};static gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */GdkRectangle area;gtk_widget_get_clip(widget, &area);cairo_save(cr);cairo_set_source_rgb(cr, 0.0,0.0,0.0);cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);cairo_paint(cr);for (int i = 0; i < channels; i++) {if (peak >= red_limit)cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);elseif (peak <= green_limit)cairo_set_source_rgb(cr, 0.0, 0.5 + 0.5*peak/green_limit, 0.0);else {const double c = (peak - green_limit) / (red_limit - green_limit);cairo_set_source_rgb(cr, c, 1.0-c, 0.0);}const double c = (peak < 0.0f) ? 0.0 : (peak < 1.0f) ? peak : 1.0;if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {cairo_rectangle(cr, area.x + bar_space + i * (bar_space + bar_size),area.y + bar_space + (1.0 - c)*(area.height - 2*bar_space),bar_size, c*area.height);cairo_fill(cr);} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {cairo_rectangle(cr, area.x + bar_space,area.y + bar_space + i * (bar_space + bar_size),c * (area.width - 2*bar_space), bar_size);cairo_fill(cr);}}cairo_set_line_width(cr, 1.0);if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int y = area.y + bar_space + (1.0f - tickmarks.amplitude)*(area.height - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, area.x + 2, y);cairo_line_to(cr, area.x + area.width - 2, y);cairo_stroke(cr);}} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int x = area.x + bar_space + tickmarks.amplitude*(area.width - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, x, area.y + 2);cairo_line_to(cr, x, area.y + area.width - 2);cairo_stroke(cr);}}cairo_restore(cr);return TRUE;}static gboolean tick(GtkWidget *widget, GdkFrameClock *fclk, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */(void)fclk;if (done) {gtk_window_close(GTK_WINDOW(widget));return G_SOURCE_REMOVE;}float new_peak;if (vu_peak(new_peak, channels) == channels) {for (int c = 0; c < channels; c++) {peak *= decay;peak = (new_peak > peak) ? new_peak : peak;}gtk_widget_queue_draw(widget);}return G_SOURCE_CONTINUE;}static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer user_data){(void)user_data; (void)old_screen; /* Silence unused parameter warning; generates no code */gtk_widget_set_visual(widget, gdk_screen_get_system_visual(gtk_widget_get_screen(widget)));}static void place(GdkRectangle *to){if (!to)return;to->x = 0;to->y = 0;to->width = bar_space + (bar_size + bar_space) * channels;to->height = bar_space + (bar_size + bar_space) * channels;GdkDisplay *d = gdk_display_get_default();GdkMonitor *m = gdk_display_get_monitor(d, display_monitor);GdkRectangle w;if (!m)m = gdk_display_get_primary_monitor(d);if (!m)return;gdk_monitor_get_workarea(m, &w);switch (display_placement) {case PLACEMENT_LEFT:to->x = w.x;to->height = w.height;return;case PLACEMENT_RIGHT:to->x = w.x + w.width - to->width;to->height = w.height;return;case PLACEMENT_TOP:to->y = w.y;to->width = w.width;return;case PLACEMENT_BOTTOM:to->y = w.y + w.height - to->height;to->width = w.width;return;}}static void activate(GtkApplication *app, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code *//* Compute where to place the window */GdkRectangle pos;place(&pos);/* Create window */GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDOW(window), "VU-Bar");gtk_window_set_icon_name(GTK_WINDOW(window), "multimedia-volume-control");gtk_window_set_default_size(GTK_WINDOW(window), pos.width, pos.height);gtk_window_set_resizable(GTK_WINDOW(window), TRUE);gtk_window_move(GTK_WINDOW(window), pos.x, pos.y);gtk_window_set_decorated(GTK_WINDOW(window), FALSE);gtk_widget_add_tick_callback(window, tick, NULL, NULL);gtk_widget_set_app_paintable(window, TRUE);g_signal_connect(window, "draw", G_CALLBACK(draw), NULL);g_signal_connect(window, "screen-changed", G_CALLBACK(screen_changed), NULL);gtk_application_add_window(app, GTK_WINDOW(window));gtk_widget_show_all(window);gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);}static const char *skip_lws(const char *from){if (!from)return NULL;while (isspace((unsigned char)(*from)))from++;return from;}static const char *parse_int(const char *from, int *to){const char *next = from;long val;if (!from || *from == '\0') {errno = EINVAL;return NULL;}errno = 0;val = strtol(from, (char **)(&next), 0);if (errno)return NULL;if (next == from) {errno = EINVAL;return NULL;}if ((long)(int)(val) != val) {errno = ERANGE;return NULL;}if (to)*to = val;errno = 0;return next;}int usage(const char *arg0){fprintf(stderr, "\n");fprintf(stderr, "Usage: %s -h | --help\n", arg0);fprintf(stderr, " %s [ OPTIONS ]\n", arg0);fprintf(stderr, "Options:\n");fprintf(stderr, " -s SERVER PulseAudio server\n");fprintf(stderr, " -d DEVICE Source to monitor\n");fprintf(stderr, " -c CHANNELS Number of channels\n");fprintf(stderr, " -r RATE Samples per second\n");fprintf(stderr, " -u COUNT Peak calculations per second\n");fprintf(stderr, " -m MONITOR Display monitor number\n");fprintf(stderr, " -p WHERE Meter placement on display\n");fprintf(stderr, " -B PIXELS Bar thickness in pixels\n");fprintf(stderr, " -S PIXELS Bar spacing in pixels\n");fprintf(stderr, "Placement:\n");fprintf(stderr, " -p left Left edge of monitor\n");fprintf(stderr, " -p right Right edge of monitor\n");fprintf(stderr, " -p top Top edge of monitor\n");fprintf(stderr, " -p bottom Bottom edge of monitor\n");fprintf(stderr, "\n");return EXIT_SUCCESS;}int main(int argc, char *argv[]){const char *arg0 = (argc > 0 && argv && argv && argv) ? argv : "(this)";const char *p;int opt, val;setlocale(LC_ALL, "");if (argc > 1 && !strcmp(argv, "--help"))return usage(arg0);gtk_init(&argc, &argv);while ((opt = getopt(argc, argv, "hs:d:c:r:u:m:p:B:S:")) != -1) {switch (opt) {case 'h':return usage(arg0);case 's':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))server = NULL;elseserver = optarg;break;case 'd':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))device = NULL;elsedevice = optarg;break;case 'c':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > MAX_CHANNELS) {fprintf(stderr, "%s: Invalid number of channels.\n", optarg);return EXIT_FAILURE;}channels = val;break;case 'r':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 128 || val > MAX_RATE) {fprintf(stderr, "%s: Invalid sample rate.\n", optarg);return EXIT_FAILURE;}rate = val;break;case 'u':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > 200) {fprintf(stderr, "%s: Invalid number of peak updates per second.\n", optarg);return EXIT_FAILURE;}updates = val;break;case 'm':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < -1) {fprintf(stderr, "%s: Invalid monitor number.\n", optarg);return EXIT_FAILURE;}display_monitor = val;break;case 'p':if (!strcasecmp(optarg, "left"))display_placement = PLACEMENT_LEFT;elseif (!strcasecmp(optarg, "right"))display_placement = PLACEMENT_RIGHT;elseif (!strcasecmp(optarg, "top"))display_placement = PLACEMENT_TOP;elseif (!strcasecmp(optarg, "bottom"))display_placement = PLACEMENT_BOTTOM;else {fprintf(stderr, "%s: Unsupported placement.\n", optarg);return EXIT_FAILURE;}break;case 'B':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1) {fprintf(stderr, "%s: Invalid bar thickness in pixels.\n", optarg);return EXIT_FAILURE;}bar_size = val;break;case 'S':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 0) {fprintf(stderr, "%s: Invalid bar spacing in pixels.\n", optarg);return EXIT_FAILURE;}bar_space = val;break;case '?':/* getopt() has already printed an error message. */return EXIT_FAILURE;default:/* Bug catcher: This should never occur. */fprintf(stderr, "getopt() returned %d ('%c')!\n", opt, opt);return EXIT_FAILURE;}}if (install_done(SIGINT) ||install_done(SIGHUP) ||install_done(SIGTERM) ||install_done(SIGQUIT)) {fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));return EXIT_FAILURE;}if (optind < argc) {fprintf(stderr, "%s: Unsupported parameter.\n", argv);return EXIT_FAILURE;}GtkApplication *app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE);if (!app) {fprintf(stderr, "Cannot start GTK+ application.\n");return EXIT_FAILURE;}g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);size_t samples = rate / updates;if (samples < 1)samples = 1;val = vu_start(server, "vu-bar", device, "VU monitor", channels, rate, samples);if (val) {fprintf(stderr, "Cannot monitor audio source: %s.\n", vu_error(val));g_object_unref(app);return EXIT_FAILURE;}peak = calloc((size_t)channels * sizeof (float), samples);if (!peak) {fprintf(stderr, "Out of memory.\n");g_object_unref(app);vu_stop();return EXIT_FAILURE;}val = g_application_run(G_APPLICATION(app), 0, NULL);g_object_unref(app);vu_stop();return val;}Makefile: You can runsed -e 's|^ *|\t|' -i Makefileto fix indentation, if you have issues.Code:CC := gccCFLAGS := -Wall -Wextra -O2 `pkg-config --cflags gtk+-3.0 libpulse-simple`LDFLAGS := -pthread -lm `pkg-config --libs gtk+-3.0 libpulse-simple`PROGS := vu-barall: clean $(PROGS).PHONY: cleanclean:rm -f *.o $(PROGS)%.o: %.c$(CC) $(CFLAGS) -c $^vu-bar: gui.o vu.o$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@Runmake clean allto compile, and then./vu-bar -hto see the usage. Normally,./vu-bardoes the right thing, showing the default audio source as vertical bars on the right side of the main display.The tick marks are at 1 dB intervals. Close it by making sure it is the current application, then Alt+F4 as usual (or right-clicking on the application in your panel, and Close). Or runpkill -HUP vu-barto send it a HUP signal, which also causes it to exit cleanly.Note: This is utter crap, since I just wrote it from scratch in one sitting. Licensed under CC0-1.0 (i.e., do what you will, just don't blame me). Needs better organization and refactoring. Almost certainly contains bugs. But should provide ideas.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 03, 2021, 04:24:04 pm
content: Quote from: Nominal Animal on January 03, 2021, 01:51:41 pmWow. That's perfect.QuoteIt detects peaks quite well, I send the audio over s/pdif, which is unforgiving. This is great for me to gnaw on, though for the few mins it's been running, I'm not sure I'll need to fiddle with it as it is, but I can comprehend the code. It ticks all the boxes, man!

author: gnif
author_rank: Unknown
timestamp: January 04, 2021, 01:56:56 pm
content: Quote from: Ed.Kloonk on January 02, 2021, 03:12:03 amFair enough, but please be aware that needing low-latency kernels is a thing of the past, just RT scheduling is all that is needed. I am using JACK to back 6 QEMU VMs, one of which is a workstation, another other a gaming machine (VFIO VGA Passthrough) which all put high loads on the system. With the proper tuning on a stock kernel, it's possible to get perfect audio even under such an extreme workload.

author: RoGeorge
author_rank: Super Contributor
timestamp: January 04, 2021, 02:54:49 pm
content: Quote from: Nominal Animal on January 03, 2021, 01:51:41 pmWow!

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 04, 2021, 10:03:30 pm
content: Quote from: RoGeorge on January 04, 2021, 02:54:49 pmI know, right?I must be the luckiest boy on the whole forum.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 04, 2021, 10:16:16 pm
content: Quote from: gnif on January 04, 2021, 01:56:56 pmCannot say I had the same luck with virtualbox and a attempt at good old fashioned virtual distro incest. A host and guest both using PA, managed (mangled?) by VB was a complete disaster.RT kernels. Yeah, I'm a bit out of date. Life got in the way. I started to lose interest when they were squabbling about who had the better scheduler.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 05, 2021, 01:52:19 am
content: Quote from: Ed.Kloonk on January 04, 2021, 10:03:30 pmYou'd be surprised how many programmers would be willing to cobble something together, if it were not the language barrier.I don't mean language as in programming language (heh); I mean that users usually cannot describe their actual need in terms that programmers can act upon. Things like Paint sketches of what the user wants to see are way underappreciated; yet, they describe the needed thingy much better than dozens if not hundreds of words.Me, I've dealt with the same problem, trying to adjust the microphone volume (or amplification, or source audio volume) to keep most of dynamic range, but with enough headroom to not clip in the end result. I've used pavucontrol – it has a nice wide volume display, and worked fine for me – and although not an audio person, have seen proper studio audio setups and been interested enough to look up the details like amplitude, loudness, compression (the audio effect), and the relationships between perceived volume, amplitude, and the dB scale. I only thought I understood your problem you were trying to solve, because it has bugged me too.I still don'tknowwhat kind of tool would solve itbetter– I think at minimum we should add lingering peak lines to the live bars –, and as it is, it really needs to be configured to suit ones needs by supplying suitable command-line parameters. Switching to the PulseAudio Async interface would make it possible to reduce the I/O (and thus CPU load), as it can provide only the peak samples instead of the full sample stream. We could have a context window to select the audio source, and optionally to control the volume of the source, which initially pops up as the application is started, but which can be closed, and reopened via right-clicking on the VU bar. We could even make the VU bar freely placeable and resizable, say whenever the context window is open; and have that location be the default for the next startup (via gsettings).To make it more useful, the optional volume control would be very interesting to make scriptable. You'd have additional markers, like tab stops, on the volume bar to restrict the allowed auto-control volume range, and use an external program (plugin, simply by putting them in a specific directory) that consumes the audio peak data (at ~ 100 peaks per second) in floating-point format (0.0 - 1.0), and which outputs (at arbitrary intervals) the desired audio volume level (0.0 - 1.0), both in linear amplitude scale. The application would update the volume (within the set limits), so the script would be very simple, and users could write their own scripts for their own needs.If anyone finds a real good algorithm, those can be built-in to the application, reducing the overall CPU load again.(The most commonly used algorithm just bumps up the volume whenever the peaks are too low, and drops the volume whenever the peaks are too high. Another uses a low-pass filter (with pass-band on the order of 1 Hz or less) onA/peak, whereAis the desired peak amplitude, and the filtered output is the source volume or amplification in terms of amplitude. So, this plugin interface is really simple, and a few examples are enough to get people with basic Python skills to implement their own logic as to how the volume should be adjusted. I think I can safely assume we both know there is no auto-gain algorithm that works in all cases; this plugin method would be a nice simple platform of exploring and experimenting with auto-gain algorithms in a simple numerical domain that requires basically no audio domain knowledge.)See? Since you and me have battled with this, I'm sure there are lots of others wasting time with this too, on Linux machines. Making something better to better cater our needs is how Free/Open Source ecosystems work. The hard part isnotthe code; I've already proven by the example that these things can be whipped up within a few hours. The hard part is designing the tool so that it fits the underlying problem; that it fixes the problem at hand. And that it and the problem are described in an interesting way, to attract the attention of both developers and users. Otherwise, it's just ... kinda wasted time. A one-off.The one I showed is crap because it hasn't been checked for bugs and has a horribly messy structure (it waswritten, not designed!), but moreso because it is just my guess of what might work.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 05, 2021, 03:10:48 am
content: The only quibble I have is that I wish it could 'hog' the desktop like the way the task panel does when another window is maximized. I mean, it's nice that the meter is always on top, but it covers the slider bar on a maximized browser, for example. I've adjusted the size and pos of firefox to suit, but I wonder how hard it might be to have the meter just push the windows out of the way when they are being maximized?

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 05, 2021, 09:03:19 am
content: Sure! (See, that's one of the things I didn't even think about.)Updatedgui.c:Code:#define _POSIX_C_SOURCE 200809L#include <stdlib.h>#include <unistd.h>#include <locale.h>#include <signal.h>#include <gtk/gtk.h>#include <ctype.h>#include <string.h>#include <strings.h>#include <errno.h>#include "vu.h"#ifndef MAX_CHANNELS#define MAX_CHANNELS 32#endif#ifndef MAX_RATE#define MAX_RATE 250000#endifstatic volatile sig_atomic_t done = 0;static void handle_done(int signum){if (!done)done = signum;}static int install_done(int signum){struct sigaction act;memset(&act, 0, sizeof act);sigemptyset(&act.sa_mask);act.sa_handler = handle_done;act.sa_flags = SA_RESTART;if (sigaction(signum, &act, NULL) == -1)return errno;return 0;}struct tickmark {float amplitude;float red;float green;float blue;};enum placement {PLACEMENT_LEFT = 1,PLACEMENT_RIGHT = 2,PLACEMENT_TOP = 3,PLACEMENT_BOTTOM = 4};static const char *server = NULL;static const char *device = NULL;static int channels = 2;static int rate = 48000;static int updates = 60;static int bar_size = 4;static int bar_space = 3;static int display_monitor = -1;static enum placement display_placement = PLACEMENT_RIGHT;static float *peak_line = NULL;static float *peak = NULL;static float decay_line = 0.99f;static float decay = 0.95f;static float red_limit = 0.891251f; /* Amplitude within 1 dB of clipping */static float green_limit = 0.707946f; /* Amplitude within 3 dB of clipping */static struct tickmark tickmarks[] = {{ .amplitude = 0.891251f, .red = 1.0f, .green = 0.00f, .blue = 0.0f }, /* 1 dB */{ .amplitude = 0.794328f, .red = 1.0f, .green = 1.00f, .blue = 0.0f }, /* 2 dB */{ .amplitude = 0.707946f, .red = 0.0f, .green = 1.00f, .blue = 0.0f }, /* 3 dB */{ .amplitude = 0.630957f, .red = 0.0f, .green = 0.95f, .blue = 0.0f }, /* 4 dB */{ .amplitude = 0.562341f, .red = 0.0f, .green = 0.90f, .blue = 0.0f }, /* 5 dB */{ .amplitude = 0.501187f, .red = 0.0f, .green = 0.85f, .blue = 0.0f }, /* 6 dB */{ .amplitude = 0.446684f, .red = 0.0f, .green = 0.80f, .blue = 0.0f }, /* 7 dB */{ .amplitude = 0.398107f, .red = 0.0f, .green = 0.75f, .blue = 0.0f }, /* 8 dB */{ .amplitude = 0.354813f, .red = 0.0f, .green = 0.70f, .blue = 0.0f }, /* 9 dB */{ .amplitude = 0.316228f, .red = 0.0f, .green = 0.65f, .blue = 0.0f }, /* 10 dB */{ .amplitude = -1.0f }};static gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */GdkRectangle area;gtk_widget_get_clip(widget, &area);cairo_save(cr);cairo_set_source_rgb(cr, 0.0,0.0,0.0);cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);cairo_paint(cr);for (int i = 0; i < channels; i++) {if (peak >= red_limit)cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);elseif (peak <= green_limit)cairo_set_source_rgb(cr, 0.0, 0.5 + 0.5*peak/green_limit, 0.0);else {const double c = (peak - green_limit) / (red_limit - green_limit);cairo_set_source_rgb(cr, c, 1.0-c, 0.0);}const double c = (peak < 0.0f) ? 0.0 : (peak < 1.0f) ? peak : 1.0;if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {cairo_rectangle(cr, area.x + bar_space + i * (bar_space + bar_size),area.y + bar_space + (1.0 - c)*(area.height - 2*bar_space),bar_size, c*area.height);cairo_fill(cr);} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {cairo_rectangle(cr, area.x + bar_space,area.y + bar_space + i * (bar_space + bar_size),c * (area.width - 2*bar_space), bar_size);cairo_fill(cr);}}cairo_set_line_width(cr, 1.0);if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int y = area.y + bar_space + (1.0f - tickmarks.amplitude)*(area.height - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, area.x + 2, y);cairo_line_to(cr, area.x + area.width - 2, y);cairo_stroke(cr);}} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int x = area.x + bar_space + tickmarks.amplitude*(area.width - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, x, area.y + 2);cairo_line_to(cr, x, area.y + area.width - 2);cairo_stroke(cr);}}cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);cairo_set_line_width(cr, 2.0);for (int i = 0; i < channels; i++) {const double c = (peak_line < 0.0f) ? 0.0 : (peak_line < 1.0f) ? peak_line : 1.0;if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {const int y = area.y + bar_space + (1.0f - c)*(area.height - 2*bar_space);cairo_move_to(cr, area.x + i *(bar_space + bar_size), y);cairo_line_to(cr, area.x + (i+1)*(bar_space + bar_size), y);cairo_stroke(cr);} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {const int x = area.x + bar_space + c*(area.width - 2*bar_space);cairo_move_to(cr, x, area.y + i *(bar_space + bar_size));cairo_line_to(cr, x, area.y + (i+1)*(bar_space + bar_size));cairo_stroke(cr);}}cairo_restore(cr);return TRUE;}static gboolean tick(GtkWidget *widget, GdkFrameClock *fclk, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */(void)fclk;if (done) {gtk_window_close(GTK_WINDOW(widget));return G_SOURCE_REMOVE;}float new_peak;if (vu_peak(new_peak, channels) == channels) {for (int c = 0; c < channels; c++) {peak *= decay;peak = (new_peak > peak) ? new_peak : peak;peak_line *= decay_line;peak_line = (new_peak > peak_line) ? new_peak : peak_line;}gtk_widget_queue_draw(widget);}return G_SOURCE_CONTINUE;}static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer user_data){(void)user_data; (void)old_screen; /* Silence unused parameter warning; generates no code */gtk_widget_set_visual(widget, gdk_screen_get_system_visual(gtk_widget_get_screen(widget)));}static void place(GdkRectangle *to, gulong *reserve){if (!to || !reserve)return;to->x = 0;to->y = 0;to->width = bar_space + (bar_size + bar_space) * channels;to->height = bar_space + (bar_size + bar_space) * channels;reserve[ 0] = 0; /* left */reserve[ 1] = 0; /* right */reserve[ 2] = 0; /* top */reserve[ 3] = 0; /* bottom */reserve[ 4] = 0; /* left_start_y */reserve[ 5] = 0; /* left_end_y */reserve[ 6] = 0; /* right_start_y */reserve[ 7] = 0; /* right_end_y */reserve[ 8] = 0; /* top_start_x */reserve[ 9] = 0; /* top_end_x */reserve = 0; /* bottom_start_x */reserve = 0; /* bottom_end_x */GdkDisplay *d = gdk_display_get_default();GdkMonitor *m = gdk_display_get_monitor(d, display_monitor);GdkRectangle w;if (!m)m = gdk_display_get_primary_monitor(d);if (!m)return;gdk_monitor_get_workarea(m, &w);switch (display_placement) {case PLACEMENT_LEFT:to->x = w.x;to->height = w.height;reserve = to->width;reserve = w.y;reserve = w.y + w.height - 1;return;case PLACEMENT_RIGHT:to->x = w.x + w.width - to->width;to->height = w.height;reserve = to->width;reserve = w.y;reserve = w.y + w.height - 1;return;case PLACEMENT_TOP:to->y = w.y;to->width = w.width;reserve = to->height;reserve = w.x;reserve = w.x + w.width - 1;return;case PLACEMENT_BOTTOM:to->y = w.y + w.height - to->height;to->width = w.width;reserve = to->height;reserve = w.x;reserve = w.x + w.width - 1;return;}}static void activate(GtkApplication *app, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */gulong reserve = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };/* Compute where to place the window */GdkRectangle pos;place(&pos, reserve);/* Create window */GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDOW(window), "VU-Bar");gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DOCK);gtk_window_set_icon_name(GTK_WINDOW(window), "multimedia-volume-control");gtk_window_set_default_size(GTK_WINDOW(window), pos.width, pos.height);gtk_window_set_resizable(GTK_WINDOW(window), TRUE);gtk_window_move(GTK_WINDOW(window), pos.x, pos.y);gtk_window_set_decorated(GTK_WINDOW(window), FALSE);gtk_widget_add_tick_callback(window, tick, NULL, NULL);gtk_widget_set_app_paintable(window, TRUE);g_signal_connect(window, "draw", G_CALLBACK(draw), NULL);g_signal_connect(window, "screen-changed", G_CALLBACK(screen_changed), NULL);gtk_application_add_window(app, GTK_WINDOW(window));gtk_widget_show_all(window);gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);GdkWindow *w = gtk_widget_get_window(window);if (w) {GdkAtom strut = gdk_atom_intern_static_string("_NET_WM_STRUT");GdkAtom partial = gdk_atom_intern_static_string("_NET_WM_STRUT_PARTIAL");GdkAtom cardinal = gdk_atom_intern_static_string("CARDINAL");gdk_property_change(w, strut, cardinal, 32, GDK_PROP_MODE_REPLACE, (guchar *)reserve, 4);gdk_property_change(w, partial, cardinal, 32, GDK_PROP_MODE_REPLACE, (guchar *)reserve, 12);}}static const char *skip_lws(const char *from){if (!from)return NULL;while (isspace((unsigned char)(*from)))from++;return from;}static const char *parse_int(const char *from, int *to){const char *next = from;long val;if (!from || *from == '\0') {errno = EINVAL;return NULL;}errno = 0;val = strtol(from, (char **)(&next), 0);if (errno)return NULL;if (next == from) {errno = EINVAL;return NULL;}if ((long)(int)(val) != val) {errno = ERANGE;return NULL;}if (to)*to = val;errno = 0;return next;}int usage(const char *arg0){fprintf(stderr, "\n");fprintf(stderr, "Usage: %s -h | --help\n", arg0);fprintf(stderr, " %s [ OPTIONS ]\n", arg0);fprintf(stderr, "Options:\n");fprintf(stderr, " -s SERVER PulseAudio server\n");fprintf(stderr, " -d DEVICE Source to monitor\n");fprintf(stderr, " -c CHANNELS Number of channels\n");fprintf(stderr, " -r RATE Samples per second\n");fprintf(stderr, " -u COUNT Peak calculations per second\n");fprintf(stderr, " -m MONITOR Display monitor number\n");fprintf(stderr, " -p WHERE Meter placement on display\n");fprintf(stderr, " -B PIXELS Bar thickness in pixels\n");fprintf(stderr, " -S PIXELS Bar spacing in pixels\n");fprintf(stderr, "Placement:\n");fprintf(stderr, " -p left Left edge of monitor\n");fprintf(stderr, " -p right Right edge of monitor\n");fprintf(stderr, " -p top Top edge of monitor\n");fprintf(stderr, " -p bottom Bottom edge of monitor\n");fprintf(stderr, "\n");return EXIT_SUCCESS;}int main(int argc, char *argv[]){const char *arg0 = (argc > 0 && argv && argv && argv) ? argv : "(this)";const char *p;int opt, val;setlocale(LC_ALL, "");if (argc > 1 && !strcmp(argv, "--help"))return usage(arg0);gtk_init(&argc, &argv);while ((opt = getopt(argc, argv, "hs:d:c:r:u:m:p:B:S:")) != -1) {switch (opt) {case 'h':return usage(arg0);case 's':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))server = NULL;elseserver = optarg;break;case 'd':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))device = NULL;elsedevice = optarg;break;case 'c':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > MAX_CHANNELS) {fprintf(stderr, "%s: Invalid number of channels.\n", optarg);return EXIT_FAILURE;}channels = val;break;case 'r':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 128 || val > MAX_RATE) {fprintf(stderr, "%s: Invalid sample rate.\n", optarg);return EXIT_FAILURE;}rate = val;break;case 'u':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > 200) {fprintf(stderr, "%s: Invalid number of peak updates per second.\n", optarg);return EXIT_FAILURE;}updates = val;break;case 'm':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < -1) {fprintf(stderr, "%s: Invalid monitor number.\n", optarg);return EXIT_FAILURE;}display_monitor = val;break;case 'p':if (!strcasecmp(optarg, "left"))display_placement = PLACEMENT_LEFT;elseif (!strcasecmp(optarg, "right"))display_placement = PLACEMENT_RIGHT;elseif (!strcasecmp(optarg, "top"))display_placement = PLACEMENT_TOP;elseif (!strcasecmp(optarg, "bottom"))display_placement = PLACEMENT_BOTTOM;else {fprintf(stderr, "%s: Unsupported placement.\n", optarg);return EXIT_FAILURE;}break;case 'B':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1) {fprintf(stderr, "%s: Invalid bar thickness in pixels.\n", optarg);return EXIT_FAILURE;}bar_size = val;break;case 'S':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 0) {fprintf(stderr, "%s: Invalid bar spacing in pixels.\n", optarg);return EXIT_FAILURE;}bar_space = val;break;case '?':/* getopt() has already printed an error message. */return EXIT_FAILURE;default:/* Bug catcher: This should never occur. */fprintf(stderr, "getopt() returned %d ('%c')!\n", opt, opt);return EXIT_FAILURE;}}if (install_done(SIGINT) ||install_done(SIGHUP) ||install_done(SIGTERM) ||install_done(SIGQUIT)) {fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));return EXIT_FAILURE;}if (optind < argc) {fprintf(stderr, "%s: Unsupported parameter.\n", argv);return EXIT_FAILURE;}GtkApplication *app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE);if (!app) {fprintf(stderr, "Cannot start GTK+ application.\n");return EXIT_FAILURE;}g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);size_t samples = rate / updates;if (samples < 1)samples = 1;val = vu_start(server, "vu-bar", device, "VU monitor", channels, rate, samples);if (val) {fprintf(stderr, "Cannot monitor audio source: %s.\n", vu_error(val));g_object_unref(app);return EXIT_FAILURE;}peak = calloc((size_t)channels * sizeof (float), samples);peak_line = calloc((size_t)channels * sizeof (float), samples);if (!peak) {fprintf(stderr, "Out of memory.\n");g_object_unref(app);vu_stop();return EXIT_FAILURE;}val = g_application_run(G_APPLICATION(app), 0, NULL);g_object_unref(app);vu_stop();return val;}I also added the slower-decay peak lines.Do note that I still think this is crappy, because it hasn't been checked for bugs, could be more efficient, et cetera.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 05, 2021, 09:17:54 am
content: Quote from: Nominal Animal on January 05, 2021, 09:03:19 amNeither did I. Since it's so slimline, it deserves it's own sanctioned territory.QuoteCool. Works great.QuoteIt's so crappy, I've just gone and added it to the auto start file.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 05, 2021, 09:26:30 am
content: And here is a picture...

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 27, 2022, 06:46:01 am
content: Update.A whole year has passed and the Vu meter program (above) has been perfect for my needs. I owe NA a beer.Well, that multi-monitor thread the other day motivated me to dust off a 32" TV laying around and constructed the most horrific wall bracket so now I have a "L" shaped twin monitor setup. Yay!My problem now is I need to tweak the gui code because the wrong monitor is pushing the windows out of the way and leaving a gap and the monitor displaying the vu meter covers the programs under it (the way it did in the first gui version).I don't think that the 2nd monitor being rotated 90deg has anything to do with this issue.So far I've got my heart set on fiddling with this line:Code:static int display_monitor = -1;I have been running vu-bar as is with no command args.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 27, 2022, 08:04:57 am
content: Quote from: Ed.Kloonk on January 27, 2022, 06:46:01 amOr, you know, just runvu-bar -m 1orvu-bar -m 2as they achieve the same thing.I'm pretty sure it's the region reserving code (place()function) that needs fixing, but darnit, I don't have an extra display right now to test and verify...

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 27, 2022, 08:42:13 am
content: Quote from: Nominal Animal on January 27, 2022, 08:04:57 amvu-bar -m 1 runs it in the new monitor.All values not 1, it runs in the original monitor.It pushes windows around in the other monitor. And puts them back when you kill the program.QuoteI'll have a poke around, thanks.For years, I've had a spare HDMI cable that I thought was kept near the TV so I could fire it up quickly when I got around to it. Couldn't find the cable today, of course!

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 27, 2022, 09:29:45 am
content: So, let me see if I understand this right.If you runvu-bar -m 0the meter is displayed in the primary monitor, but the windows are pushed around in the second monitor; meaning windows cover the meter in the primary monitor and there is unusable space in the second monitor.If you runvu-bar -m 1the meter is displayed in the secondary monitor, but the windows are pushed around in the primary monitor; meaning again windows cover the meter, with the other monitor having unusable space.Or, do the windows get pushed around always only in the secondary monitor?I need to know the actual error pattern, because I cannot duplicate it right now, to fix it. I think I understand how the error occurs, but I need to know the exact symptoms before I can suggest a fix.Edit: Actually, I should just make it draggable, with a context menu (right-clickable) to select orientation et cetera, with the command-line parameters just setting the defaults (so that one can make trivial .desktop shortcuts or menu entries for specific configurations). Let me see if I can be arsed to, in a day or two; brain too tired today...

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 27, 2022, 09:41:39 am
content: Quote from: Nominal Animal on January 27, 2022, 09:29:45 amYes, that's it. I might have misunderstood the behaviour, but now realise it only pushes around the windows on the 2nd monitor but the meter window can indeed be selected properly.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 27, 2022, 10:33:51 am
content: Code:GdkDisplay *d = gdk_display_get_default();GdkMonitor *m = gdk_display_get_monitor(d, display_monitor);GdkRectangle w;fprintf(stderr, "display_monitor = %i\n", display_monitor );fprintf(stderr, "d = %p\n", d );fprintf(stderr, "m = %p\n", m );GdkMonitor *mon0 = gdk_display_get_monitor(d, 0);GdkMonitor *mon1 = gdk_display_get_monitor(d, 1);fprintf(stderr, "mon0 = %p\n", mon0 );fprintf(stderr, "mon1 = %p\n", mon1 );if (!m)fprintf(stderr, "Not a monitor. display_monitor = %i\n", display_monitor );if (!m)m = gdk_display_get_primary_monitor(d);if (!m)return;gdk_monitor_get_workarea(m, &w);Code:[ed@kloonkmanor ~]$ ./vu-bar -m 0display_monitor = 0d = 0x11af0e0m = 0x119b240mon0 = 0x119b240mon1 = 0x119b2b0[ed@kloonkmanor ~]$ ./vu-bar -m 1display_monitor = 1d = 0x1a1d0e0m = 0x1a092b0mon0 = 0x1a09240mon1 = 0x1a092b0[ed@kloonkmanor ~]$ ./vu-bar -m 2display_monitor = 2d = 0x21210e0m = (nil)mon0 = 0x210d240mon1 = 0x210d2b0Not a monitor. display_monitor = 2So -m 2 still pushes the windows, so maybe the function is barking up the wrong tree.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 27, 2022, 10:42:33 pm
content: This example in Python suffers the same problem..https://gist.github.com/johnlane/351adff97df196add08aCode:[ed@kloonkmanor ~]$ python2.7 dockbar-gtk3.pyGtk 3.24.31width: 2688there are 2 monitorsmonitor 0: 1920 x 1080monitor 1: 768 x 1366monitor 0: 1920 x 1080 (current, offset 0)bar: start=0 end=1919Red bar appears at the top of monitor 0. Windows are pushed around at the top of Monitor 1.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 28, 2022, 07:23:02 pm
content: (The monitors are actually numbered 0, 1, ...; not 1, 2, ...; and when given an invalid monitor number, it defaults to the default one automatically.)Okay, I think I understand where the error lies. I'll need to do some simple tests first to ensure I can fix it right; I don't 'do' scotch tape fixes. In the mean time, sit tight; I'm not dropping this on the floor.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 28, 2022, 08:40:23 pm
content: Ed.Kloonk –– and anyone else running Linux with multiple monitors, having GTK+3.0 development packages installed! –– , could you compile and run the following GDK information dumper program, when you have more than one monitor connected, and either post here or send me the output via PM or email?It does not open any windows. It only connects to the default display (which you can override using the--displayparameter, if you have more than one X server running), and then dumps the information on the monitors and their geometry. This information on multi-monitor setups would be very, very informative.Code:// SPDX-License-Identifier: CC0-1.0//// Save this file as 'list.c', and compile to './list' using for example// gcc -Wall -O2 `pkg-config --cflags gdk-3.0` list.c `pkg-config --libs gdk-3.0` -o list//#include <stdlib.h>#include <gdk/gdk.h>int main(int argc, char *argv[]){/* Initialize GDK. Supports display selection via --displayparameter and the DISPLAY environment variable. */if (!gdk_init_check(&argc, &argv)) {fprintf(stderr, "Cannot initialize GDK.\n");return EXIT_FAILURE;}/* Get access to the default display. */GdkDisplay *display = gdk_display_get_default();if (!display) {fprintf(stderr, "No default display.\n");return EXIT_FAILURE;}const char *display_name = gdk_display_get_name(display);if (!display_name || !*display_name)display_name = "(Unnamed default)";/* Get number of monitors */int monitors = gdk_display_get_n_monitors(display);if (monitors < 0) {fprintf(stderr, "No monitors in default display.\n");return EXIT_FAILURE;}printf("Display %s has %d %s:\n", display_name, monitors, (monitors > 1) ? "monitors" : "monitor");/* Loop over each monitor. */for (int i = 0; i < monitors; i++) {GdkMonitor *monitor = gdk_display_get_monitor(display, i);if (!monitor)continue;GdkRectangle geometry, workarea;gdk_monitor_get_geometry(monitor, &geometry);gdk_monitor_get_workarea(monitor, &workarea);const char *manufacturer = gdk_monitor_get_manufacturer(monitor);const char *model = gdk_monitor_get_model(monitor);gboolean primary = gdk_monitor_is_primary(monitor);int rate = gdk_monitor_get_refresh_rate(monitor);int scale = gdk_monitor_get_scale_factor(monitor);int width_mm = gdk_monitor_get_width_mm(monitor);int height_mm = gdk_monitor_get_height_mm(monitor);printf("Monitor %d%s:\n", i, (primary) ? " (Primary monitor)" : "");printf(" Manufacturer: %s\n", (manufacturer && *manufacturer) ? manufacturer : "(unknown manufacturer)");printf(" Model: %s\n", (model && *model) ? model : "(unknown model)");printf(" Physical size: %d mm by %d mm\n", width_mm, height_mm);printf(" Pixel size: %d x %d pixels\n", geometry.width * scale, geometry.height * scale);printf(" Refresh rate: %.1f Hz\n", rate/1000.0);printf(" Viewport: (%d, %d) - (%d, %d)\n", geometry.x, geometry.y, geometry.x + geometry.width, geometry.y + geometry.height);printf(" Work area: (%d, %d) - (%d, %d)\n", workarea.x, workarea.y, workarea.x + workarea.width, workarea.y + workarea.height);fflush(stdout);}return EXIT_SUCCESS;}On my HP EliteBook 840 G4 running Cinnamon desktop environment, the output isDisplay :0 has 1 monitor:Monitor 0 (Primary monitor):Manufacturer: (unknown manufacturer)Model: eDP-1Physical size: 309 mm by 173 mmPixel size: 1920 x 1080 pixelsRefresh rate: 60.0 HzViewport: (0, 0) - (1920, 1080)Work area: (0, 0) - (1920, 1054)If you want, you can "anonymize" the Manufacturer and Model. I included them, because I'd like to know whether one could use the Manufacturer and Model strings in a menu list, for example as "ManufacturerModel(pixel size,refresh rate;physical size)", or whether "MonitorN(pixel size,refresh rate;physical size)" is more human-friendly.Pixel size, Viewport, and Work area are very important. For HiDPI displays (4K and such), the Pixel size should be correct, but Viewport and Work area should reflect the application size, i.e. can be smaller for HiDPI displays; this too is expected. Work area is the part of the Viewport that is not excluded from application windows. In other words, the area in Viewport but not in Work area is taken up by panels and stuff like the VU-Bar thingy.Iassume, and wish to confirm, that in multi-monitor setups, the Viewports do not coincide. (The first point is inclusive, latter point exclusive; I deliberately chose this to make size calculation in ones head easier. One really should subtract one.) That is, they may overlap if the desktop view in them overlaps. The coordinates of each viewport should reflect the displays' relative views of the entire desktop.If so, it is just a matter of moving the displayed "window" where the bars are drawn to the proper monitor.I do suspect that the easiest way to make the widget work better is to have it present a simple pop-up menu, where the default (or command-line selected) options are (audio source and meter placement as drop-down lists, withShow,Cancel, andQuit), and let the user drag that menu to the display they want to use. Closing it wouldn't affect the VU-meter, unless you choose Quit. One could close the pop-up menu (by selecting Show or Cancel), without affecting the VU-meter.I'll probably need to do a second test program to verify that approach works. It would be nice in that it would let one have more than one VU-meter active, too, for example mic input and global output, just by selecting the source at startup...

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 28, 2022, 09:20:53 pm
content: Quote from: Nominal Animal on January 28, 2022, 08:40:23 pmCode:Display :0 has 2 monitors:Monitor 0 (Primary monitor):Manufacturer: AOCModel: HDMI1Physical size: 600 mm by 340 mmPixel size: 1920 x 1080 pixelsRefresh rate: 60.0 HzViewport: (0, 1003) - (1920, 2083)Work area: (0, 1003) - (1920, 2047)Monitor 1:Manufacturer: XXXModel: HDMI2Physical size: 203 mm by 361 mmPixel size: 768 x 1366 pixelsRefresh rate: 60.0 HzViewport: (1920, 0) - (2688, 1366)Work area: (1920, 0) - (2688, 1366)The layout pic shows the setup in plain ole mate-display-properties, and it represents the physical layout also. What is interesting is the different results I'm getting where the reserved area is connected to the another monitor.For example, setting '-p left -m 0' does work fine without interfering with the other monitor.<- Not placing meter correctly. Bottom of meter is in-line with bottom of mon 1.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 28, 2022, 09:35:53 pm
content: I edited the post above and updated some incorrect info.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 28, 2022, 10:14:27 pm
content: Could you check if the modifiedgui.cbelow works?It is based on the idea that the problem is not the space reservation, but where the actual vu bar area ends up, and attempts to fix that by resizing and moving the "window" after it has been mapped. Pay attention to the output: it will print"Window mispositioned at ..."or"Window size is ..."if the window position is detected to not apply correctly. (I expect it to print the former, if the vu bar ends up in the wrong monitor.)I'd like you to try the following four scenarios:./vu-bar -m 0 -p leftThis should put the vu-bar on the left edge of the primary (bottom/left) monitor./vu-bar -m 0 -p rightThis should put the vu-bar on the right edge of the primary (bottom/left) monitor./vu-bar -m 1 -p leftThis should put the vu-bar on the left edge of the secondary (top/right) monitor./vu-bar -m 1 -p rightThis should put the vu-bar on the right edge of the secondary (top/right) monitorand report whether the bar is in the correct monitor and correct location, with the correct area reserved, and whether there was any output from the program, for each of the four cases.(Dammit, I need to get me a display, instead of getting others to do my testing and debugging for me. I've been putting off getting one, since I'mverysensitive to panel/pixel/subpixel errors, and I want one with good viewing angles and color reproduction too – so IPS panel, instead of TN or VA ––, and Ireallydislike having to return a product...)Code:#define _POSIX_C_SOURCE 200809L#include <stdlib.h>#include <unistd.h>#include <locale.h>#include <signal.h>#include <gtk/gtk.h>#include <ctype.h>#include <string.h>#include <strings.h>#include <errno.h>#include "vu.h"#ifndef MAX_CHANNELS#define MAX_CHANNELS 32#endif#ifndef MAX_RATE#define MAX_RATE 250000#endifstatic volatile sig_atomic_t done = 0;static void handle_done(int signum){if (!done)done = signum;}static int install_done(int signum){struct sigaction act;memset(&act, 0, sizeof act);sigemptyset(&act.sa_mask);act.sa_handler = handle_done;act.sa_flags = SA_RESTART;if (sigaction(signum, &act, NULL) == -1)return errno;return 0;}struct tickmark {float amplitude;float red;float green;float blue;};enum placement {PLACEMENT_LEFT = 1,PLACEMENT_RIGHT = 2,PLACEMENT_TOP = 3,PLACEMENT_BOTTOM = 4};static const char *server = NULL;static const char *device = NULL;static int channels = 2;static int rate = 48000;static int updates = 60;static int bar_size = 4;static int bar_space = 3;static int display_monitor = -1;static enum placement display_placement = PLACEMENT_RIGHT;static float *peak_line = NULL;static float *peak = NULL;static float decay_line = 0.99f;static float decay = 0.95f;static float red_limit = 0.891251f; /* Amplitude within 1 dB of clipping */static float green_limit = 0.707946f; /* Amplitude within 3 dB of clipping */static struct tickmark tickmarks[] = {{ .amplitude = 0.891251f, .red = 1.0f, .green = 0.00f, .blue = 0.0f }, /* 1 dB */{ .amplitude = 0.794328f, .red = 1.0f, .green = 1.00f, .blue = 0.0f }, /* 2 dB */{ .amplitude = 0.707946f, .red = 0.0f, .green = 1.00f, .blue = 0.0f }, /* 3 dB */{ .amplitude = 0.630957f, .red = 0.0f, .green = 0.95f, .blue = 0.0f }, /* 4 dB */{ .amplitude = 0.562341f, .red = 0.0f, .green = 0.90f, .blue = 0.0f }, /* 5 dB */{ .amplitude = 0.501187f, .red = 0.0f, .green = 0.85f, .blue = 0.0f }, /* 6 dB */{ .amplitude = 0.446684f, .red = 0.0f, .green = 0.80f, .blue = 0.0f }, /* 7 dB */{ .amplitude = 0.398107f, .red = 0.0f, .green = 0.75f, .blue = 0.0f }, /* 8 dB */{ .amplitude = 0.354813f, .red = 0.0f, .green = 0.70f, .blue = 0.0f }, /* 9 dB */{ .amplitude = 0.316228f, .red = 0.0f, .green = 0.65f, .blue = 0.0f }, /* 10 dB */{ .amplitude = -1.0f }};static gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */GdkRectangle area;gtk_widget_get_clip(widget, &area);cairo_save(cr);cairo_set_source_rgb(cr, 0.0,0.0,0.0);cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);cairo_paint(cr);for (int i = 0; i < channels; i++) {if (peak >= red_limit)cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);elseif (peak <= green_limit)cairo_set_source_rgb(cr, 0.0, 0.5 + 0.5*peak/green_limit, 0.0);else {const double c = (peak - green_limit) / (red_limit - green_limit);cairo_set_source_rgb(cr, c, 1.0-c, 0.0);}const double c = (peak < 0.0f) ? 0.0 : (peak < 1.0f) ? peak : 1.0;if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {cairo_rectangle(cr, area.x + bar_space + i * (bar_space + bar_size),area.y + bar_space + (1.0 - c)*(area.height - 2*bar_space),bar_size, c*area.height);cairo_fill(cr);} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {cairo_rectangle(cr, area.x + bar_space,area.y + bar_space + i * (bar_space + bar_size),c * (area.width - 2*bar_space), bar_size);cairo_fill(cr);}}cairo_set_line_width(cr, 1.0);if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int y = area.y + bar_space + (1.0f - tickmarks.amplitude)*(area.height - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, area.x + 2, y);cairo_line_to(cr, area.x + area.width - 2, y);cairo_stroke(cr);}} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int x = area.x + bar_space + tickmarks.amplitude*(area.width - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, x, area.y + 2);cairo_line_to(cr, x, area.y + area.width - 2);cairo_stroke(cr);}}cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);cairo_set_line_width(cr, 2.0);for (int i = 0; i < channels; i++) {const double c = (peak_line < 0.0f) ? 0.0 : (peak_line < 1.0f) ? peak_line : 1.0;if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {const int y = area.y + bar_space + (1.0f - c)*(area.height - 2*bar_space);cairo_move_to(cr, area.x + i *(bar_space + bar_size), y);cairo_line_to(cr, area.x + (i+1)*(bar_space + bar_size), y);cairo_stroke(cr);} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {const int x = area.x + bar_space + c*(area.width - 2*bar_space);cairo_move_to(cr, x, area.y + i *(bar_space + bar_size));cairo_line_to(cr, x, area.y + (i+1)*(bar_space + bar_size));cairo_stroke(cr);}}cairo_restore(cr);return TRUE;}static gboolean tick(GtkWidget *widget, GdkFrameClock *fclk, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */(void)fclk;if (done) {gtk_window_close(GTK_WINDOW(widget));return G_SOURCE_REMOVE;}float new_peak;if (vu_peak(new_peak, channels) == channels) {for (int c = 0; c < channels; c++) {peak *= decay;peak = (new_peak > peak) ? new_peak : peak;peak_line *= decay_line;peak_line = (new_peak > peak_line) ? new_peak : peak_line;}gtk_widget_queue_draw(widget);}return G_SOURCE_CONTINUE;}static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer user_data){(void)user_data; (void)old_screen; /* Silence unused parameter warning; generates no code */gtk_widget_set_visual(widget, gdk_screen_get_system_visual(gtk_widget_get_screen(widget)));}static void place(GdkRectangle *to, gulong *reserve){if (!to || !reserve)return;to->x = 0;to->y = 0;to->width = bar_space + (bar_size + bar_space) * channels;to->height = bar_space + (bar_size + bar_space) * channels;reserve[ 0] = 0; /* left */reserve[ 1] = 0; /* right */reserve[ 2] = 0; /* top */reserve[ 3] = 0; /* bottom */reserve[ 4] = 0; /* left_start_y */reserve[ 5] = 0; /* left_end_y */reserve[ 6] = 0; /* right_start_y */reserve[ 7] = 0; /* right_end_y */reserve[ 8] = 0; /* top_start_x */reserve[ 9] = 0; /* top_end_x */reserve = 0; /* bottom_start_x */reserve = 0; /* bottom_end_x */GdkDisplay *d = gdk_display_get_default();GdkMonitor *m = gdk_display_get_monitor(d, display_monitor);GdkRectangle w;if (!m)m = gdk_display_get_primary_monitor(d);if (!m)return;gdk_monitor_get_workarea(m, &w);switch (display_placement) {case PLACEMENT_LEFT:to->x = w.x;to->height = w.height;reserve = to->width;reserve = w.y;reserve = w.y + w.height - 1;return;case PLACEMENT_RIGHT:to->x = w.x + w.width - to->width;to->height = w.height;reserve = to->width;reserve = w.y;reserve = w.y + w.height - 1;return;case PLACEMENT_TOP:to->y = w.y;to->width = w.width;reserve = to->height;reserve = w.x;reserve = w.x + w.width - 1;return;case PLACEMENT_BOTTOM:to->y = w.y + w.height - to->height;to->width = w.width;reserve = to->height;reserve = w.x;reserve = w.x + w.width - 1;return;}}static void activate(GtkApplication *app, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */gulong reserve = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };/* Compute where to place the window */GdkRectangle pos;place(&pos, reserve);/* Create window */GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDOW(window), "VU-Bar");gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DOCK);gtk_window_set_icon_name(GTK_WINDOW(window), "multimedia-volume-control");gtk_window_set_default_size(GTK_WINDOW(window), pos.width, pos.height);gtk_window_resize(GTK_WINDOW(window), pos.width, pos.height);gtk_window_set_resizable(GTK_WINDOW(window), TRUE);gtk_window_move(GTK_WINDOW(window), pos.x, pos.y);gtk_window_set_decorated(GTK_WINDOW(window), FALSE);gtk_widget_add_tick_callback(window, tick, NULL, NULL);gtk_widget_set_app_paintable(window, TRUE);g_signal_connect(window, "draw", G_CALLBACK(draw), NULL);g_signal_connect(window, "screen-changed", G_CALLBACK(screen_changed), NULL);gtk_application_add_window(app, GTK_WINDOW(window));gtk_widget_show_all(window);gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);gtk_window_resize(GTK_WINDOW(window), 1, 1);gtk_window_move(GTK_WINDOW(window), pos.x, pos.y);GdkWindow *w = gtk_widget_get_window(window);if (w) {GdkAtom strut = gdk_atom_intern_static_string("_NET_WM_STRUT");GdkAtom partial = gdk_atom_intern_static_string("_NET_WM_STRUT_PARTIAL");GdkAtom cardinal = gdk_atom_intern_static_string("CARDINAL");gdk_property_change(w, strut, cardinal, 32, GDK_PROP_MODE_REPLACE, (guchar *)reserve, 4);gdk_property_change(w, partial, cardinal, 32, GDK_PROP_MODE_REPLACE, (guchar *)reserve, 12);}gtk_window_move(GTK_WINDOW(window), pos.x, pos.y);gtk_window_resize(GTK_WINDOW(window), pos.width, pos.height);// Check if the window position matches the required position.{gint x, y, w, h;gtk_window_get_position(GTK_WINDOW(window), &x, &y);gtk_window_get_size(GTK_WINDOW(window), &w, &h);if (x != pos.x || y != pos.y)fprintf(stderr, "Window mispositioned at (%d,%d) instead of at (%d, %d) as requested.\n", x, y, pos.x, pos.y);if (w != pos.width || h != pos.height)fprintf(stderr, "Window size is %dx%d instead of the requested %dx%d\n", w, h, pos.width, pos.height);}}static const char *skip_lws(const char *from){if (!from)return NULL;while (isspace((unsigned char)(*from)))from++;return from;}static const char *parse_int(const char *from, int *to){const char *next = from;long val;if (!from || *from == '\0') {errno = EINVAL;return NULL;}errno = 0;val = strtol(from, (char **)(&next), 0);if (errno)return NULL;if (next == from) {errno = EINVAL;return NULL;}if ((long)(int)(val) != val) {errno = ERANGE;return NULL;}if (to)*to = val;errno = 0;return next;}int usage(const char *arg0){fprintf(stderr, "\n");fprintf(stderr, "Usage: %s -h | --help\n", arg0);fprintf(stderr, " %s [ OPTIONS ]\n", arg0);fprintf(stderr, "Options:\n");fprintf(stderr, " -s SERVER PulseAudio server\n");fprintf(stderr, " -d DEVICE Source to monitor\n");fprintf(stderr, " -c CHANNELS Number of channels\n");fprintf(stderr, " -r RATE Samples per second\n");fprintf(stderr, " -u COUNT Peak calculations per second\n");fprintf(stderr, " -m MONITOR Display monitor number\n");fprintf(stderr, " -p WHERE Meter placement on display\n");fprintf(stderr, " -B PIXELS Bar thickness in pixels\n");fprintf(stderr, " -S PIXELS Bar spacing in pixels\n");fprintf(stderr, "Placement:\n");fprintf(stderr, " -p left Left edge of monitor\n");fprintf(stderr, " -p right Right edge of monitor\n");fprintf(stderr, " -p top Top edge of monitor\n");fprintf(stderr, " -p bottom Bottom edge of monitor\n");fprintf(stderr, "\n");return EXIT_SUCCESS;}int main(int argc, char *argv[]){const char *arg0 = (argc > 0 && argv && argv && argv) ? argv : "(this)";const char *p;int opt, val;setlocale(LC_ALL, "");if (argc > 1 && !strcmp(argv, "--help"))return usage(arg0);gtk_init(&argc, &argv);while ((opt = getopt(argc, argv, "hs:d:c:r:u:m:p:B:S:")) != -1) {switch (opt) {case 'h':return usage(arg0);case 's':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))server = NULL;elseserver = optarg;break;case 'd':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))device = NULL;elsedevice = optarg;break;case 'c':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > MAX_CHANNELS) {fprintf(stderr, "%s: Invalid number of channels.\n", optarg);return EXIT_FAILURE;}channels = val;break;case 'r':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 128 || val > MAX_RATE) {fprintf(stderr, "%s: Invalid sample rate.\n", optarg);return EXIT_FAILURE;}rate = val;break;case 'u':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > 200) {fprintf(stderr, "%s: Invalid number of peak updates per second.\n", optarg);return EXIT_FAILURE;}updates = val;break;case 'm':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < -1) {fprintf(stderr, "%s: Invalid monitor number.\n", optarg);return EXIT_FAILURE;}display_monitor = val;break;case 'p':if (!strcasecmp(optarg, "left"))display_placement = PLACEMENT_LEFT;elseif (!strcasecmp(optarg, "right"))display_placement = PLACEMENT_RIGHT;elseif (!strcasecmp(optarg, "top"))display_placement = PLACEMENT_TOP;elseif (!strcasecmp(optarg, "bottom"))display_placement = PLACEMENT_BOTTOM;else {fprintf(stderr, "%s: Unsupported placement.\n", optarg);return EXIT_FAILURE;}break;case 'B':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1) {fprintf(stderr, "%s: Invalid bar thickness in pixels.\n", optarg);return EXIT_FAILURE;}bar_size = val;break;case 'S':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 0) {fprintf(stderr, "%s: Invalid bar spacing in pixels.\n", optarg);return EXIT_FAILURE;}bar_space = val;break;case '?':/* getopt() has already printed an error message. */return EXIT_FAILURE;default:/* Bug catcher: This should never occur. */fprintf(stderr, "getopt() returned %d ('%c')!\n", opt, opt);return EXIT_FAILURE;}}if (install_done(SIGINT) ||install_done(SIGHUP) ||install_done(SIGTERM) ||install_done(SIGQUIT)) {fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));return EXIT_FAILURE;}if (optind < argc) {fprintf(stderr, "%s: Unsupported parameter.\n", argv);return EXIT_FAILURE;}GtkApplication *app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE);if (!app) {fprintf(stderr, "Cannot start GTK+ application.\n");return EXIT_FAILURE;}g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);size_t samples = rate / updates;if (samples < 1)samples = 1;val = vu_start(server, "vu-bar", device, "VU monitor", channels, rate, samples);if (val) {fprintf(stderr, "Cannot monitor audio source: %s.\n", vu_error(val));g_object_unref(app);return EXIT_FAILURE;}peak = calloc((size_t)channels * sizeof (float), samples);peak_line = calloc((size_t)channels * sizeof (float), samples);if (!peak) {fprintf(stderr, "Out of memory.\n");g_object_unref(app);vu_stop();return EXIT_FAILURE;}val = g_application_run(G_APPLICATION(app), 0, NULL);g_object_unref(app);vu_stop();return val;}

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 28, 2022, 11:44:31 pm
content: Quote from: Nominal Animal on January 28, 2022, 10:14:27 pmIt's taken me 25 years to mount a 2nd monitor. Who knows, in another 25 years, I might be ready to try the triple monitor setup you all the kids rave about.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 28, 2022, 11:56:48 pm
content: Quote from: Nominal Animal on January 28, 2022, 10:14:27 pmMonitor 1:'-m 1 -p left' and '-m 1 -p right' work perfect on monitor 1 (the secondary monitor). And they push windows around as they should.Monitor 0:'-m 0 -p left' pushes the windows away in the correct spot but places the bottom of the meter too high (in-line with mon1?), and meter runs off past the top of the screen.'-m 0 -p right' pushes the windows away on the wrong monitor (mon 1) but places the meter on the correct monitor but too high as indicated above.Hope that makes sense.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 29, 2022, 12:10:41 am
content: Could you retry with the attachedgui.c? And also report ifvu-baroutputs anything in the terminal?Code:#define _POSIX_C_SOURCE 200809L#include <stdlib.h>#include <unistd.h>#include <locale.h>#include <signal.h>#include <gtk/gtk.h>#include <ctype.h>#include <string.h>#include <strings.h>#include <errno.h>#include "vu.h"#ifndef MAX_CHANNELS#define MAX_CHANNELS 32#endif#ifndef MAX_RATE#define MAX_RATE 250000#endifstatic volatile sig_atomic_t done = 0;static void handle_done(int signum){if (!done)done = signum;}static int install_done(int signum){struct sigaction act;memset(&act, 0, sizeof act);sigemptyset(&act.sa_mask);act.sa_handler = handle_done;act.sa_flags = SA_RESTART;if (sigaction(signum, &act, NULL) == -1)return errno;return 0;}struct tickmark {float amplitude;float red;float green;float blue;};enum placement {PLACEMENT_LEFT = 1,PLACEMENT_RIGHT = 2,PLACEMENT_TOP = 3,PLACEMENT_BOTTOM = 4};static const char *server = NULL;static const char *device = NULL;static int channels = 2;static int rate = 48000;static int updates = 60;static int bar_size = 4;static int bar_space = 3;static int display_monitor = -1;static enum placement display_placement = PLACEMENT_RIGHT;static float *peak_line = NULL;static float *peak = NULL;static float decay_line = 0.99f;static float decay = 0.95f;static float red_limit = 0.891251f; /* Amplitude within 1 dB of clipping */static float green_limit = 0.707946f; /* Amplitude within 3 dB of clipping */static struct tickmark tickmarks[] = {{ .amplitude = 0.891251f, .red = 1.0f, .green = 0.00f, .blue = 0.0f }, /* 1 dB */{ .amplitude = 0.794328f, .red = 1.0f, .green = 1.00f, .blue = 0.0f }, /* 2 dB */{ .amplitude = 0.707946f, .red = 0.0f, .green = 1.00f, .blue = 0.0f }, /* 3 dB */{ .amplitude = 0.630957f, .red = 0.0f, .green = 0.95f, .blue = 0.0f }, /* 4 dB */{ .amplitude = 0.562341f, .red = 0.0f, .green = 0.90f, .blue = 0.0f }, /* 5 dB */{ .amplitude = 0.501187f, .red = 0.0f, .green = 0.85f, .blue = 0.0f }, /* 6 dB */{ .amplitude = 0.446684f, .red = 0.0f, .green = 0.80f, .blue = 0.0f }, /* 7 dB */{ .amplitude = 0.398107f, .red = 0.0f, .green = 0.75f, .blue = 0.0f }, /* 8 dB */{ .amplitude = 0.354813f, .red = 0.0f, .green = 0.70f, .blue = 0.0f }, /* 9 dB */{ .amplitude = 0.316228f, .red = 0.0f, .green = 0.65f, .blue = 0.0f }, /* 10 dB */{ .amplitude = -1.0f }};static gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */GdkRectangle area;gtk_widget_get_clip(widget, &area);cairo_save(cr);cairo_set_source_rgb(cr, 0.0,0.0,0.0);cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);cairo_paint(cr);for (int i = 0; i < channels; i++) {if (peak >= red_limit)cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);elseif (peak <= green_limit)cairo_set_source_rgb(cr, 0.0, 0.5 + 0.5*peak/green_limit, 0.0);else {const double c = (peak - green_limit) / (red_limit - green_limit);cairo_set_source_rgb(cr, c, 1.0-c, 0.0);}const double c = (peak < 0.0f) ? 0.0 : (peak < 1.0f) ? peak : 1.0;if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {cairo_rectangle(cr, area.x + bar_space + i * (bar_space + bar_size),area.y + bar_space + (1.0 - c)*(area.height - 2*bar_space),bar_size, c*area.height);cairo_fill(cr);} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {cairo_rectangle(cr, area.x + bar_space,area.y + bar_space + i * (bar_space + bar_size),c * (area.width - 2*bar_space), bar_size);cairo_fill(cr);}}cairo_set_line_width(cr, 1.0);if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int y = area.y + bar_space + (1.0f - tickmarks.amplitude)*(area.height - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, area.x + 2, y);cairo_line_to(cr, area.x + area.width - 2, y);cairo_stroke(cr);}} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {for (int i = 0; tickmarks.amplitude >= 0.0f; i++) {const int x = area.x + bar_space + tickmarks.amplitude*(area.width - 2*bar_space);cairo_set_source_rgb(cr, tickmarks.red, tickmarks.green, tickmarks.blue);cairo_move_to(cr, x, area.y + 2);cairo_line_to(cr, x, area.y + area.width - 2);cairo_stroke(cr);}}cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);cairo_set_line_width(cr, 2.0);for (int i = 0; i < channels; i++) {const double c = (peak_line < 0.0f) ? 0.0 : (peak_line < 1.0f) ? peak_line : 1.0;if (display_placement == PLACEMENT_LEFT || display_placement == PLACEMENT_RIGHT) {const int y = area.y + bar_space + (1.0f - c)*(area.height - 2*bar_space);cairo_move_to(cr, area.x + i *(bar_space + bar_size), y);cairo_line_to(cr, area.x + (i+1)*(bar_space + bar_size), y);cairo_stroke(cr);} elseif (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {const int x = area.x + bar_space + c*(area.width - 2*bar_space);cairo_move_to(cr, x, area.y + i *(bar_space + bar_size));cairo_line_to(cr, x, area.y + (i+1)*(bar_space + bar_size));cairo_stroke(cr);}}cairo_restore(cr);return TRUE;}static gboolean tick(GtkWidget *widget, GdkFrameClock *fclk, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */(void)fclk;if (done) {gtk_window_close(GTK_WINDOW(widget));return G_SOURCE_REMOVE;}float new_peak;if (vu_peak(new_peak, channels) == channels) {for (int c = 0; c < channels; c++) {peak *= decay;peak = (new_peak > peak) ? new_peak : peak;peak_line *= decay_line;peak_line = (new_peak > peak_line) ? new_peak : peak_line;}gtk_widget_queue_draw(widget);}return G_SOURCE_CONTINUE;}static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer user_data){(void)user_data; (void)old_screen; /* Silence unused parameter warning; generates no code */gtk_widget_set_visual(widget, gdk_screen_get_system_visual(gtk_widget_get_screen(widget)));}static void place(GdkRectangle *to, gulong *reserve){if (!to || !reserve)return;to->x = 0;to->y = 0;to->width = bar_space + (bar_size + bar_space) * channels;to->height = bar_space + (bar_size + bar_space) * channels;reserve[ 0] = 0; /* left */reserve[ 1] = 0; /* right */reserve[ 2] = 0; /* top */reserve[ 3] = 0; /* bottom */reserve[ 4] = 0; /* left_start_y */reserve[ 5] = 0; /* left_end_y */reserve[ 6] = 0; /* right_start_y */reserve[ 7] = 0; /* right_end_y */reserve[ 8] = 0; /* top_start_x */reserve[ 9] = 0; /* top_end_x */reserve = 0; /* bottom_start_x */reserve = 0; /* bottom_end_x */GdkDisplay *d = gdk_display_get_default();GdkMonitor *m = gdk_display_get_monitor(d, display_monitor);GdkRectangle w;if (!m)m = gdk_display_get_primary_monitor(d);if (!m)return;gdk_monitor_get_workarea(m, &w);switch (display_placement) {case PLACEMENT_LEFT:to->x = w.x;to->height = w.height;reserve = to->width;reserve = w.y;reserve = w.y + w.height - 1;break;case PLACEMENT_RIGHT:to->x = w.x + w.width - to->width;to->height = w.height;reserve = to->width;reserve = w.y;reserve = w.y + w.height - 1;break;case PLACEMENT_TOP:to->y = w.y;to->width = w.width;reserve = to->height;reserve = w.x;reserve = w.x + w.width - 1;break;case PLACEMENT_BOTTOM:to->y = w.y + w.height - to->height;to->width = w.width;reserve = to->height;reserve = w.x;reserve = w.x + w.width - 1;break;}reserve = to->x;reserve = to->y;reserve = to->x + to->width - 1;reserve = to->y + to->height - 1;}static void activate(GtkApplication *app, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */gulong reserve = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };/* Compute where to place the window */GdkRectangle pos;place(&pos, reserve);/* Create window */GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDOW(window), "VU-Bar");gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DOCK);gtk_window_set_icon_name(GTK_WINDOW(window), "multimedia-volume-control");gtk_window_set_default_size(GTK_WINDOW(window), pos.width, pos.height);gtk_window_resize(GTK_WINDOW(window), pos.width, pos.height);gtk_window_set_resizable(GTK_WINDOW(window), TRUE);gtk_window_move(GTK_WINDOW(window), pos.x, pos.y);gtk_window_set_decorated(GTK_WINDOW(window), FALSE);gtk_widget_add_tick_callback(window, tick, NULL, NULL);gtk_widget_set_app_paintable(window, TRUE);g_signal_connect(window, "draw", G_CALLBACK(draw), NULL);g_signal_connect(window, "screen-changed", G_CALLBACK(screen_changed), NULL);gtk_application_add_window(app, GTK_WINDOW(window));gtk_widget_show_all(window);gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);GdkWindow *w = gtk_widget_get_window(window);if (w) {GdkAtom strut = gdk_atom_intern_static_string("_NET_WM_STRUT");GdkAtom partial = gdk_atom_intern_static_string("_NET_WM_STRUT_PARTIAL");GdkAtom cardinal = gdk_atom_intern_static_string("CARDINAL");gdk_property_change(w, strut, cardinal, 32, GDK_PROP_MODE_REPLACE, (guchar *)reserve, 4);gdk_property_change(w, partial, cardinal, 32, GDK_PROP_MODE_REPLACE, (guchar *)reserve, 12);}// Check if the window position matches the required position.{gint x, y, w, h;gtk_window_get_position(GTK_WINDOW(window), &x, &y);gtk_window_get_size(GTK_WINDOW(window), &w, &h);if (x != pos.x || y != pos.y)fprintf(stderr, "Window mispositioned at (%d,%d) instead of at (%d, %d) as requested.\n", x, y, pos.x, pos.y);if (w != pos.width || h != pos.height)fprintf(stderr, "Window size is %dx%d instead of the requested %dx%d\n", w, h, pos.width, pos.height);}}static const char *skip_lws(const char *from){if (!from)return NULL;while (isspace((unsigned char)(*from)))from++;return from;}static const char *parse_int(const char *from, int *to){const char *next = from;long val;if (!from || *from == '\0') {errno = EINVAL;return NULL;}errno = 0;val = strtol(from, (char **)(&next), 0);if (errno)return NULL;if (next == from) {errno = EINVAL;return NULL;}if ((long)(int)(val) != val) {errno = ERANGE;return NULL;}if (to)*to = val;errno = 0;return next;}int usage(const char *arg0){fprintf(stderr, "\n");fprintf(stderr, "Usage: %s -h | --help\n", arg0);fprintf(stderr, " %s [ OPTIONS ]\n", arg0);fprintf(stderr, "Options:\n");fprintf(stderr, " -s SERVER PulseAudio server\n");fprintf(stderr, " -d DEVICE Source to monitor\n");fprintf(stderr, " -c CHANNELS Number of channels\n");fprintf(stderr, " -r RATE Samples per second\n");fprintf(stderr, " -u COUNT Peak calculations per second\n");fprintf(stderr, " -m MONITOR Display monitor number\n");fprintf(stderr, " -p WHERE Meter placement on display\n");fprintf(stderr, " -B PIXELS Bar thickness in pixels\n");fprintf(stderr, " -S PIXELS Bar spacing in pixels\n");fprintf(stderr, "Placement:\n");fprintf(stderr, " -p left Left edge of monitor\n");fprintf(stderr, " -p right Right edge of monitor\n");fprintf(stderr, " -p top Top edge of monitor\n");fprintf(stderr, " -p bottom Bottom edge of monitor\n");fprintf(stderr, "\n");return EXIT_SUCCESS;}int main(int argc, char *argv[]){const char *arg0 = (argc > 0 && argv && argv && argv) ? argv : "(this)";const char *p;int opt, val;setlocale(LC_ALL, "");if (argc > 1 && !strcmp(argv, "--help"))return usage(arg0);gtk_init(&argc, &argv);while ((opt = getopt(argc, argv, "hs:d:c:r:u:m:p:B:S:")) != -1) {switch (opt) {case 'h':return usage(arg0);case 's':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))server = NULL;elseserver = optarg;break;case 'd':if (!optarg || optarg == '\0' || !strcmp(optarg, "default"))device = NULL;elsedevice = optarg;break;case 'c':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > MAX_CHANNELS) {fprintf(stderr, "%s: Invalid number of channels.\n", optarg);return EXIT_FAILURE;}channels = val;break;case 'r':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 128 || val > MAX_RATE) {fprintf(stderr, "%s: Invalid sample rate.\n", optarg);return EXIT_FAILURE;}rate = val;break;case 'u':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1 || val > 200) {fprintf(stderr, "%s: Invalid number of peak updates per second.\n", optarg);return EXIT_FAILURE;}updates = val;break;case 'm':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < -1) {fprintf(stderr, "%s: Invalid monitor number.\n", optarg);return EXIT_FAILURE;}display_monitor = val;break;case 'p':if (!strcasecmp(optarg, "left"))display_placement = PLACEMENT_LEFT;elseif (!strcasecmp(optarg, "right"))display_placement = PLACEMENT_RIGHT;elseif (!strcasecmp(optarg, "top"))display_placement = PLACEMENT_TOP;elseif (!strcasecmp(optarg, "bottom"))display_placement = PLACEMENT_BOTTOM;else {fprintf(stderr, "%s: Unsupported placement.\n", optarg);return EXIT_FAILURE;}break;case 'B':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 1) {fprintf(stderr, "%s: Invalid bar thickness in pixels.\n", optarg);return EXIT_FAILURE;}bar_size = val;break;case 'S':p = skip_lws(parse_int(optarg, &val));if (!p || *p != '\0' || val < 0) {fprintf(stderr, "%s: Invalid bar spacing in pixels.\n", optarg);return EXIT_FAILURE;}bar_space = val;break;case '?':/* getopt() has already printed an error message. */return EXIT_FAILURE;default:/* Bug catcher: This should never occur. */fprintf(stderr, "getopt() returned %d ('%c')!\n", opt, opt);return EXIT_FAILURE;}}if (install_done(SIGINT) ||install_done(SIGHUP) ||install_done(SIGTERM) ||install_done(SIGQUIT)) {fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));return EXIT_FAILURE;}if (optind < argc) {fprintf(stderr, "%s: Unsupported parameter.\n", argv);return EXIT_FAILURE;}GtkApplication *app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE);if (!app) {fprintf(stderr, "Cannot start GTK+ application.\n");return EXIT_FAILURE;}g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);size_t samples = rate / updates;if (samples < 1)samples = 1;val = vu_start(server, "vu-bar", device, "VU monitor", channels, rate, samples);if (val) {fprintf(stderr, "Cannot monitor audio source: %s.\n", vu_error(val));g_object_unref(app);return EXIT_FAILURE;}peak = calloc((size_t)channels * sizeof (float), samples);peak_line = calloc((size_t)channels * sizeof (float), samples);if (!peak) {fprintf(stderr, "Out of memory.\n");g_object_unref(app);vu_stop();return EXIT_FAILURE;}val = g_application_run(G_APPLICATION(app), 0, NULL);g_object_unref(app);vu_stop();return val;}

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 29, 2022, 12:28:01 am
content: '-m 0 -p left' pushes no windows. Places the meter too high on the left side of monitor 0'-m 0 -p right' pushes no windows. Places the meter too high on the right hand side of monitor 0'-m 1 -p left' places the meter on the left of m1, pushes no windows but does move the windows from the top 1/3 of the screen ofm0'-m 1 -p right' works as it should. Meter on the right of m1, windows are pushed out of it's way.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 29, 2022, 12:36:39 am
content: I just fired up virtual box and gave it two small monitors (640x480). What I found was the params seem to work fine while ever the two monitors are dead in-line horizontally. When I bumped mon1 up a bit, all bets were off.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 29, 2022, 01:46:33 am
content: What about the output?

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 29, 2022, 02:30:06 am
content: Quote from: Nominal Animal on January 29, 2022, 01:46:33 amCommand line output is very tight-lipped.No output.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 29, 2022, 03:44:12 am
content: Okay, I think I understand the problem now.I am using the_NET_WM_STRUT_PARTIALapplication window property (with its simpler, older_NET_WM_STRUTas a backup) to reserve a region for the VU bar, and didn't realize its limitations correctly.Let's call the union of all your monitors, an axis-aligned rectangle, the desktop rectangle. This rectangle contains all of your visual desktop, plus possibly some un-displayed areas.The_NET_WM_STRUT_PARTIALproperty can only reserve a rectangular area with at least one edge at the edge of the desktop rectangle.Some of the regions are however miscalculated right now, because the desired region is defined with respect to that desktop rectangle.A bit of logic is needed to choose how to define the reserved rectangle, because for example in your case, if one wants the vu-bar in either monitor on the shared edge, that can be done by extending a narrow rectangle from the bottom or top of the desktop rectangle. It will need some testing, but theoretically all edges for up to four monitors should be supportable, if there are no panels. The fact that already existing reservations affect the placing of the vu-bar makes it a bit more complicated.I'll do some testing, then post the next version for you to try; probably tomorrow.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 31, 2022, 06:49:04 am
content: Funnily enough, the 'format 32' for window manager properties like_NET_WM_PARTIAL_STRUT(and all others) doesnotmean 32-bit unsigned integers; it meansunsigned longon the current architecture.That's what the deleted post was about: I thought I might have a bug in usinggulong, but no,format==32usesgulongelements in the array.So, the next thing is to explore how the struts (reserved areas on a workspace) actually work. Could you please compile and run the followingstrut.c:Code:// SPDX-License-Identifier: CC0-1.0//// Save this file as 'strut.c', and compile to './strut' using for example// gcc -Wall -O2 `pkg-config --cflags gtk+-3.0` strut.c `pkg-config --libs gtk+-3.0` -o strut//#include <stdlib.h>#include <stdint.h>#include <gdk/gdk.h>#include <gtk/gtk.h>#include <stdio.h>#include <errno.h>enum {ANCHOR_NONE = 0,ANCHOR_LEFT = 1,ANCHOR_TOP = 2,ANCHOR_RIGHT = 3,ANCHOR_BOTTOM = 4,};#define PARTIAL_STRUT_LEFT 0#define PARTIAL_STRUT_RIGHT 1#define PARTIAL_STRUT_TOP 2#define PARTIAL_STRUT_BOTTOM 3#define PARTIAL_STRUT_LEFT_YMIN 4#define PARTIAL_STRUT_LEFT_YMAX 5#define PARTIAL_STRUT_RIGHT_YMIN 6#define PARTIAL_STRUT_RIGHT_YMAX 7#define PARTIAL_STRUT_TOP_XMIN 8#define PARTIAL_STRUT_TOP_XMAX 9#define PARTIAL_STRUT_BOTTOM_XMIN 10#define PARTIAL_STRUT_BOTTOM_XMAX 11struct rect {int xmin;int ymin;int xmax;int ymax;};struct monitor {struct rect geometry;struct rect workarea;};static GdkDisplay *display = NULL;static struct rect desktop = { .xmin = 0, .ymin = 0, .xmax = 0, .ymax = 0 };static int monitors = 0;static struct monitor *monitor = NULL;static int anchor = ANCHOR_NONE;static struct rect strut = { .xmin = -1, .ymin = -1, .xmax = -1, .ymax = -1 };static gulong partial_strut = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };static int rescan_monitors(void){desktop.xmin = 0;desktop.ymin = 0;desktop.xmax = 0;desktop.ymax = 0;monitors = 0;if (monitor) {free(monitor);monitor = NULL;}/* Find out the maximum number of monitors */int max_monitors = (display) ? gdk_display_get_n_monitors(display) : 0;if (max_monitors < 1) {errno = ENOENT;return -1;}/* Allocate a monitor array */monitor = malloc(max_monitors * sizeof monitor);if (!monitor) {errno = ENOMEM;return -1;}for (int i = 0; i < max_monitors; i++) {GdkRectangle box;GdkMonitor *one = gdk_display_get_monitor(display, i);if (!one)continue;gdk_monitor_get_geometry(one, &box);if (box.x < 0 || box.y < 0 || box.width < 1 || box.height < 1)continue;monitor.geometry.xmin = box.x;monitor.geometry.ymin = box.y;monitor.geometry.xmax = box.x + box.width - 1;monitor.geometry.ymax = box.y + box.height - 1;if (desktop.xmax < monitor.geometry.xmax)desktop.xmax = monitor.geometry.xmax;if (desktop.ymax < monitor.geometry.ymax)desktop.ymax = monitor.geometry.ymax;gdk_monitor_get_workarea(one, &box);if (box.x < 0 || box.y < 0 || box.width < 1 || box.height < 1)continue;monitor.workarea.xmin = box.x;monitor.workarea.ymin = box.y;monitor.workarea.xmax = box.x + box.width - 1;monitor.workarea.ymax = box.y + box.height - 1;if (monitor.workarea.xmin < monitor.geometry.xmin ||monitor.workarea.xmax > monitor.geometry.xmax ||monitor.workarea.ymin < monitor.geometry.ymin ||monitor.workarea.ymax > monitor.geometry.ymax)continue;monitors++;}if (!monitors) {free(monitor);monitor = NULL;}return monitors;}static gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer user_data){(void)user_data; /* Silence unused parameter warning; generates no code */GdkRectangle area;gtk_widget_get_clip(widget, &area);cairo_save(cr);cairo_set_source_rgb(cr, 0.0,0.0,0.0);cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);cairo_paint(cr);cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);cairo_rectangle(cr, area.x, area.y, area.width, area.height);cairo_fill(cr);cairo_restore(cr);return TRUE;}static void activate(GtkApplication *app, gpointer user_data){(void)user_data; /* Silence warning about unused parameter. */GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DOCK);gtk_window_set_default_size(GTK_WINDOW(window), strut.xmax - strut.xmin + 1, strut.ymax - strut.ymin + 1);gtk_window_set_resizable(GTK_WINDOW(window), TRUE);gtk_window_resize(GTK_WINDOW(window), strut.xmax - strut.xmin + 1, strut.ymax - strut.ymin + 1);gtk_window_move(GTK_WINDOW(window), strut.xmin, strut.ymin);gtk_window_set_decorated(GTK_WINDOW(window), FALSE);gtk_application_add_window(app, GTK_WINDOW(window));gtk_widget_set_app_paintable(window, TRUE);g_signal_connect(window, "draw", G_CALLBACK(draw), NULL);g_signal_connect(window, "button-release-event", G_CALLBACK(gtk_window_close), window);gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);gtk_widget_show_all(window);GdkWindow *w = gtk_widget_get_window(window);if (w) {gdk_window_move_resize(w, strut.xmin, strut.ymin, strut.xmax - strut.xmin + 1, strut.ymax - strut.ymin + 1);GdkAtom partial = gdk_atom_intern_static_string("_NET_WM_STRUT_PARTIAL");GdkAtom cardinal = gdk_atom_intern_static_string("CARDINAL");gdk_property_change(w, partial, cardinal, 32, GDK_PROP_MODE_REPLACE, (guchar *)partial_strut, 12);fprintf(stderr, "Note: Set _NET_WM_STRUT_PARTIAL: %d %d %d %d %d %d %d %d %d %d %d %d\n",(int)partial_strut, (int)partial_strut, (int)partial_strut, (int)partial_strut,(int)partial_strut, (int)partial_strut, (int)partial_strut, (int)partial_strut,(int)partial_strut, (int)partial_strut, (int)partial_strut, (int)partial_strut);}}static int match(const char *src, const char *alt1, const char *alt2){if (!src || !alt1 || !alt2) {return 0;}while (*src) {if (*src == *alt1 || *src == *alt2) {src++;alt1++;alt2++;} else {return 0;}}return (*alt1 == '\0' || *alt2 == '\0');}static int parse_int(const char *src, int *dst){const char *end;long val;end = src;errno = 0;val = strtol(src, (char **)&end, 0);if (errno)return -1;if (end == src)return -1;while (*end == '\t' || *end == '\n' || *end == '\v' ||*end == '\f' || *end == '\r' || *end == ' ')end++;if (*end)return -1;if ((long)((int)val) != val)return -1;if (dst)*dst = (int)val;return 0;}int main(int argc, char *argv[]){const char *arg0 = (argc > 0 && argv && argv && argv) ? argv : "(this)";/* Initialize GDK. Supports display selection via --displayparameter and the DISPLAY environment variable. */if (!gdk_init_check(&argc, &argv)) {fprintf(stderr, "Cannot initialize GDK.\n");return EXIT_FAILURE;}/* Get access to the default display. */display = gdk_display_get_default();if (!display) {fprintf(stderr, "No default display.\n");return EXIT_FAILURE;}/* Rescan monitors. */int n = rescan_monitors();if (n < 0) {fprintf(stderr, "%s.\n", strerror(errno));return EXIT_FAILURE;} elseif (!n) {fprintf(stderr, "No monitors.\n");return EXIT_FAILURE;}if (argc < 2) {printf("Desktop: (%d,%d)-(%d,%d)\n", desktop.xmin, desktop.ymin,desktop.xmax, desktop.ymax);for (int i = 0; i < monitors; i++)printf("Monitor: (%d,%d)-(%d,%d) with work area (%d,%d)-(%d,%d)\n",monitor.geometry.xmin, monitor.geometry.ymin,monitor.geometry.xmax, monitor.geometry.ymax,monitor.workarea.xmin, monitor.workarea.ymin,monitor.workarea.xmax, monitor.workarea.ymax);return EXIT_SUCCESS;} elseif (argc != 6) {fprintf(stderr, "\n");fprintf(stderr, "Usage: %s -h | --help\n", arg0);fprintf(stderr, " %s\n", arg0);fprintf(stderr, " %s ANCHOR XMIN YMIN XMAX YMAX\n", arg0);fprintf(stderr, "Where ANCHOR is one of:\n");fprintf(stderr, " L or LEFT\n");fprintf(stderr, " T or TOP\n");fprintf(stderr, " R or RIGHT\n");fprintf(stderr, " B or BOTTOM\n");fprintf(stderr, "and (XMIN,YMIN)-(XMAX,YMAX) is the reserved strut area.\n");fprintf(stderr, "Run without parameters to see current desktop and monitors.\n");fprintf(stderr, "\n");return EXIT_FAILURE;}if (match(argv, "left", "LEFT") || match(argv, "l", "L"))anchor = ANCHOR_LEFT;elseif (match(argv, "top", "TOP") || match(argv, "t", "T") || match(argv, "up", "UP") || match(argv, "u", "U"))anchor = ANCHOR_TOP;elseif (match(argv, "right", "RIGHT") || match(argv, "r", "R"))anchor = ANCHOR_RIGHT;elseif (match(argv, "bottom", "BOTTOM") || match(argv, "b", "B") || match(argv, "down", "DOWN") || match(argv, "d", "D"))anchor = ANCHOR_BOTTOM;else {fprintf(stderr, "%s: Invalid anchor direction.\n", argv);return EXIT_FAILURE;}if (parse_int(argv, &strut.xmin) || strut.xmin < desktop.xmin || strut.xmin > desktop.xmax) {fprintf(stderr, "%s: Invalid strut rectangle minimum x coordinate.\n", argv);return EXIT_FAILURE;}if (parse_int(argv, &strut.ymin) || strut.ymin < desktop.ymin || strut.ymin > desktop.ymax) {fprintf(stderr, "%s: Invalid strut rectangle minimum y coordinate.\n", argv);return EXIT_FAILURE;}if (parse_int(argv, &strut.xmax) || strut.xmax < strut.xmin || strut.xmax > desktop.xmax) {fprintf(stderr, "%s: Invalid strut rectangle maximum x coordinate.\n", argv);return EXIT_FAILURE;}if (parse_int(argv, &strut.ymax) || strut.ymax < strut.ymin || strut.ymax > desktop.ymax) {fprintf(stderr, "%s: Invalid strut rectangle maximum y coordinate.\n", argv);return EXIT_FAILURE;}switch (anchor) {case ANCHOR_LEFT:partial_strut = strut.xmax + 1;partial_strut = strut.ymin;partial_strut = strut.ymax;break;case ANCHOR_RIGHT:partial_strut = desktop.xmax - strut.xmin + 1;partial_strut = strut.ymin;partial_strut = strut.ymax;break;case ANCHOR_TOP:partial_strut = strut.ymax + 1;partial_strut = strut.xmin;partial_strut = strut.xmax;break;case ANCHOR_BOTTOM:partial_strut = desktop.ymax - strut.ymin + 1;partial_strut = strut.xmin;partial_strut = strut.xmax;break;default:fprintf(stderr, "Invalid anchor.\n");return EXIT_FAILURE;}GtkApplication *app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE);if (!app) {fprintf(stderr, "Cannot start GTK+ application.\n");return EXIT_FAILURE;}g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);int val = g_application_run(G_APPLICATION(app), 0, NULL);g_object_unref(app);return val;}Run./strut --helpto see a short usage info.Run./strutto see the desktop rectangle (which is (0,0)-(xmax,ymax) always), plus the rectangles and work areas of all monitors. All monitors will reside within the desktop rectangle.A strut, or a reserved area for a panel or similar, is a rectangular region connected to one of the four edges of the desktop rectangle.Run./strut ANCHOR XMIN YMIN XMAX YMAXwhereANCHORisleft,top,right, orbottom; and the rectangular region in desktop coordinates is (XMIN,YMIN)-(XMAX,YMAX), to test the strut. It will be shown as a simple red rectangle. Clicking on the rectangle will close the test program.Now, I assume that when Ed.Kloonk runs./strut, he'll seeDesktop: (0, 0) - (2687, 2082)Monitor: (0, 1003) - (1919, 2082) with work area (0, 1003) - (1919, 2046)Monitor: (1920, 0) - (2687, 1365) with work area (1920, 0) - (2687, 1365)Here are the most interesting test cases. Note that if the desktop gets wonky, you can just press Ctrl+C in the terminal running the command, and it should revert back to normal instantly. Clicking on the red area to close the window is just a nicety, not a requirement.These should put the strut on the left edge of the bottom left monitor:./strut left 0 1003 15 2046./strut top 0 1003 15 2046These should put the strut on the top edge of the bottom left monitor:./strut left 0 1003 1919 1018./strut top 0 1003 1919 1018These should put the strut on the top edge of the top right monitor:./strut top 1920 0 2687 15./strut right 1920 0 2687 15These should put the strut on the right edge of the top right monitor:./strut top 2672 0 2687 1365./strut right 2672 0 2687 1365./strut bottom 2672 0 2687 1365These should put the strut on the bottom edge of the top right monitor:./strut left 1920 1350 2687 1365./strut right 1920 1350 2687 1365./strut bottom 1920 1350 2687 1365These should put the strut on the right edge of the bottom left monitor:./strut top 1913 1003 1919 2046./strut bottom 1913 1003 1919 2046These should put the strut on the left edge of the top right monitor:./strut top 1920 0 1935 1365./strut bottom 1920 0 1935 1365These should put the strut near the bottom edge of the bottom left monitor, but above the existing panel:./strut bottom 0 2032 1919 2047./strut left 0 2032 1919 2047I really only need to know which commands, if any, do exactly what the description says, "pushing" other windows correctly, without wonky effects.(The note the program prints is not important, unless something really wonky happens. It just describes the exact partial strut setup.)

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 06:56:05 am
content: stby

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 07:57:06 am
content: Now we're cooking with gas!It seems that I have changed the screen metrics on you. So I took the liberty of adjusting the numbers where obvious. I haven't checked my work yet.Code:Desktop: (0,0)-(2687,1732)Monitor: (0,653)-(1919,1732) with work area (0,653)-(1919,1696)Monitor: (1920,0)-(2687,1365) with work area (1920,0)-(2687,1365)with that in mind...Code:Now, I assume that when Ed.Kloonk runs ./strut, he'll seeDesktop: (0, 0) - (2687, 1732)Monitor: (0, 653) - (1919, 1732) with work area (0, 653) - (1919, 1696)Monitor: (1920, 0) - (2687, 1365) with work area (1920, 0) - (2687, 1365)Here are the most interesting test cases. Note that if the desktop gets wonky, you can just press Ctrl+C in the terminal running the command, and it should revert back to normal instantly. Clicking on the red area to close the window is just a nicety, not a requirement.These should put the strut on the left edge of the bottom left monitor:./strut left 0 653 15 1696./strut top 0 653 15 1696These should put the strut on the top edge of the bottom left monitor:./strut left 0 653 1919 1018./strut top 0 653 1919 1018* occupies 1/3 of the top of the left monitor. (might be my fault)These should put the strut on the top edge of the top right monitor:./strut top 1920 0 2687 15./strut right 1920 0 2687 15These should put the strut on the right edge of the top right monitor:./strut top 2672 0 2687 1365./strut right 2672 0 2687 1365./strut bottom 2672 0 2687 1365These should put the strut on the bottom edge of the top right monitor:./strut left 1920 1350 2687 1365./strut right 1920 1350 2687 1365./strut bottom 1920 1350 2687 1365These should put the strut on the right edge of the bottom left monitor:./strut top 1913 653 1919 1696./strut bottom 1913 653 1919 1696* these two are thinner than the others. These should put the strut on the left edge of the top right monitor:./strut top 1920 0 1935 1365./strut bottom 1920 0 1935 1365These should put the strut near the bottom edge of the bottom left monitor, but above the existing panel:./strut bottom 0 2032 1919 2047* "2032: Invalid strut rectangle minimum y coordinate."./strut left 0 2032 1919 2047* "2032: Invalid strut rectangle minimum y coordinate."I really only need to know which commands, if any, do exactly what the description says, "pushing" other windows correctly, without wonky effects.(The note the program prints is not important, unless something really wonky happens. It just describes the exact partial strut setup.)

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 08:00:23 am
content: The examples with no footnote work as intended, if that wasn't obvious.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 31, 2022, 08:15:42 am
content: I think I had some goofs in the numbers, too. These should yield a 16-pixel sized red bar, givenDesktop: (0,0)-(2687,1732)Monitor: (0,653)-(1919,1732) with work area (0,653)-(1919,1696)Monitor: (1920,0)-(2687,1365) with work area (1920,0)-(2687,1365)Edit: The below had a typo (635,650 instead of 653,668).Note that we really use just Desktop, and the two work areas.The logic is that expanding the specified region in the anchor direction to the edge of the desktop should not overlap with the work area of any other monitor; and we do not overlap across the monitor itself (that is, if we calculate a region in the left edge, we don't use right as the anchor direction).These should put the strut on the left edge of the bottom left monitor:./strut left 0 653 15 1696./strut top 0 653 15 1696These should put the strut on the top edge of the bottom left monitor:./strut left 0 653 1919 668./strut top 0 653 1919 668These should put the strut on the right edge of the bottom left monitor:./strut top 1904 653 1919 1696./strut bottom 1904 653 1919 1696These should put the strut near the bottom edge of the bottom left monitor, but above the existing panel:./strut bottom 0 1681 1919 1696./strut left 0 1681 1919 1696These should put the strut on the top edge of the top right monitor:./strut top 1920 0 2687 15./strut right 1920 0 2687 15These should put the strut on the right edge of the top right monitor:./strut top 2672 0 2687 1365./strut right 2672 0 2687 1365./strut bottom 2672 0 2687 1365These should put the strut on the bottom edge of the top right monitor:./strut left 1920 1350 2687 1365./strut right 1920 1350 2687 1365./strut bottom 1920 1350 2687 1365These should put the strut on the left edge of the top right monitor:./strut top 1920 0 1935 1365./strut bottom 1920 0 1935 1365

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 08:30:18 am
content: 4 works now. (Above the existing panel)but not pushing the windows away.2 yields no response from the displays or any backchat from the console.3 is the correct width now.Edit: beg pardon. 4 is indeed pushing windows away.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 31, 2022, 09:00:12 am
content: Gah! I read 653 as 635, so the first three test cases were actually above the visible display. Could you recheck 1-3 too?(It is Most Definitely a Monday today, for me at least.)Oh yeah: for cases 3 and58, I'd like to know if the strut (reserved area) behaves intuitively and as expected when you try to move small windows against/around/beneath it, since it is between the two monitors. You should be able to put a wide window across both monitors; in that case, the reserved area would stay visible and just hide the overlapped part of the wide window.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 09:21:04 am
content: Quote from: Nominal Animal on January 31, 2022, 09:00:12 amSeems fine. The space reserved in between monitors is observed by the WM when moving a window. But there are two distinct snap-to positions.In the case of (3), dragging a window...Window can straddle both monitors with the window underneath the bar.Window can snap-to the position to the left of the bar and therefore covering that part of the left of the window getting dragged.Window can snap to the left of the right-hand monitor.All fine.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 09:27:05 am
content: When you say 5 I wonder if you mean 2. (top of left monitor)Anyway. Seems to work OK.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 31, 2022, 09:29:08 am
content: No, I meant 8 (and not 5).3 and 8 are between the two monitors. 3 is the right edge of the bottom left monitor, and 8 is the left edge of the top right monitor.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 09:34:32 am
content: Quote from: Nominal Animal on January 31, 2022, 09:29:08 amRoger.A window straddled across both monitors gets shoved to the left all the way to the right hand side of the left monitor when running strut.A Window on the hard left of the right hand monitor gets bumped out of the way of the strut, when ran.Looks OK.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 31, 2022, 09:47:43 am
content: Ok. I'll refactor the code, and post a fixed version of the volume meter later (today or tomorrow).

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: January 31, 2022, 09:57:33 am
content: Quote from: Nominal Animal on January 31, 2022, 09:47:43 amI threw them all in a script so I could whiz through them and found out that the first one of number 7 ( ./strut left 1920 1350 2687 1365) places the bar on the bottom of the right-hand monitor and works ok pushing the windows up, but it also severely interferes with the windows on the left monitor.The remaining two in (7) are fine.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 31, 2022, 10:25:34 am
content: That makes sense, because the./strut left 1920 1350 2687 1365case, pulling (1920,1350)-(2687,1365) left makes it into (0,1350)-(2687,1365), which intersects with the first monitor, (0,653)-(1919,1732), and therefore should not have been included in the list of tests!That is, you caught another of my bugs.Good thing is, I think I have a sound logic now.Basically, we use the selected monitor and desired edge to find the rectangle where the VU meter should be displayed. Then, we try extending it to the edge of the desktop in the direction of the selected edge. If the extended rectangle does not intersect with any of the other monitors, we're golden. Otherwise, we try extending in the two directions perpendicular to the previous direction, and if one does not intersect with any of the other monitors, we use that; otherwise, we fail. (For example, if someone has a 3×3 grid of displays, we cannot really put it anywhere in the center display.)

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 03, 2022, 08:01:22 am
content: Oh my, PulseAudio isannoyingly bad. (Before you label me, just look at some example documentation, for examplestream.h:pa_stream_peek(), and try to find out if the buffer contains a full number of samples for all channels, or whether it is just a byte stream the reader must synchronize itself; i.e., whether each callback starts at a sample for the first channel or not. All the documentation says is "it can be less or more than a complete fragment", a fragment being the basic self-contained block of samples on the server side.)You see, to make this "worthwhile", I want to add run-time source selection (by right-clicking on the bar) and some other goodies. To do that, I need to switch from the "simple" interface to the asynchronous interface. No problem; I'm very familiar and comfy with async interfaces. Except this one is 1) designed by a baboon using crayons, and 2) with even worse documentation. Sure, it is *easy* to get something to seemingly work, and that's fine for proof of concept and quick crappy stuff, but my OCD just don't let me keep doing that with this thing, knowing Ed.Kloonk has been using it for a year already. Besides, if I do it right, I could add Debian packaging in the sources, maybe visual plugin support (if one wants to write their own peak visualizer), andbe happywith the result. It might be useful for others, too.Ed.Kloonk, do you mind if I use GPL-3.0+ for the rewritten version, and publish the sources somewhere, maybe GitHub? (I think it'd be the most appropriate license for this kind of application on Linux. But, if you or anyone else has a reasonable argument for some other license, I'm open.)

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: February 03, 2022, 08:22:14 am
content: Quote from: Nominal Animal on February 03, 2022, 08:01:22 amI don't care. I'm just here to learn.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: February 03, 2022, 10:36:47 am
content: Having had a moment to dwell on this more, I feel the Beer-ware licence is more my speed...Code:/** ----------------------------------------------------------------------------* "THE BEER-WARE LICENSE" (Revision 42):* <phk@FreeBSD.ORG> wrote this file. As long as you retain this notice you* can do whatever you want with this stuff. If we meet some day, and you think* this stuff is worth it, you can buy me a beer in return Poul-Henning Kamp* ----------------------------------------------------------------------------*/https://fedoraproject.org/wiki/Licensing/Beerware

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 06, 2022, 06:09:29 am
content: Just a progress report: I'm still trying to decide whether threaded operation (having the audio code run in its own thread, completely separate from the UI; except for reporting changes in available sources usinggdk_threads_add_timeout_full()with the single-shot callee (that returnsFALSE) function sending a custom 'audio-sources' signal to the GtkApplication or GtkWindow or GtkWidget that uses the sources), or a unified main loop in a single thread, is the way to go. Both have their benefits and downsides.It is funny how easy it is to write the code, compared to how difficult it is to select a robust, reliable approach.I know other developers often pooh-pooh this, and just implement whatever comes to mind first, and then move on; but I don't like that, because it leads to inferior tools.Sure, it does make much more commercial sense, because then your output and product portfolio gets bigger. But, I'm after quality, not quantity here.Apologies if it annoys anyone, but me being me, this is the way for me.

author: chytrex
author_rank: Newbie
timestamp: September 29, 2025, 01:18:12 am
content: Hi, I apologize for necro posting, but, is your vu-bar somewhere on github or so? I would love to have latest version.