mirror of
https://github.com/tmux/tmux.git
synced 2026-07-22 08:51:17 +00:00
527 lines
12 KiB
C
527 lines
12 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <event.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/* Subscription pane. */
|
|
struct monitor_pane {
|
|
u_int pane;
|
|
u_int idx;
|
|
char *last;
|
|
|
|
RB_ENTRY(monitor_pane) entry;
|
|
};
|
|
RB_HEAD(monitor_panes, monitor_pane);
|
|
|
|
/* Subscription window. */
|
|
struct monitor_window {
|
|
u_int window;
|
|
u_int idx;
|
|
char *last;
|
|
|
|
RB_ENTRY(monitor_window) entry;
|
|
};
|
|
RB_HEAD(monitor_windows, monitor_window);
|
|
|
|
/* Subscription. */
|
|
struct monitor_item {
|
|
char *name;
|
|
char *format;
|
|
|
|
enum monitor_type type;
|
|
u_int id;
|
|
|
|
char *last;
|
|
struct monitor_panes panes;
|
|
struct monitor_windows windows;
|
|
|
|
RB_ENTRY(monitor_item) entry;
|
|
};
|
|
RB_HEAD(monitor_items, monitor_item);
|
|
|
|
/* Monitored subscription set. */
|
|
struct monitor_set {
|
|
struct client *client;
|
|
monitor_cb cb;
|
|
void *data;
|
|
|
|
struct monitor_items items;
|
|
struct event timer;
|
|
};
|
|
|
|
static void monitor_timer(__unused int, __unused short, void *);
|
|
|
|
/* Compare subscriptions. */
|
|
static int
|
|
monitor_item_cmp(struct monitor_item *m1, struct monitor_item *m2)
|
|
{
|
|
return (strcmp(m1->name, m2->name));
|
|
}
|
|
RB_GENERATE_STATIC(monitor_items, monitor_item, entry, monitor_item_cmp);
|
|
|
|
/* Compare subscription panes. */
|
|
static int
|
|
monitor_pane_cmp(struct monitor_pane *mp1, struct monitor_pane *mp2)
|
|
{
|
|
if (mp1->pane < mp2->pane)
|
|
return (-1);
|
|
if (mp1->pane > mp2->pane)
|
|
return (1);
|
|
if (mp1->idx < mp2->idx)
|
|
return (-1);
|
|
if (mp1->idx > mp2->idx)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
RB_GENERATE_STATIC(monitor_panes, monitor_pane, entry, monitor_pane_cmp);
|
|
|
|
/* Compare subscription windows. */
|
|
static int
|
|
monitor_window_cmp(struct monitor_window *mw1, struct monitor_window *mw2)
|
|
{
|
|
if (mw1->window < mw2->window)
|
|
return (-1);
|
|
if (mw1->window > mw2->window)
|
|
return (1);
|
|
if (mw1->idx < mw2->idx)
|
|
return (-1);
|
|
if (mw1->idx > mw2->idx)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
RB_GENERATE_STATIC(monitor_windows, monitor_window, entry, monitor_window_cmp);
|
|
|
|
/* Free a subscription. */
|
|
static void
|
|
monitor_free_item(struct monitor_set *ms, struct monitor_item *me)
|
|
{
|
|
struct monitor_pane *mp, *mp1;
|
|
struct monitor_window *mw, *mw1;
|
|
|
|
RB_FOREACH_SAFE(mp, monitor_panes, &me->panes, mp1) {
|
|
RB_REMOVE(monitor_panes, &me->panes, mp);
|
|
free(mp->last);
|
|
free(mp);
|
|
}
|
|
RB_FOREACH_SAFE(mw, monitor_windows, &me->windows, mw1) {
|
|
RB_REMOVE(monitor_windows, &me->windows, mw);
|
|
free(mw->last);
|
|
free(mw);
|
|
}
|
|
free(me->last);
|
|
|
|
RB_REMOVE(monitor_items, &ms->items, me);
|
|
free(me->name);
|
|
free(me->format);
|
|
free(me);
|
|
}
|
|
|
|
/* Report a changed value. */
|
|
static void
|
|
monitor_report(struct monitor_set *ms, struct monitor_item *me,
|
|
struct session *s, struct winlink *wl, struct window_pane *wp,
|
|
const char *value)
|
|
{
|
|
struct monitor_change change;
|
|
|
|
change.name = me->name;
|
|
change.value = value;
|
|
change.c = ms->client;
|
|
change.s = s;
|
|
change.wl = wl;
|
|
change.wp = wp;
|
|
log_debug("%s: %s changed to %s", __func__, me->name, value);
|
|
ms->cb(&change, ms->data);
|
|
}
|
|
|
|
/* Check session subscription. */
|
|
static void
|
|
monitor_check_session(struct monitor_set *ms, struct monitor_item *me,
|
|
struct format_tree *ft)
|
|
{
|
|
struct session *s = ms->client->session;
|
|
char *value;
|
|
|
|
value = format_expand(ft, me->format);
|
|
|
|
if (me->last != NULL && strcmp(value, me->last) == 0) {
|
|
free(value);
|
|
return;
|
|
}
|
|
|
|
monitor_report(ms, me, s, NULL, NULL, value);
|
|
free(me->last);
|
|
me->last = value;
|
|
}
|
|
|
|
/* Check pane subscription. */
|
|
static void
|
|
monitor_check_pane(struct monitor_set *ms, struct monitor_item *me)
|
|
{
|
|
struct client *c = ms->client;
|
|
struct session *s = c->session;
|
|
struct window_pane *wp;
|
|
struct window *w;
|
|
struct winlink *wl;
|
|
struct format_tree *ft;
|
|
char *value;
|
|
struct monitor_pane *mp, find;
|
|
|
|
wp = window_pane_find_by_id(me->id);
|
|
if (wp == NULL || wp->fd == -1)
|
|
return;
|
|
w = wp->window;
|
|
|
|
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
|
|
if (wl->session != s)
|
|
continue;
|
|
|
|
ft = format_create_defaults(NULL, c, s, wl, wp);
|
|
value = format_expand(ft, me->format);
|
|
format_free(ft);
|
|
|
|
find.pane = wp->id;
|
|
find.idx = wl->idx;
|
|
mp = RB_FIND(monitor_panes, &me->panes, &find);
|
|
if (mp == NULL) {
|
|
mp = xcalloc(1, sizeof *mp);
|
|
mp->pane = wp->id;
|
|
mp->idx = wl->idx;
|
|
RB_INSERT(monitor_panes, &me->panes, mp);
|
|
}
|
|
|
|
if (mp->last != NULL && strcmp(value, mp->last) == 0) {
|
|
free(value);
|
|
continue;
|
|
}
|
|
|
|
monitor_report(ms, me, s, wl, wp, value);
|
|
free(mp->last);
|
|
mp->last = value;
|
|
}
|
|
}
|
|
|
|
/* Check one all-panes subscription. */
|
|
static void
|
|
monitor_check_all_panes_one(struct monitor_set *ms, struct monitor_item *me,
|
|
struct format_tree *ft, struct winlink *wl, struct window_pane *wp)
|
|
{
|
|
struct session *s = ms->client->session;
|
|
char *value;
|
|
struct monitor_pane *mp, find;
|
|
|
|
value = format_expand(ft, me->format);
|
|
|
|
find.pane = wp->id;
|
|
find.idx = wl->idx;
|
|
mp = RB_FIND(monitor_panes, &me->panes, &find);
|
|
if (mp == NULL) {
|
|
mp = xcalloc(1, sizeof *mp);
|
|
mp->pane = wp->id;
|
|
mp->idx = wl->idx;
|
|
RB_INSERT(monitor_panes, &me->panes, mp);
|
|
}
|
|
|
|
if (mp->last != NULL && strcmp(value, mp->last) == 0) {
|
|
free(value);
|
|
return;
|
|
}
|
|
|
|
monitor_report(ms, me, s, wl, wp, value);
|
|
free(mp->last);
|
|
mp->last = value;
|
|
}
|
|
|
|
/* Check window subscription. */
|
|
static void
|
|
monitor_check_window(struct monitor_set *ms, struct monitor_item *me)
|
|
{
|
|
struct client *c = ms->client;
|
|
struct session *s = c->session;
|
|
struct window *w;
|
|
struct winlink *wl;
|
|
struct format_tree *ft;
|
|
char *value;
|
|
struct monitor_window *mw, find;
|
|
|
|
w = window_find_by_id(me->id);
|
|
if (w == NULL)
|
|
return;
|
|
|
|
TAILQ_FOREACH(wl, &w->winlinks, wentry) {
|
|
if (wl->session != s)
|
|
continue;
|
|
|
|
ft = format_create_defaults(NULL, c, s, wl, NULL);
|
|
value = format_expand(ft, me->format);
|
|
format_free(ft);
|
|
|
|
find.window = w->id;
|
|
find.idx = wl->idx;
|
|
mw = RB_FIND(monitor_windows, &me->windows, &find);
|
|
if (mw == NULL) {
|
|
mw = xcalloc(1, sizeof *mw);
|
|
mw->window = w->id;
|
|
mw->idx = wl->idx;
|
|
RB_INSERT(monitor_windows, &me->windows, mw);
|
|
}
|
|
|
|
if (mw->last != NULL && strcmp(value, mw->last) == 0) {
|
|
free(value);
|
|
continue;
|
|
}
|
|
|
|
monitor_report(ms, me, s, wl, NULL, value);
|
|
free(mw->last);
|
|
mw->last = value;
|
|
}
|
|
}
|
|
|
|
/* Check one all-windows subscription. */
|
|
static void
|
|
monitor_check_all_windows_one(struct monitor_set *ms, struct monitor_item *me,
|
|
struct format_tree *ft, struct winlink *wl)
|
|
{
|
|
struct session *s = ms->client->session;
|
|
struct window *w = wl->window;
|
|
char *value;
|
|
struct monitor_window *mw, find;
|
|
|
|
value = format_expand(ft, me->format);
|
|
|
|
find.window = w->id;
|
|
find.idx = wl->idx;
|
|
mw = RB_FIND(monitor_windows, &me->windows, &find);
|
|
if (mw == NULL) {
|
|
mw = xcalloc(1, sizeof *mw);
|
|
mw->window = w->id;
|
|
mw->idx = wl->idx;
|
|
RB_INSERT(monitor_windows, &me->windows, mw);
|
|
}
|
|
|
|
if (mw->last != NULL && strcmp(value, mw->last) == 0) {
|
|
free(value);
|
|
return;
|
|
}
|
|
|
|
monitor_report(ms, me, s, wl, NULL, value);
|
|
free(mw->last);
|
|
mw->last = value;
|
|
}
|
|
|
|
/* Check session subscriptions. */
|
|
static void
|
|
monitor_check_sessions(struct monitor_set *ms)
|
|
{
|
|
struct client *c = ms->client;
|
|
struct session *s = c->session;
|
|
struct monitor_item *me, *me1;
|
|
struct format_tree *ft;
|
|
|
|
ft = format_create_defaults(NULL, c, s, NULL, NULL);
|
|
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
|
if (me->type == MONITOR_SESSION)
|
|
monitor_check_session(ms, me, ft);
|
|
}
|
|
format_free(ft);
|
|
}
|
|
|
|
/* Check pane and window subscriptions. */
|
|
static void
|
|
monitor_check_panes_windows(struct monitor_set *ms)
|
|
{
|
|
struct monitor_item *me, *me1;
|
|
|
|
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
|
switch (me->type) {
|
|
case MONITOR_PANE:
|
|
monitor_check_pane(ms, me);
|
|
break;
|
|
case MONITOR_WINDOW:
|
|
monitor_check_window(ms, me);
|
|
break;
|
|
case MONITOR_SESSION:
|
|
case MONITOR_ALL_PANES:
|
|
case MONITOR_ALL_WINDOWS:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check all-panes subscriptions. */
|
|
static void
|
|
monitor_check_all_panes(struct monitor_set *ms)
|
|
{
|
|
struct client *c = ms->client;
|
|
struct session *s = c->session;
|
|
struct monitor_item *me, *me1;
|
|
struct window_pane *wp;
|
|
struct format_tree *ft;
|
|
struct winlink *wl;
|
|
|
|
RB_FOREACH(wl, winlinks, &s->windows) {
|
|
TAILQ_FOREACH(wp, &wl->window->panes, entry) {
|
|
ft = format_create_defaults(NULL, c, s, wl, wp);
|
|
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
|
if (me->type != MONITOR_ALL_PANES)
|
|
continue;
|
|
monitor_check_all_panes_one(ms, me, ft, wl, wp);
|
|
}
|
|
format_free(ft);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check all-windows subscriptions. */
|
|
static void
|
|
monitor_check_all_windows(struct monitor_set *ms)
|
|
{
|
|
struct client *c = ms->client;
|
|
struct session *s = c->session;
|
|
struct monitor_item *me, *me1;
|
|
struct format_tree *ft;
|
|
struct winlink *wl;
|
|
|
|
RB_FOREACH(wl, winlinks, &s->windows) {
|
|
ft = format_create_defaults(NULL, c, s, wl, NULL);
|
|
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
|
|
if (me->type != MONITOR_ALL_WINDOWS)
|
|
continue;
|
|
monitor_check_all_windows_one(ms, me, ft, wl);
|
|
}
|
|
format_free(ft);
|
|
}
|
|
}
|
|
|
|
/* Check subscriptions. */
|
|
static void
|
|
monitor_timer(__unused int fd, __unused short events, void *data)
|
|
{
|
|
struct monitor_set *ms = data;
|
|
struct client *c = ms->client;
|
|
struct monitor_item *me;
|
|
struct timeval tv = { .tv_sec = 1 };
|
|
int have_session = 0, have_all_panes = 0;
|
|
int have_all_windows = 0;
|
|
|
|
log_debug("%s: timer fired", __func__);
|
|
evtimer_add(&ms->timer, &tv);
|
|
|
|
if (c->session == NULL)
|
|
return;
|
|
|
|
RB_FOREACH(me, monitor_items, &ms->items) {
|
|
switch (me->type) {
|
|
case MONITOR_SESSION:
|
|
have_session = 1;
|
|
break;
|
|
case MONITOR_ALL_PANES:
|
|
have_all_panes = 1;
|
|
break;
|
|
case MONITOR_ALL_WINDOWS:
|
|
have_all_windows = 1;
|
|
break;
|
|
case MONITOR_PANE:
|
|
case MONITOR_WINDOW:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (have_session)
|
|
monitor_check_sessions(ms);
|
|
monitor_check_panes_windows(ms);
|
|
if (have_all_panes)
|
|
monitor_check_all_panes(ms);
|
|
if (have_all_windows)
|
|
monitor_check_all_windows(ms);
|
|
}
|
|
|
|
/* Create a monitor set. */
|
|
struct monitor_set *
|
|
monitor_create(struct client *c, monitor_cb cb, void *data)
|
|
{
|
|
struct monitor_set *ms;
|
|
|
|
ms = xcalloc(1, sizeof *ms);
|
|
ms->client = c;
|
|
ms->cb = cb;
|
|
ms->data = data;
|
|
RB_INIT(&ms->items);
|
|
return (ms);
|
|
}
|
|
|
|
/* Destroy a monitor set. */
|
|
void
|
|
monitor_destroy(struct monitor_set *ms)
|
|
{
|
|
struct monitor_item *me, *me1;
|
|
|
|
if (ms == NULL)
|
|
return;
|
|
|
|
if (evtimer_initialized(&ms->timer))
|
|
evtimer_del(&ms->timer);
|
|
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1)
|
|
monitor_free_item(ms, me);
|
|
free(ms);
|
|
}
|
|
|
|
/* Add a subscription. */
|
|
void
|
|
monitor_add(struct monitor_set *ms, const char *name, enum monitor_type type,
|
|
int id, const char *format)
|
|
{
|
|
struct monitor_item *me, find = { .name = (char *)name };
|
|
struct timeval tv = { .tv_sec = 1 };
|
|
|
|
if ((me = RB_FIND(monitor_items, &ms->items, &find)) != NULL)
|
|
monitor_free_item(ms, me);
|
|
|
|
me = xcalloc(1, sizeof *me);
|
|
me->name = xstrdup(name);
|
|
me->format = xstrdup(format);
|
|
me->type = type;
|
|
me->id = id;
|
|
RB_INIT(&me->panes);
|
|
RB_INIT(&me->windows);
|
|
RB_INSERT(monitor_items, &ms->items, me);
|
|
|
|
if (!evtimer_initialized(&ms->timer))
|
|
evtimer_set(&ms->timer, monitor_timer, ms);
|
|
if (!evtimer_pending(&ms->timer, NULL))
|
|
evtimer_add(&ms->timer, &tv);
|
|
}
|
|
|
|
/* Remove a subscription. */
|
|
void
|
|
monitor_remove(struct monitor_set *ms, const char *name)
|
|
{
|
|
struct monitor_item *me, find = { .name = (char *)name };
|
|
|
|
if ((me = RB_FIND(monitor_items, &ms->items, &find)) != NULL)
|
|
monitor_free_item(ms, me);
|
|
if (RB_EMPTY(&ms->items) && evtimer_initialized(&ms->timer))
|
|
evtimer_del(&ms->timer);
|
|
}
|