/* $OpenBSD: events-payload.c,v 1.1 2026/07/10 13:38:45 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott * * 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 #include #include #include #include #include #include "tmux.h" /* Event payload item. */ struct event_payload_item { char *name; enum event_payload_type type; union { char *string; time_t time; int number; u_int unsigned_number; struct client *client; struct session *session; struct window *window; struct window_pane *pane; struct { void *ptr; event_payload_free_cb free_cb; event_payload_print_cb print_cb; } pointer; }; RB_ENTRY(event_payload_item) entry; }; RB_HEAD(event_payload_tree, event_payload_item); struct event_payload { struct event_payload_tree items; struct cmd_find_state target; }; static int event_payload_cmp(struct event_payload_item *epi1, struct event_payload_item *epi2) { return (strcmp(epi1->name, epi2->name)); } RB_GENERATE_STATIC(event_payload_tree, event_payload_item, entry, event_payload_cmp); /* Find an item. */ static struct event_payload_item * event_payload_find(struct event_payload *ep, const char *name) { struct event_payload_item find = { .name = (char *)name }; return (RB_FIND(event_payload_tree, &ep->items, &find)); } /* Free the target in a payload. */ static void event_payload_free_target(struct event_payload *ep) { struct cmd_find_state *target = &ep->target; if (target->s != NULL) session_remove_ref(target->s, __func__); if (target->w != NULL) window_remove_ref(target->w, __func__); if (target->wp != NULL) window_pane_remove_ref(target->wp, __func__); cmd_find_clear_state(target, 0); } /* Free the value in an item. */ static void event_payload_free_value(struct event_payload_item *epi) { switch (epi->type) { case EVENT_PAYLOAD_STRING: free(epi->string); break; case EVENT_PAYLOAD_CLIENT: server_client_unref(epi->client); break; case EVENT_PAYLOAD_SESSION: session_remove_ref(epi->session, __func__); break; case EVENT_PAYLOAD_WINDOW: window_remove_ref(epi->window, __func__); break; case EVENT_PAYLOAD_PANE: window_pane_remove_ref(epi->pane, __func__); break; case EVENT_PAYLOAD_POINTER: if (epi->pointer.free_cb != NULL) epi->pointer.free_cb(epi->pointer.ptr); break; case EVENT_PAYLOAD_INT: case EVENT_PAYLOAD_UINT: case EVENT_PAYLOAD_TIME: break; } } /* Set an item. */ static void event_payload_set_item(struct event_payload *ep, const char *name, struct event_payload_item *new) { struct event_payload_item *old; new->name = xstrdup(name); old = RB_INSERT(event_payload_tree, &ep->items, new); if (old != NULL) { RB_REMOVE(event_payload_tree, &ep->items, old); event_payload_free_value(old); free(old->name); free(old); RB_INSERT(event_payload_tree, &ep->items, new); } } /* Create an event payload. */ struct event_payload * event_payload_create(void) { struct event_payload *ep; ep = xcalloc(1, sizeof *ep); RB_INIT(&ep->items); cmd_find_clear_state(&ep->target, 0); return (ep); } /* Free an event payload. */ void event_payload_free(struct event_payload *ep) { struct event_payload_item *epi, *epi1; if (ep != NULL) { RB_FOREACH_SAFE(epi, event_payload_tree, &ep->items, epi1) { RB_REMOVE(event_payload_tree, &ep->items, epi); event_payload_free_value(epi); free(epi->name); free(epi); } event_payload_free_target(ep); free(ep); } } /* Set the target. */ void event_payload_set_target(struct event_payload *ep, struct cmd_find_state *fs) { struct cmd_find_state *target = &ep->target; event_payload_free_target(ep); if (fs->s != NULL) { session_add_ref(fs->s, __func__); target->s = fs->s; } if (fs->wl != NULL) { target->idx = fs->wl->idx; if (target->s == NULL) { session_add_ref(fs->wl->session, __func__); target->s = fs->wl->session; } } else target->idx = -1; if (fs->w != NULL) { window_add_ref(fs->w, __func__); target->w = fs->w; } else if (fs->wl != NULL) { window_add_ref(fs->wl->window, __func__); target->w = fs->wl->window; } if (fs->wp != NULL) { window_pane_add_ref(fs->wp, __func__); target->wp = fs->wp; } } /* Get the target. */ int event_payload_get_target(struct event_payload *ep, struct cmd_find_state *fs) { struct cmd_find_state *t = &ep->target; struct winlink *wl = NULL; int flags = fs->flags; if (t->idx != -1 && t->s != NULL && t->w != NULL && session_alive(t->s)) { wl = winlink_find_by_index(&t->s->windows, t->idx); if (wl != NULL && wl->window != t->w) wl = NULL; } cmd_find_clear_state(fs, flags); fs->s = t->s; fs->w = t->w; fs->wp = t->wp; fs->wl = wl; fs->idx = (wl != NULL ? wl->idx : -1); if (cmd_find_valid_state(fs)) return (1); if (wl != NULL && t->wp != NULL && window_has_pane(wl->window, t->wp)) { cmd_find_from_winlink_pane(fs, wl, t->wp, flags); if (cmd_find_valid_state(fs)) return (1); } if (t->wp != NULL && cmd_find_from_pane(fs, t->wp, flags) == 0 && cmd_find_valid_state(fs)) return (1); if (wl != NULL) { cmd_find_from_winlink(fs, wl, flags); if (cmd_find_valid_state(fs)) return (1); } if (t->s != NULL && t->w != NULL && session_alive(t->s) && cmd_find_from_session_window(fs, t->s, t->w, flags) == 0 && cmd_find_valid_state(fs)) return (1); if (t->s != NULL && session_alive(t->s)) { cmd_find_from_session(fs, t->s, flags); if (cmd_find_valid_state(fs)) return (1); } if (cmd_find_from_nothing(fs, flags) == 0) return (1); cmd_find_clear_state(fs, flags); return (0); } /* Set a string item. */ void event_payload_set_string(struct event_payload *ep, const char *name, const char *fmt, ...) { struct event_payload_item *epi; va_list ap; va_start(ap, fmt); epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_STRING; xvasprintf(&epi->string, fmt, ap); event_payload_set_item(ep, name, epi); va_end(ap); } /* Set a time item. */ void event_payload_set_time(struct event_payload *ep, const char *name, time_t value) { struct event_payload_item *epi; epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_TIME; epi->time = value; event_payload_set_item(ep, name, epi); } /* Set a number item. */ void event_payload_set_int(struct event_payload *ep, const char *name, int value) { struct event_payload_item *epi; epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_INT; epi->number = value; event_payload_set_item(ep, name, epi); } /* Set an unsigned number item. */ void event_payload_set_uint(struct event_payload *ep, const char *name, u_int value) { struct event_payload_item *epi; epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_UINT; epi->unsigned_number = value; event_payload_set_item(ep, name, epi); } /* Set a client item. */ void event_payload_set_client(struct event_payload *ep, const char *name, struct client *c) { struct event_payload_item *epi; c->references++; epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_CLIENT; epi->client = c; event_payload_set_item(ep, name, epi); } /* Set a session item. */ void event_payload_set_session(struct event_payload *ep, const char *name, struct session *s) { struct event_payload_item *epi; session_add_ref(s, __func__); epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_SESSION; epi->session = s; event_payload_set_item(ep, name, epi); } /* Set a window item. */ void event_payload_set_window(struct event_payload *ep, const char *name, struct window *w) { struct event_payload_item *epi; window_add_ref(w, __func__); epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_WINDOW; epi->window = w; event_payload_set_item(ep, name, epi); } /* Set a pane item. */ void event_payload_set_pane(struct event_payload *ep, const char *name, struct window_pane *wp) { struct event_payload_item *epi; window_pane_add_ref(wp, __func__); epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_PANE; epi->pane = wp; event_payload_set_item(ep, name, epi); } /* Set a pointer item. */ void event_payload_set_pointer(struct event_payload *ep, const char *name, void *ptr, event_payload_free_cb free_cb, event_payload_print_cb print_cb) { struct event_payload_item *epi; epi = xcalloc(1, sizeof *epi); epi->type = EVENT_PAYLOAD_POINTER; epi->pointer.ptr = ptr; epi->pointer.free_cb = free_cb; epi->pointer.print_cb = print_cb; event_payload_set_item(ep, name, epi); } /* Get a string item. */ const char * event_payload_get_string(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_STRING) return (NULL); return (epi->string); } /* Print a payload item. */ static void event_payload_add_item(struct event_payload_item *epi, struct evbuffer *evb) { switch (epi->type) { case EVENT_PAYLOAD_STRING: evbuffer_add_printf(evb, "%s", epi->string); break; case EVENT_PAYLOAD_TIME: evbuffer_add_printf(evb, "%lld", (long long)epi->time); break; case EVENT_PAYLOAD_INT: evbuffer_add_printf(evb, "%d", epi->number); break; case EVENT_PAYLOAD_UINT: evbuffer_add_printf(evb, "%u", epi->unsigned_number); break; case EVENT_PAYLOAD_CLIENT: evbuffer_add_printf(evb, "%s", epi->client->name); break; case EVENT_PAYLOAD_SESSION: evbuffer_add_printf(evb, "$%u", epi->session->id); break; case EVENT_PAYLOAD_WINDOW: evbuffer_add_printf(evb, "@%u", epi->window->id); break; case EVENT_PAYLOAD_PANE: evbuffer_add_printf(evb, "%%%u", epi->pane->id); break; case EVENT_PAYLOAD_POINTER: if (epi->pointer.print_cb != NULL) epi->pointer.print_cb(epi->pointer.ptr, evb); else evbuffer_add_printf(evb, "%p", epi->pointer.ptr); break; } } /* Print a payload item. */ char * event_payload_item_print(struct event_payload_item *epi) { struct evbuffer *evb; char *value = NULL; size_t size; evb = evbuffer_new(); if (evb == NULL) fatalx("out of memory"); event_payload_add_item(epi, evb); if ((size = EVBUFFER_LENGTH(evb)) != 0) value = xmemdup(EVBUFFER_DATA(evb), size); else value = xstrdup(""); evbuffer_free(evb); return (value); } /* Print an item value. */ char * event_payload_print(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL) return (NULL); return (event_payload_item_print(epi)); } /* Add payload items as formats. */ void event_payload_add_formats(struct event_payload *ep, struct format_tree *ft, const char *prefix) { struct event_payload_item *epi; char *name, *value; const char *key; if (prefix == NULL) prefix = ""; RB_FOREACH(epi, event_payload_tree, &ep->items) { key = epi->name; if (*key == '_') continue; value = event_payload_item_print(epi); xasprintf(&name, "%s%s", prefix, key); format_add(ft, name, "%s", value); free(name); free(value); if (epi->type == EVENT_PAYLOAD_SESSION) { xasprintf(&name, "%s%s_name", prefix, key); format_add(ft, name, "%s", epi->session->name); free(name); } else if (epi->type == EVENT_PAYLOAD_WINDOW) { xasprintf(&name, "%s%s_name", prefix, key); format_add(ft, name, "%s", epi->window->name); free(name); } } } /* Get the first payload item. */ struct event_payload_item * event_payload_first(struct event_payload *ep) { return (RB_MIN(event_payload_tree, &ep->items)); } /* Get the next payload item. */ struct event_payload_item * event_payload_next(struct event_payload_item *epi) { return (RB_NEXT(event_payload_tree, , epi)); } /* Get a payload item name. */ const char * event_payload_item_name(struct event_payload_item *epi) { return (epi->name); } /* Get a payload item type. */ enum event_payload_type event_payload_item_type(struct event_payload_item *epi) { return (epi->type); } /* Log a payload. */ void event_payload_log(struct event_payload *ep, const char *fmt, ...) { struct event_payload_item *epi; struct evbuffer *evb; va_list ap; char *prefix; va_start(ap, fmt); xvasprintf(&prefix, fmt, ap); va_end(ap); evb = evbuffer_new(); if (evb == NULL) fatalx("out of memory"); if (ep != NULL) { RB_FOREACH(epi, event_payload_tree, &ep->items) { if (EVBUFFER_LENGTH(evb) != 0) evbuffer_add_printf(evb, ", "); evbuffer_add_printf(evb, "%s=", epi->name); event_payload_add_item(epi, evb); } } log_debug("%s%.*s", prefix, (int)EVBUFFER_LENGTH(evb), (char *)EVBUFFER_DATA(evb)); evbuffer_free(evb); free(prefix); } /* Get a time item. */ time_t event_payload_get_time(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_TIME) return (0); return (epi->time); } /* Get a number item. */ int event_payload_get_int(struct event_payload *ep, const char *name, int *value) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_INT) return (-1); *value = epi->number; return (0); } /* Get an unsigned number item. */ int event_payload_get_uint(struct event_payload *ep, const char *name, u_int *value) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_UINT) return (-1); *value = epi->unsigned_number; return (0); } /* Get a client item. */ struct client * event_payload_get_client(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_CLIENT) return (NULL); return (epi->client); } /* Get a session item. */ struct session * event_payload_get_session(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_SESSION) return (NULL); return (epi->session); } /* Get a window item. */ struct window * event_payload_get_window(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_WINDOW) return (NULL); return (epi->window); } /* Get a pane item. */ struct window_pane * event_payload_get_pane(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_PANE) return (NULL); return (epi->pane); } /* Get a pointer item. */ void * event_payload_get_pointer(struct event_payload *ep, const char *name) { struct event_payload_item *epi; epi = event_payload_find(ep, name); if (epi == NULL || epi->type != EVENT_PAYLOAD_POINTER) return (NULL); return (epi->pointer.ptr); }