Line | Count | Source (jump to first uncovered line) |
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 |
43 | | alerts_timer(__unused int fd, __unused short events, void *arg) |
44 | 0 | { |
45 | 0 | struct window *w = arg; |
46 | |
|
47 | 0 | log_debug("@%u alerts timer expired", w->id); |
48 | 0 | alerts_queue(w, WINDOW_SILENCE); |
49 | 0 | } |
50 | | |
51 | | static void |
52 | | alerts_callback(__unused int fd, __unused short events, __unused void *arg) |
53 | 0 | { |
54 | 0 | struct window *w, *w1; |
55 | 0 | int alerts; |
56 | |
|
57 | 0 | TAILQ_FOREACH_SAFE(w, &alerts_list, alerts_entry, w1) { |
58 | 0 | alerts = alerts_check_all(w); |
59 | 0 | log_debug("@%u alerts check, alerts %#x", w->id, alerts); |
60 | |
|
61 | 0 | w->alerts_queued = 0; |
62 | 0 | TAILQ_REMOVE(&alerts_list, w, alerts_entry); |
63 | |
|
64 | 0 | w->flags &= ~WINDOW_ALERTFLAGS; |
65 | 0 | window_remove_ref(w, __func__); |
66 | 0 | } |
67 | 0 | alerts_fired = 0; |
68 | 0 | } |
69 | | |
70 | | static int |
71 | | alerts_action_applies(struct winlink *wl, const char *name) |
72 | 0 | { |
73 | 0 | 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 | 0 | action = options_get_number(wl->session->options, name); |
82 | 0 | if (action == ALERT_ANY) |
83 | 0 | return (1); |
84 | 0 | if (action == ALERT_CURRENT) |
85 | 0 | return (wl == wl->session->curw); |
86 | 0 | if (action == ALERT_OTHER) |
87 | 0 | return (wl != wl->session->curw); |
88 | 0 | return (0); |
89 | 0 | } |
90 | | |
91 | | static int |
92 | | alerts_check_all(struct window *w) |
93 | 0 | { |
94 | 0 | int alerts; |
95 | |
|
96 | 0 | alerts = alerts_check_bell(w); |
97 | 0 | alerts |= alerts_check_activity(w); |
98 | 0 | alerts |= alerts_check_silence(w); |
99 | 0 | return (alerts); |
100 | 0 | } |
101 | | |
102 | | void |
103 | | alerts_check_session(struct session *s) |
104 | 0 | { |
105 | 0 | struct winlink *wl; |
106 | |
|
107 | 0 | RB_FOREACH(wl, winlinks, &s->windows) |
108 | 0 | alerts_check_all(wl->window); |
109 | 0 | } |
110 | | |
111 | | static int |
112 | | alerts_enabled(struct window *w, int flags) |
113 | 25.7k | { |
114 | 25.7k | if (flags & WINDOW_BELL) { |
115 | 3.27k | if (options_get_number(w->options, "monitor-bell")) |
116 | 0 | return (1); |
117 | 3.27k | } |
118 | 25.7k | if (flags & WINDOW_ACTIVITY) { |
119 | 22.4k | if (options_get_number(w->options, "monitor-activity")) |
120 | 0 | return (1); |
121 | 22.4k | } |
122 | 25.7k | if (flags & WINDOW_SILENCE) { |
123 | 0 | if (options_get_number(w->options, "monitor-silence") != 0) |
124 | 0 | return (1); |
125 | 0 | } |
126 | 25.7k | return (0); |
127 | 25.7k | } |
128 | | |
129 | | void |
130 | | alerts_reset_all(void) |
131 | 0 | { |
132 | 0 | struct window *w; |
133 | |
|
134 | 0 | RB_FOREACH(w, windows, &windows) |
135 | 0 | alerts_reset(w); |
136 | 0 | } |
137 | | |
138 | | static void |
139 | | alerts_reset(struct window *w) |
140 | 25.7k | { |
141 | 25.7k | struct timeval tv; |
142 | | |
143 | 25.7k | if (!event_initialized(&w->alerts_timer)) |
144 | 11.2k | evtimer_set(&w->alerts_timer, alerts_timer, w); |
145 | | |
146 | 25.7k | w->flags &= ~WINDOW_SILENCE; |
147 | 25.7k | event_del(&w->alerts_timer); |
148 | | |
149 | 25.7k | timerclear(&tv); |
150 | 25.7k | tv.tv_sec = options_get_number(w->options, "monitor-silence"); |
151 | | |
152 | 25.7k | log_debug("@%u alerts timer reset %u", w->id, (u_int)tv.tv_sec); |
153 | 25.7k | if (tv.tv_sec != 0) |
154 | 0 | event_add(&w->alerts_timer, &tv); |
155 | 25.7k | } |
156 | | |
157 | | void |
158 | | alerts_queue(struct window *w, int flags) |
159 | 25.7k | { |
160 | 25.7k | alerts_reset(w); |
161 | | |
162 | 25.7k | if ((w->flags & flags) != flags) { |
163 | 11.3k | w->flags |= flags; |
164 | 11.3k | log_debug("@%u alerts flags added %#x", w->id, flags); |
165 | 11.3k | } |
166 | | |
167 | 25.7k | if (alerts_enabled(w, flags)) { |
168 | 0 | if (!w->alerts_queued) { |
169 | 0 | w->alerts_queued = 1; |
170 | 0 | TAILQ_INSERT_TAIL(&alerts_list, w, alerts_entry); |
171 | 0 | window_add_ref(w, __func__); |
172 | 0 | } |
173 | |
|
174 | 0 | if (!alerts_fired) { |
175 | 0 | log_debug("alerts check queued (by @%u)", w->id); |
176 | 0 | event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL); |
177 | 0 | alerts_fired = 1; |
178 | 0 | } |
179 | 0 | } |
180 | 25.7k | } |
181 | | |
182 | | static int |
183 | | alerts_check_bell(struct window *w) |
184 | 0 | { |
185 | 0 | struct winlink *wl; |
186 | 0 | struct session *s; |
187 | |
|
188 | 0 | if (~w->flags & WINDOW_BELL) |
189 | 0 | return (0); |
190 | 0 | if (!options_get_number(w->options, "monitor-bell")) |
191 | 0 | return (0); |
192 | | |
193 | 0 | TAILQ_FOREACH(wl, &w->winlinks, wentry) |
194 | 0 | wl->session->flags &= ~SESSION_ALERTED; |
195 | |
|
196 | 0 | 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 | 0 | s = wl->session; |
202 | 0 | if (s->curw != wl || s->attached == 0) { |
203 | 0 | wl->flags |= WINLINK_BELL; |
204 | 0 | server_status_session(s); |
205 | 0 | } |
206 | 0 | if (!alerts_action_applies(wl, "bell-action")) |
207 | 0 | continue; |
208 | 0 | notify_winlink("alert-bell", wl); |
209 | |
|
210 | 0 | if (s->flags & SESSION_ALERTED) |
211 | 0 | continue; |
212 | 0 | s->flags |= SESSION_ALERTED; |
213 | |
|
214 | 0 | alerts_set_message(wl, "Bell", "visual-bell"); |
215 | 0 | } |
216 | |
|
217 | 0 | return (WINDOW_BELL); |
218 | 0 | } |
219 | | |
220 | | static int |
221 | | alerts_check_activity(struct window *w) |
222 | 0 | { |
223 | 0 | struct winlink *wl; |
224 | 0 | struct session *s; |
225 | |
|
226 | 0 | if (~w->flags & WINDOW_ACTIVITY) |
227 | 0 | return (0); |
228 | 0 | if (!options_get_number(w->options, "monitor-activity")) |
229 | 0 | return (0); |
230 | | |
231 | 0 | TAILQ_FOREACH(wl, &w->winlinks, wentry) |
232 | 0 | wl->session->flags &= ~SESSION_ALERTED; |
233 | |
|
234 | 0 | TAILQ_FOREACH(wl, &w->winlinks, wentry) { |
235 | 0 | if (wl->flags & WINLINK_ACTIVITY) |
236 | 0 | continue; |
237 | 0 | s = wl->session; |
238 | 0 | if (s->curw != wl || s->attached == 0) { |
239 | 0 | wl->flags |= WINLINK_ACTIVITY; |
240 | 0 | server_status_session(s); |
241 | 0 | } |
242 | 0 | if (!alerts_action_applies(wl, "activity-action")) |
243 | 0 | continue; |
244 | 0 | notify_winlink("alert-activity", wl); |
245 | |
|
246 | 0 | if (s->flags & SESSION_ALERTED) |
247 | 0 | continue; |
248 | 0 | s->flags |= SESSION_ALERTED; |
249 | |
|
250 | 0 | alerts_set_message(wl, "Activity", "visual-activity"); |
251 | 0 | } |
252 | |
|
253 | 0 | return (WINDOW_ACTIVITY); |
254 | 0 | } |
255 | | |
256 | | static int |
257 | | alerts_check_silence(struct window *w) |
258 | 0 | { |
259 | 0 | struct winlink *wl; |
260 | 0 | struct session *s; |
261 | |
|
262 | 0 | if (~w->flags & WINDOW_SILENCE) |
263 | 0 | return (0); |
264 | 0 | if (options_get_number(w->options, "monitor-silence") == 0) |
265 | 0 | return (0); |
266 | | |
267 | 0 | TAILQ_FOREACH(wl, &w->winlinks, wentry) |
268 | 0 | wl->session->flags &= ~SESSION_ALERTED; |
269 | |
|
270 | 0 | TAILQ_FOREACH(wl, &w->winlinks, wentry) { |
271 | 0 | if (wl->flags & WINLINK_SILENCE) |
272 | 0 | continue; |
273 | 0 | s = wl->session; |
274 | 0 | if (s->curw != wl || s->attached == 0) { |
275 | 0 | wl->flags |= WINLINK_SILENCE; |
276 | 0 | server_status_session(s); |
277 | 0 | } |
278 | 0 | if (!alerts_action_applies(wl, "silence-action")) |
279 | 0 | continue; |
280 | 0 | notify_winlink("alert-silence", wl); |
281 | |
|
282 | 0 | if (s->flags & SESSION_ALERTED) |
283 | 0 | continue; |
284 | 0 | s->flags |= SESSION_ALERTED; |
285 | |
|
286 | 0 | alerts_set_message(wl, "Silence", "visual-silence"); |
287 | 0 | } |
288 | |
|
289 | 0 | return (WINDOW_SILENCE); |
290 | 0 | } |
291 | | |
292 | | static void |
293 | | alerts_set_message(struct winlink *wl, const char *type, const char *option) |
294 | 0 | { |
295 | 0 | struct client *c; |
296 | 0 | 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 | 0 | visual = options_get_number(wl->session->options, option); |
309 | 0 | TAILQ_FOREACH(c, &clients, entry) { |
310 | 0 | if (c->session != wl->session || c->flags & CLIENT_CONTROL) |
311 | 0 | continue; |
312 | | |
313 | 0 | if (visual == VISUAL_OFF || visual == VISUAL_BOTH) |
314 | 0 | tty_putcode(&c->tty, TTYC_BEL); |
315 | 0 | if (visual == VISUAL_OFF) |
316 | 0 | continue; |
317 | 0 | if (c->session->curw == wl) { |
318 | 0 | status_message_set(c, -1, 1, 0, 0, |
319 | 0 | "%s in current window", type); |
320 | 0 | } else { |
321 | 0 | status_message_set(c, -1, 1, 0, 0, |
322 | 0 | "%s in window %d", type, wl->idx); |
323 | 0 | } |
324 | 0 | } |
325 | 0 | } |