Skip to content

Commit

Permalink
Merge pull request #3 from m-bartlett/dunst-1.9.0
Browse files Browse the repository at this point in the history
Dunst 1.9.0
  • Loading branch information
m-bartlett authored Oct 1, 2022
2 parents 284dd63 + 9fe567e commit 2ea1c79
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 61 deletions.
93 changes: 89 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ A keybindable application to modify volume levels on audio sinks within PulseAud
* [Dunstrc config](#dunstrc-config)
* [Extra](#extra)
* [About](#about)
* [Optional Features](#optional-features)
* [Key Binding](#key-binding)
* [Custom Icons](#custom-icons)
* [PulseAudio Support](#pulseaudio-support)
Expand Down Expand Up @@ -184,6 +185,64 @@ sections below.

## About

### Optional Features
There are certain features which can be enabled at compile-time. These are features that would would add overhead to the generation and display of the notification if they were specified with a boolean CLI flag, and are generally things that users would either be interested in having all the time or never. The feature inclusion is ultimately decided by the preprocessor, but I've added some logic in the `Makefile` to make it simpler for the user to specify the inclusion of these optional features.

From the table below, export the **Feature name variable** value as a non-empty value to `make`. Note that specifying a feature variable *after* building will not indicate to `make` that it needs to rebuild; one should use the `-B` flag to force `make` to rebuild.

For example, to enable the `FORMAT_VOLUME_IN_NOTIFICATION_BODY` feature, I would recommend executing:
`FORMAT_VOLUME_IN_NOTIFICATION_BODY=1 make -B`

<br/>

<table>
<tr align="center">
<th>Feature name variable</th>
<th>Feature description</th>
<th>Example</th>
</tr>
<tr>
<td><code>ENABLE_TRANSIENT_HINT</code></td>
<td>Transient notifications will still timeout even if the user is considered idle. The default <code>dunst</code> config disables idle timeout, so only enable this if you use this dunst feature and would like volume notifications to still disappear.</td>
<td></td>
</tr>
<tr>
<td><code>FORMAT_VOLUME_IN_NOTIFICATION_BODY</code></td>
<td>

Support formatting the volume percentage integer into the notification body. This literally runs `sprintf` with the volume integer as an argument. For example with a body argument of `~%d~` and the current volume being 25, the resulting notification body would show `~25~`. <br/>**WARNING**: This feature has no sanitation of the user provided body value. If you provide any formatter besides `%d` expect to get a segmentation fault.

</td>
<td>
<table>
<tr align="center">
<th>Disabled</th>
</tr>
<tr>
<td>
<img alt="Body NOT formatted with volume"
title="Body NOT formatted with volume"
width="220"
src="https://user-images.githubusercontent.com/85039141/190920774-58d4c9db-417e-4fe5-88d2-be387d1b4247.png">
</td>
</tr>
<tr align="center">
<th>Enabled</th>
</tr>
<tr>
<td>
<img alt="Body formatted with volume"
title="Body formatted with volume"
width="220"
src="https://user-images.githubusercontent.com/85039141/190920714-15924138-fe49-46b5-aede-16d71d968b69.png">
</td>
</tr>
</table>
</td>
</tr>
</table>


### Key Binding
This will obviously depend on your Linux distribution, desktop environment, and preferred means of creating global keyboard shortcuts.

Expand Down Expand Up @@ -231,7 +290,7 @@ This application supports reading the CSS colors for the SVG icon renderring fro

For example the user may test this feature with:
```sh
xrdb -merge <(echo -e "pavol-dunst.primaryColor: #f00 \n pavol-dunst.primaryColor: #0ff")
xrdb -merge <(echo -e "pavol-dunst.primaryColor: #f00\npavol-dunst.secondaryColor: #0ff")
```


Expand All @@ -242,7 +301,33 @@ If the process unexpectedly exits due to an unforeseen error, this single-proces

### Developer Notes

When scaling the rendered SVG to GdkPixBuf&mdash;especially in the context of creating an icon for libnotify&mdash;it seemed obvious to me that the appropriate function to reference the allocated pixbuf would be `rsvg_handle_get_pixbuf` using the RSVG handle containing the rendered graphic. However this function failed to produce a re-scaled image, i.e. the resulting icon was always whatever the intrinsic document scale was despite passing in differing viewbox values. As a workaround, I found that rendering to the cairo surface directly and then producing the pixbuf from the cairo surface was successful in producing a re-scaled image. In short, using the pixbuf from `gdk_pixbuf_get_from_surface` as the notification icon was sufficient to enable dynamic image scaling. This required linking the gdk main library.

- [List of all hints supported by dunst](https://dunst-project.org/documentation/#NOTIFY-SEND)

- When scaling the rendered SVG to GdkPixBuf&mdash;especially in the context of creating an icon for libnotify&mdash;it seemed obvious to me that the appropriate function to reference the allocated pixbuf would be `rsvg_handle_get_pixbuf` using the RSVG handle containing the rendered graphic. However this function failed to produce a re-scaled image, i.e. the resulting icon was always whatever the intrinsic document scale was despite passing in differing viewbox values. As a workaround, I found that rendering to the cairo surface directly and then producing the pixbuf from the cairo surface was successful in producing a re-scaled image. In short, using the pixbuf from `gdk_pixbuf_get_from_surface` as the notification icon was sufficient to enable dynamic image scaling. This requires linking the gdk main library with `$ pkg-config --libs gdk-3.0`.

- dunst 1.9.0 seems to now cache the icon image for synchronous notifications. This results in the notification icon not changing to reflect the volume magnitude visually if the process is executed again while the previous notification is still displayed despite having the image explicitly set. Recent releases of `pavol-dunst` add a workaround preventing this caching by first displaying a transparent image with the same dimensions and then updating the notification with the real image data with afterward. Caching the blank image should be easy for dunst, but the post-display update allows us to circumvent the notification caching and display the respective symbolic icon showing proportional volume level if `pavol-dunst` is executed in rapid succession

- An alternative to `notify_notification_set_image_from_pixbuf` is using the `image-data` hint supported by `dunst`. This code was informed from the [test case for the `image-data` hint](https://github.com/dunst-project/dunst/blob/1280c9a9f20f46b24b08ebc99d29a788e5256a43/test/helpers.c#L7-L35):

```C
GVariant *hint_data = g_variant_new_from_data(G_VARIANT_TYPE("ay"),
gdk_pixbuf_read_pixels(pixbuf),
gdk_pixbuf_get_byte_length(pixbuf),
TRUE,
(GDestroyNotify) g_object_unref,
g_object_ref(pixbuf));
GVariant *hint = g_variant_new(
"(iiibii@ay)",
gdk_pixbuf_get_width(pixbuf),
gdk_pixbuf_get_height(pixbuf),
gdk_pixbuf_get_rowstride(pixbuf),
gdk_pixbuf_get_has_alpha(pixbuf),
gdk_pixbuf_get_bits_per_sample(pixbuf),
gdk_pixbuf_get_n_channels(pixbuf),
hint_data);

notify_notification_set_hint(notification, "image-data", hint);
```
### To Do
View the [new feature kanban](https://github.com/m-bartlett/pavol-dunst/projects/1) to follow what ideas for features exist and their integration progress.
View the [new feature kanban](https://github.com/m-bartlett/pavol-dunst/projects/1) to follow what ideas for features exist and their integration progress.
9 changes: 8 additions & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ OBJECTS := $(SOURCES:.c=.o)
ICON_SVGS := $(wildcard svg/*.svg)
ICON_HEADERS := $(patsubst svg/%.svg, icon/%.h, $(ICON_SVGS))

FEATURES :=
ifneq ($(ENABLE_TRANSIENT_HINT),)
FEATURES += -DENABLE_TRANSIENT_HINT
endif
ifneq ($(FORMAT_VOLUME_IN_NOTIFICATION_BODY),)
FEATURES += -DFORMAT_VOLUME_IN_NOTIFICATION_BODY
endif

all: # Multi-threaded make by default
$(MAKE) -j $(shell nproc) $(TARGET)
Expand All @@ -35,7 +42,7 @@ $(TARGET): $(OBJECTS)
$(OBJECTS): $(SOURCES) $(HEADERS) $(ICON_HEADERS)

%.o: %.c
$(CC) $(CFLAGS) $(LDFLAGS) $(LIB_FLAGS) -c $< -o $@
$(CC) $(FEATURES) $(CFLAGS) $(LDFLAGS) $(LIB_FLAGS) -c $< -o $@

$(SOURCES): $(MAKEFILE) # If Makefile changes, recompile
@touch $(SOURCES)
Expand Down
61 changes: 33 additions & 28 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdlib.h> // EXIT_*
#include <stdio.h> // printf
#include <string.h> // strcpy
#include <stdbool.h> // "true"
#include <stdlib.h> // EXIT_OK/EXIT_FAILURE
#include <stdio.h> // printf
#include <string.h> // strcpy

#include "userdata.h"
#include "notification.h"
Expand All @@ -21,27 +21,32 @@ static int usage(char *argv[]) {
"%s\n"
" [-h|--help] - print this usage information and exit\n"
" [-m|--mute] [ [1|\"on\"] | [0|\"off\"] | [-1|\"toggle\"] ]\n"
" mute audio if arg is \"1\" or \"on\"\n"
" unmute audio if arg is \"0\" or \"off\"\n"
" toggle audio muted if arg is \"-1\" or \"toggle\"\n"
" mute audio if arg is \"1\" or \"on\"\n"
" unmute audio if arg is \"0\" or \"off\"\n"
" toggle audio muted if arg is \"-1\" or \"toggle\"\n"
" [-v|--volume] [+|-]VAL\n"
" if arg starts with \"+\" increase by VAL -> +5 is current volume + 5\n"
" if arg starts with \"-\" decrease by VAL -> -7 is current volume - 7\n"
" set absolute VAL if neither \"+\" or \"-\" are present -> 50 sets volume to 50\n"
" if arg starts with \"+\" increase by VAL -> +5 is current volume + 5\n"
" if arg starts with \"-\" decrease by VAL -> -7 is current volume - 7\n"
" set absolute VAL if neither \"+\" or \"-\" are present -> 50 sets volume to 50\n"
" [-t|--timeout] MILLISECONDS - end volume notification after MILLISECONDS milliseconds.\n"
" [-b|--body] BODY - set volume notification body to whatever string is provided as BODY.\n"
" [-u|--unlock]\n"
" Forcibly unlock (or prevent the locking of) the shared-memory mutex lock that prevents concurrent instances of this process from running. \n"
" Forcibly unlock (or prevent the locking of) the shared-memory mutex lock that\n"
" prevents concurrent instances of this process from running.\n"
" [-P|--primary-color] CSS_COLOR - set volume notification icon primary color.\n"
" If this arg is unset it will be read from the Xresources key %s or a default value.\n"
" If this arg is unset it will be read from the Xresources key %s or a default value.\n"
" [-S|--secondary-color] CSS_COLOR - set volume notification icon secondary color.\n"
" If this arg is unset it will be read from the Xresources key %s or a default value.\n"
" If this arg is unset it will be read from the Xresources key %s or a default value.\n"
" [-I|--icon-size] PIXELS - render volume notification icon size to be PIXELS pixels big.\n"
" -- [+|-]VAL\n"
" if stray positional arg starts with \"+\" increase by VAL -> +5 is current volume + 5\n"
" if stray positional arg starts with \"-\" decrease by VAL -> -7 is current volume - 7\n"
" set absolute VAL if neither \"+\" or \"-\" are present -> 50 sets volume to 50\n"
,
argv[0],
XRESOURCE_KEY_ICON_PRIMARY_COLOR,
XRESOURCE_KEY_ICON_SECONDARY_COLOR
);
);
exit(EXIT_FAILURE);
}

Expand All @@ -57,15 +62,15 @@ static bool parse_volume_argument(char *optarg, userdata_t *userdata) {


int main(int argc, char *argv[]) {
userdata_t userdata = { .volume = -1,
.new_volume = -1,
.volume_delta = false,
.mute = MUTE_UNKNOWN,
.notification_timeout = DEFAULT_NOTIFICATION_TIMEOUT,
.notification_body = (char*)"",
.icon_primary_color = NULL,
.icon_secondary_color = NULL,
.icon_size = DEFAULT_ICON_SIZE };
userdata_t userdata = {.volume = -1,
.new_volume = -1,
.volume_delta = false,
.mute = MUTE_UNKNOWN,
.notification_timeout = DEFAULT_NOTIFICATION_TIMEOUT,
.notification_body = (char*)"",
.icon_primary_color = NULL,
.icon_secondary_color = NULL,
.icon_size = DEFAULT_ICON_SIZE};
bool process_mutex = true;


Expand Down Expand Up @@ -181,11 +186,11 @@ int main(int argc, char *argv[]) {
}

char *default_sink_name[256];
wait_loop(pa_context_get_server_info(context, get_server_info_callback, &default_sink_name));
wait_loop(pa_context_get_sink_info_by_name( context,
(char *) default_sink_name,
set_volume_callback,
&userdata ) );
pa_wait_loop(pa_context_get_server_info(context, callback__get_server_info, &default_sink_name));
pa_wait_loop(pa_context_get_sink_info_by_name(context,
(char *) default_sink_name,
callback__set_volume,
&userdata ));;
display_volume_notification(&userdata);

process_mutex_unlock();
Expand Down
54 changes: 38 additions & 16 deletions src/notification.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "svg.h"

const char NOTIFICATION_BODY_FORMAT[] = NOTIFICATION_LITERAL_BODY_FORMAT;
const int NOTIFICATION_BODY_FORMAT_SIZE = strlen(NOTIFICATION_BODY_FORMAT) - 2;
const int NOTIFICATION_BODY_FORMAT_SIZE = strlen(NOTIFICATION_BODY_FORMAT) - 2;

const guint8* icon_bodies[] = { (guint8*) silent_svg_raw,
(guint8*) low_svg_raw,
Expand Down Expand Up @@ -42,35 +42,57 @@ void display_volume_notification(userdata_t *userdata) {
volume = (volume % 100) * 100 / PULSEAUDIO_OVERAMPLIFIED_RANGE; }
}

size_t body_width = NOTIFICATION_BODY_FORMAT_SIZE + strlen(userdata->notification_body)+1;
char body[body_width];
sprintf(body, NOTIFICATION_BODY_FORMAT, userdata->notification_body);
#ifdef FORMAT_VOLUME_IN_NOTIFICATION_BODY

/* Attempt to format the current volume percentage integer into the user's notification body */
size_t formatted_body_size =strlen(userdata->notification_body) - 2/*len("%d")*/ + 3/*len("100")*/;
char formatted_body[formatted_body_size];
sprintf(formatted_body, userdata->notification_body, volume);

size_t body_size = NOTIFICATION_BODY_FORMAT_SIZE + strlen(formatted_body) + 1;
char body[body_size];
sprintf(body, NOTIFICATION_BODY_FORMAT, formatted_body);

#else

size_t body_width = NOTIFICATION_BODY_FORMAT_SIZE + strlen(userdata->notification_body)+1;
char body[body_width];
sprintf(body, NOTIFICATION_BODY_FORMAT, userdata->notification_body, volume);

#endif

notify_init(summary);
NotifyNotification *notification = notify_notification_new(summary, body, NULL);
notify_notification_set_category(notification, NOTIFICATION_LITERAL_CATEGORY);
notify_notification_set_timeout(notification, userdata->notification_timeout);

bool icon_success = render_notification_icon(notification, icon_body, icon_body_size,
userdata->icon_size,
userdata->icon_primary_color,
userdata->icon_secondary_color);
if (!icon_success) { fprintf(stderr, "Error rendering notification icon\n");
exit(EXIT_FAILURE); }

/* https://people.gnome.org/~desrt/glib-docs/glib-GVariant.html
info on GVariants since notify_notification_set_hint_* methods are deprecated */

// Set notification synchronous (overwrite existing notification instead of making a fresh one)
notify_notification_set_hint( notification,
NOTIFICATION_LITERAL_HINT_SYNCHRONOUS,
g_variant_new_string(NOTIFICATION_LITERAL_CATEGORY) );
/* Set notification synchronous (overwrite existing notification instead of making a fresh one) */
notify_notification_set_hint(notification,
NOTIFICATION_LITERAL_HINT_STACKTAG,
g_variant_new_string(NOTIFICATION_LITERAL_CATEGORY));

// Add 'value' hint for dunst progress bar to show new volume
/* Add 'value' hint for dunst progress bar to show new volume */
notify_notification_set_hint( notification,
NOTIFICATION_LITERAL_HINT_VALUE,
g_variant_new_int32(volume) );

#ifdef ENABLE_TRANSIENT_HINT
/* transient notifications will still timeout even if the user is considered idle */
notify_notification_set_hint(notification, "transient", g_variant_new_boolean(true));
#endif

bool icon_success = render_notification_icon(notification,
icon_body,
icon_body_size,
userdata->icon_size,
userdata->icon_primary_color,
userdata->icon_secondary_color);

if (!icon_success) { fprintf(stderr, "Error rendering notification icon\n"); exit(EXIT_FAILURE); }

notify_notification_show(notification, NULL);

g_object_unref(notification);
Expand Down
9 changes: 7 additions & 2 deletions src/notification.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@

#define NOTIFICATION_LITERAL_BODY_FORMAT "<span>%s</span>"
#define NOTIFICATION_LITERAL_CATEGORY "volume"
#define NOTIFICATION_LITERAL_HINT_SYNCHRONOUS "synchronous"
#define NOTIFICATION_LITERAL_HINT_STACKTAG "synchronous"
/* Alternative stacktag values supported by dunst to replace an existing notification:
#define NOTIFICATION_LITERAL_HINT_STACKTAG "private-synchronous",
#define NOTIFICATION_LITERAL_HINT_STACKTAG "x-canonical-private-synchronous",
#define NOTIFICATION_LITERAL_HINT_STACKTAG "x-dunst-stack-tag",
*/
#define NOTIFICATION_LITERAL_HINT_VALUE "value"

extern const char NOTIFICATION_BODY_FORMAT[];
extern const int NOTIFICATION_BODY_FORMAT_SIZE;

void display_volume_notification(userdata_t *userdata);

#endif
#endif
Loading

0 comments on commit 2ea1c79

Please sign in to comment.