xref: /openbsd/usr.bin/tmux/alerts.c (revision e7e79d0a)
1 /* $OpenBSD: alerts.c,v 1.33 2021/04/12 09:36:12 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_action_applies(struct winlink *, const char *);
34 static int	alerts_check_all(struct window *);
35 static int	alerts_check_bell(struct window *);
36 static int	alerts_check_activity(struct window *);
37 static int	alerts_check_silence(struct window *);
38 static void	alerts_set_message(struct winlink *, const char *,
39 		    const char *);
40 
41 static TAILQ_HEAD(, window) alerts_list = TAILQ_HEAD_INITIALIZER(alerts_list);
42 
43 static void
alerts_timer(__unused int fd,__unused short events,void * arg)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_queue(w, WINDOW_SILENCE);
50 }
51 
52 static void
alerts_callback(__unused int fd,__unused short events,__unused void * arg)53 alerts_callback(__unused int fd, __unused short events, __unused void *arg)
54 {
55 	struct window	*w, *w1;
56 	int		 alerts;
57 
58 	TAILQ_FOREACH_SAFE(w, &alerts_list, alerts_entry, w1) {
59 		alerts = alerts_check_all(w);
60 		log_debug("@%u alerts check, alerts %#x", w->id, alerts);
61 
62 		w->alerts_queued = 0;
63 		TAILQ_REMOVE(&alerts_list, w, alerts_entry);
64 
65 		w->flags &= ~WINDOW_ALERTFLAGS;
66 		window_remove_ref(w, __func__);
67 	}
68 	alerts_fired = 0;
69 }
70 
71 static int
alerts_action_applies(struct winlink * wl,const char * name)72 alerts_action_applies(struct winlink *wl, const char *name)
73 {
74 	int	action;
75 
76 	/*
77 	 * {bell,activity,silence}-action determines when to alert: none means
78 	 * nothing happens, current means only do something for the current
79 	 * window and other means only for windows other than the current.
80 	 */
81 
82 	action = options_get_number(wl->session->options, name);
83 	if (action == ALERT_ANY)
84 		return (1);
85 	if (action == ALERT_CURRENT)
86 		return (wl == wl->session->curw);
87 	if (action == ALERT_OTHER)
88 		return (wl != wl->session->curw);
89 	return (0);
90 }
91 
92 static int
alerts_check_all(struct window * w)93 alerts_check_all(struct window *w)
94 {
95 	int	alerts;
96 
97 	alerts	= alerts_check_bell(w);
98 	alerts |= alerts_check_activity(w);
99 	alerts |= alerts_check_silence(w);
100 	return (alerts);
101 }
102 
103 void
alerts_check_session(struct session * s)104 alerts_check_session(struct session *s)
105 {
106 	struct winlink	*wl;
107 
108 	RB_FOREACH(wl, winlinks, &s->windows)
109 		alerts_check_all(wl->window);
110 }
111 
112 static int
alerts_enabled(struct window * w,int flags)113 alerts_enabled(struct window *w, int flags)
114 {
115 	if (flags & WINDOW_BELL) {
116 		if (options_get_number(w->options, "monitor-bell"))
117 			return (1);
118 	}
119 	if (flags & WINDOW_ACTIVITY) {
120 		if (options_get_number(w->options, "monitor-activity"))
121 			return (1);
122 	}
123 	if (flags & WINDOW_SILENCE) {
124 		if (options_get_number(w->options, "monitor-silence") != 0)
125 			return (1);
126 	}
127 	return (0);
128 }
129 
130 void
alerts_reset_all(void)131 alerts_reset_all(void)
132 {
133 	struct window	*w;
134 
135 	RB_FOREACH(w, windows, &windows)
136 		alerts_reset(w);
137 }
138 
139 static void
alerts_reset(struct window * w)140 alerts_reset(struct window *w)
141 {
142 	struct timeval	tv;
143 
144 	if (!event_initialized(&w->alerts_timer))
145 		evtimer_set(&w->alerts_timer, alerts_timer, w);
146 
147 	w->flags &= ~WINDOW_SILENCE;
148 	event_del(&w->alerts_timer);
149 
150 	timerclear(&tv);
151 	tv.tv_sec = options_get_number(w->options, "monitor-silence");
152 
153 	log_debug("@%u alerts timer reset %u", w->id, (u_int)tv.tv_sec);
154 	if (tv.tv_sec != 0)
155 		event_add(&w->alerts_timer, &tv);
156 }
157 
158 void
alerts_queue(struct window * w,int flags)159 alerts_queue(struct window *w, int flags)
160 {
161 	alerts_reset(w);
162 
163 	if ((w->flags & flags) != flags) {
164 		w->flags |= flags;
165 		log_debug("@%u alerts flags added %#x", w->id, flags);
166 	}
167 
168 	if (alerts_enabled(w, flags)) {
169 		if (!w->alerts_queued) {
170 			w->alerts_queued = 1;
171 			TAILQ_INSERT_TAIL(&alerts_list, w, alerts_entry);
172 			window_add_ref(w, __func__);
173 		}
174 
175 		if (!alerts_fired) {
176 			log_debug("alerts check queued (by @%u)", w->id);
177 			event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL);
178 			alerts_fired = 1;
179 		}
180 	}
181 }
182 
183 static int
alerts_check_bell(struct window * w)184 alerts_check_bell(struct window *w)
185 {
186 	struct winlink	*wl;
187 	struct session	*s;
188 
189 	if (~w->flags & WINDOW_BELL)
190 		return (0);
191 	if (!options_get_number(w->options, "monitor-bell"))
192 		return (0);
193 
194 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
195 		wl->session->flags &= ~SESSION_ALERTED;
196 
197 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
198 		/*
199 		 * Bells are allowed even if there is an existing bell (so do
200 		 * not check WINLINK_BELL).
201 		 */
202 		s = wl->session;
203 		if (s->curw != wl || s->attached == 0) {
204 			wl->flags |= WINLINK_BELL;
205 			server_status_session(s);
206 		}
207 		if (!alerts_action_applies(wl, "bell-action"))
208 			continue;
209 		notify_winlink("alert-bell", wl);
210 
211 		if (s->flags & SESSION_ALERTED)
212 			continue;
213 		s->flags |= SESSION_ALERTED;
214 
215 		alerts_set_message(wl, "Bell", "visual-bell");
216 	}
217 
218 	return (WINDOW_BELL);
219 }
220 
221 static int
alerts_check_activity(struct window * w)222 alerts_check_activity(struct window *w)
223 {
224 	struct winlink	*wl;
225 	struct session	*s;
226 
227 	if (~w->flags & WINDOW_ACTIVITY)
228 		return (0);
229 	if (!options_get_number(w->options, "monitor-activity"))
230 		return (0);
231 
232 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
233 		wl->session->flags &= ~SESSION_ALERTED;
234 
235 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
236 		if (wl->flags & WINLINK_ACTIVITY)
237 			continue;
238 		s = wl->session;
239 		if (s->curw != wl || s->attached == 0) {
240 			wl->flags |= WINLINK_ACTIVITY;
241 			server_status_session(s);
242 		}
243 		if (!alerts_action_applies(wl, "activity-action"))
244 			continue;
245 		notify_winlink("alert-activity", wl);
246 
247 		if (s->flags & SESSION_ALERTED)
248 			continue;
249 		s->flags |= SESSION_ALERTED;
250 
251 		alerts_set_message(wl, "Activity", "visual-activity");
252 	}
253 
254 	return (WINDOW_ACTIVITY);
255 }
256 
257 static int
alerts_check_silence(struct window * w)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") == 0)
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 || s->attached == 0) {
276 			wl->flags |= WINLINK_SILENCE;
277 			server_status_session(s);
278 		}
279 		if (!alerts_action_applies(wl, "silence-action"))
280 			continue;
281 		notify_winlink("alert-silence", wl);
282 
283 		if (s->flags & SESSION_ALERTED)
284 			continue;
285 		s->flags |= SESSION_ALERTED;
286 
287 		alerts_set_message(wl, "Silence", "visual-silence");
288 	}
289 
290 	return (WINDOW_SILENCE);
291 }
292 
293 static void
alerts_set_message(struct winlink * wl,const char * type,const char * option)294 alerts_set_message(struct winlink *wl, const char *type, const char *option)
295 {
296 	struct client	*c;
297 	int		 visual;
298 
299 	/*
300 	 * We have found an alert (bell, activity or silence), so we need to
301 	 * pass it on to the user. For each client attached to this session,
302 	 * decide whether a bell, message or both is needed.
303 	 *
304 	 * If visual-{bell,activity,silence} is on, then a message is
305 	 * substituted for a bell; if it is off, a bell is sent as normal; both
306 	 * mean both a bell and message is sent.
307 	 */
308 
309 	visual = options_get_number(wl->session->options, option);
310 	TAILQ_FOREACH(c, &clients, entry) {
311 		if (c->session != wl->session || c->flags & CLIENT_CONTROL)
312 			continue;
313 
314 		if (visual == VISUAL_OFF || visual == VISUAL_BOTH)
315 			tty_putcode(&c->tty, TTYC_BEL);
316 		if (visual == VISUAL_OFF)
317 			continue;
318 		if (c->session->curw == wl) {
319 			status_message_set(c, -1, 1, 0, "%s in current window",
320 			    type);
321 		} else {
322 			status_message_set(c, -1, 1, 0, "%s in window %d", type,
323 			    wl->idx);
324 		}
325 	}
326 }
327