15494e770Schristos /* $OpenBSD$ */
25494e770Schristos
35494e770Schristos /*
4ed4e6cd4Schristos * Copyright (c) 2015 Nicholas Marriott <nicholas.marriott@gmail.com>
55494e770Schristos *
65494e770Schristos * Permission to use, copy, modify, and distribute this software for any
75494e770Schristos * purpose with or without fee is hereby granted, provided that the above
85494e770Schristos * copyright notice and this permission notice appear in all copies.
95494e770Schristos *
105494e770Schristos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
115494e770Schristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
125494e770Schristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
135494e770Schristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
145494e770Schristos * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
155494e770Schristos * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
165494e770Schristos * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
175494e770Schristos */
185494e770Schristos
195494e770Schristos #include <sys/types.h>
205494e770Schristos
214e179ddaSchristos #include <stdlib.h>
225494e770Schristos
235494e770Schristos #include "tmux.h"
245494e770Schristos
254e179ddaSchristos static int alerts_fired;
265494e770Schristos
274e179ddaSchristos static void alerts_timer(int, short, void *);
284e179ddaSchristos static int alerts_enabled(struct window *, int);
294e179ddaSchristos static void alerts_callback(int, short, void *);
304e179ddaSchristos static void alerts_reset(struct window *);
315494e770Schristos
32c9ad075bSchristos static int alerts_action_applies(struct winlink *, const char *);
334e179ddaSchristos static int alerts_check_all(struct window *);
344e179ddaSchristos static int alerts_check_bell(struct window *);
354e179ddaSchristos static int alerts_check_activity(struct window *);
364e179ddaSchristos static int alerts_check_silence(struct window *);
37c9ad075bSchristos static void alerts_set_message(struct winlink *, const char *,
38c9ad075bSchristos const char *);
395494e770Schristos
404e179ddaSchristos static TAILQ_HEAD(, window) alerts_list = TAILQ_HEAD_INITIALIZER(alerts_list);
414e179ddaSchristos
424e179ddaSchristos static void
alerts_timer(__unused int fd,__unused short events,void * arg)43ed4e6cd4Schristos alerts_timer(__unused int fd, __unused short events, void *arg)
445494e770Schristos {
455494e770Schristos struct window *w = arg;
465494e770Schristos
475494e770Schristos log_debug("@%u alerts timer expired", w->id);
485494e770Schristos alerts_queue(w, WINDOW_SILENCE);
495494e770Schristos }
505494e770Schristos
514e179ddaSchristos static void
alerts_callback(__unused int fd,__unused short events,__unused void * arg)52ed4e6cd4Schristos alerts_callback(__unused int fd, __unused short events, __unused void *arg)
535494e770Schristos {
544e179ddaSchristos struct window *w, *w1;
554e179ddaSchristos int alerts;
565494e770Schristos
574e179ddaSchristos TAILQ_FOREACH_SAFE(w, &alerts_list, alerts_entry, w1) {
584e179ddaSchristos alerts = alerts_check_all(w);
594e179ddaSchristos log_debug("@%u alerts check, alerts %#x", w->id, alerts);
605494e770Schristos
614e179ddaSchristos w->alerts_queued = 0;
624e179ddaSchristos TAILQ_REMOVE(&alerts_list, w, alerts_entry);
635494e770Schristos
644e179ddaSchristos w->flags &= ~WINDOW_ALERTFLAGS;
65c9ad075bSchristos window_remove_ref(w, __func__);
665494e770Schristos }
675494e770Schristos alerts_fired = 0;
685494e770Schristos }
695494e770Schristos
704e179ddaSchristos static int
alerts_action_applies(struct winlink * wl,const char * name)71c9ad075bSchristos alerts_action_applies(struct winlink *wl, const char *name)
72c9ad075bSchristos {
73c9ad075bSchristos int action;
74c9ad075bSchristos
75c9ad075bSchristos /*
76c9ad075bSchristos * {bell,activity,silence}-action determines when to alert: none means
77c9ad075bSchristos * nothing happens, current means only do something for the current
78c9ad075bSchristos * window and other means only for windows other than the current.
79c9ad075bSchristos */
80c9ad075bSchristos
81c9ad075bSchristos action = options_get_number(wl->session->options, name);
82c9ad075bSchristos if (action == ALERT_ANY)
83c9ad075bSchristos return (1);
84c9ad075bSchristos if (action == ALERT_CURRENT)
85c9ad075bSchristos return (wl == wl->session->curw);
86c9ad075bSchristos if (action == ALERT_OTHER)
87c9ad075bSchristos return (wl != wl->session->curw);
88c9ad075bSchristos return (0);
89c9ad075bSchristos }
90c9ad075bSchristos
91c9ad075bSchristos static int
alerts_check_all(struct window * w)924e179ddaSchristos alerts_check_all(struct window *w)
93ed4e6cd4Schristos {
94ed4e6cd4Schristos int alerts;
95ed4e6cd4Schristos
964e179ddaSchristos alerts = alerts_check_bell(w);
974e179ddaSchristos alerts |= alerts_check_activity(w);
984e179ddaSchristos alerts |= alerts_check_silence(w);
99ed4e6cd4Schristos return (alerts);
100ed4e6cd4Schristos }
101ed4e6cd4Schristos
102ed4e6cd4Schristos void
alerts_check_session(struct session * s)103ed4e6cd4Schristos alerts_check_session(struct session *s)
104ed4e6cd4Schristos {
105ed4e6cd4Schristos struct winlink *wl;
106ed4e6cd4Schristos
107ed4e6cd4Schristos RB_FOREACH(wl, winlinks, &s->windows)
1084e179ddaSchristos alerts_check_all(wl->window);
109ed4e6cd4Schristos }
110ed4e6cd4Schristos
1114e179ddaSchristos static int
alerts_enabled(struct window * w,int flags)1125494e770Schristos alerts_enabled(struct window *w, int flags)
1135494e770Schristos {
114c9ad075bSchristos if (flags & WINDOW_BELL) {
115c9ad075bSchristos if (options_get_number(w->options, "monitor-bell"))
116ed4e6cd4Schristos return (1);
117c9ad075bSchristos }
1185494e770Schristos if (flags & WINDOW_ACTIVITY) {
119ed4e6cd4Schristos if (options_get_number(w->options, "monitor-activity"))
1205494e770Schristos return (1);
1215494e770Schristos }
1225494e770Schristos if (flags & WINDOW_SILENCE) {
123ed4e6cd4Schristos if (options_get_number(w->options, "monitor-silence") != 0)
1245494e770Schristos return (1);
1255494e770Schristos }
1265494e770Schristos return (0);
1275494e770Schristos }
1285494e770Schristos
1295494e770Schristos void
alerts_reset_all(void)1305494e770Schristos alerts_reset_all(void)
1315494e770Schristos {
1325494e770Schristos struct window *w;
1335494e770Schristos
1345494e770Schristos RB_FOREACH(w, windows, &windows)
1355494e770Schristos alerts_reset(w);
1365494e770Schristos }
1375494e770Schristos
1384e179ddaSchristos static void
alerts_reset(struct window * w)1395494e770Schristos alerts_reset(struct window *w)
1405494e770Schristos {
1415494e770Schristos struct timeval tv;
1425494e770Schristos
143c9ad075bSchristos if (!event_initialized(&w->alerts_timer))
144c9ad075bSchristos evtimer_set(&w->alerts_timer, alerts_timer, w);
145c9ad075bSchristos
1465494e770Schristos w->flags &= ~WINDOW_SILENCE;
1475494e770Schristos event_del(&w->alerts_timer);
1485494e770Schristos
1495494e770Schristos timerclear(&tv);
150ed4e6cd4Schristos tv.tv_sec = options_get_number(w->options, "monitor-silence");
1515494e770Schristos
1525494e770Schristos log_debug("@%u alerts timer reset %u", w->id, (u_int)tv.tv_sec);
1535494e770Schristos if (tv.tv_sec != 0)
1545494e770Schristos event_add(&w->alerts_timer, &tv);
1555494e770Schristos }
1565494e770Schristos
1575494e770Schristos void
alerts_queue(struct window * w,int flags)1585494e770Schristos alerts_queue(struct window *w, int flags)
1595494e770Schristos {
1605494e770Schristos alerts_reset(w);
1615494e770Schristos
1624e179ddaSchristos if ((w->flags & flags) != flags) {
1635494e770Schristos w->flags |= flags;
1645494e770Schristos log_debug("@%u alerts flags added %#x", w->id, flags);
1654e179ddaSchristos }
1665494e770Schristos
167c9ad075bSchristos if (alerts_enabled(w, flags)) {
1684e179ddaSchristos if (!w->alerts_queued) {
1694e179ddaSchristos w->alerts_queued = 1;
1704e179ddaSchristos TAILQ_INSERT_TAIL(&alerts_list, w, alerts_entry);
171c9ad075bSchristos window_add_ref(w, __func__);
1724e179ddaSchristos }
1734e179ddaSchristos
174c9ad075bSchristos if (!alerts_fired) {
1755494e770Schristos log_debug("alerts check queued (by @%u)", w->id);
1765494e770Schristos event_once(-1, EV_TIMEOUT, alerts_callback, NULL, NULL);
1775494e770Schristos alerts_fired = 1;
1785494e770Schristos }
1795494e770Schristos }
180c9ad075bSchristos }
1815494e770Schristos
1824e179ddaSchristos static int
alerts_check_bell(struct window * w)1834e179ddaSchristos alerts_check_bell(struct window *w)
1845494e770Schristos {
1854e179ddaSchristos struct winlink *wl;
1864e179ddaSchristos struct session *s;
1875494e770Schristos
1884e179ddaSchristos if (~w->flags & WINDOW_BELL)
1895494e770Schristos return (0);
190c9ad075bSchristos if (!options_get_number(w->options, "monitor-bell"))
191c9ad075bSchristos return (0);
1924e179ddaSchristos
1934e179ddaSchristos TAILQ_FOREACH(wl, &w->winlinks, wentry)
1944e179ddaSchristos wl->session->flags &= ~SESSION_ALERTED;
1954e179ddaSchristos
1964e179ddaSchristos TAILQ_FOREACH(wl, &w->winlinks, wentry) {
197c9ad075bSchristos /*
198c9ad075bSchristos * Bells are allowed even if there is an existing bell (so do
199c9ad075bSchristos * not check WINLINK_BELL).
200c9ad075bSchristos */
2014e179ddaSchristos s = wl->session;
202*9fb66d81Schristos if (s->curw != wl || s->attached == 0) {
2035494e770Schristos wl->flags |= WINLINK_BELL;
2048f3b9483Schristos server_status_session(s);
2058f3b9483Schristos }
206c9ad075bSchristos if (!alerts_action_applies(wl, "bell-action"))
207c9ad075bSchristos continue;
208c9ad075bSchristos notify_winlink("alert-bell", wl);
2094e179ddaSchristos
2104e179ddaSchristos if (s->flags & SESSION_ALERTED)
2114e179ddaSchristos continue;
2124e179ddaSchristos s->flags |= SESSION_ALERTED;
2135494e770Schristos
214c9ad075bSchristos alerts_set_message(wl, "Bell", "visual-bell");
2155494e770Schristos }
2165494e770Schristos
2175494e770Schristos return (WINDOW_BELL);
2185494e770Schristos }
2195494e770Schristos
2204e179ddaSchristos static int
alerts_check_activity(struct window * w)2214e179ddaSchristos alerts_check_activity(struct window *w)
2225494e770Schristos {
2234e179ddaSchristos struct winlink *wl;
2244e179ddaSchristos struct session *s;
2255494e770Schristos
2264e179ddaSchristos if (~w->flags & WINDOW_ACTIVITY)
2275494e770Schristos return (0);
228ed4e6cd4Schristos if (!options_get_number(w->options, "monitor-activity"))
2295494e770Schristos return (0);
2305494e770Schristos
2314e179ddaSchristos TAILQ_FOREACH(wl, &w->winlinks, wentry)
2324e179ddaSchristos wl->session->flags &= ~SESSION_ALERTED;
2334e179ddaSchristos
2344e179ddaSchristos TAILQ_FOREACH(wl, &w->winlinks, wentry) {
2354e179ddaSchristos if (wl->flags & WINLINK_ACTIVITY)
2364e179ddaSchristos continue;
2374e179ddaSchristos s = wl->session;
238*9fb66d81Schristos if (s->curw != wl || s->attached == 0) {
2394e179ddaSchristos wl->flags |= WINLINK_ACTIVITY;
2408f3b9483Schristos server_status_session(s);
2418f3b9483Schristos }
242c9ad075bSchristos if (!alerts_action_applies(wl, "activity-action"))
243c9ad075bSchristos continue;
244c9ad075bSchristos notify_winlink("alert-activity", wl);
2454e179ddaSchristos
2464e179ddaSchristos if (s->flags & SESSION_ALERTED)
2474e179ddaSchristos continue;
2484e179ddaSchristos s->flags |= SESSION_ALERTED;
2494e179ddaSchristos
250c9ad075bSchristos alerts_set_message(wl, "Activity", "visual-activity");
2515494e770Schristos }
2525494e770Schristos
2535494e770Schristos return (WINDOW_ACTIVITY);
2545494e770Schristos }
2555494e770Schristos
2564e179ddaSchristos static int
alerts_check_silence(struct window * w)2574e179ddaSchristos alerts_check_silence(struct window *w)
2585494e770Schristos {
2594e179ddaSchristos struct winlink *wl;
2604e179ddaSchristos struct session *s;
2615494e770Schristos
2624e179ddaSchristos if (~w->flags & WINDOW_SILENCE)
2635494e770Schristos return (0);
264c9ad075bSchristos if (options_get_number(w->options, "monitor-silence") == 0)
2654e179ddaSchristos return (0);
2664e179ddaSchristos
2674e179ddaSchristos TAILQ_FOREACH(wl, &w->winlinks, wentry)
2684e179ddaSchristos wl->session->flags &= ~SESSION_ALERTED;
2694e179ddaSchristos
2704e179ddaSchristos TAILQ_FOREACH(wl, &w->winlinks, wentry) {
2714e179ddaSchristos if (wl->flags & WINLINK_SILENCE)
2724e179ddaSchristos continue;
2734e179ddaSchristos s = wl->session;
274*9fb66d81Schristos if (s->curw != wl || s->attached == 0) {
2754e179ddaSchristos wl->flags |= WINLINK_SILENCE;
2768f3b9483Schristos server_status_session(s);
2778f3b9483Schristos }
278c9ad075bSchristos if (!alerts_action_applies(wl, "silence-action"))
279c9ad075bSchristos continue;
280c9ad075bSchristos notify_winlink("alert-silence", wl);
2815494e770Schristos
2824e179ddaSchristos if (s->flags & SESSION_ALERTED)
2834e179ddaSchristos continue;
2844e179ddaSchristos s->flags |= SESSION_ALERTED;
2855494e770Schristos
286c9ad075bSchristos alerts_set_message(wl, "Silence", "visual-silence");
2875494e770Schristos }
2885494e770Schristos
2895494e770Schristos return (WINDOW_SILENCE);
2905494e770Schristos }
2915494e770Schristos
2924e179ddaSchristos static void
alerts_set_message(struct winlink * wl,const char * type,const char * option)293c9ad075bSchristos alerts_set_message(struct winlink *wl, const char *type, const char *option)
2944e179ddaSchristos {
2954e179ddaSchristos struct client *c;
296c9ad075bSchristos int visual;
2974e179ddaSchristos
298c9ad075bSchristos /*
299c9ad075bSchristos * We have found an alert (bell, activity or silence), so we need to
300c9ad075bSchristos * pass it on to the user. For each client attached to this session,
301c9ad075bSchristos * decide whether a bell, message or both is needed.
302c9ad075bSchristos *
303c9ad075bSchristos * If visual-{bell,activity,silence} is on, then a message is
304c9ad075bSchristos * substituted for a bell; if it is off, a bell is sent as normal; both
305c9ad075bSchristos * mean both a bell and message is sent.
306c9ad075bSchristos */
3074e179ddaSchristos
308c9ad075bSchristos visual = options_get_number(wl->session->options, option);
3094e179ddaSchristos TAILQ_FOREACH(c, &clients, entry) {
310c9ad075bSchristos if (c->session != wl->session || c->flags & CLIENT_CONTROL)
311c9ad075bSchristos continue;
3124e179ddaSchristos
313c9ad075bSchristos if (visual == VISUAL_OFF || visual == VISUAL_BOTH)
3145494e770Schristos tty_putcode(&c->tty, TTYC_BEL);
315c9ad075bSchristos if (visual == VISUAL_OFF)
316c9ad075bSchristos continue;
317*9fb66d81Schristos if (c->session->curw == wl) {
318*9fb66d81Schristos status_message_set(c, -1, 1, 0, "%s in current window",
319*9fb66d81Schristos type);
320*9fb66d81Schristos } else {
321*9fb66d81Schristos status_message_set(c, -1, 1, 0, "%s in window %d", type,
322*9fb66d81Schristos wl->idx);
323*9fb66d81Schristos }
3245494e770Schristos }
3255494e770Schristos }
326