Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export rules via dbus #1212

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ distro's repositories, don't worry, it's not hard to build it yourself.
- wayland-client (can build without, see [make parameters](#make-parameters))
- wayland-protocols (optional, for recompiling protocols)
- xdg-utils (optional, xdg-open is the default 'browser' for opening URLs)
- jq (optional, for installed completions and tools in contrib)

The names will be different depending on your [distribution](https://github.com/dunst-project/dunst/wiki/Dependencies).

Expand Down
5 changes: 2 additions & 3 deletions completions/_dunstctl.zshcomp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
local curcontext="$curcontext" ret=1
local -a state line subs

local DUNSTRC="${XDG_CONFIG_HOME:-$HOME/.config}/dunst/dunstrc"

_arguments -C \
'1:cmd:->cmds' \
'2:opt:->opts' \
Expand All @@ -30,6 +28,7 @@ case $state in
'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'
'rules:Displays configured rules'
'debug:Print debugging information'
'help:Show this help'
)
Expand Down Expand Up @@ -63,7 +62,7 @@ case $state in
rule)
local -a rules;
rules=(
`awk '/^\[.*\]/{ if ( match($0, /^\[global|urgency|experimental/) == 0 ) { print substr($0, 2, length($0)-2) } }' < "$DUNSTRC"`
`dunstctl rules --json | jq -r '.data[][].name.data'`
)
_describe rules_opts rules && ret=0
;;
Expand Down
9 changes: 5 additions & 4 deletions completions/dunstctl.bashcomp
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
_dunstctl() {
local opts cur prev split=false
local DUNSTRC=${XDG_CONFIG_HOME:-$HOME/.config}/dunst/dunstrc
_get_comp_words_by_ref cur prev
COMPREPLY=()
opts='action close close-all context count debug help history history-clear history-pop history-rm is-paused rule set-paused'
opts='action close close-all context count debug help history history-clear history-pop history-rm is-paused rule rules set-paused'

case "$prev" in
count) COMPREPLY=('displayed' 'history' 'waiting')
Expand All @@ -15,8 +14,10 @@ _dunstctl() {
awk '/"data" :/{print $3}' | sort -u) )
return ;;
rule)
COMPREPLY=( $(awk \
'/^\[.*\]/{ if ( match($0, /^\[global|experimental/) == 0 ) print substr($0, 2, length($0)-2) }' "$DUNSTRC" ) )
COMPREPLY=( $( compgen -W "$(dunstctl rules --json | jq -r '.data[][].name.data')" -- $cur ) )
return ;;
rules)
COMPREPLY=( $( compgen -W "--json" ) )
return ;;
esac

Expand Down
25 changes: 15 additions & 10 deletions completions/dunstctl.fishcomp
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
if command -q jq
function __fish_dunstctl_history
dunstctl history | jq -r '.data[][] | "\(.id.data)\t\(.appname.data)"'
end
else
function __fish_dunstctl_history
dunstctl history | awk '/"id" :/ {getline; getline; print $3}'
function __fish_dunstctl_info
dunstctl (string split ' ' $argv[1]) | jq -r ".data[][] | \"\(.$argv[2].data)\t\(.$argv[3].data)\""
end

function __fish_dunstctl_rule_complete
set -l parts (string split ' ' $argv[1])
if test (count $parts[-1]) -eq 0 || test $parts[-2] = rule
__fish_dunstctl_info 'rules --json' name enabled
return
end
# TODO? enable disable might not make sense when the rule is already in the correct state
echo -e 'enable\ndisable\ntoggle'
end

# commands
Expand All @@ -20,16 +24,17 @@ complete -c dunstctl -f -n __fish_use_subcommand -a history-pop -d 'Pop the late
complete -c dunstctl -f -n __fish_use_subcommand -a history-rm -d 'Remove the notification from history with given ID'
complete -c dunstctl -f -n __fish_use_subcommand -a is-paused -d 'Check if dunst is running or paused'
complete -c dunstctl -f -n __fish_use_subcommand -a set-paused -d 'Set the pause status'
complete -c dunstctl -f -n __fish_use_subcommand -a rules -d 'Displays configured rules (optionally in JSON)'
complete -c dunstctl -f -n __fish_use_subcommand -a rule -d 'Enable or disable a rule by its name'
complete -c dunstctl -f -n __fish_use_subcommand -a debug -d 'Print debugging information'
complete -c dunstctl -f -n __fish_use_subcommand -a help -d 'Show this help'

