Author Topic: PulseAudio Volume Meter. Would like to add VU ticks.  (Read 9618 times)

0 Members and 1 Guest are viewing this topic.

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #25 on: January 27, 2022, 09:41:39 am »
So, let me see if I understand this right.


Or, do the windows get pushed around always only in the secondary monitor?



Yes, 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.

iratus parum formica
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #26 on: January 27, 2022, 10:33:51 am »
Code: [Select]
    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: [Select]
[ed@kloonkmanor ~]$ ./vu-bar -m 0
display_monitor = 0
d = 0x11af0e0
m = 0x119b240
mon0 = 0x119b240
mon1 = 0x119b2b0

[ed@kloonkmanor ~]$ ./vu-bar -m 1
display_monitor = 1
d = 0x1a1d0e0
m = 0x1a092b0
mon0 = 0x1a09240
mon1 = 0x1a092b0

[ed@kloonkmanor ~]$ ./vu-bar -m 2
display_monitor = 2
d = 0x21210e0
m = (nil)
mon0 = 0x210d240
mon1 = 0x210d2b0
Not a monitor. display_monitor = 2

So -m 2 still pushes the windows, so maybe the function is barking up the wrong tree.
iratus parum formica
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #27 on: January 27, 2022, 10:42:33 pm »
This example in Python suffers the same problem..

https://gist.github.com/johnlane/351adff97df196add08a


Code: [Select]
[ed@kloonkmanor ~]$ python2.7 dockbar-gtk3.py
Gtk 3.24.31
width: 2688
there are 2 monitors
monitor 0: 1920 x 1080
monitor 1: 768 x 1366
monitor 0: 1920 x 1080 (current, offset 0)
bar: start=0 end=1919

