Coverage Report

Created: 2025-07-12 06:33

/src/tmux/alerts.c
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
}