alerts.c revision 1.30
1/* $OpenBSD: alerts.c,v 1.30 2020/05/16 15:54:20 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
26static int	alerts_fired;
27
28static void	alerts_timer(int, short, void *);
29static int	alerts_enabled(struct window *, int);
30static void	alerts_callback(int, short, void *);
31static void	alerts_reset(struct window *);
32
33static int	alerts_action_applies(struct winlink *, const char *);
34static int	alerts_check_all(struct window *);
35static int	alerts_check_bell(struct window *);
36static int	alerts_check_activity(struct window *);
37static int	alerts_check_silence(struct window *);
38static void	alerts_set_message(struct winlink *, const char *,
39		    const char *);
40
41static TAILQ_HEAD(, window) alerts_list = TAILQ_HEAD_INITIALIZER(alerts_list);
42
43static void
44alerts_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
52static void
53alerts_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
71static int
72alerts_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
92static int
93alerts_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
103void
104alerts_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
112static int
113alerts_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
130void
131alerts_reset_all(void)
132{
133	struct window	*w;
134
135	RB_FOREACH(w, windows, &windows)
136		alerts_reset(w);
137}
138
139static void
140alerts_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
158void
159alerts_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
183static int
184alerts_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) {
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
221static int
222alerts_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) {
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
257static int
258alerts_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) {
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
293static void
294alerts_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, "%s in current window", type);
320		else {
321			status_message_set(c, 1, "%s in window %d", type,
322			    wl->idx);
323		}
324	}
325}
326