Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott (at) gmail.com>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
     15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
     16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  */
     18 
     19 #include <sys/types.h>
     20 
     21 #include <stdlib.h>
     22 
     23 #include "tmux.h"
     24 
     25 static int	alerts_fired;
     26 
     27 static void	alerts_timer(int, short, void *);
     28 static int	alerts_enabled(struct window *, int);
     29 static void	alerts_callback(int, short, void *);
     30 static void	alerts_reset(struct window *);
     31 
     32 static int	alerts_action_applies(struct winlink *, const char *);
     33 static int	alerts_check_all(struct window *);
     34 static int	alerts_check_bell(struct window *);
     35 static int	alerts_check_activity(struct window *);
     36 static int	alerts_check_silence(struct window *);
     37 static void	alerts_set_message(struct winlink *, const char *,
     38 		    const char *);
     39 
     40 static TAILQ_HEAD(, window) alerts_list = TAILQ_HEAD_INITIALIZER(alerts_list);
     41 
     42 static void
     43 alerts_timer(__unused int fd, __unused short events, void *arg)
     44 {
     45 	struct window	*w = arg;
     46 
     47 	log_debug("@%u alerts timer expired", w->id);
     48 	alerts_queue(w, WINDOW_SILENCE);
     49 }
     50 
     51 static void
     52 alerts_callback(__unused int fd, __unused short events, __unused void *arg)
     53 {
     54 	struct window	*w, *w1;
     55 	int		 alerts;
     56 
     57 	TAILQ_FOREACH_SAFE(w, &alerts_list, alerts_entry, w1) {
     58 		alerts = alerts_check_all(w);
     59 		log_debug("@%u alerts check, alerts %#x", w->id, alerts);
     60 
     61 		w->alerts_queued = 0;
     62 		TAILQ_REMOVE(&alerts_list, w, alerts_entry);
     63 
     64 		w->flags &= ~WINDOW_ALERTFLAGS;
     65 		window_remove_ref(w, __func__);
     66 	}
     67 	alerts_fired = 0;
     68 }
     69 
     70 static int
     71 alerts_action_applies(struct winlink *wl, const char *name)
     72 {
     73 	int	action;
     74 
     75 	/*
     76 	 * {bell,activity,silence}-action determines when to alert: none means
     77 	 * nothing happens, current means only do something for the current
     78 	 * window and other means only for windows other than the current.
     79 	 */
     80 
     81 	action = options_get_number(wl->session->options, name);
     82 	if (action == ALERT_ANY)
     83 		return (1);
     84 	if (action == ALERT_CURRENT)
     85 		return (wl == wl->session->curw);
     86 	if (action == ALERT_OTHER)
     87 		return (wl != wl->session->curw);
     88 	return (0);
     89 }
     90 
     91 static int
     92 alerts_check_all(struct window *w)
     93 {
     94 	int	alerts;
     95 
     96 	alerts	= alerts_check_bell(w);
     97 	alerts |= alerts_check_activity(w);
     98 	alerts |= alerts_check_silence(w);
     99 	return (alerts);
    100 }
    101 
    102 void
    103 alerts_check_session(struct session *s)
    104 {
    105 	struct winlink	*wl;
    106 
    107 	RB_FOREACH(wl, winlinks, &s->windows)
    108 		alerts_check_all(wl->window);
    109 }
    110 
    111 static int
    112 alerts_enabled(struct window *w, int flags)
    113 {
    114 	if (flags & WINDOW_BELL) {
    115 		if (options_get_number(w->options, "monitor-bell"))
    116 			return (1);
    117 	}
    118 	if (flags & WINDOW_ACTIVITY) {
    119 		if (options_get_number(w->options, "monitor-activity"))
    120 			return (1);
    121 	}
    122 	if (flags & WINDOW_SILENCE) {
    123 		if (options_get_number(w->options, "monitor-silence") != 0)
    124 			return (1);
    125 	}
    126 	return (0);
    127 }
    128 
    129 void
    130 alerts_reset_all(void)
    131 {
    132 	struct window	*w;
    133 
    134 	RB_FOREACH(w, windows, &windows)
    135 		alerts_reset(w);
    136 }
    137 
    138 static void
    139 alerts_reset(struct window *w)
    140 {
    141 	struct timeval	tv;
    142 
    143 	if (!event_initialized(&w->alerts_timer))
    144 		evtimer_set(&w->alerts_timer, alerts_timer, w);
    145 
    146 	w->flags &= ~WINDOW_SILENCE;
    147 	event_del(&w->alerts_timer);
    148 
    149 	timerclear(&tv);
    150 	tv.tv_sec = options_get_number(w->options, "monitor-silence");
    151 
    152 	log_debug("@%u alerts timer reset %u", w->id, (u_int)tv.tv_sec);
    153 	if (tv.tv_sec != 0)
    154 		event_add(&w->alerts_timer, &tv);
    155 }
    156 
    157 void
    158 alerts_queue(struct window *w, int flags)
    159 {
    160 	alerts_reset(w);
    161 
    162 	if ((w->flags & flags) != flags) {
    163 		w->flags |= flags;
    164 		log_debug("@%u alerts flags added %#x", w->id, flags);
    165 	}
    166 
    167 	if (alerts_enabled(w, flags)) {
    168 		if (!w->alerts_queued) {
    169 			w->alerts_queued = 1;
    170 			TAILQ_INSERT_TAIL(&alerts_list, w, alerts_entry);
    171 			window_add_ref(w, __func__);
    172 		}
    173 
    174 		if (!alerts_fired) {
    175 			log_debug("alerts check queued (by @%u)", w->id);
    176 			event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL);
    177 			alerts_fired = 1;
    178 		}
    179 	}
    180 }
    181 
    182 static int
    183 alerts_check_bell(struct window *w)
    184 {
    185 	struct winlink	*wl;
    186 	struct session	*s;
    187 
    188 	if (~w->flags & WINDOW_BELL)
    189 		return (0);
    190 	if (!options_get_number(w->options, "monitor-bell"))
    191 		return (0);
    192 
    193 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
    194 		wl->session->flags &= ~SESSION_ALERTED;
    195 
    196 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
    197 		/*
    198 		 * Bells are allowed even if there is an existing bell (so do
    199 		 * not check WINLINK_BELL).
    200 		 */
    201 		s = wl->session;
    202 		if (s->curw != wl || s->attached == 0) {
    203 			wl->flags |= WINLINK_BELL;
    204 			server_status_session(s);
    205 		}
    206 		if (!alerts_action_applies(wl, "bell-action"))
    207 			continue;
    208 		notify_winlink("alert-bell", wl);
    209 
    210 		if (s->flags & SESSION_ALERTED)
    211 			continue;
    212 		s->flags |= SESSION_ALERTED;
    213 
    214 		alerts_set_message(wl, "Bell", "visual-bell");
    215 	}
    216 
    217 	return (WINDOW_BELL);
    218 }
    219 
    220 static int
    221 alerts_check_activity(struct window *w)
    222 {
    223 	struct winlink	*wl;
    224 	struct session	*s;
    225 
    226 	if (~w->flags & WINDOW_ACTIVITY)
    227 		return (0);
    228 	if (!options_get_number(w->options, "monitor-activity"))
    229 		return (0);
    230 
    231 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
    232 		wl->session->flags &= ~SESSION_ALERTED;
    233 
    234 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
    235 		if (wl->flags & WINLINK_ACTIVITY)
    236 			continue;
    237 		s = wl->session;
    238 		if (s->curw != wl || s->attached == 0) {
    239 			wl->flags |= WINLINK_ACTIVITY;
    240 			server_status_session(s);
    241 		}
    242 		if (!alerts_action_applies(wl, "activity-action"))
    243 			continue;
    244 		notify_winlink("alert-activity", wl);
    245 
    246 		if (s->flags & SESSION_ALERTED)
    247 			continue;
    248 		s->flags |= SESSION_ALERTED;
    249 
    250 		alerts_set_message(wl, "Activity", "visual-activity");
    251 	}
    252 
    253 	return (WINDOW_ACTIVITY);
    254 }
    255 
    256 static int
    257 alerts_check_silence(struct window *w)
    258 {
    259 	struct winlink	*wl;
    260 	struct session	*s;
    261 
    262 	if (~w->flags & WINDOW_SILENCE)
    263 		return (0);
    264 	if (options_get_number(w->options, "monitor-silence") == 0)
    265 		return (0);
    266 
    267 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
    268 		wl->session->flags &= ~SESSION_ALERTED;
    269 
    270 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
    271 		if (wl->flags & WINLINK_SILENCE)
    272 			continue;
    273 		s = wl->session;
    274 		if (s->curw != wl || s->attached == 0) {
    275 			wl->flags |= WINLINK_SILENCE;
    276 			server_status_session(s);
    277 		}
    278 		if (!alerts_action_applies(wl, "silence-action"))
    279 			continue;
    280 		notify_winlink("alert-silence", wl);
    281 
    282 		if (s->flags & SESSION_ALERTED)
    283 			continue;
    284 		s->flags |= SESSION_ALERTED;
    285 
    286 		alerts_set_message(wl, "Silence", "visual-silence");
    287 	}
    288 
    289 	return (WINDOW_SILENCE);
    290 }
    291 
    292 static void
    293 alerts_set_message(struct winlink *wl, const char *type, const char *option)
    294 {
    295 	struct client	*c;
    296 	int		 visual;
    297 
    298 	/*
    299 	 * We have found an alert (bell, activity or silence), so we need to
    300 	 * pass it on to the user. For each client attached to this session,
    301 	 * decide whether a bell, message or both is needed.
    302 	 *
    303 	 * If visual-{bell,activity,silence} is on, then a message is
    304 	 * substituted for a bell; if it is off, a bell is sent as normal; both
    305 	 * mean both a bell and message is sent.
    306 	 */
    307 
    308 	visual = options_get_number(wl->session->options, option);
    309 	TAILQ_FOREACH(c, &clients, entry) {
    310 		if (c->session != wl->session || c->flags & CLIENT_CONTROL)
    311 			continue;
    312 
    313 		if (visual == VISUAL_OFF || visual == VISUAL_BOTH)
    314 			tty_putcode(&c->tty, TTYC_BEL);
    315 		if (visual == VISUAL_OFF)
    316 			continue;
    317 		if (c->session->curw == wl) {
    318 			status_message_set(c, -1, 1, 0, 0,
    319 			    "%s in current window", type);
    320 		} else {
    321 			status_message_set(c, -1, 1, 0, 0,
    322 			    "%s in window %d", type, wl->idx);
    323 		}
    324 	}
    325 }
    326