xref: /openbsd-src/usr.bin/tmux/alerts.c (revision e7e79d0ae675314b9abae016041874e1c221953b)
1*e7e79d0aSnicm /* $OpenBSD: alerts.c,v 1.33 2021/04/12 09:36:12 nicm Exp $ */
281fe4598Snicm 
381fe4598Snicm /*
498ca8272Snicm  * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
581fe4598Snicm  *
681fe4598Snicm  * Permission to use, copy, modify, and distribute this software for any
781fe4598Snicm  * purpose with or without fee is hereby granted, provided that the above
881fe4598Snicm  * copyright notice and this permission notice appear in all copies.
981fe4598Snicm  *
1081fe4598Snicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1181fe4598Snicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1281fe4598Snicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1381fe4598Snicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1481fe4598Snicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
1581fe4598Snicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
1681fe4598Snicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1781fe4598Snicm  */
1881fe4598Snicm 
1981fe4598Snicm #include <sys/types.h>
2081fe4598Snicm 
2181fe4598Snicm #include <event.h>
22de5a0fddSnicm #include <stdlib.h>
2381fe4598Snicm 
2481fe4598Snicm #include "tmux.h"
2581fe4598Snicm 
26413e5e52Snicm static int	alerts_fired;
2781fe4598Snicm 
28413e5e52Snicm static void	alerts_timer(int, short, void *);
29413e5e52Snicm static int	alerts_enabled(struct window *, int);
30413e5e52Snicm static void	alerts_callback(int, short, void *);
31413e5e52Snicm static void	alerts_reset(struct window *);
3281fe4598Snicm 
337d08f26cSnicm static int	alerts_action_applies(struct winlink *, const char *);
34de5a0fddSnicm static int	alerts_check_all(struct window *);
35de5a0fddSnicm static int	alerts_check_bell(struct window *);
36de5a0fddSnicm static int	alerts_check_activity(struct window *);
37de5a0fddSnicm static int	alerts_check_silence(struct window *);
387d08f26cSnicm static void	alerts_set_message(struct winlink *, const char *,
397d08f26cSnicm 		    const char *);
4081fe4598Snicm 
41de5a0fddSnicm static TAILQ_HEAD(, window) alerts_list = TAILQ_HEAD_INITIALIZER(alerts_list);
42de5a0fddSnicm 
43413e5e52Snicm static void
alerts_timer(__unused int fd,__unused short events,void * arg)44d0e2e7f1Snicm alerts_timer(__unused int fd, __unused short events, void *arg)
4581fe4598Snicm {
4681fe4598Snicm 	struct window	*w = arg;
4781fe4598Snicm 
4881fe4598Snicm 	log_debug("@%u alerts timer expired", w->id);
4981fe4598Snicm 	alerts_queue(w, WINDOW_SILENCE);
5081fe4598Snicm }
5181fe4598Snicm 
52413e5e52Snicm static void
alerts_callback(__unused int fd,__unused short events,__unused void * arg)53d0e2e7f1Snicm alerts_callback(__unused int fd, __unused short events, __unused void *arg)
5481fe4598Snicm {
55de5a0fddSnicm 	struct window	*w, *w1;
56de5a0fddSnicm 	int		 alerts;
5781fe4598Snicm 
58de5a0fddSnicm 	TAILQ_FOREACH_SAFE(w, &alerts_list, alerts_entry, w1) {
59de5a0fddSnicm 		alerts = alerts_check_all(w);
60de5a0fddSnicm 		log_debug("@%u alerts check, alerts %#x", w->id, alerts);
6181fe4598Snicm 
62de5a0fddSnicm 		w->alerts_queued = 0;
63de5a0fddSnicm 		TAILQ_REMOVE(&alerts_list, w, alerts_entry);
6423a32ceeSnicm 
6523a32ceeSnicm 		w->flags &= ~WINDOW_ALERTFLAGS;
6654279ec3Snicm 		window_remove_ref(w, __func__);
6781fe4598Snicm 	}
6881fe4598Snicm 	alerts_fired = 0;
6981fe4598Snicm }
7081fe4598Snicm 
71413e5e52Snicm static int
alerts_action_applies(struct winlink * wl,const char * name)727d08f26cSnicm alerts_action_applies(struct winlink *wl, const char *name)
737d08f26cSnicm {
747d08f26cSnicm 	int	action;
757d08f26cSnicm 
767d08f26cSnicm 	/*
777d08f26cSnicm 	 * {bell,activity,silence}-action determines when to alert: none means
787d08f26cSnicm 	 * nothing happens, current means only do something for the current
797d08f26cSnicm 	 * window and other means only for windows other than the current.
807d08f26cSnicm 	 */
817d08f26cSnicm 
827d08f26cSnicm 	action = options_get_number(wl->session->options, name);
837d08f26cSnicm 	if (action == ALERT_ANY)
847d08f26cSnicm 		return (1);
857d08f26cSnicm 	if (action == ALERT_CURRENT)
867d08f26cSnicm 		return (wl == wl->session->curw);
877d08f26cSnicm 	if (action == ALERT_OTHER)
887d08f26cSnicm 		return (wl != wl->session->curw);
897d08f26cSnicm 	return (0);
907d08f26cSnicm }
917d08f26cSnicm 
927d08f26cSnicm static int
alerts_check_all(struct window * w)93de5a0fddSnicm alerts_check_all(struct window *w)
94705f6e28Snicm {
95705f6e28Snicm 	int	alerts;
96705f6e28Snicm 
97de5a0fddSnicm 	alerts	= alerts_check_bell(w);
98de5a0fddSnicm 	alerts |= alerts_check_activity(w);
99de5a0fddSnicm 	alerts |= alerts_check_silence(w);
100705f6e28Snicm 	return (alerts);
101705f6e28Snicm }
102705f6e28Snicm 
103705f6e28Snicm void
alerts_check_session(struct session * s)104705f6e28Snicm alerts_check_session(struct session *s)
105705f6e28Snicm {
106705f6e28Snicm 	struct winlink	*wl;
107705f6e28Snicm 
108705f6e28Snicm 	RB_FOREACH(wl, winlinks, &s->windows)
109de5a0fddSnicm 		alerts_check_all(wl->window);
110705f6e28Snicm }
111705f6e28Snicm 
112413e5e52Snicm static int
alerts_enabled(struct window * w,int flags)11381fe4598Snicm alerts_enabled(struct window *w, int flags)
11481fe4598Snicm {
1159a48cd7dSnicm 	if (flags & WINDOW_BELL) {
1169a48cd7dSnicm 		if (options_get_number(w->options, "monitor-bell"))
11783079eb9Snicm 			return (1);
1189a48cd7dSnicm 	}
11981fe4598Snicm 	if (flags & WINDOW_ACTIVITY) {
120d89252e5Snicm 		if (options_get_number(w->options, "monitor-activity"))
12181fe4598Snicm 			return (1);
12281fe4598Snicm 	}
12381fe4598Snicm 	if (flags & WINDOW_SILENCE) {
124d89252e5Snicm 		if (options_get_number(w->options, "monitor-silence") != 0)
12581fe4598Snicm 			return (1);
12681fe4598Snicm 	}
12781fe4598Snicm 	return (0);
12881fe4598Snicm }
12981fe4598Snicm 
13081fe4598Snicm void
alerts_reset_all(void)13181fe4598Snicm alerts_reset_all(void)
13281fe4598Snicm {
13381fe4598Snicm 	struct window	*w;
13481fe4598Snicm 
13581fe4598Snicm 	RB_FOREACH(w, windows, &windows)
13681fe4598Snicm 		alerts_reset(w);
13781fe4598Snicm }
13881fe4598Snicm 
139413e5e52Snicm static void
alerts_reset(struct window * w)14081fe4598Snicm alerts_reset(struct window *w)
14181fe4598Snicm {
14281fe4598Snicm 	struct timeval	tv;
14381fe4598Snicm 
14451ae4248Snicm 	if (!event_initialized(&w->alerts_timer))
14551ae4248Snicm 		evtimer_set(&w->alerts_timer, alerts_timer, w);
14651ae4248Snicm 
14781fe4598Snicm 	w->flags &= ~WINDOW_SILENCE;
14881fe4598Snicm 	event_del(&w->alerts_timer);
14981fe4598Snicm 
15081fe4598Snicm 	timerclear(&tv);
151d89252e5Snicm 	tv.tv_sec = options_get_number(w->options, "monitor-silence");
15281fe4598Snicm 
15381fe4598Snicm 	log_debug("@%u alerts timer reset %u", w->id, (u_int)tv.tv_sec);
15481fe4598Snicm 	if (tv.tv_sec != 0)
15581fe4598Snicm 		event_add(&w->alerts_timer, &tv);
15681fe4598Snicm }
15781fe4598Snicm 
15881fe4598Snicm void
alerts_queue(struct window * w,int flags)15981fe4598Snicm alerts_queue(struct window *w, int flags)
16081fe4598Snicm {
161a08c4a20Snicm 	alerts_reset(w);
162a08c4a20Snicm 
1630c25f9dcSnicm 	if ((w->flags & flags) != flags) {
16481fe4598Snicm 		w->flags |= flags;
16581fe4598Snicm 		log_debug("@%u alerts flags added %#x", w->id, flags);
1660c25f9dcSnicm 	}
16781fe4598Snicm 
168b3c6572bSnicm 	if (alerts_enabled(w, flags)) {
169de5a0fddSnicm 		if (!w->alerts_queued) {
170de5a0fddSnicm 			w->alerts_queued = 1;
171de5a0fddSnicm 			TAILQ_INSERT_TAIL(&alerts_list, w, alerts_entry);
17254279ec3Snicm 			window_add_ref(w, __func__);
173de5a0fddSnicm 		}
174de5a0fddSnicm 
175b3c6572bSnicm 		if (!alerts_fired) {
17681fe4598Snicm 			log_debug("alerts check queued (by @%u)", w->id);
17781fe4598Snicm 			event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL);
17881fe4598Snicm 			alerts_fired = 1;
17981fe4598Snicm 		}
18081fe4598Snicm 	}
181b3c6572bSnicm }
18281fe4598Snicm 
183413e5e52Snicm static int
alerts_check_bell(struct window * w)184de5a0fddSnicm alerts_check_bell(struct window *w)
18581fe4598Snicm {
186de5a0fddSnicm 	struct winlink	*wl;
187de5a0fddSnicm 	struct session	*s;
18881fe4598Snicm 
189de5a0fddSnicm 	if (~w->flags & WINDOW_BELL)
19081fe4598Snicm 		return (0);
1919a48cd7dSnicm 	if (!options_get_number(w->options, "monitor-bell"))
1929a48cd7dSnicm 		return (0);
193de5a0fddSnicm 
194de5a0fddSnicm 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
195de5a0fddSnicm 		wl->session->flags &= ~SESSION_ALERTED;
196de5a0fddSnicm 
197de5a0fddSnicm 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
198ac0a37a1Snicm 		/*
199ac0a37a1Snicm 		 * Bells are allowed even if there is an existing bell (so do
200ac0a37a1Snicm 		 * not check WINLINK_BELL).
201ac0a37a1Snicm 		 */
202de5a0fddSnicm 		s = wl->session;
2036434f49bSnicm 		if (s->curw != wl || s->attached == 0) {
20481fe4598Snicm 			wl->flags |= WINLINK_BELL;
205a48fd00bSnicm 			server_status_session(s);
206a48fd00bSnicm 		}
2077d08f26cSnicm 		if (!alerts_action_applies(wl, "bell-action"))
2087d08f26cSnicm 			continue;
2096aa2e59aSnicm 		notify_winlink("alert-bell", wl);
210de5a0fddSnicm 
211de5a0fddSnicm 		if (s->flags & SESSION_ALERTED)
212de5a0fddSnicm 			continue;
213de5a0fddSnicm 		s->flags |= SESSION_ALERTED;
21481fe4598Snicm 
2157d08f26cSnicm 		alerts_set_message(wl, "Bell", "visual-bell");
21681fe4598Snicm 	}
21781fe4598Snicm 
21881fe4598Snicm 	return (WINDOW_BELL);
21981fe4598Snicm }
22081fe4598Snicm 
221413e5e52Snicm static int
alerts_check_activity(struct window * w)222de5a0fddSnicm alerts_check_activity(struct window *w)
22381fe4598Snicm {
224de5a0fddSnicm 	struct winlink	*wl;
225de5a0fddSnicm 	struct session	*s;
22681fe4598Snicm 
227de5a0fddSnicm 	if (~w->flags & WINDOW_ACTIVITY)
22881fe4598Snicm 		return (0);
229d89252e5Snicm 	if (!options_get_number(w->options, "monitor-activity"))
23081fe4598Snicm 		return (0);
23181fe4598Snicm 
232de5a0fddSnicm 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
233de5a0fddSnicm 		wl->session->flags &= ~SESSION_ALERTED;
234de5a0fddSnicm 
235de5a0fddSnicm 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
236de5a0fddSnicm 		if (wl->flags & WINLINK_ACTIVITY)
237de5a0fddSnicm 			continue;
238de5a0fddSnicm 		s = wl->session;
2396434f49bSnicm 		if (s->curw != wl || s->attached == 0) {
240de5a0fddSnicm 			wl->flags |= WINLINK_ACTIVITY;
241a48fd00bSnicm 			server_status_session(s);
242a48fd00bSnicm 		}
2437d08f26cSnicm 		if (!alerts_action_applies(wl, "activity-action"))
2447d08f26cSnicm 			continue;
2456aa2e59aSnicm 		notify_winlink("alert-activity", wl);
246de5a0fddSnicm 
247de5a0fddSnicm 		if (s->flags & SESSION_ALERTED)
248de5a0fddSnicm 			continue;
249de5a0fddSnicm 		s->flags |= SESSION_ALERTED;
250de5a0fddSnicm 
2517d08f26cSnicm 		alerts_set_message(wl, "Activity", "visual-activity");
25281fe4598Snicm 	}
25381fe4598Snicm 
25481fe4598Snicm 	return (WINDOW_ACTIVITY);
25581fe4598Snicm }
25681fe4598Snicm 
257413e5e52Snicm static int
alerts_check_silence(struct window * w)258de5a0fddSnicm alerts_check_silence(struct window *w)
25981fe4598Snicm {
260de5a0fddSnicm 	struct winlink	*wl;
261de5a0fddSnicm 	struct session	*s;
26281fe4598Snicm 
263de5a0fddSnicm 	if (~w->flags & WINDOW_SILENCE)
26481fe4598Snicm 		return (0);
2651328eebfSnicm 	if (options_get_number(w->options, "monitor-silence") == 0)
26681fe4598Snicm 		return (0);
26781fe4598Snicm 
268de5a0fddSnicm 	TAILQ_FOREACH(wl, &w->winlinks, wentry)
269de5a0fddSnicm 		wl->session->flags &= ~SESSION_ALERTED;
270de5a0fddSnicm 
271de5a0fddSnicm 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
272de5a0fddSnicm 		if (wl->flags & WINLINK_SILENCE)
273de5a0fddSnicm 			continue;
274de5a0fddSnicm 		s = wl->session;
2756434f49bSnicm 		if (s->curw != wl || s->attached == 0) {
276de5a0fddSnicm 			wl->flags |= WINLINK_SILENCE;
277a48fd00bSnicm 			server_status_session(s);
278a48fd00bSnicm 		}
2797d08f26cSnicm 		if (!alerts_action_applies(wl, "silence-action"))
2807d08f26cSnicm 			continue;
2816aa2e59aSnicm 		notify_winlink("alert-silence", wl);
282de5a0fddSnicm 
283de5a0fddSnicm 		if (s->flags & SESSION_ALERTED)
284de5a0fddSnicm 			continue;
285de5a0fddSnicm 		s->flags |= SESSION_ALERTED;
28681fe4598Snicm 
2877d08f26cSnicm 		alerts_set_message(wl, "Silence", "visual-silence");
28881fe4598Snicm 	}
28981fe4598Snicm 
29081fe4598Snicm 	return (WINDOW_SILENCE);
29181fe4598Snicm }
29281fe4598Snicm 
293413e5e52Snicm static void
alerts_set_message(struct winlink * wl,const char * type,const char * option)2947d08f26cSnicm alerts_set_message(struct winlink *wl, const char *type, const char *option)
295de5a0fddSnicm {
296de5a0fddSnicm 	struct client	*c;
2977d08f26cSnicm 	int		 visual;
298de5a0fddSnicm 
2991328eebfSnicm 	/*
3001fd731f6Snicm 	 * We have found an alert (bell, activity or silence), so we need to
3011fd731f6Snicm 	 * pass it on to the user. For each client attached to this session,
3027d08f26cSnicm 	 * decide whether a bell, message or both is needed.
3031328eebfSnicm 	 *
3041328eebfSnicm 	 * If visual-{bell,activity,silence} is on, then a message is
3051328eebfSnicm 	 * substituted for a bell; if it is off, a bell is sent as normal; both
3067d08f26cSnicm 	 * mean both a bell and message is sent.
3071328eebfSnicm 	 */
308de5a0fddSnicm 
3097d08f26cSnicm 	visual = options_get_number(wl->session->options, option);
310de5a0fddSnicm 	TAILQ_FOREACH(c, &clients, entry) {
3117d08f26cSnicm 		if (c->session != wl->session || c->flags & CLIENT_CONTROL)
3121328eebfSnicm 			continue;
313de5a0fddSnicm 
3141328eebfSnicm 		if (visual == VISUAL_OFF || visual == VISUAL_BOTH)
315e42da932Snicm 			tty_putcode(&c->tty, TTYC_BEL);
3161328eebfSnicm 		if (visual == VISUAL_OFF)
3171328eebfSnicm 			continue;
318*e7e79d0aSnicm 		if (c->session->curw == wl) {
319*e7e79d0aSnicm 			status_message_set(c, -1, 1, 0, "%s in current window",
320*e7e79d0aSnicm 			    type);
321*e7e79d0aSnicm 		} else {
322*e7e79d0aSnicm 			status_message_set(c, -1, 1, 0, "%s in window %d", type,
3234f4307f9Snicm 			    wl->idx);
3244f4307f9Snicm 		}
32581fe4598Snicm 	}
32681fe4598Snicm }
327