xref: /openbsd-src/usr.bin/tmux/alerts.c (revision 6a13ef69787db04ae501a22e92fa10865b44fd7c)
1 /* $OpenBSD: alerts.c,v 1.16 2016/11/01 09:07:18 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@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 <event.h>
22 #include <stdlib.h>
23 
24 #include "tmux.h"
25 
26 static int	alerts_fired;
27 
28 static void	alerts_timer(int, short, void *);
29 static int	alerts_enabled(struct window *, int);
30 static void	alerts_callback(int, short, void *);
31 static void	alerts_reset(struct window *);
32 
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 printflike(2, 3) alerts_set_message(struct session *, const char *,
38 		    ...);
39 static void	alerts_ring_bell(struct session *);
40 
41 static TAILQ_HEAD(, window) alerts_list = TAILQ_HEAD_INITIALIZER(alerts_list);
42 
43 static void
44 alerts_timer(__unused int fd, __unused short events, void *arg)
45 {
46 	struct window	*w = arg;
47 
48 	log_debug("@%u alerts timer expired", w->id);
49 	alerts_reset(w);
50 	alerts_queue(w, WINDOW_SILENCE);
51 }
52 
53 static void
54 alerts_callback(__unused int fd, __unused short events, __unused void *arg)
55 {
56 	struct window	*w, *w1;
57 	int		 alerts;
58 
59 	TAILQ_FOREACH_SAFE(w, &alerts_list, alerts_entry, w1) {
60 		alerts = alerts_check_all(w);
61 		log_debug("@%u alerts check, alerts %#x", w->id, alerts);
62 
63 		w->alerts_queued = 0;
64 		TAILQ_REMOVE(&alerts_list, w, alerts_entry);
65 
66 		w->flags &= ~WINDOW_ALERTFLAGS;
67 		window_remove_ref(w);
68 	}
69 	alerts_fired = 0;
70 }
71 
72 static int
73 alerts_check_all(struct window *w)
74 {
75 	int	alerts;
76 
77 	alerts  = alerts_check_bell(w);
78 	alerts |= alerts_check_activity(w);
79 	alerts |= alerts_check_silence(w);
80 	return (alerts);
81 }
82 
83 void
84 alerts_check_session(struct session *s)
85 {
86 	struct winlink	*wl;
87 
88 	RB_FOREACH(wl, winlinks, &s->windows)
89 		alerts_check_all(wl->window);
90 }
91 
92 static int
93 alerts_enabled(struct window *w, int flags)
94 {
95 	if (flags & WINDOW_BELL)
96 		return (1);
97 	if (flags & WINDOW_ACTIVITY) {
98 		if (options_get_number(w->options, "monitor-activity"))
99 			return (1);
100 	}
101 	if (flags & WINDOW_SILENCE) {
102 		if (options_get_number(w->options, "monitor-silence") != 0)
103 			return (1);
104 	}
105 	return (0);
106 }
107 
108 void
109 alerts_reset_all(void)
110 {
111 	struct window	*w;
112 
113 	RB_FOREACH(w, windows, &windows)
114 		alerts_reset(w);
115 }
116 
117 static void
118 alerts_reset(struct window *w)
119 {
120 	struct timeval	tv;
121 
122 	w->flags &= ~WINDOW_SILENCE;
123 	event_del(&w->alerts_timer);
124 
125 	timerclear(&tv);
126 	tv.tv_sec = options_get_number(w->options, "monitor-silence");
127 
128 	log_debug("@%u alerts timer reset %u", w->id, (u_int)tv.tv_sec);
129 	if (tv.tv_sec != 0)
130 		event_add(&w->alerts_timer, &tv);
131 }
132 
133 void
134 alerts_queue(struct window *w, int flags)
135 {
136 	if (w->flags & WINDOW_ACTIVITY)
137 		alerts_reset(w);
138 
139 	if (!event_initialized(&w->alerts_timer))
140 		evtimer_set(&w->alerts_timer, alerts_timer, w);
141 
142 	if ((w->flags & flags) != flags) {
143 		w->flags |= flags;
144 		log_debug("@%u alerts flags added %#x", w->id, flags);
145 	}
146 
147 	if (!w->alerts_queued) {
148 		w->alerts_queued = 1;
149 		TAILQ_INSERT_TAIL(&alerts_list, w, alerts_entry);
150 		w->references++;
151 	}
152 
153 	if (!alerts_fired && alerts_enabled(w, flags)) {
154 		log_debug("alerts check queued (by @%u)", w->id);
155 		event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL);
156 		alerts_fired = 1;
157 	}
158 }
159 
160 static int
161 alerts_check_bell(struct window *w)
162 {
163 	struct window	*ws;
164 	struct winlink	*wl;
165 	struct session	*s;
166 	struct client	*c;
167 	int		 action, visual;
168 
169 	if (~w->flags & WINDOW_BELL)
170 		return (0);
171 
172 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
173 		wl->session->flags &= ~SESSION_ALERTED;
174 
175 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
176 		if (wl->flags & WINLINK_BELL)
177 			continue;
178 		s = wl->session;
179 		if (s->curw != wl) {
180 			wl->flags |= WINLINK_BELL;
181 			notify_winlink("alert-bell", s, wl);
182 		}
183 
184 		if (s->flags & SESSION_ALERTED)
185 			continue;
186 		s->flags |= SESSION_ALERTED;
187 
188 		action = options_get_number(s->options, "bell-action");
189 		if (action == BELL_NONE)
190 			return (0);
191 
192 		visual = options_get_number(s->options, "visual-bell");
193 		TAILQ_FOREACH(c, &clients, entry) {
194 			if (c->session != s || c->flags & CLIENT_CONTROL)
195 				continue;
196 			ws = c->session->curw->window;
197 
198 			if (action == BELL_CURRENT && ws != w)
199 				action = BELL_NONE;
200 			if (action == BELL_OTHER && ws != w)
201 				action = BELL_NONE;
202 
203 			if (!visual) {
204 				if (action != BELL_NONE)
205 					tty_putcode(&c->tty, TTYC_BEL);
206 				continue;
207 			}
208 			if (action == BELL_CURRENT)
209 				status_message_set(c, "Bell in current window");
210 			else if (action != BELL_NONE) {
211 				status_message_set(c, "Bell in window %d",
212 				    wl->idx);
213 			}
214 		}
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)
239 			continue;
240 
241 		wl->flags |= WINLINK_ACTIVITY;
242 		notify_winlink("alert-activity", s, wl);
243 
244 		if (s->flags & SESSION_ALERTED)
245 			continue;
246 		s->flags |= SESSION_ALERTED;
247 
248 		if (options_get_number(s->options, "bell-on-alert"))
249 			alerts_ring_bell(s);
250 		if (options_get_number(s->options, "visual-activity"))
251 			alerts_set_message(s, "Activity in window %d", wl->idx);
252 	}
253 
254 	return (WINDOW_ACTIVITY);
255 }
256 
257 static int
258 alerts_check_silence(struct window *w)
259 {
260 	struct winlink	*wl;
261 	struct session	*s;
262 
263 	if (~w->flags & WINDOW_SILENCE)
264 		return (0);
265 	if (!options_get_number(w->options, "monitor-silence"))
266 		return (0);
267 
268 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
269 		wl->session->flags &= ~SESSION_ALERTED;
270 
271 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
272 		if (wl->flags & WINLINK_SILENCE)
273 			continue;
274 		s = wl->session;
275 		if (s->curw == wl)
276 			continue;
277 		wl->flags |= WINLINK_SILENCE;
278 		notify_winlink("alert-silence", s, wl);
279 
280 		if (s->flags & SESSION_ALERTED)
281 			continue;
282 		s->flags |= SESSION_ALERTED;
283 
284 		if (options_get_number(s->options, "bell-on-alert"))
285 			alerts_ring_bell(s);
286 
287 		if (!options_get_number(s->options, "visual-silence"))
288 			alerts_set_message(s, "Silence in window %d", wl->idx);
289 	}
290 
291 	return (WINDOW_SILENCE);
292 }
293 
294 static void
295 alerts_set_message(struct session *s, const char *fmt, ...)
296 {
297 	struct client	*c;
298 	va_list		 ap;
299 	char		*message;
300 
301 	va_start(ap, fmt);
302 	xvasprintf(&message, fmt, ap);
303 	va_end(ap);
304 
305 	TAILQ_FOREACH(c, &clients, entry) {
306 		if (c->session == s)
307 			status_message_set(c, "%s", message);
308 	}
309 
310 	free(message);
311 }
312 
313 static void
314 alerts_ring_bell(struct session *s)
315 {
316 	struct client	*c;
317 
318 	TAILQ_FOREACH(c, &clients, entry) {
319 		if (c->session == s && !(c->flags & CLIENT_CONTROL))
320 			tty_putcode(&c->tty, TTYC_BEL);
321 	}
322 }
323