# command specific arguments
complete -c dunstctl -x -n '__fish_seen_subcommand_from action close close-all context history history-clear is-paused debug help'
complete -c dunstctl -x -n '__fish_seen_subcommand_from count' -a 'displayed history waiting'
complete -c dunstctl -x -n '__fish_seen_subcommand_from history-pop history-rm' -a '(__fish_dunstctl_history)'
complete -c dunstctl -x -n '__fish_seen_subcommand_from history-pop history-rm' -a '(__fish_dunstctl_info history id appname)'
complete -c dunstctl -x -n '__fish_seen_subcommand_from set-paused' -a 'true false toggle'

# TODO: add completion for rule when there is a proper way to get configured rules
complete -c dunstctl -x -n '__fish_seen_subcommand_from rule' -a '(__fish_dunstctl_rule_complete (commandline -c))'
complete -c dunstctl -x -n '__fish_seen_subcommand_from rules' -a --json

# ex: filetype=fish
4 changes: 4 additions & 0 deletions docs/dunstctl.pod
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ display specific notifications while paused.
Enables, disables or toggles the rule identified by its name. This can be used
to temporarily activate or deactivate specific rules.

=item B<rules> [--json]

Exports all currently configured rules (optionally JSON formatted).

=item B<debug>

Tries to contact dunst and checks for common faults between dunstctl and dunst.
Expand Down
21 changes: 21 additions & 0 deletions dunstctl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ show_help() {
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
rules [--json] Displays configured rules (optionally
in JSON)
debug Print debugging information
help Show this help
EOH
Expand Down Expand Up @@ -123,6 +125,25 @@ case "${1:-}" in
property_set paused variant:boolean:"$2"
fi
;;
"rules")
case "${2:-}" in
"" | --json)
busctl --user --json=pretty --no-pager call "${DBUS_NAME}" "${DBUS_PATH}" "${DBUS_IFAC_DUNST}" RuleList \
| {
if [ "${2:-}" = '--json' ]
then
cat
else
jq --raw-output '.data[][] | ["[\(.name.data)]"], [to_entries[] | select(.key != "name") | " \(.key) = \(.value.data)"] | join("\n")'
fi
} \
|| die "Dunst is not running."
;;
*)
die "Unknown format \"${2}\". Please use either \"--json\" or no option at all."
;;
esac
;;
"rule")
[ "${2:-}" ] \
|| die "No rule name parameter specified. Please give the rule name"
Expand Down
147 changes: 147 additions & 0 deletions src/dbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
#include <stdio.h>
#include <stdlib.h>

#include "draw.h"
#include "dunst.h"
#include "log.h"
#include "menu.h"
#include "notification.h"
#include "queues.h"
#include "settings.h"
#include "settings_data.h"
#include "utils.h"
#include "rules.h"
#include "option_parser.h"
Expand Down Expand Up @@ -95,6 +97,9 @@ static const char *introspection_xml =
" <arg name=\"name\" type=\"s\"/>"
" <arg name=\"state\" type=\"i\"/>"
" </method>"
" <method name=\"RuleList\">"
" <arg direction=\"out\" name=\"rules\" type=\"aa{sv}\"/>"
" </method>"
" <method name=\"Ping\" />"

" <property name=\"paused\" type=\"b\" access=\"readwrite\">"
Expand Down Expand Up @@ -187,6 +192,7 @@ DBUS_METHOD(dunst_NotificationPopHistory);
DBUS_METHOD(dunst_NotificationRemoveFromHistory);
DBUS_METHOD(dunst_NotificationShow);
DBUS_METHOD(dunst_RuleEnable);
DBUS_METHOD(dunst_RuleList);
DBUS_METHOD(dunst_Ping);
static struct dbus_method methods_dunst[] = {
{"ContextMenuCall", dbus_cb_dunst_ContextMenuCall},
Expand All @@ -200,6 +206,7 @@ static struct dbus_method methods_dunst[] = {
{"NotificationShow", dbus_cb_dunst_NotificationShow},
{"Ping", dbus_cb_dunst_Ping},
{"RuleEnable", dbus_cb_dunst_RuleEnable},
{"RuleList", dbus_cb_dunst_RuleList},
};

void dbus_cb_dunst_methods(GDBusConnection *connection,
Expand Down Expand Up @@ -411,6 +418,146 @@ static void dbus_cb_dunst_NotificationRemoveFromHistory(GDBusConnection *connect
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}

static const char *enum_to_string(const struct string_to_enum_def values[], int enum_value)
{
for (size_t i = 0; values[i].string != NULL; i++) {
if (values[i].enum_value == enum_value) {
return values[i].string;
}
}
return NULL;
}

static void color_entry(const struct color c, GVariantDict *dict, const char *field_name) {
char buf[10];
if (color_to_string(c, buf)) {
g_variant_dict_insert(dict, field_name, "s", buf);
}
}

static void dbus_cb_dunst_RuleList(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
GDBusMethodInvocation *invocation)
{
LOG_D("CMD: Listing all configured rules");

GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));

for (GSList *iter = rules; iter; iter = iter->next) {
struct rule *r = iter->data;

if (is_special_section(r->name)) {
continue;
}

GVariantDict dict;
g_variant_dict_init(&dict, NULL);
g_variant_dict_insert(&dict, "name", "s", r->name);

// filters - order according to rule_matches_notification
g_variant_dict_insert(&dict, "enabled", "b", r->enabled);
// undocumented filter?
if (r->match_dbus_timeout > -1)
g_variant_dict_insert(&dict, "match_dbus_timeout", "i", r->match_dbus_timeout);
if (r->msg_urgency != URG_NONE)
g_variant_dict_insert(&dict,
"msg_urgency",
"s",
enum_to_string(urgency_enum_data, r->msg_urgency));
if (r->match_transient > -1)
g_variant_dict_insert(&dict, "match_transient", "b", r->match_transient);
if (r->appname)
g_variant_dict_insert(&dict, "appname", "s", r->appname);
if (r->desktop_entry)
g_variant_dict_insert(&dict, "desktop_entry", "s", r->desktop_entry);
if (r->summary)
g_variant_dict_insert(&dict, "summary", "s", r->summary);
if (r->body)
g_variant_dict_insert(&dict, "body", "s", r->body);
if (r->category)
g_variant_dict_insert(&dict, "category", "s", r->category);
if (r->stack_tag)
g_variant_dict_insert(&dict, "stack_tag", "s", r->stack_tag);

// settings to apply - order according to rule_apply
if (r->timeout != -1)
g_variant_dict_insert(&dict, "timeout", "x", r->timeout);
if (r->override_dbus_timeout != -1)
g_variant_dict_insert(&dict, "override_dbus_timeout", "x", r->override_dbus_timeout);
if (r->urgency != URG_NONE)
g_variant_dict_insert(&dict, "urgency", "s", enum_to_string(urgency_enum_data, r->urgency));
if (r->fullscreen != FS_NULL)
g_variant_dict_insert(&dict,
"fullscreen",
"s",
enum_to_string(fullscreen_enum_data, r->fullscreen));
if (r->history_ignore != -1)
g_variant_dict_insert(&dict, "history_ignore", "b", r->history_ignore);
if (r->set_transient != -1)
g_variant_dict_insert(&dict, "set_transient", "b", r->set_transient);
if (r->skip_display != -1)
g_variant_dict_insert(&dict, "skip_display", "b", r->skip_display);
if (r->word_wrap != -1)
g_variant_dict_insert(&dict, "word_wrap", "b", r->word_wrap);
if (r->ellipsize != -1)
g_variant_dict_insert(&dict,
"ellipsize",
"s",
enum_to_string(ellipsize_enum_data, r->ellipsize));
if (r->alignment != -1)
g_variant_dict_insert(&dict,
"alignment",
"s",
enum_to_string(horizontal_alignment_enum_data, r->alignment));
if (r->hide_text != -1)
g_variant_dict_insert(&dict, "hide_text", "b", r->hide_text);
if (r->progress_bar_alignment != -1)
g_variant_dict_insert(&dict,
"progress_bar_alignment",
"s",
enum_to_string(horizontal_alignment_enum_data,
r->progress_bar_alignment));
if (r->min_icon_size != -1)
g_variant_dict_insert(&dict, "min_icon_size", "i", r->min_icon_size);
if (r->max_icon_size != -1)
g_variant_dict_insert(&dict, "max_icon_size", "i", r->max_icon_size);
if (r->action_name)
g_variant_dict_insert(&dict, "action_name", "s", r->action_name);
if (r->set_category)
g_variant_dict_insert(&dict, "set_category", "s", r->set_category);
if (r->markup != MARKUP_NULL)
g_variant_dict_insert(&dict, "markup", "s", enum_to_string(markup_mode_enum_data, r->markup));
if (r->icon_position != -1)
g_variant_dict_insert(&dict,
"icon_position",
"s",
enum_to_string(icon_position_enum_data, r->icon_position));
color_entry(r->fg, &dict, "fg");
color_entry(r->bg, &dict, "bg");
color_entry(r->highlight, &dict, "highlight");
color_entry(r->fc, &dict, "fc");
if (r->format)
g_variant_dict_insert(&dict, "format", "s", r->format);
if (r->default_icon)
g_variant_dict_insert(&dict, "default_icon", "s", r->default_icon);
if (r->new_icon)
g_variant_dict_insert(&dict, "new_icon", "s", r->new_icon);
if (r->script)
g_variant_dict_insert(&dict, "script", "s", r->script);
if (r->set_stack_tag)
g_variant_dict_insert(&dict, "set_stack_tag", "s", r->set_stack_tag);
if (r->override_pause_level != -1)
g_variant_dict_insert(&dict, "override_pause_level", "i", r->override_pause_level);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this function, is it not possible to use the data in settings_data.h to programmatically fill the dictionary? All rules in the settings data struct have a non-zero rule (except probably for the first one, if that's a problem that's fixable).
This would make it more easy to maintain. Otherwise it's easy to forget to add it here after adding a rule. If you have idea's for how to do this, please let me know. Then we can discuss if it's worth doing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My knowledge of C is rather limited, but if I got you right, this would mean compile time code generation, which would mean using macros. I'm not 100% sure on this, but I would highly doubt, that this is possible. Run-time code generation via reflection is not possible in C AFAIK.

My (maybe overly simplistic) approach: just write some comment to the definition of rule to remind the implementer to add it to dbus_cb_dunst_RuleList as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, could you then add some documentation to settings_data.h?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I can make it automatic when I refactor rule parsing. for now it is fine though


g_variant_builder_add_value(&builder, g_variant_dict_end(&dict));
}

g_dbus_method_invocation_return_value(invocation, g_variant_new("(aa{sv})", &builder));
g_dbus_connection_flush(connection, NULL, NULL, NULL);
}

static void dbus_cb_dunst_RuleEnable(GDBusConnection *connection,
const gchar *sender,
GVariant *parameters,
Expand Down
1 change: 1 addition & 0 deletions src/settings_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ struct setting {
* - Add the default rule value in `settings_data.h` (usually -1 or NULL)
* - Set default value in notification.c (`notification_create`). This is where
* the real default is set.
* - Add the rule to the output of `dbus_cb_dunst_RuleList` in `dbus.c`.
* - Free the variable in `notification.c` if dynamically allocated.
* - Free the variable in `rules.c` if dynamically allocated.
* - Remove the setting from the global settings struct in `settings.h`.
Expand Down
Loading