Merge pull request #472 from bebehei/fullscreen

Implement hiding on fullscreen
This commit is contained in:
Benedikt Heine 2018-02-25 20:50:54 +01:00 committed by GitHub
commit f80e6fc579
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 265 additions and 16 deletions

View File

@ -2,6 +2,10 @@
## Unreleased ## Unreleased
### Added
- `fullscreen` rule to hide notifications when a fullscreen window is active
## 1.3.0 - 2018-01-05 ## 1.3.0 - 2018-01-05
### Added ### Added

View File

@ -594,8 +594,9 @@ Shell-like globing is supported.
=item B<modifying> =item B<modifying>
The following attributes can be overridden: timeout, urgency, foreground, The following attributes can be overridden: timeout, urgency, foreground,
background, new_icon, set_transient, format where, as with the filtering attributes, background, new_icon, set_transient, format, fullscreen where,
each one corresponds to the respective notification attribute to be modified. 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 As with filtering, to make a rule modify an attribute simply assign it in the
rule definition. rule definition.

15
dunstrc
View File

@ -279,7 +279,7 @@
# override settings for certain messages. # override settings for certain messages.
# Messages can be matched by "appname", "summary", "body", "icon", "category", # Messages can be matched by "appname", "summary", "body", "icon", "category",
# "msg_urgency" and you can override the "timeout", "urgency", "foreground", # "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. # Shell-like globbing will get expanded.
# #
# SCRIPTING # SCRIPTING
@ -294,6 +294,19 @@
# NOTE: It might be helpful to run dunst -print in a terminal in order # NOTE: It might be helpful to run dunst -print in a terminal in order
# to find fitting options for rules. # 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] #[espeak]
# summary = "*" # summary = "*"
# script = dunst_espeak.sh # script = dunst_espeak.sh

View File

