xref: /netbsd-src/external/bsd/tmux/dist/alerts.c (revision 9fb66d812c00ebfb445c0b47dea128f32aa6fe96)
1 /* $OpenBSD$ */
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 <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
alerts_timer(__unused int fd,__unused short events,void * arg)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
alerts_callback(__unused int fd,__unused short events,__unused void * arg)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
alerts_action_applies(struct winlink * wl,const char * name)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
alerts_check_all(struct window * w)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
alerts_check_session(struct session * s)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
alerts_enabled(struct window * w,int flags)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
alerts_reset_all(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
alerts_reset(struct window * w)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
alerts_queue(struct window * w,int flags)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
alerts_check_bell(struct window * w)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
alerts_check_activity(struct window * w)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
alerts_check_silence(struct window * w)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
alerts_set_message(struct winlink * wl,const char * type,const char * option)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, "%s in current window",
319 			    type);
320 		} else {
321 			status_message_set(c, -1, 1, 0, "%s in window %d", type,
322 			    wl->idx);
323 		}
324 	}
325 }
326