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 <event.h>
22 
23 #include "tmux.h"
24 
25 int	alerts_fired;
26 
27 void	alerts_timer(int, short, void *);
28 int	alerts_enabled(struct window *, int);
29 void	alerts_callback(int, short, void *);
30 void	alerts_reset(struct window *);
31 
32 void	alerts_run_hook(struct session *, struct winlink *, int);
33 int	alerts_check_all(struct session *, struct winlink *);
34 int	alerts_check_bell(struct session *, struct winlink *);
35 int	alerts_check_activity(struct session *, struct winlink *);
36 int	alerts_check_silence(struct session *, struct winlink *);
37 void	alerts_ring_bell(struct session *);
38 
39 void
alerts_timer(__unused int fd,__unused short events,void * arg)40 alerts_timer(__unused int fd, __unused short events, void *arg)
41 {
42 	struct window	*w = arg;
43 
44 	log_debug("@%u alerts timer expired", w->id);
45 	alerts_reset(w);
46 	alerts_queue(w, WINDOW_SILENCE);
47 }
48 
49 void
alerts_callback(__unused int fd,__unused short events,__unused void * arg)50 alerts_callback(__unused int fd, __unused short events, __unused void *arg)
51 {
52 	struct window	*w;
53 	struct session	*s;
54 	struct winlink	*wl;
55 	int		 flags, alerts;
56 
57 	RB_FOREACH(w, windows, &windows) {
58 		RB_FOREACH(s, sessions, &sessions) {
59 			RB_FOREACH(wl, winlinks, &s->windows) {
60 				if (wl->window != w)
61 					continue;
62 				flags = w->flags;
63 
64 				alerts = alerts_check_all(s, wl);
65 
66 				log_debug("%s:%d @%u alerts check, alerts %#x, "
67 				    "flags %#x", s->name, wl->idx, w->id,
68 				    alerts, flags);
69 			}
70 		}
71 	}
72 	alerts_fired = 0;
73 }
74 
75 void
alerts_run_hook(struct session * s,struct winlink * wl,int flags)76 alerts_run_hook(struct session *s, struct winlink *wl, int flags)
77 {
78 	struct cmd_find_state	 fs;
79 
80 	if (cmd_find_from_winlink(&fs, s, wl) != 0)
81 		return;
82 
83 	if (flags & WINDOW_BELL)
84 		hooks_run(s->hooks, NULL, &fs, "alert-bell");
85 	if (flags & WINDOW_SILENCE)
86 		hooks_run(s->hooks, NULL, &fs, "alert-silence");
87 	if (flags & WINDOW_ACTIVITY)
88 		hooks_run(s->hooks, NULL, &fs, "alert-activity");
89 }
90 
91 int
alerts_check_all(struct session * s,struct winlink * wl)92 alerts_check_all(struct session *s, struct winlink *wl)
93 {
94 	int	alerts;
95 
96 	alerts  = alerts_check_bell(s, wl);
97 	alerts |= alerts_check_activity(s, wl);
98 	alerts |= alerts_check_silence(s, wl);
99 	if (alerts != 0) {
100 		alerts_run_hook(s, wl, alerts);
101 		server_status_session(s);
102 	}
103 
104 	return (alerts);
105 }
106 
107 void
alerts_check_session(struct session * s)108 alerts_check_session(struct session *s)
109 {
110 	struct winlink	*wl;
111 
112 	RB_FOREACH(wl, winlinks, &s->windows)
113 		alerts_check_all(s, wl);
114 }
115 
116 int
alerts_enabled(struct window * w,int flags)117 alerts_enabled(struct window *w, int flags)
118 {
119 	if (flags & WINDOW_BELL)
120 		return (1);
121 	if (flags & WINDOW_ACTIVITY) {
122 		if (options_get_number(w->options, "monitor-activity"))
123 			return (1);
124 	}
125 	if (flags & WINDOW_SILENCE) {
126 		if (options_get_number(w->options, "monitor-silence") != 0)
127 			return (1);
128 	}
129 	return (0);
130 }
131 
132 void
alerts_reset_all(void)133 alerts_reset_all(void)
134 {
135 	struct window	*w;
136 
137 	RB_FOREACH(w, windows, &windows)
138 		alerts_reset(w);
139 }
140 
141 void
alerts_reset(struct window * w)142 alerts_reset(struct window *w)
143 {
144 	struct timeval	tv;
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 	if (w->flags & WINDOW_ACTIVITY)
161 		alerts_reset(w);
162 
163 	if (!event_initialized(&w->alerts_timer))
164 		evtimer_set(&w->alerts_timer, alerts_timer, w);
165 
166 	if (!alerts_fired) {
167 		w->flags |= flags;
168 		log_debug("@%u alerts flags added %#x", w->id, flags);
169 
170 		if (alerts_enabled(w, flags)) {
171 			log_debug("alerts check queued (by @%u)", w->id);
172 			event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL);
173 			alerts_fired = 1;
174 		}
175 	}
176 }
177 
178 int
alerts_check_bell(struct session * s,struct winlink * wl)179 alerts_check_bell(struct session *s, struct winlink *wl)
180 {
181 	struct client	*c;
182 	struct window	*w = wl->window;
183 	int		 action, visual;
184 
185 	if (!(w->flags & WINDOW_BELL))
186 		return (0);
187 	if (s->curw != wl) {
188 		wl->flags |= WINLINK_BELL;
189 		w->flags &= ~WINDOW_BELL;
190 	}
191 	if (s->curw->window == w)
192 		w->flags &= ~WINDOW_BELL;
193 
194 	action = options_get_number(s->options, "bell-action");
195 	if (action == BELL_NONE)
196 		return (0);
197 
198 	visual = options_get_number(s->options, "visual-bell");
199 	TAILQ_FOREACH(c, &clients, entry) {
200 		if (c->session != s || c->flags & CLIENT_CONTROL)
201 			continue;
202 		if (!visual) {
203 			if ((action == BELL_CURRENT &&
204 			    c->session->curw->window == w) ||
205 			    (action == BELL_OTHER &&
206 			    c->session->curw->window != w) ||
207 			    action == BELL_ANY)
208 				tty_putcode(&c->tty, TTYC_BEL);
209 			continue;
210 		}
211 		if (action == BELL_CURRENT && c->session->curw->window == w)
212 			status_message_set(c, "Bell in current window");
213 		else if (action == BELL_ANY || (action == BELL_OTHER &&
214 		    c->session->curw->window != w))
215 			status_message_set(c, "Bell in window %d", wl->idx);
216 	}
217 
218 	return (WINDOW_BELL);
219 }
220 
221 int
alerts_check_activity(struct session * s,struct winlink * wl)222 alerts_check_activity(struct session *s, struct winlink *wl)
223 {
224 	struct client	*c;
225 	struct window	*w = wl->window;
226 
227 	if (s->curw->window == w)
228 		w->flags &= ~WINDOW_ACTIVITY;
229 
230 	if (!(w->flags & WINDOW_ACTIVITY) || wl->flags & WINLINK_ACTIVITY)
231 		return (0);
232 	if (s->curw == wl)
233 		return (0);
234 
235 	if (!options_get_number(w->options, "monitor-activity"))
236 		return (0);
237 
238 	if (options_get_number(s->options, "bell-on-alert"))
239 		alerts_ring_bell(s);
240 	wl->flags |= WINLINK_ACTIVITY;
241 
242 	if (options_get_number(s->options, "visual-activity")) {
243 		TAILQ_FOREACH(c, &clients, entry) {
244 			if (c->session != s)
245 				continue;
246 			status_message_set(c, "Activity in window %d", wl->idx);
247 		}
248 	}
249 
250 	return (WINDOW_ACTIVITY);
251 }
252 
253 int
alerts_check_silence(struct session * s,struct winlink * wl)254 alerts_check_silence(struct session *s, struct winlink *wl)
255 {
256 	struct client	*c;
257 	struct window	*w = wl->window;
258 
259 	if (s->curw->window == w)
260 		w->flags &= ~WINDOW_SILENCE;
261 
262 	if (!(w->flags & WINDOW_SILENCE) || wl->flags & WINLINK_SILENCE)
263 		return (0);
264 	if (s->curw == wl)
265 		return (0);
266 
267 	if (options_get_number(w->options, "monitor-silence") == 0)
268 		return (0);
269 
270 	if (options_get_number(s->options, "bell-on-alert"))
271 		alerts_ring_bell(s);
272 	wl->flags |= WINLINK_SILENCE;
273 
274 	if (options_get_number(s->options, "visual-silence")) {
275 		TAILQ_FOREACH(c, &clients, entry) {
276 			if (c->session != s)
277 				continue;
278 			status_message_set(c, "Silence in window %d", wl->idx);
279 		}
280 	}
281 
282 	return (WINDOW_SILENCE);
283 }
284 
285 void
alerts_ring_bell(struct session * s)286 alerts_ring_bell(struct session *s)
287 {
288 	struct client	*c;
289 
290 	TAILQ_FOREACH(c, &clients, entry) {
291 		if (c->session == s && !(c->flags & CLIENT_CONTROL))
292 			tty_putcode(&c->tty, TTYC_BEL);
293 	}
294 }
295