xref: /openbsd-src/usr.bin/tmux/control-notify.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /* $OpenBSD: control-notify.c,v 1.25 2019/12/12 11:39:56 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
5  * Copyright (c) 2012 George Nachman <tmux@georgester.com>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 
22 #include <stdlib.h>
23 
24 #include "tmux.h"
25 
26 #define CONTROL_SHOULD_NOTIFY_CLIENT(c) \
27 	((c) != NULL && ((c)->flags & CLIENT_CONTROL))
28 
29 void
30 control_notify_input(struct client *c, struct window_pane *wp,
31     const u_char *buf, size_t len)
32 {
33 	struct evbuffer *message;
34 	u_int		 i;
35 
36 	if (c->session == NULL)
37 	    return;
38 
39 	if (c->flags & CLIENT_CONTROL_NOOUTPUT)
40 		return;
41 
42 	/*
43 	 * Only write input if the window pane is linked to a window belonging
44 	 * to the client's session.
45 	 */
46 	if (winlink_find_by_window(&c->session->windows, wp->window) != NULL) {
47 		message = evbuffer_new();
48 		if (message == NULL)
49 			fatalx("out of memory");
50 		evbuffer_add_printf(message, "%%output %%%u ", wp->id);
51 		for (i = 0; i < len; i++) {
52 			if (buf[i] < ' ' || buf[i] == '\\')
53 			    evbuffer_add_printf(message, "\\%03o", buf[i]);
54 			else
55 			    evbuffer_add_printf(message, "%c", buf[i]);
56 		}
57 		control_write(c, "%s", EVBUFFER_DATA(message));
58 		evbuffer_free(message);
59 	}
60 }
61 
62 void
63 control_notify_pane_mode_changed(int pane)
64 {
65 	struct client	*c;
66 
67 	TAILQ_FOREACH(c, &clients, entry) {
68 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
69 			continue;
70 
71 		control_write(c, "%%pane-mode-changed %%%u", pane);
72 	}
73 }
74 
75 void
76 control_notify_window_layout_changed(struct window *w)
77 {
78 	struct client	*c;
79 	struct session	*s;
80 	struct winlink	*wl;
81 	const char	*template;
82 	char		*cp;
83 
84 	template = "%layout-change #{window_id} #{window_layout} "
85 	    "#{window_visible_layout} #{window_flags}";
86 
87 	TAILQ_FOREACH(c, &clients, entry) {
88 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
89 			continue;
90 		s = c->session;
91 
92 		if (winlink_find_by_window_id(&s->windows, w->id) == NULL)
93 			continue;
94 
95 		/*
96 		 * When the last pane in a window is closed it won't have a
97 		 * layout root and we don't need to inform the client about the
98 		 * layout change because the whole window will go away soon.
99 		 */
100 		if (w->layout_root == NULL)
101 			continue;
102 
103 		wl = winlink_find_by_window(&s->windows, w);
104 		if (wl != NULL) {
105 			cp = format_single(NULL, template, c, NULL, wl, NULL);
106 			control_write(c, "%s", cp);
107 			free(cp);
108 		}
109 	}
110 }
111 
112 void
113 control_notify_window_pane_changed(struct window *w)
114 {
115 	struct client	*c;
116 
117 	TAILQ_FOREACH(c, &clients, entry) {
118 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
119 			continue;
120 
121 		control_write(c, "%%window-pane-changed @%u %%%u", w->id,
122 		    w->active->id);
123 	}
124 }
125 
126 void
127 control_notify_window_unlinked(__unused struct session *s, struct window *w)
128 {
129 	struct client	*c;
130 	struct session	*cs;
131 
132 	TAILQ_FOREACH(c, &clients, entry) {
133 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
134 			continue;
135 		cs = c->session;
136 
137 		if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
138 			control_write(c, "%%window-close @%u", w->id);
139 		else
140 			control_write(c, "%%unlinked-window-close @%u", w->id);
141 	}
142 }
143 
144 void
145 control_notify_window_linked(__unused struct session *s, struct window *w)
146 {
147 	struct client	*c;
148 	struct session	*cs;
149 
150 	TAILQ_FOREACH(c, &clients, entry) {
151 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
152 			continue;
153 		cs = c->session;
154 
155 		if (winlink_find_by_window_id(&cs->windows, w->id) != NULL)
156 			control_write(c, "%%window-add @%u", w->id);
157 		else
158 			control_write(c, "%%unlinked-window-add @%u", w->id);
159 	}
160 }
161 
162 void
163 control_notify_window_renamed(struct window *w)
164 {
165 	struct client	*c;
166 	struct session	*cs;
167 
168 	TAILQ_FOREACH(c, &clients, entry) {
169 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
170 			continue;
171 		cs = c->session;
172 
173 		if (winlink_find_by_window_id(&cs->windows, w->id) != NULL) {
174 			control_write(c, "%%window-renamed @%u %s", w->id,
175 			    w->name);
176 		} else {
177 			control_write(c, "%%unlinked-window-renamed @%u %s",
178 			    w->id, w->name);
179 		}
180 	}
181 }
182 
183 void
184 control_notify_client_session_changed(struct client *cc)
185 {
186 	struct client	*c;
187 	struct session	*s;
188 
189 	if (cc->session == NULL)
190 		return;
191 	s = cc->session;
192 
193 	TAILQ_FOREACH(c, &clients, entry) {
194 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c) || c->session == NULL)
195 			continue;
196 
197 		if (cc == c) {
198 			control_write(c, "%%session-changed $%u %s", s->id,
199 			    s->name);
200 		} else {
201 			control_write(c, "%%client-session-changed %s $%u %s",
202 			    cc->name, s->id, s->name);
203 		}
204 	}
205 }
206 
207 void
208 control_notify_session_renamed(struct session *s)
209 {
210 	struct client	*c;
211 
212 	TAILQ_FOREACH(c, &clients, entry) {
213 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
214 			continue;
215 
216 		control_write(c, "%%session-renamed $%u %s", s->id, s->name);
217 	}
218 }
219 
220 void
221 control_notify_session_created(__unused struct session *s)
222 {
223 	struct client	*c;
224 
225 	TAILQ_FOREACH(c, &clients, entry) {
226 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
227 			continue;
228 
229 		control_write(c, "%%sessions-changed");
230 	}
231 }
232 
233 void
234 control_notify_session_closed(__unused struct session *s)
235 {
236 	struct client	*c;
237 
238 	TAILQ_FOREACH(c, &clients, entry) {
239 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
240 			continue;
241 
242 		control_write(c, "%%sessions-changed");
243 	}
244 }
245 
246 void
247 control_notify_session_window_changed(struct session *s)
248 {
249 	struct client	*c;
250 
251 	TAILQ_FOREACH(c, &clients, entry) {
252 		if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
253 			continue;
254 
255 		control_write(c, "%%session-window-changed $%u @%u", s->id,
256 		    s->curw->window->id);
257 	}
258 }
259