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