diff --git a/README.md b/README.md index 8e8e75b74..942021a35 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,10 @@ If you want to take a break and not receive any notifications for a while, just pause dunst. All notifications will be saved for you to catch up later. +Additionally, you can set a numeric pause level, which allows you to pause dunst +selectively for some notifications, where more urgent notifications get through, +but less urgent stay paused. + ## 🕘 History Catch an unread notification disappearing from the corner of your eye? Just tap diff --git a/contrib/_dunstctl.zshcomp b/contrib/_dunstctl.zshcomp index 0d30e942b..856fbe0a5 100644 --- a/contrib/_dunstctl.zshcomp +++ b/contrib/_dunstctl.zshcomp @@ -27,6 +27,8 @@ case $state in 'history-pop:Pop the latest notification from history or optionally the notification with given ID.' 'is-paused:Check if dunst is running or paused' 'set-paused:Set the pause status' + 'get-pause-level:Get current dunst's pause level' + 'set-pause-level:Set current dunst's pause level' 'rule:Enable or disable a rule by its name' 'debug:Print debugging information' 'help:Show this help' diff --git a/docs/dunst.5.pod b/docs/dunst.5.pod index d65d8e938..05fbed741 100644 --- a/docs/dunst.5.pod +++ b/docs/dunst.5.pod @@ -553,6 +553,16 @@ Ignore the dbus closeNotification message. This is useful to enforce the timeout set by dunst configuration. Without this parameter, an application may close the notification sent before the user defined timeout. +=item B (values: [0-100], default: 0) + +A Notification will appear whenever notification's +override_pause_level >= dunst's paused level. Setting this to values other than 0 +allows you to create partial pause modes, where more urgent notifications get through, +but less urgent stay paused. For example, when you can set a low battery noficiation's +override_pause_level to 60 and then set dunst's pause level to 60. This will cause dunst to +only show battery level notification (and other notifications with override_pause_level >= 60), +while suspending others. + =back =head2 Keyboard shortcuts (X11 only) diff --git a/docs/dunstctl.pod b/docs/dunstctl.pod index addde448b..ef9856890 100644 --- a/docs/dunstctl.pod +++ b/docs/dunstctl.pod @@ -49,8 +49,20 @@ will be kept but not shown until it is unpaused. =item B true/false/toggle Set the paused status of dunst. If false, dunst is running normally, if true, -dunst is paused. See the is-paused command and the dunst man page for more -information. +dunst is paused (with maximum pause level of 100). +See the is-paused command and the dunst man page for more information. + +=item B + +Get current dunst's pause level, where 0 is not paused and 100 is maximally paused. + +This can be combined with notification's override_pause_level to selectively display specific notifications while paused. + +=item B [level] + +Set the pause level, where 0 is not paused and 100 is maximally paused. + +This can be combined with notification's override_pause_level to selectively display specific notifications while paused. =item B diff --git a/dunstctl b/dunstctl index df1de1249..a35cc912d 100755 --- a/dunstctl +++ b/dunstctl @@ -29,8 +29,10 @@ show_help() { notification with given ID. history-rm [ID] Remove the notification from history with given ID. - is-paused Check if dunst is running or paused - set-paused [true|false|toggle] Set the pause status + is-paused Check if pause level is > 0 + set-paused [true|false|toggle] Set the pause status + get-pause-level Get the current pause level + set-pause-level [level] Set the pause level rule name [enable|disable|toggle] Enable or disable a rule by its name debug Print debugging information help Show this help @@ -132,6 +134,27 @@ case "${1:-}" in && die "No valid rule state parameter specified. Please give either 'enable', 'disable' or 'toggle'" method_call "${DBUS_IFAC_DUNST}.RuleEnable" "string:${2:-1}" "int32:${state}" >/dev/null ;; + "get-pause-level") + property_get pauseLevel | ( read -r _ _ paused; printf "%s\n" "${paused}"; ) + ;; + "set-pause-level") + [ "${2:-}" ] \ + || die "No status parameter specified. Please give a number as paused parameter." + [ "$2" ] && [ -z "${2//[0-9]}" ] \ + || die "Please give a number as paused level parameter." + property_set pauseLevel variant:uint32:"$2" + ;; + "rule") + [ "${2:-}" ] \ + || die "No rule name parameter specified. Please give the rule name" + state=nope + [ "${3}" = "disable" ] && state=0 + [ "${3}" = "enable" ] && state=1 + [ "${3}" = "toggle" ] && state=2 + [ "${state}" = "nope" ] \ + && die "No valid rule state parameter specified. Please give either 'enable', 'disable' or 'toggle'" + method_call "${DBUS_IFAC_DUNST}.RuleEnable" "string:${2:-1}" "int32:${state}" >/dev/null + ;; "help"|"--help"|"-h") show_help ;; diff --git a/dunstrc b/dunstrc index b92482b77..65def387c 100644 --- a/dunstrc +++ b/dunstrc @@ -323,6 +323,7 @@ background = "#285577" foreground = "#ffffff" timeout = 10 + override_pause_level = 30 # Icon for notifications with normal urgency, uncomment to enable #default_icon = /path/to/icon @@ -331,6 +332,7 @@ foreground = "#ffffff" frame_color = "#ff0000" timeout = 0 + override_pause_level = 60 # Icon for notifications with critical urgency, uncomment to enable #default_icon = /path/to/icon @@ -368,6 +370,7 @@ # ellipsize # alignment # hide_text +# override_pause_level # # Shell-like globbing will get expanded. # @@ -375,6 +378,10 @@ # GLib based applications export their desktop-entry name. In comparison to the appname, # the desktop-entry won't get localized. # +# You can also allow a notification to appear even when paused. Notification will appear whenever notification's override_pause_level >= dunst's paused level. +# This can be used to set partial pause modes, where more urgent notifications get through, but less urgent stay paused. To do that, you can override the following in the rules: +# override_pause_level = X + # SCRIPTING # You can specify a script that gets run when the rule matches by # setting the "script" option. diff --git a/src/dbus.c b/src/dbus.c index 91dc1a43d..da3ff9dd9 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -100,6 +100,10 @@ static const char *introspection_xml = " " " " + " " + " " + " " + " " " " " " @@ -923,7 +927,9 @@ GVariant *dbus_cb_dunst_Properties_Get(GDBusConnection *connection, struct dunst_status status = dunst_status_get(); if (STR_EQ(property_name, "paused")) { - return g_variant_new_boolean(!status.running); + return g_variant_new_boolean(status.pause_level != 0); + } else if (STR_EQ(property_name, "pauseLevel")) { + return g_variant_new_uint32(status.pause_level); } else if (STR_EQ(property_name, "displayedLength")) { unsigned int displayed = queues_length_displayed(); return g_variant_new_uint32(displayed); @@ -949,15 +955,32 @@ gboolean dbus_cb_dunst_Properties_Set(GDBusConnection *connection, GError **error, gpointer user_data) { + int targetPauseLevel = -1; if (STR_EQ(property_name, "paused")) { - dunst_status(S_RUNNING, !g_variant_get_boolean(value)); + if (g_variant_get_boolean(value)) { + targetPauseLevel = MAX_PAUSE_LEVEL; + } else { + targetPauseLevel = 0; + } + } else if STR_EQ(property_name, "pauseLevel") { + targetPauseLevel = g_variant_get_uint32(value); + if (targetPauseLevel > MAX_PAUSE_LEVEL) { + targetPauseLevel = MAX_PAUSE_LEVEL; + } + } + + if (targetPauseLevel >= 0) { + dunst_status_int(S_PAUSE_LEVEL, targetPauseLevel); wake_up(); GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); GVariantBuilder *invalidated_builder = g_variant_builder_new(G_VARIANT_TYPE_STRING_ARRAY); g_variant_builder_add(builder, "{sv}", - "paused", g_variant_new_boolean(g_variant_get_boolean(value))); + "paused", g_variant_new_boolean(targetPauseLevel != 0)); + g_variant_builder_add(builder, + "{sv}", + "pauseLevel", g_variant_new_uint32(targetPauseLevel)); g_dbus_connection_emit_signal(connection, NULL, object_path, diff --git a/src/dunst.c b/src/dunst.c index 11e4d98d9..6d8d3646b 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -37,11 +37,21 @@ void dunst_status(const enum dunst_status_field field, case S_IDLE: status.idle = value; break; - case S_RUNNING: - status.running = value; + default: + LOG_E("Invalid %s enum value in %s:%d for bool type", "dunst_status", __FILE__, __LINE__); + break; + } +} + +void dunst_status_int(const enum dunst_status_field field, + int value) +{ + switch (field) { + case S_PAUSE_LEVEL: + status.pause_level = value; break; default: - LOG_E("Invalid %s enum value in %s:%d", "dunst_status", __FILE__, __LINE__); + LOG_E("Invalid %s enum value in %s:%d fot int type", "dunst_status", __FILE__, __LINE__); break; } } @@ -123,7 +133,7 @@ static gboolean run(void *data) gboolean pause_signal(gpointer data) { - dunst_status(S_RUNNING, false); + dunst_status_int(S_PAUSE_LEVEL, MAX_PAUSE_LEVEL); wake_up(); return G_SOURCE_CONTINUE; @@ -131,7 +141,7 @@ gboolean pause_signal(gpointer data) gboolean unpause_signal(gpointer data) { - dunst_status(S_RUNNING, true); + dunst_status_int(S_PAUSE_LEVEL, 0); wake_up(); return G_SOURCE_CONTINUE; @@ -156,7 +166,7 @@ static void teardown(void) int dunst_main(int argc, char *argv[]) { - dunst_status(S_RUNNING, true); + dunst_status_int(S_PAUSE_LEVEL, 0); dunst_status(S_IDLE, false); queues_init(); diff --git a/src/dunst.h b/src/dunst.h index 839a2b338..dc5267e0f 100644 --- a/src/dunst.h +++ b/src/dunst.h @@ -10,17 +10,19 @@ #include "notification.h" +#define MAX_PAUSE_LEVEL 100 + //!< A structure to describe dunst's global window status struct dunst_status { bool fullscreen; //!< a fullscreen window is currently focused - bool running; //!< set true if dunst is currently running + int pause_level; //!< current pause level. 0 = all notifications come through, 100 = no notifications come through bool idle; //!< set true if the user is idle }; enum dunst_status_field { S_FULLSCREEN, S_IDLE, - S_RUNNING, + S_PAUSE_LEVEL, }; /** @@ -30,6 +32,8 @@ enum dunst_status_field { */ void dunst_status(const enum dunst_status_field field, bool value); +void dunst_status_int(const enum dunst_status_field field, + int value); struct dunst_status dunst_status_get(void); diff --git a/src/notification.c b/src/notification.c index d23ab7da8..029fe7664 100644 --- a/src/notification.c +++ b/src/notification.c @@ -488,6 +488,8 @@ void notification_init(struct notification *n) if (n->progress < 0) n->progress = -1; + n->override_pause_level = 0; + /* Process rules */ rule_apply_all(n); diff --git a/src/notification.h b/src/notification.h index 4627808db..3572f9600 100644 --- a/src/notification.h +++ b/src/notification.h @@ -56,6 +56,7 @@ struct notification { char *category; char *desktop_entry; /**< The desktop entry hint sent via every GApplication */ enum urgency urgency; + int override_pause_level; cairo_surface_t *icon; /**< The raw cached icon data used to draw */ char *icon_id; /**< Plain icon information, which acts as the icon's id. diff --git a/src/queues.c b/src/queues.c index ac3dae76d..1dfc34cec 100644 --- a/src/queues.c +++ b/src/queues.c @@ -120,7 +120,10 @@ static void queues_swap_notifications(GQueue *queueA, */ static bool queues_notification_is_ready(const struct notification *n, struct dunst_status status, bool shown) { - ASSERT_OR_RET(status.running, false); + if (status.pause_level > n->override_pause_level) { + return false; + } + if (status.fullscreen && shown) return n && n->fullscreen != FS_PUSHBACK; else if (status.fullscreen && !shown) diff --git a/src/rules.c b/src/rules.c index 2ceb6450f..ecc34d723 100644 --- a/src/rules.c +++ b/src/rules.c @@ -99,6 +99,9 @@ void rule_apply(struct rule *r, struct notification *n) g_free(n->stack_tag); n->stack_tag = g_strdup(r->set_stack_tag); } + if (r->override_pause_level != -1) { + n->override_pause_level = r->override_pause_level; + } } /* diff --git a/src/rules.h b/src/rules.h index 1a70e43d2..ac50c1bf4 100644 --- a/src/rules.h +++ b/src/rules.h @@ -45,6 +45,7 @@ struct rule { int icon_position; int min_icon_size; int max_icon_size; + int override_pause_level; char *new_icon; char *fg; char *bg; diff --git a/src/settings_data.h b/src/settings_data.h index 5c68bb4ff..756ba97ea 100644 --- a/src/settings_data.h +++ b/src/settings_data.h @@ -165,6 +165,7 @@ static const struct rule empty_rule = { .progress_bar_alignment = -1, .min_icon_size = -1, .max_icon_size = -1, + .override_pause_level = -1 }; @@ -760,6 +761,17 @@ static const struct setting allowed_settings[] = { .parser_data = NULL, .rule_offset = offsetof(struct rule, max_icon_size), }, + { + .name = "override_pause_level", + .section = "*", + .description = "TODO", + .type = TYPE_INT, + .default_value = "-1", + .value = NULL, + .parser = NULL, + .parser_data = NULL, + .rule_offset = offsetof(struct rule, override_pause_level), + }, // end of modifying rules // other settings below diff --git a/test/dbus.c b/test/dbus.c index 47ed2f48a..d7aa3539b 100644 --- a/test/dbus.c +++ b/test/dbus.c @@ -395,7 +395,7 @@ TEST test_dbus_cb_dunst_Properties_Get(void) ASSERT_FALSE(g_variant_get_boolean(pause_variant)); g_variant_unref(pause_variant); - dunst_status(S_RUNNING, false); + dunst_status_int(S_PAUSE_LEVEL, 100); pause_variant = dbus_cb_dunst_Properties_Get(connection_client, FDN_NAME, @@ -467,6 +467,56 @@ TEST test_dbus_cb_dunst_Properties_Set(void) PASS(); } +TEST test_dbus_cb_dunst_Properties_Set_pause_level(void) +{ + + GDBusConnection *connection_client; + GError *error = NULL; + struct signal_propertieschanged sig = {NULL, NULL, NULL, -1}; + + dbus_signal_subscribe_propertieschanged(&sig); + + connection_client = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL); + + GVariant *pause_variant = g_variant_new_uint32(33); + + + ASSERT(dbus_cb_dunst_Properties_Set(connection_client, + FDN_NAME, + FDN_PATH, + DUNST_IFAC, + "pauseLevel", + pause_variant, + &error, + NULL)); + + if (error) { + printf("Error while calling dbus_cb_dunst_Properties_Set: %s\n", error->message); + g_error_free(error); + } + + uint waiting = 0; + while (!sig.interface && waiting < 2000) { + usleep(500); + waiting++; + } + + ASSERT_STR_EQ(sig.interface, DUNST_IFAC); + + guint pauseLevel; + g_variant_lookup(sig.array_dict_sv_data, "pauseLevel", "u", &pauseLevel); + + ASSERT(pauseLevel == 33); + + g_variant_unref(pause_variant); + g_free(sig.interface); + g_variant_unref(sig.array_dict_sv_data); + g_variant_unref(sig.array_s_data); + dbus_signal_unsubscribe_propertieschanged(&sig); + g_object_unref(connection_client); + PASS(); +} + TEST test_empty_notification(void) { struct dbus_notification *n = dbus_notification_new(); @@ -1125,6 +1175,7 @@ gpointer run_threaded_tests(gpointer data) RUN_TEST(test_get_fdn_daemon_info); RUN_TEST(test_dbus_cb_dunst_Properties_Get); RUN_TEST(test_dbus_cb_dunst_Properties_Set); + RUN_TEST(test_dbus_cb_dunst_Properties_Set_pause_level); RUN_TEST(test_empty_notification); RUN_TEST(test_basic_notification); diff --git a/test/dunst.c b/test/dunst.c index 827f98dcc..3c949e662 100644 --- a/test/dunst.c +++ b/test/dunst.c @@ -9,8 +9,8 @@ TEST test_dunst_status(void) ASSERT(status.fullscreen); dunst_status(S_IDLE, true); ASSERT(status.idle); - dunst_status(S_RUNNING, true); - ASSERT(status.running); + dunst_status_int(S_PAUSE_LEVEL, 0); + ASSERT(status.pause_level == 0); PASS(); } diff --git a/test/queues.c b/test/queues.c index a0568cb37..a31593124 100644 --- a/test/queues.c +++ b/test/queues.c @@ -825,6 +825,43 @@ TEST test_queues_update_paused(void) PASS(); } +TEST test_queues_update_pause_level(void) +{ + settings.notification_limit = 5; + struct notification *n1, *n2, *n3; + queues_init(); + + n1 = test_notification("n1", 0); + n2 = test_notification("n2", 0); + n3 = test_notification("n3", 0); + + n1->override_pause_level = 0; + n2->override_pause_level = 5; + n3->override_pause_level = 10; + + queues_notification_insert(n1); + queues_notification_insert(n2); + queues_notification_insert(n3); + + queues_update(STATUS_PAUSE_7, time_monotonic_now()); + QUEUE_LEN_ALL(2,1,0); + ASSERT(strcmp(((struct notification*) g_queue_peek_nth(QUEUE(DISP), 0))->summary, "n3") == 0); + ASSERT(strcmp(((struct notification*) g_queue_peek_nth(QUEUE(WAIT), 0))->summary, "n1") == 0); + ASSERT(strcmp(((struct notification*) g_queue_peek_nth(QUEUE(WAIT), 1))->summary, "n2") == 0); + + queues_update(STATUS_NORMAL, time_monotonic_now()); + QUEUE_LEN_ALL(0,3,0); + + queues_update(STATUS_PAUSE_7, time_monotonic_now()); + QUEUE_LEN_ALL(2,1,0); + ASSERT(strcmp(((struct notification*) g_queue_peek_nth(QUEUE(DISP), 0))->summary, "n3") == 0); + ASSERT(strcmp(((struct notification*) g_queue_peek_nth(QUEUE(WAIT), 0))->summary, "n1") == 0); + ASSERT(strcmp(((struct notification*) g_queue_peek_nth(QUEUE(WAIT), 1))->summary, "n2") == 0); + + queues_teardown(); + PASS(); +} + TEST test_queues_update_seeping(void) { settings.notification_limit = 5; @@ -1105,6 +1142,7 @@ SUITE(suite_queues) RUN_TEST(test_queue_insert_id_replacement); RUN_TEST(test_queue_insert_id_valid_newid); RUN_TEST(test_queue_length); + RUN_TEST(test_queue_notification_close); RUN_TEST(test_queue_notification_close_histignore); RUN_TEST(test_queue_notification_skip_display); @@ -1118,6 +1156,8 @@ SUITE(suite_queues) RUN_TEST(test_queue_timeout); RUN_TEST(test_queues_update_fullscreen); RUN_TEST(test_queues_update_paused); + RUN_TEST(test_queues_update_pause_level + ); RUN_TEST(test_queues_update_seep_showlowurg); RUN_TEST(test_queues_update_seeping); RUN_TEST(test_queues_update_xmore); diff --git a/test/queues.h b/test/queues.h index ec1f74be7..86e5863d1 100644 --- a/test/queues.h +++ b/test/queues.h @@ -9,11 +9,12 @@ #include "../src/notification.h" #include "../src/queues.h" -#define STATUS_NORMAL ((struct dunst_status) {.fullscreen=false, .running=true, .idle=false}) -#define STATUS_IDLE ((struct dunst_status) {.fullscreen=false, .running=true, .idle=true}) -#define STATUS_FSIDLE ((struct dunst_status) {.fullscreen=true, .running=true, .idle=true}) -#define STATUS_FS ((struct dunst_status) {.fullscreen=true, .running=true, .idle=false}) -#define STATUS_PAUSE ((struct dunst_status) {.fullscreen=false, .running=false, .idle=false}) +#define STATUS_NORMAL ((struct dunst_status) {.fullscreen=false, .pause_level=0, .idle=false}) +#define STATUS_IDLE ((struct dunst_status) {.fullscreen=false, .pause_level=0, .idle=true}) +#define STATUS_FSIDLE ((struct dunst_status) {.fullscreen=true, .pause_level=0, .idle=true}) +#define STATUS_FS ((struct dunst_status) {.fullscreen=true, .pause_level=0, .idle=false}) +#define STATUS_PAUSE ((struct dunst_status) {.fullscreen=false, .pause_level=100, .idle=false}) +#define STATUS_PAUSE_7 ((struct dunst_status) {.fullscreen=false, .pause_level=7, .idle=false}) #define QUEUE_WAIT waiting #define QUEUE_DISP displayed