Red bar appears at the top of monitor 0. Windows are pushed around at the top of Monitor 1.

 :(
iratus parum formica
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #28 on: January 28, 2022, 07:23:02 pm »
(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.  :)
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #29 on: January 28, 2022, 08:40:23 pm »
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 --display parameter, 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: [Select]
// 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 --display
       parameter 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 is
    Display :0 has 1 monitor:
    Monitor 0 (Primary monitor):
        Manufacturer: (unknown manufacturer)
               Model: eDP-1
       Physical size: 309 mm by 173 mm
          Pixel size: 1920 x 1080 pixels
        Refresh rate: 60.0 Hz
            Viewport: (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 "Manufacturer Model (pixel size, refresh rate; physical size)", or whether "Monitor N (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.

I assume, 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, with Show, Cancel, and Quit), 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...
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #30 on: January 28, 2022, 09:20:53 pm »
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 --display parameter, 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: [Select]
Display :0 has 2 monitors:
Monitor 0 (Primary monitor):
    Manufacturer: AOC
           Model: HDMI1
   Physical size: 600 mm by 340 mm
      Pixel size: 1920 x 1080 pixels
    Refresh rate: 60.0 Hz
        Viewport: (0, 1003) - (1920, 2083)
       Work area: (0, 1003) - (1920, 2047)
Monitor 1:
    Manufacturer: XXX
           Model: HDMI2
   Physical size: 203 mm by 361 mm
      Pixel size: 768 x 1366 pixels
    Refresh rate: 60.0 Hz
        Viewport: (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.  |O


« Last Edit: January 28, 2022, 09:34:04 pm by Ed.Kloonk »
iratus parum formica
 
The following users thanked this post: Nominal Animal

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #31 on: January 28, 2022, 09:35:53 pm »
I edited the post above and updated some incorrect info.
iratus parum formica
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #32 on: January 28, 2022, 10:14:27 pm »
Could you check if the modified gui.c below 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 left
    This should put the vu-bar on the left edge of the primary (bottom/left) monitor
  • ./vu-bar -m 0 -p right
    This should put the vu-bar on the right edge of the primary (bottom/left) monitor
  • ./vu-bar -m 1 -p left
    This should put the vu-bar on the left edge of the secondary (top/right) monitor
  • ./vu-bar -m 1 -p right
    This should put the vu-bar on the right edge of the secondary (top/right) monitor
and 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'm very sensitive 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 I really dislike having to return a product...)
Code: [Select]
#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
#endif

static 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[i] >= red_limit)
            cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
        else
        if (peak[i] <= green_limit)
            cairo_set_source_rgb(cr, 0.0, 0.5 + 0.5*peak[i]/green_limit, 0.0);
        else {
            const double c = (peak[i] - green_limit) / (red_limit - green_limit);
            cairo_set_source_rgb(cr, c, 1.0-c, 0.0);
        }

        const double c = (peak[i] < 0.0f) ? 0.0 : (peak[i] < 1.0f) ? peak[i] : 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);
        } else
        if (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[i].amplitude >= 0.0f; i++) {
            const int  y = area.y + bar_space + (1.0f - tickmarks[i].amplitude)*(area.height - 2*bar_space);
            cairo_set_source_rgb(cr, tickmarks[i].red, tickmarks[i].green, tickmarks[i].blue);
            cairo_move_to(cr, area.x + 2, y);
            cairo_line_to(cr, area.x + area.width - 2, y);
            cairo_stroke(cr);
        }
    } else
    if (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {
        for (int i = 0; tickmarks[i].amplitude >= 0.0f; i++) {
            const int  x = area.x + bar_space + tickmarks[i].amplitude*(area.width - 2*bar_space);
            cairo_set_source_rgb(cr, tickmarks[i].red, tickmarks[i].green, tickmarks[i].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[i] < 0.0f) ? 0.0 : (peak_line[i] < 1.0f) ? peak_line[i] : 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);
        } else
        if (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[channels];
    if (vu_peak(new_peak, channels) == channels) {
        for (int c = 0; c < channels; c++) {
            peak[c] *= decay;
            peak[c]  = (new_peak[c] > peak[c]) ? new_peak[c] : peak[c];

            peak_line[c] *= decay_line;
            peak_line[c]  = (new_peak[c] > peak_line[c]) ? new_peak[c] : peak_line[c];
        }
        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[10] = 0;  /* bottom_start_x */
    reserve[11] = 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[0] = to->width;
        reserve[4] = w.y;
        reserve[5] = w.y + w.height - 1;
        return;

    case PLACEMENT_RIGHT:
        to->x = w.x + w.width - to->width;
        to->height = w.height;
        reserve[1] = to->width;
        reserve[6] = w.y;
        reserve[7] = w.y + w.height - 1;
        return;

    case PLACEMENT_TOP:
        to->y = w.y;
        to->width = w.width;
        reserve[2] = to->height;
        reserve[8] = w.x;
        reserve[9] = w.x + w.width - 1;
        return;

    case PLACEMENT_BOTTOM:
        to->y = w.y + w.height - to->height;
        to->width = w.width;
        reserve[3] = to->height;
        reserve[10] = w.x;
        reserve[11] = 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[12] = { 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[0] && argv[0][0]) ? argv[0] : "(this)";
    const char *p;
    int         opt, val;

    setlocale(LC_ALL, "");

    if (argc > 1 && !strcmp(argv[1], "--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] == '\0' || !strcmp(optarg, "default"))
                server = NULL;
            else
                server = optarg;
            break;

        case 'd':
            if (!optarg || optarg[0] == '\0' || !strcmp(optarg, "default"))
                device = NULL;
            else
                device = 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;
            else
            if (!strcasecmp(optarg, "right"))
                display_placement = PLACEMENT_RIGHT;
            else
            if (!strcasecmp(optarg, "top"))
                display_placement = PLACEMENT_TOP;
            else
            if (!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[optind]);
        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;
}
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #33 on: January 28, 2022, 11:44:31 pm »

(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'm very sensitive 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 I really dislike having to return a product...)


It'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.
iratus parum formica
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #34 on: January 28, 2022, 11:56:48 pm »
Could you check if the modified gui.c below 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 left
    This should put the vu-bar on the left edge of the primary (bottom/left) monitor
  • ./vu-bar -m 0 -p right
    This should put the vu-bar on the right edge of the primary (bottom/left) monitor
  • ./vu-bar -m 1 -p left
    This should put the vu-bar on the left edge of the secondary (top/right) monitor
  • ./vu-bar -m 1 -p right
    This should put the vu-bar on the right edge of the secondary (top/right) monitor
and 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.


Monitor 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.
iratus parum formica
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #35 on: January 29, 2022, 12:10:41 am »
Could you retry with the attached gui.c?  And also report if vu-bar outputs anything in the terminal?
Code: [Select]
#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
#endif

static 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[i] >= red_limit)
            cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
        else
        if (peak[i] <= green_limit)
            cairo_set_source_rgb(cr, 0.0, 0.5 + 0.5*peak[i]/green_limit, 0.0);
        else {
            const double c = (peak[i] - green_limit) / (red_limit - green_limit);
            cairo_set_source_rgb(cr, c, 1.0-c, 0.0);
        }

        const double c = (peak[i] < 0.0f) ? 0.0 : (peak[i] < 1.0f) ? peak[i] : 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);
        } else
        if (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[i].amplitude >= 0.0f; i++) {
            const int  y = area.y + bar_space + (1.0f - tickmarks[i].amplitude)*(area.height - 2*bar_space);
            cairo_set_source_rgb(cr, tickmarks[i].red, tickmarks[i].green, tickmarks[i].blue);
            cairo_move_to(cr, area.x + 2, y);
            cairo_line_to(cr, area.x + area.width - 2, y);
            cairo_stroke(cr);
        }
    } else
    if (display_placement == PLACEMENT_TOP || display_placement == PLACEMENT_BOTTOM) {
        for (int i = 0; tickmarks[i].amplitude >= 0.0f; i++) {
            const int  x = area.x + bar_space + tickmarks[i].amplitude*(area.width - 2*bar_space);
            cairo_set_source_rgb(cr, tickmarks[i].red, tickmarks[i].green, tickmarks[i].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[i] < 0.0f) ? 0.0 : (peak_line[i] < 1.0f) ? peak_line[i] : 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);
        } else
        if (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[channels];
    if (vu_peak(new_peak, channels) == channels) {
        for (int c = 0; c < channels; c++) {
            peak[c] *= decay;
            peak[c]  = (new_peak[c] > peak[c]) ? new_peak[c] : peak[c];

            peak_line[c] *= decay_line;
            peak_line[c]  = (new_peak[c] > peak_line[c]) ? new_peak[c] : peak_line[c];
        }
        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[10] = 0;  /* bottom_start_x */
    reserve[11] = 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[0] = to->width;
        reserve[4] = w.y;
        reserve[5] = w.y + w.height - 1;
        break;

    case PLACEMENT_RIGHT:
        to->x = w.x + w.width - to->width;
        to->height = w.height;
        reserve[1] = to->width;
        reserve[6] = w.y;
        reserve[7] = w.y + w.height - 1;
        break;

    case PLACEMENT_TOP:
        to->y = w.y;
        to->width = w.width;
        reserve[2] = to->height;
        reserve[8] = w.x;
        reserve[9] = w.x + w.width - 1;
        break;

    case PLACEMENT_BOTTOM:
        to->y = w.y + w.height - to->height;
        to->width = w.width;
        reserve[3] = to->height;
        reserve[10] = w.x;
        reserve[11] = w.x + w.width - 1;
        break;
    }

    reserve[0] = to->x;
    reserve[1] = to->y;
    reserve[2] = to->x + to->width - 1;
    reserve[3] = 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[12] = { 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[0] && argv[0][0]) ? argv[0] : "(this)";
    const char *p;
    int         opt, val;

    setlocale(LC_ALL, "");

    if (argc > 1 && !strcmp(argv[1], "--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] == '\0' || !strcmp(optarg, "default"))
                server = NULL;
            else
                server = optarg;
            break;

        case 'd':
            if (!optarg || optarg[0] == '\0' || !strcmp(optarg, "default"))
                device = NULL;
            else
                device = 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;
            else
            if (!strcasecmp(optarg, "right"))
                display_placement = PLACEMENT_RIGHT;
            else
            if (!strcasecmp(optarg, "top"))
                display_placement = PLACEMENT_TOP;
            else
            if (!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[optind]);
        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;
}
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #36 on: January 29, 2022, 12:28:01 am »
'-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 of m0
'-m 1 -p right' works as it should. Meter on the right of m1, windows are pushed out of it's way.

iratus parum formica
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #37 on: January 29, 2022, 12:36:39 am »
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.
iratus parum formica
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #38 on: January 29, 2022, 01:46:33 am »
What about the output?
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #39 on: January 29, 2022, 02:30:06 am »
What about the output?

Command line output is very tight-lipped.

No output.
iratus parum formica
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #40 on: January 29, 2022, 03:44:12 am »
Okay, I think I understand the problem now.

I am using the _NET_WM_STRUT_PARTIAL application window property (with its simpler, older _NET_WM_STRUT as 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_PARTIAL property 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.
 
The following users thanked this post: Ed.Kloonk

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #41 on: January 31, 2022, 06:49:04 am »
Funnily enough, the 'format 32' for window manager properties like _NET_WM_PARTIAL_STRUT (and all others) does not mean 32-bit unsigned integers; it means unsigned long on the current architecture.  :palm:

That's what the deleted post was about: I thought I might have a bug in using gulong, but no, format==32 uses gulong elements 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 following strut.c:
Code: [Select]
// 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  11

struct 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[12] = { 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[0]);
    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[monitors].geometry.xmin = box.x;
        monitor[monitors].geometry.ymin = box.y;
        monitor[monitors].geometry.xmax = box.x + box.width - 1;
        monitor[monitors].geometry.ymax = box.y + box.height - 1;

        if (desktop.xmax < monitor[monitors].geometry.xmax)
            desktop.xmax = monitor[monitors].geometry.xmax;
        if (desktop.ymax < monitor[monitors].geometry.ymax)
            desktop.ymax = monitor[monitors].geometry.ymax;

        gdk_monitor_get_workarea(one, &box);
        if (box.x < 0 || box.y < 0 || box.width < 1 || box.height < 1)
            continue;

        monitor[monitors].workarea.xmin = box.x;
        monitor[monitors].workarea.ymin = box.y;
        monitor[monitors].workarea.xmax = box.x + box.width - 1;
        monitor[monitors].workarea.ymax = box.y + box.height - 1;

        if (monitor[monitors].workarea.xmin < monitor[monitors].geometry.xmin ||
            monitor[monitors].workarea.xmax > monitor[monitors].geometry.xmax ||
            monitor[monitors].workarea.ymin < monitor[monitors].geometry.ymin ||
            monitor[monitors].workarea.ymax > monitor[monitors].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[0], (int)partial_strut[1], (int)partial_strut[2], (int)partial_strut[3],
                        (int)partial_strut[4], (int)partial_strut[5], (int)partial_strut[6], (int)partial_strut[7],
                        (int)partial_strut[8], (int)partial_strut[9], (int)partial_strut[10], (int)partial_strut[11]);
    }
}

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[0] && argv[0][0]) ? argv[0] : "(this)";

    /* Initialize GDK.  Supports display selection via --display
       parameter 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;
    } else
    if (!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[i].geometry.xmin, monitor[i].geometry.ymin,
                    monitor[i].geometry.xmax, monitor[i].geometry.ymax,
                    monitor[i].workarea.xmin, monitor[i].workarea.ymin,
                    monitor[i].workarea.xmax, monitor[i].workarea.ymax);
        return EXIT_SUCCESS;
    } else
    if (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[1], "left", "LEFT") || match(argv[1], "l", "L"))
        anchor = ANCHOR_LEFT;
    else
    if (match(argv[1], "top", "TOP") || match(argv[1], "t", "T") || match(argv[1], "up", "UP") || match(argv[1], "u", "U"))
        anchor = ANCHOR_TOP;
    else
    if (match(argv[1], "right", "RIGHT") || match(argv[1], "r", "R"))
        anchor = ANCHOR_RIGHT;
    else
    if (match(argv[1], "bottom", "BOTTOM") || match(argv[1], "b", "B") || match(argv[1], "down", "DOWN") || match(argv[1], "d", "D"))
        anchor = ANCHOR_BOTTOM;
    else {
        fprintf(stderr, "%s: Invalid anchor direction.\n", argv[1]);
        return EXIT_FAILURE;
    }

    if (parse_int(argv[2], &strut.xmin) || strut.xmin < desktop.xmin || strut.xmin > desktop.xmax) {
        fprintf(stderr, "%s: Invalid strut rectangle minimum x coordinate.\n", argv[2]);
        return EXIT_FAILURE;
    }
    if (parse_int(argv[3], &strut.ymin) || strut.ymin < desktop.ymin || strut.ymin > desktop.ymax) {
        fprintf(stderr, "%s: Invalid strut rectangle minimum y coordinate.\n", argv[3]);
        return EXIT_FAILURE;
    }
    if (parse_int(argv[4], &strut.xmax) || strut.xmax < strut.xmin || strut.xmax > desktop.xmax) {
        fprintf(stderr, "%s: Invalid strut rectangle maximum x coordinate.\n", argv[4]);
        return EXIT_FAILURE;
    }
    if (parse_int(argv[5], &strut.ymax) || strut.ymax < strut.ymin || strut.ymax > desktop.ymax) {
        fprintf(stderr, "%s: Invalid strut rectangle maximum y coordinate.\n", argv[5]);
        return EXIT_FAILURE;
    }

    switch (anchor) {

    case ANCHOR_LEFT:
        partial_strut[PARTIAL_STRUT_LEFT] = strut.xmax + 1;
        partial_strut[PARTIAL_STRUT_LEFT_YMIN] = strut.ymin;
        partial_strut[PARTIAL_STRUT_LEFT_YMAX] = strut.ymax;
        break;

    case ANCHOR_RIGHT:
        partial_strut[PARTIAL_STRUT_RIGHT] = desktop.xmax - strut.xmin + 1;
        partial_strut[PARTIAL_STRUT_RIGHT_YMIN] = strut.ymin;
        partial_strut[PARTIAL_STRUT_RIGHT_YMAX] = strut.ymax;
        break;

    case ANCHOR_TOP:
        partial_strut[PARTIAL_STRUT_TOP] = strut.ymax + 1;
        partial_strut[PARTIAL_STRUT_TOP_XMIN] = strut.xmin;
        partial_strut[PARTIAL_STRUT_TOP_XMAX] = strut.xmax;
        break;

    case ANCHOR_BOTTOM:
        partial_strut[PARTIAL_STRUT_BOTTOM] = desktop.ymax - strut.ymin + 1;
        partial_strut[PARTIAL_STRUT_BOTTOM_XMIN] = strut.xmin;
        partial_strut[PARTIAL_STRUT_BOTTOM_XMAX] = 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 --help to see a short usage info.

Run
    ./strut
to 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 YMAX
where ANCHOR is left, top, right, or bottom; 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 see
    Desktop: (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 2046
     
  • These should put the strut on the top edge of the bottom left monitor:
        ./strut left 0 1003 1919 1018
        ./strut top 0 1003 1919 1018
     
  • 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 15
     
  • These 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 1365
     
  • These 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 1365
     
  • These should put the strut on the right edge of the bottom left monitor:
        ./strut top 1913 1003 1919 2046
        ./strut bottom 1913 1003 1919 2046
     
  • 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 1365
     
  • These 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 2047
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.)
« Last Edit: January 31, 2022, 06:52:43 am by Nominal Animal »
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #42 on: January 31, 2022, 06:56:05 am »
stby

 :)
iratus parum formica
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #43 on: January 31, 2022, 07:57:06 am »
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: [Select]
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: [Select]
Now, I assume that when Ed.Kloonk runs ./strut, he'll see
    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)

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 1696
     
    These 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
[b]* occupies 1/3 of the top of the left monitor. (might be my fault)[/b]
     
    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 15
     
    These 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 1365
     
    These 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 1365
     
    These 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
[b]* these two are thinner than the others. [/b]
     
    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 1365
     
    These should put the strut near the bottom edge of the bottom left monitor, but above the existing panel:
        ./strut bottom 0 2032 1919 2047
[b]* "2032: Invalid strut rectangle minimum y coordinate."[/b]
        ./strut left 0 2032 1919 2047
[b]* "2032: Invalid strut rectangle minimum y coordinate."[/b]

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.)
« Last Edit: January 31, 2022, 08:01:13 am by Ed.Kloonk »
iratus parum formica
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #44 on: January 31, 2022, 08:00:23 am »
The examples with no footnote work as intended, if that wasn't obvious.  :)
iratus parum formica
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #45 on: January 31, 2022, 08:15:42 am »
I think I had some goofs in the numbers, too.  These should yield a 16-pixel sized red bar, given
    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)

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 1696
     
  • These should put the strut on the top edge of the bottom left monitor:
        ./strut  left       0  653   1919  668
        ./strut  top        0  653   1919  668
     
  • These should put the strut on the right edge of the bottom left monitor:
        ./strut  top     1904  653   1919 1696
        ./strut  bottom  1904  653   1919 1696
     
  • These 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 1696
     
  • 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   15
     
  • These 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 1365
     
  • These 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 1365
     
  • 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 1365
« Last Edit: January 31, 2022, 08:58:46 am by Nominal Animal »
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #46 on: January 31, 2022, 08:30:18 am »
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.  :rant:
« Last Edit: January 31, 2022, 08:34:36 am by Ed.Kloonk »
iratus parum formica
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #47 on: January 31, 2022, 09:00:12 am »
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. :palm:)

Oh yeah: for cases 3 and 5 8, 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.
« Last Edit: January 31, 2022, 09:27:21 am by Nominal Animal »
 
The following users thanked this post: Ed.Kloonk

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #48 on: January 31, 2022, 09:21:04 am »
Gah!  I read 653 as 635, so the first three test cases were actually above the visible display.  Could you recheck 1-3 too?

Oh yeah: for cases 3 and 5, 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.

Seems 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.
iratus parum formica
 

Offline Ed.KloonkTopic starter

  • Super Contributor
  • ***
  • Posts: 4000
  • Country: au
  • Cat video aficionado
Re: PulseAudio Volume Meter. Would like to add VU ticks.
« Reply #49 on: January 31, 2022, 09:27:05 am »
When you say 5 I wonder if you mean 2. (top of left monitor)

Anyway. Seems to work OK.
iratus parum formica
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf