diff --git a/CHANGELOG.md b/CHANGELOG.md index 3602a69..3d74192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- `fullscreen` rule to hide notifications when a fullscreen window is active + ## 1.3.0 - 2018-01-05 ### Added diff --git a/docs/dunst.pod b/docs/dunst.pod index d2c7a0b..24636ab 100644 --- a/docs/dunst.pod +++ b/docs/dunst.pod @@ -594,8 +594,9 @@ Shell-like globing is supported. =item B The following attributes can be overridden: timeout, urgency, foreground, -background, new_icon, set_transient, format where, as with the filtering attributes, -each one corresponds to the respective notification attribute to be modified. +background, new_icon, set_transient, format, fullscreen where, +as with the filtering attributes, each one corresponds to the respective +notification attribute to be modified. As with filtering, to make a rule modify an attribute simply assign it in the rule definition. diff --git a/dunstrc b/dunstrc index 0f4c4aa..6707411 100644 --- a/dunstrc +++ b/dunstrc @@ -279,7 +279,7 @@ # override settings for certain messages. # Messages can be matched by "appname", "summary", "body", "icon", "category", # "msg_urgency" and you can override the "timeout", "urgency", "foreground", -# "background", "new_icon" and "format". +# "background", "new_icon" and "format", "fullscreen". # Shell-like globbing will get expanded. # # SCRIPTING @@ -294,6 +294,19 @@ # NOTE: It might be helpful to run dunst -print in a terminal in order # to find fitting options for rules. +# fullscreen values +# show: show the notifications, regardless if there is a fullscreen window opened +# delay: displays the new notification, if there is there is no fullscreen window active +# If the notification is already drawn, it won't get undrawn. +# pushback: same as delay, but when switching into fullscreen, the notification will get +# withdrawn from screen again and will get delayed like a new notification + +#[fullscreen_delay_everything] +# fullscreen = delay +#[fullscreen_show_critical] +# msg_urgency = critical +# fullscreen = show + #[espeak] # summary = "*" # script = dunst_espeak.sh diff --git a/src/dunst.c b/src/dunst.c index 53fb2fc..c76e078 100644 --- a/src/dunst.c +++ b/src/dunst.c @@ -48,8 +48,12 @@ void wake_up(void) static gboolean run(void *data) { - queues_check_timeouts(x_is_idle()); - queues_update(); + LOG_D("RUN"); + + bool fullscreen = have_fullscreen_window(); + + queues_check_timeouts(x_is_idle(), fullscreen); + queues_update(fullscreen); static gint64 next_timeout = 0; diff --git a/src/notification.c b/src/notification.c index f689193..b54b79f 100644 --- a/src/notification.c +++ b/src/notification.c @@ -28,6 +28,19 @@ static void notification_extract_urls(notification *n); static void notification_format_message(notification *n); static void notification_dmenu_string(notification *n); +/* see notification.h */ +const char *enum_to_string_fullscreen(enum behavior_fullscreen in) +{ + switch (in) { + case FS_SHOW: return "show"; + case FS_DELAY: return "delay"; + case FS_PUSHBACK: return "pushback"; + case FS_NULL: return "(null)"; + default: + LOG_E("Enum behavior_fullscreen has wrong value."); + } +} + /* * print a human readable representation * of the given notification to stdout. @@ -49,6 +62,7 @@ void notification_print(notification *n) printf("\tfg: %s\n", n->colors[ColFG]); printf("\tbg: %s\n", n->colors[ColBG]); printf("\tframe: %s\n", n->colors[ColFrame]); + printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen)); printf("\tid: %d\n", n->id); if (n->urls) { char *urls = string_replace_all("\n", "\t\t\n", g_strdup(n->urls)); @@ -284,6 +298,8 @@ notification *notification_create(void) n->transient = false; n->progress = -1; + n->fullscreen = FS_SHOW; + return n; } diff --git a/src/notification.h b/src/notification.h index 11632a9..a866214 100644 --- a/src/notification.h +++ b/src/notification.h @@ -9,6 +9,13 @@ #define DUNST_NOTIF_MAX_CHARS 5000 +enum behavior_fullscreen { + FS_NULL, //!< Invalid value + FS_DELAY, //!< Delay the notification until leaving fullscreen mode + FS_PUSHBACK, //!< When entering fullscreen mode, push the notification back to waiting + FS_SHOW, //!< Show the message when in fullscreen mode +}; + /// Representing the urgencies according to the notification spec enum urgency { URG_NONE = -1, /**< Urgency not set (invalid) */ @@ -69,6 +76,7 @@ typedef struct _notification { bool first_render; /**< markup has been rendered before? */ int dup_count; /**< amount of duplicate notifications stacked onto this */ int displayed_height; + enum behavior_fullscreen fullscreen; //!< The instruction what to do with it, when desktop enters fullscreen /* derived fields */ char *msg; /**< formatted message */ @@ -94,5 +102,14 @@ void notification_update_text_to_render(notification *n); void notification_do_action(notification *n); const char *notification_urgency_to_string(enum urgency urgency); + +/** + * Return the string representation for fullscreen behavior + * + * @param in the #behavior_fullscreen enum value to represent + * @return the string representation for `in` + */ +const char *enum_to_string_fullscreen(enum behavior_fullscreen in); + #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/option_parser.c b/src/option_parser.c index 6573d73..b95f911 100644 --- a/src/option_parser.c +++ b/src/option_parser.c @@ -551,4 +551,22 @@ const char *cmdline_create_usage(void) return usage_str; } +/* see option_parser.h */ +enum behavior_fullscreen parse_enum_fullscreen(const char *string, enum behavior_fullscreen def) +{ + if (!string) + return def; + + if (strcmp(string, "show") == 0) + return FS_SHOW; + else if (strcmp(string, "delay") == 0) + return FS_DELAY; + else if (strcmp(string, "pushback") == 0) + return FS_PUSHBACK; + else { + LOG_W("Unknown fullscreen value: '%s'\n", string); + return def; + } +} + /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/option_parser.h b/src/option_parser.h index e816b7e..d712fa8 100644 --- a/src/option_parser.h +++ b/src/option_parser.h @@ -6,6 +6,8 @@ #include #include +#include "dunst.h" + int load_ini_file(FILE *); char *ini_get_path(const char *section, const char *key, const char *def); char *ini_get_string(const char *section, const char *key, const char *def); @@ -63,5 +65,17 @@ int option_get_bool(const char *ini_section, */ const char *next_section(const char *section); +/** + * Parse the fullscreen behavior value of the given string + * + * @param string the string representation of #behavior_fullscreen. + * The string must not contain any waste characters. + * @param def value to return in case of errors + * + * @return the #behavior_fullscreen representation of `string` + * @return `def` if `string` is invalid or `NULL` + */ +enum behavior_fullscreen parse_enum_fullscreen(const char *string, enum behavior_fullscreen def); + #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/queues.c b/src/queues.c index 78f6c6a..cf229a2 100644 --- a/src/queues.c +++ b/src/queues.c @@ -284,12 +284,14 @@ void queues_history_push_all(void) } /* see queues.h */ -void queues_check_timeouts(bool idle) +void queues_check_timeouts(bool idle, bool fullscreen) { /* nothing to do */ if (displayed->length == 0) return; + bool is_idle = fullscreen ? false : idle; + GList *iter = g_queue_peek_head_link(displayed); while (iter) { notification *n = iter->data; @@ -302,7 +304,7 @@ void queues_check_timeouts(bool idle) iter = iter->next; /* don't timeout when user is idle */ - if (idle && !n->transient) { + if (is_idle && !n->transient) { n->start = g_get_monotonic_time(); continue; } @@ -320,7 +322,7 @@ void queues_check_timeouts(bool idle) } /* see queues.h */ -void queues_update(void) +void queues_update(bool fullscreen) { if (pause_displayed) { while (displayed->length > 0) { @@ -330,26 +332,52 @@ void queues_update(void) return; } + /* move notifications back to queue, which are set to pushback */ + if (fullscreen) { + GList *iter = g_queue_peek_head_link(displayed); + while (iter) { + notification *n = iter->data; + GList *nextiter = iter->next; + + if (n->fullscreen == FS_PUSHBACK){ + g_queue_delete_link(displayed, iter); + g_queue_insert_sorted(waiting, n, notification_cmp_data, NULL); + } + + iter = nextiter; + } + } + /* move notifications from queue to displayed */ - while (waiting->length > 0) { + GList *iter = g_queue_peek_head_link(waiting); + while (iter) { + notification *n = iter->data; + GList *nextiter = iter->next; if (displayed_limit > 0 && displayed->length >= displayed_limit) { /* the list is full */ break; } - notification *n = g_queue_pop_head(waiting); - if (!n) return; + if (fullscreen + && (n->fullscreen == FS_DELAY || n->fullscreen == FS_PUSHBACK)) { + iter = nextiter; + continue; + } + n->start = g_get_monotonic_time(); if (!n->redisplayed && n->script) { notification_run_script(n); } + g_queue_delete_link(waiting, iter); g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL); + + iter = nextiter; } } diff --git a/src/queues.h b/src/queues.h index 2fa9900..6bb10d7 100644 --- a/src/queues.h +++ b/src/queues.h @@ -125,8 +125,10 @@ void queues_history_push_all(void); * * @param idle the program's idle status. Important to calculate the * timeout for transient notifications + * @param fullscreen the desktop's fullscreen status. Important to + * calculate the timeout for transient notifications */ -void queues_check_timeouts(bool idle); +void queues_check_timeouts(bool idle, bool fullscreen); /** * Move inserted notifications from waiting queue to displayed queue @@ -135,8 +137,11 @@ void queues_check_timeouts(bool idle); * * @post Call wake_up() to synchronize the queues with the UI * (which closes old and shows new notifications on screen) + * + * @param fullscreen the desktop's fullscreen status. Important to + * move notifications to the right queue */ -void queues_update(void); +void queues_update(bool fullscreen); /** * Calculate the distance to the next event, when an element in the diff --git a/src/rules.c b/src/rules.c index 7be1abb..22e5bf3 100644 --- a/src/rules.c +++ b/src/rules.c @@ -16,6 +16,8 @@ void rule_apply(rule_t *r, notification *n) n->timeout = r->timeout; if (r->urgency != URG_NONE) n->urgency = r->urgency; + if (r->fullscreen != FS_NULL) + n->fullscreen = r->fullscreen; if (r->history_ignore != -1) n->history_ignore = r->history_ignore; if (r->set_transient != -1) @@ -69,6 +71,7 @@ void rule_init(rule_t *r) r->msg_urgency = URG_NONE; r->timeout = -1; r->urgency = URG_NONE; + r->fullscreen = FS_NULL; r->markup = MARKUP_NULL; r->new_icon = NULL; r->history_ignore = false; diff --git a/src/rules.h b/src/rules.h index b8d1d87..9bee0a9 100644 --- a/src/rules.h +++ b/src/rules.h @@ -30,6 +30,7 @@ typedef struct _rule_t { char *bg; const char *format; const char *script; + enum behavior_fullscreen fullscreen; } rule_t; extern GSList *rules; diff --git a/src/settings.c b/src/settings.c index c9264f5..75aead6 100644 --- a/src/settings.c +++ b/src/settings.c @@ -684,6 +684,15 @@ void load_settings(char *cmdline_config_path) r->history_ignore = ini_get_bool(cur_section, "history_ignore", r->history_ignore); r->match_transient = ini_get_bool(cur_section, "match_transient", r->match_transient); r->set_transient = ini_get_bool(cur_section, "set_transient", r->set_transient); + { + char *c = ini_get_string( + cur_section, + "fullscreen", NULL + ); + + r->fullscreen = parse_enum_fullscreen(c, r->fullscreen); + g_free(c); + } r->script = ini_get_path(cur_section, "script", NULL); } diff --git a/src/x11/screen.c b/src/x11/screen.c index 070c1f1..2bd37b8 100644 --- a/src/x11/screen.c +++ b/src/x11/screen.c @@ -146,6 +146,8 @@ void screen_check_event(XEvent event) { if (event.type == randr_event_base + RRScreenChangeNotify) randr_update(); + else + LOG_D("XEvent: Ignored '%d'", event.type); } void xinerama_update(void) @@ -186,6 +188,89 @@ void screen_update_fallback(void) screens[0].dim.h = DisplayHeight(xctx.dpy, screen); } +/* see screen.h */ +bool have_fullscreen_window(void) +{ + return window_is_fullscreen(get_focused_window()); +} + +/** + * X11 ErrorHandler to mainly discard BadWindow parameter error + */ +static int XErrorHandlerFullscreen(Display *display, XErrorEvent *e) +{ + /* Ignore BadWindow errors. Window may have been gone */ + if (e->error_code == BadWindow) { + return 0; + } + + char err_buf[BUFSIZ]; + XGetErrorText(display, e->error_code, err_buf, BUFSIZ); + fputs(err_buf, stderr); + fputs("\n", stderr); + + return 0; +} + +/* see screen.h */ +bool window_is_fullscreen(Window window) +{ + bool fs = false; + + if (!window) + return false; + + Atom has_wm_state = XInternAtom(xctx.dpy, "_NET_WM_STATE", True); + if (has_wm_state == None){ + return false; + } + + XFlush(xctx.dpy); + XSetErrorHandler(XErrorHandlerFullscreen); + + Atom actual_type_return; + int actual_format_return; + unsigned long bytes_after_return; + unsigned char *prop_to_return; + unsigned long n_items; + int result = XGetWindowProperty( + xctx.dpy, + window, + has_wm_state, + 0, /* long_offset */ + sizeof(window), /* long_length */ + false, /* delete */ + AnyPropertyType, /* req_type */ + &actual_type_return, + &actual_format_return, + &n_items, + &bytes_after_return, + &prop_to_return); + + XFlush(xctx.dpy); + XSync(xctx.dpy, false); + XSetErrorHandler(NULL); + + if (result == Success) { + for(int i = 0; i < n_items; i++) { + char *atom = XGetAtomName(xctx.dpy, ((Atom*)prop_to_return)[i]); + + if (atom) { + if(0 == strcmp("_NET_WM_STATE_FULLSCREEN", atom)) + fs = true; + XFree(atom); + if(fs) + break; + } + } + } + + if (prop_to_return) + XFree(prop_to_return); + + return fs; +} + /* * Select the screen on which the Window * should be displayed. diff --git a/src/x11/screen.h b/src/x11/screen.h index ed71081..fcd020d 100644 --- a/src/x11/screen.h +++ b/src/x11/screen.h @@ -3,6 +3,7 @@ #define DUNST_SCREEN_H #include +#include #define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) @@ -27,5 +28,24 @@ void screen_check_event(XEvent event); screen_info *get_active_screen(void); double get_dpi_for_screen(screen_info *scr); +/** + * Find the currently focused window and check if it's in + * fullscreen mode + * + * @see window_is_fullscreen() + * @see get_focused_window() + * + * @return `true` if the focused window is in fullscreen mode + */ +bool have_fullscreen_window(void); + +/** + * Check if window is in fullscreen mode + * + * @param window the x11 window object + * @return `true` if `window` is in fullscreen mode + */ +bool window_is_fullscreen(Window window); + #endif /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ diff --git a/src/x11/x.c b/src/x11/x.c index 92942da..7ec682d 100644 --- a/src/x11/x.c +++ b/src/x11/x.c @@ -60,6 +60,7 @@ typedef struct _colored_layout { } colored_layout; cairo_ctx_t cairo_ctx; +static bool fullscreen_last = false; /* FIXME refactor setup teardown handlers into one setup and one teardown */ static void x_shortcut_setup_error_handler(void); @@ -848,10 +849,13 @@ gboolean x_mainloop_fd_check(GSource *source) */ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { + bool fullscreen_now; XEvent ev; unsigned int state; while (XPending(xctx.dpy) > 0) { XNextEvent(xctx.dpy, &ev); + LOG_D("XEvent: processing '%d'", ev.type); + switch (ev.type) { case Expose: if (ev.xexpose.count == 0 && xctx.visible) { @@ -908,13 +912,20 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer wake_up(); break; case PropertyNotify: + fullscreen_now = have_fullscreen_window(); + + if (fullscreen_now != fullscreen_last) { + fullscreen_last = fullscreen_now; + wake_up(); + } else if ( settings.f_mode != FOLLOW_NONE /* Ignore PropertyNotify, when we're still on the * same screen. PropertyNotify is only neccessary * to detect a focus change to another screen */ - if( settings.f_mode != FOLLOW_NONE - && get_active_screen()->scr != xctx.cur_screen) - wake_up(); + && xctx.visible + && get_active_screen()->scr != xctx.cur_screen) { + x_win_draw(); + } break; default: screen_check_event(ev);