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