@ -48,8 +48,12 @@ void wake_up(void)
static gboolean run(void *data) static gboolean run(void *data)
{ {
queues_check_timeouts(x_is_idle()); LOG_D("RUN");
queues_update();
bool fullscreen = have_fullscreen_window();
queues_check_timeouts(x_is_idle(), fullscreen);
queues_update(fullscreen);
static gint64 next_timeout = 0; static gint64 next_timeout = 0;

View File

@ -28,6 +28,19 @@ static void notification_extract_urls(notification *n);
static void notification_format_message(notification *n); static void notification_format_message(notification *n);
static void notification_dmenu_string(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 * print a human readable representation
* of the given notification to stdout. * of the given notification to stdout.
@ -49,6 +62,7 @@ void notification_print(notification *n)
printf("\tfg: %s\n", n->colors[ColFG]); printf("\tfg: %s\n", n->colors[ColFG]);
printf("\tbg: %s\n", n->colors[ColBG]); printf("\tbg: %s\n", n->colors[ColBG]);
printf("\tframe: %s\n", n->colors[ColFrame]); printf("\tframe: %s\n", n->colors[ColFrame]);
printf("\tfullscreen: %s\n", enum_to_string_fullscreen(n->fullscreen));
printf("\tid: %d\n", n->id); printf("\tid: %d\n", n->id);
if (n->urls) { if (n->urls) {
char *urls = string_replace_all("\n", "\t\t\n", g_strdup(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->transient = false;
n->progress = -1; n->progress = -1;
n->fullscreen = FS_SHOW;
return n; return n;
} }

View File

@ -9,6 +9,13 @@
#define DUNST_NOTIF_MAX_CHARS 5000 #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 /// Representing the urgencies according to the notification spec
enum urgency { enum urgency {
URG_NONE = -1, /**< Urgency not set (invalid) */ URG_NONE = -1, /**< Urgency not set (invalid) */
@ -69,6 +76,7 @@ typedef struct _notification {
bool first_render; /**< markup has been rendered before? */ bool first_render; /**< markup has been rendered before? */
int dup_count; /**< amount of duplicate notifications stacked onto this */ int dup_count; /**< amount of duplicate notifications stacked onto this */
int displayed_height; int displayed_height;
enum behavior_fullscreen fullscreen; //!< The instruction what to do with it, when desktop enters fullscreen
/* derived fields */ /* derived fields */
char *msg; /**< formatted message */ char *msg; /**< formatted message */
@ -94,5 +102,14 @@ void notification_update_text_to_render(notification *n);
void notification_do_action(notification *n); void notification_do_action(notification *n);
const char *notification_urgency_to_string(enum urgency urgency); 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 #endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -551,4 +551,22 @@ const char *cmdline_create_usage(void)
return usage_str; 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: */ /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -6,6 +6,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include "dunst.h"
int load_ini_file(FILE *); int load_ini_file(FILE *);
char *ini_get_path(const char *section, const char *key, const char *def); 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); 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); 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 #endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -284,12 +284,14 @@ void queues_history_push_all(void)
} }
/* see queues.h */ /* see queues.h */
void queues_check_timeouts(bool idle) void queues_check_timeouts(bool idle, bool fullscreen)
{ {
/* nothing to do */ /* nothing to do */
if (displayed->length == 0) if (displayed->length == 0)
return; return;
bool is_idle = fullscreen ? false : idle;
GList *iter = g_queue_peek_head_link(displayed); GList *iter = g_queue_peek_head_link(displayed);
while (iter) { while (iter) {
notification *n = iter->data; notification *n = iter->data;
@ -302,7 +304,7 @@ void queues_check_timeouts(bool idle)
iter = iter->next; iter = iter->next;
/* don't timeout when user is idle */ /* don't timeout when user is idle */
if (idle && !n->transient) { if (is_idle && !n->transient) {
n->start = g_get_monotonic_time(); n->start = g_get_monotonic_time();
continue; continue;
} }
@ -320,7 +322,7 @@ void queues_check_timeouts(bool idle)
} }
/* see queues.h */ /* see queues.h */
void queues_update(void) void queues_update(bool fullscreen)
{ {
if (pause_displayed) { if (pause_displayed) {
while (displayed->length > 0) { while (displayed->length > 0) {
@ -330,26 +332,52 @@ void queues_update(void)
return; 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 */ /* 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) { if (displayed_limit > 0 && displayed->length >= displayed_limit) {
/* the list is full */ /* the list is full */
break; break;
} }
notification *n = g_queue_pop_head(waiting);
if (!n) if (!n)
return; return;
if (fullscreen
&& (n->fullscreen == FS_DELAY || n->fullscreen == FS_PUSHBACK)) {
iter = nextiter;
continue;
}
n->start = g_get_monotonic_time(); n->start = g_get_monotonic_time();
if (!n->redisplayed && n->script) { if (!n->redisplayed && n->script) {
notification_run_script(n); notification_run_script(n);
} }
g_queue_delete_link(waiting, iter);
g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL); g_queue_insert_sorted(displayed, n, notification_cmp_data, NULL);
iter = nextiter;
} }
} }

View File

@ -125,8 +125,10 @@ void queues_history_push_all(void);
* *
* @param idle the program's idle status. Important to calculate the * @param idle the program's idle status. Important to calculate the
* timeout for transient notifications * 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 * 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 * @post Call wake_up() to synchronize the queues with the UI
* (which closes old and shows new notifications on screen) * (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 * Calculate the distance to the next event, when an element in the

View File

@ -16,6 +16,8 @@ void rule_apply(rule_t *r, notification *n)
n->timeout = r->timeout; n->timeout = r->timeout;
if (r->urgency != URG_NONE) if (r->urgency != URG_NONE)
n->urgency = r->urgency; n->urgency = r->urgency;
if (r->fullscreen != FS_NULL)
n->fullscreen = r->fullscreen;
if (r->history_ignore != -1) if (r->history_ignore != -1)
n->history_ignore = r->history_ignore; n->history_ignore = r->history_ignore;
if (r->set_transient != -1) if (r->set_transient != -1)
@ -69,6 +71,7 @@ void rule_init(rule_t *r)
r->msg_urgency = URG_NONE; r->msg_urgency = URG_NONE;
r->timeout = -1; r->timeout = -1;
r->urgency = URG_NONE; r->urgency = URG_NONE;
r->fullscreen = FS_NULL;
r->markup = MARKUP_NULL; r->markup = MARKUP_NULL;
r->new_icon = NULL; r->new_icon = NULL;
r->history_ignore = false; r->history_ignore = false;

View File

@ -30,6 +30,7 @@ typedef struct _rule_t {
char *bg; char *bg;
const char *format; const char *format;
const char *script; const char *script;
enum behavior_fullscreen fullscreen;
} rule_t; } rule_t;
extern GSList *rules; extern GSList *rules;

View File

@ -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->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->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); 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); r->script = ini_get_path(cur_section, "script", NULL);
} }

View File

@ -146,6 +146,8 @@ void screen_check_event(XEvent event)
{ {
if (event.type == randr_event_base + RRScreenChangeNotify) if (event.type == randr_event_base + RRScreenChangeNotify)
randr_update(); randr_update();
else
LOG_D("XEvent: Ignored '%d'", event.type);
} }
void xinerama_update(void) void xinerama_update(void)
@ -186,6 +188,89 @@ void screen_update_fallback(void)
screens[0].dim.h = DisplayHeight(xctx.dpy, screen); 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 * Select the screen on which the Window
* should be displayed. * should be displayed.

View File

@ -3,6 +3,7 @@
#define DUNST_SCREEN_H #define DUNST_SCREEN_H
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <stdbool.h>
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh)) #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); screen_info *get_active_screen(void);
double get_dpi_for_screen(screen_info *scr); 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 #endif
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */ /* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

View File

@ -60,6 +60,7 @@ typedef struct _colored_layout {
} colored_layout; } colored_layout;
cairo_ctx_t cairo_ctx; cairo_ctx_t cairo_ctx;
static bool fullscreen_last = false;
/* FIXME refactor setup teardown handlers into one setup and one teardown */ /* FIXME refactor setup teardown handlers into one setup and one teardown */
static void x_shortcut_setup_error_handler(void); 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) gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
{ {
bool fullscreen_now;
XEvent ev; XEvent ev;
unsigned int state; unsigned int state;
while (XPending(xctx.dpy) > 0) { while (XPending(xctx.dpy) > 0) {
XNextEvent(xctx.dpy, &ev); XNextEvent(xctx.dpy, &ev);
LOG_D("XEvent: processing '%d'", ev.type);
switch (ev.type) { switch (ev.type) {
case Expose: case Expose:
if (ev.xexpose.count == 0 && xctx.visible) { if (ev.xexpose.count == 0 && xctx.visible) {
@ -908,13 +912,20 @@ gboolean x_mainloop_fd_dispatch(GSource *source, GSourceFunc callback, gpointer
wake_up(); wake_up();
break; break;
case PropertyNotify: 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 /* Ignore PropertyNotify, when we're still on the
* same screen. PropertyNotify is only neccessary * same screen. PropertyNotify is only neccessary
* to detect a focus change to another screen * to detect a focus change to another screen
*/ */
if( settings.f_mode != FOLLOW_NONE && xctx.visible
&& get_active_screen()->scr != xctx.cur_screen) && get_active_screen()->scr != xctx.cur_screen) {
wake_up(); x_win_draw();
}
break; break;
default: default:
screen_check_event(ev); screen_check_event(ev);