1 /* $OpenBSD$ */
2
3 /*
4 * Copyright (c) 2007 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 #include <sys/wait.h>
21 #include <sys/uio.h>
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <unistd.h>
27
28 #include "tmux.h"
29
30 static void server_destroy_session_group(struct session *);
31
32 void
server_redraw_client(struct client * c)33 server_redraw_client(struct client *c)
34 {
35 c->flags |= CLIENT_ALLREDRAWFLAGS;
36 }
37
38 void
server_status_client(struct client * c)39 server_status_client(struct client *c)
40 {
41 c->flags |= CLIENT_REDRAWSTATUS;
42 }
43
44 void
server_redraw_session(struct session * s)45 server_redraw_session(struct session *s)
46 {
47 struct client *c;
48
49 TAILQ_FOREACH(c, &clients, entry) {
50 if (c->session == s)
51 server_redraw_client(c);
52 }
53 }
54
55 void
server_redraw_session_group(struct session * s)56 server_redraw_session_group(struct session *s)
57 {
58 struct session_group *sg;
59
60 if ((sg = session_group_contains(s)) == NULL)
61 server_redraw_session(s);
62 else {
63 TAILQ_FOREACH(s, &sg->sessions, gentry)
64 server_redraw_session(s);
65 }
66 }
67
68 void
server_status_session(struct session * s)69 server_status_session(struct session *s)
70 {
71 struct client *c;
72
73 TAILQ_FOREACH(c, &clients, entry) {
74 if (c->session == s)
75 server_status_client(c);
76 }
77 }
78
79 void
server_status_session_group(struct session * s)80 server_status_session_group(struct session *s)
81 {
82 struct session_group *sg;
83
84 if ((sg = session_group_contains(s)) == NULL)
85 server_status_session(s);
86 else {
87 TAILQ_FOREACH(s, &sg->sessions, gentry)
88 server_status_session(s);
89 }
90 }
91
92 void
server_redraw_window(struct window * w)93 server_redraw_window(struct window *w)
94 {
95 struct client *c;
96
97 TAILQ_FOREACH(c, &clients, entry) {
98 if (c->session != NULL && c->session->curw->window == w)
99 server_redraw_client(c);
100 }
101 }
102
103 void
server_redraw_window_borders(struct window * w)104 server_redraw_window_borders(struct window *w)
105 {
106 struct client *c;
107
108 TAILQ_FOREACH(c, &clients, entry) {
109 if (c->session != NULL && c->session->curw->window == w)
110 c->flags |= CLIENT_REDRAWBORDERS;
111 }
112 }
113
114 void
server_status_window(struct window * w)115 server_status_window(struct window *w)
116 {
117 struct session *s;
118
119 /*
120 * This is slightly different. We want to redraw the status line of any
121 * clients containing this window rather than anywhere it is the
122 * current window.
123 */
124
125 RB_FOREACH(s, sessions, &sessions) {
126 if (session_has(s, w))
127 server_status_session(s);
128 }
129 }
130
131 void
server_lock(void)132 server_lock(void)
133 {
134 struct client *c;
135
136 TAILQ_FOREACH(c, &clients, entry) {
137 if (c->session != NULL)
138 server_lock_client(c);
139 }
140 }
141
142 void
server_lock_session(struct session * s)143 server_lock_session(struct session *s)
144 {
145 struct client *c;
146
147 TAILQ_FOREACH(c, &clients, entry) {
148 if (c->session == s)
149 server_lock_client(c);
150 }
151 }
152
153 void
server_lock_client(struct client * c)154 server_lock_client(struct client *c)
155 {
156 const char *cmd;
157
158 if (c->flags & CLIENT_CONTROL)
159 return;
160
161 if (c->flags & CLIENT_SUSPENDED)
162 return;
163
164 cmd = options_get_string(c->session->options, "lock-command");
165 if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
166 return;
167
168 tty_stop_tty(&c->tty);
169 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP));
170 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR));
171 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3));
172
173 c->flags |= CLIENT_SUSPENDED;
174 proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1);
175 }
176
177 void
server_kill_pane(struct window_pane * wp)178 server_kill_pane(struct window_pane *wp)
179 {
180 struct window *w = wp->window;
181
182 if (window_count_panes(w) == 1) {
183 server_kill_window(w, 1);
184 recalculate_sizes();
185 } else {
186 server_unzoom_window(w);
187 server_client_remove_pane(wp);
188 layout_close_pane(wp);
189 window_remove_pane(w, wp);
190 server_redraw_window(w);
191 }
192 }
193
194 void
server_kill_window(struct window * w,int renumber)195 server_kill_window(struct window *w, int renumber)
196 {
197 struct session *s, *s1;
198 struct winlink *wl;
199
200 RB_FOREACH_SAFE(s, sessions, &sessions, s1) {
201 if (!session_has(s, w))
202 continue;
203
204 server_unzoom_window(w);
205 while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
206 if (session_detach(s, wl)) {
207 server_destroy_session_group(s);
208 break;
209 }
210 server_redraw_session_group(s);
211 }
212
213 if (renumber)
214 server_renumber_session(s);
215 }
216 recalculate_sizes();
217 }
218
219 void
server_renumber_session(struct session * s)220 server_renumber_session(struct session *s)
221 {
222 struct session_group *sg;
223
224 if (options_get_number(s->options, "renumber-windows")) {
225 if ((sg = session_group_contains(s)) != NULL) {
226 TAILQ_FOREACH(s, &sg->sessions, gentry)
227 session_renumber_windows(s);
228 } else
229 session_renumber_windows(s);
230 }
231 }
232
233 void
server_renumber_all(void)234 server_renumber_all(void)
235 {
236 struct session *s;
237
238 RB_FOREACH(s, sessions, &sessions)
239 server_renumber_session(s);
240 }
241
242 int
server_link_window(struct session * src,struct winlink * srcwl,struct session * dst,int dstidx,int killflag,int selectflag,char ** cause)243 server_link_window(struct session *src, struct winlink *srcwl,
244 struct session *dst, int dstidx, int killflag, int selectflag,
245 char **cause)
246 {
247 struct winlink *dstwl;
248 struct session_group *srcsg, *dstsg;
249
250 srcsg = session_group_contains(src);
251 dstsg = session_group_contains(dst);
252 if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
253 xasprintf(cause, "sessions are grouped");
254 return (-1);
255 }
256
257 dstwl = NULL;
258 if (dstidx != -1)
259 dstwl = winlink_find_by_index(&dst->windows, dstidx);
260 if (dstwl != NULL) {
261 if (dstwl->window == srcwl->window) {
262 xasprintf(cause, "same index: %d", dstidx);
263 return (-1);
264 }
265 if (killflag) {
266 /*
267 * Can't use session_detach as it will destroy session
268 * if this makes it empty.
269 */
270 notify_session_window("window-unlinked", dst,
271 dstwl->window);
272 dstwl->flags &= ~WINLINK_ALERTFLAGS;
273 winlink_stack_remove(&dst->lastw, dstwl);
274 winlink_remove(&dst->windows, dstwl);
275
276 /* Force select/redraw if current. */
277 if (dstwl == dst->curw) {
278 selectflag = 1;
279 dst->curw = NULL;
280 }
281 }
282 }
283
284 if (dstidx == -1)
285 dstidx = -1 - options_get_number(dst->options, "base-index");
286 dstwl = session_attach(dst, srcwl->window, dstidx, cause);
287 if (dstwl == NULL)
288 return (-1);
289
290 if (selectflag)
291 session_select(dst, dstwl->idx);
292 server_redraw_session_group(dst);
293
294 return (0);
295 }
296
297 void
server_unlink_window(struct session * s,struct winlink * wl)298 server_unlink_window(struct session *s, struct winlink *wl)
299 {
300 if (session_detach(s, wl))
301 server_destroy_session_group(s);
302 else
303 server_redraw_session_group(s);
304 }
305
306 void
server_destroy_pane(struct window_pane * wp,int notify)307 server_destroy_pane(struct window_pane *wp, int notify)
308 {
309 struct window *w = wp->window;
310 struct screen_write_ctx ctx;
311 struct grid_cell gc;
312 int remain_on_exit;
313 const char *s;
314 char *expanded;
315 u_int sx = screen_size_x(&wp->base);
316 u_int sy = screen_size_y(&wp->base);
317
318 if (wp->fd != -1) {
319 #ifdef HAVE_UTEMPTER
320 utempter_remove_record(wp->fd);
321 #endif
322 bufferevent_free(wp->event);
323 wp->event = NULL;
324 close(wp->fd);
325 wp->fd = -1;
326 }
327
328 remain_on_exit = options_get_number(wp->options, "remain-on-exit");
329 if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
330 return;
331 switch (remain_on_exit) {
332 case 0:
333 break;
334 case 2:
335 if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
336 break;
337 /* FALLTHROUGH */
338 case 1:
339 if (wp->flags & PANE_STATUSDRAWN)
340 return;
341 wp->flags |= PANE_STATUSDRAWN;
342
343 gettimeofday(&wp->dead_time, NULL);
344 if (notify)
345 notify_pane("pane-died", wp);
346
347 s = options_get_string(wp->options, "remain-on-exit-format");
348 if (*s != '\0') {
349 screen_write_start_pane(&ctx, wp, &wp->base);
350 screen_write_scrollregion(&ctx, 0, sy - 1);
351 screen_write_cursormove(&ctx, 0, sy - 1, 0);
352 screen_write_linefeed(&ctx, 1, 8);
353 memcpy(&gc, &grid_default_cell, sizeof gc);
354
355 expanded = format_single(NULL, s, NULL, NULL, NULL, wp);
356 format_draw(&ctx, &gc, sx, expanded, NULL, 0);
357 free(expanded);
358
359 screen_write_stop(&ctx);
360 }
361 wp->base.mode &= ~MODE_CURSOR;
362
363 wp->flags |= PANE_REDRAW;
364 return;
365 }
366
367 if (notify)
368 notify_pane("pane-exited", wp);
369
370 server_unzoom_window(w);
371 server_client_remove_pane(wp);
372 layout_close_pane(wp);
373 window_remove_pane(w, wp);
374
375 if (TAILQ_EMPTY(&w->panes))
376 server_kill_window(w, 1);
377 else
378 server_redraw_window(w);
379 }
380
381 static void
server_destroy_session_group(struct session * s)382 server_destroy_session_group(struct session *s)
383 {
384 struct session_group *sg;
385 struct session *s1;
386
387 if ((sg = session_group_contains(s)) == NULL) {
388 server_destroy_session(s);
389 session_destroy(s, 1, __func__);
390 } else {
391 TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
392 server_destroy_session(s);
393 session_destroy(s, 1, __func__);
394 }
395 }
396 }
397
398 static struct session *
server_find_session(struct session * s,int (* f)(struct session *,struct session *))399 server_find_session(struct session *s,
400 int (*f)(struct session *, struct session *))
401 {
402 struct session *s_loop, *s_out = NULL;
403
404 RB_FOREACH(s_loop, sessions, &sessions) {
405 if (s_loop != s && (s_out == NULL || f(s_loop, s_out)))
406 s_out = s_loop;
407 }
408 return (s_out);
409 }
410
411 static int
server_newer_session(struct session * s_loop,struct session * s_out)412 server_newer_session(struct session *s_loop, struct session *s_out)
413 {
414 return (timercmp(&s_loop->activity_time, &s_out->activity_time, >));
415 }
416
417 static int
server_newer_detached_session(struct session * s_loop,struct session * s_out)418 server_newer_detached_session(struct session *s_loop, struct session *s_out)
419 {
420 if (s_loop->attached)
421 return (0);
422 return (server_newer_session(s_loop, s_out));
423 }
424
425 void
server_destroy_session(struct session * s)426 server_destroy_session(struct session *s)
427 {
428 struct client *c;
429 struct session *s_new = NULL;
430 int detach_on_destroy;
431
432 detach_on_destroy = options_get_number(s->options, "detach-on-destroy");
433 if (detach_on_destroy == 0)
434 s_new = server_find_session(s, server_newer_session);
435 else if (detach_on_destroy == 2)
436 s_new = server_find_session(s, server_newer_detached_session);
437 else if (detach_on_destroy == 3)
438 s_new = session_previous_session(s);
439 else if (detach_on_destroy == 4)
440 s_new = session_next_session(s);
441 if (s_new == s)
442 s_new = NULL;
443 TAILQ_FOREACH(c, &clients, entry) {
444 if (c->session != s)
445 continue;
446 c->session = NULL;
447 c->last_session = NULL;
448 server_client_set_session(c, s_new);
449 if (s_new == NULL)
450 c->flags |= CLIENT_EXIT;
451 }
452 recalculate_sizes();
453 }
454
455 void
server_check_unattached(void)456 server_check_unattached(void)
457 {
458 struct session *s;
459 struct session_group *sg;
460
461 /*
462 * If any sessions are no longer attached and have destroy-unattached
463 * set, collect them.
464 */
465 RB_FOREACH(s, sessions, &sessions) {
466 if (s->attached != 0)
467 continue;
468 switch (options_get_number(s->options, "destroy-unattached")) {
469 case 0: /* off */
470 continue;
471 case 1: /* on */
472 break;
473 case 2: /* keep-last */
474 sg = session_group_contains(s);
475 if (sg == NULL || session_group_count(sg) <= 1)
476 continue;
477 break;
478 case 3: /* keep-group */
479 sg = session_group_contains(s);
480 if (sg != NULL && session_group_count(sg) == 1)
481 continue;
482 break;
483 }
484 session_destroy(s, 1, __func__);
485 }
486 }
487
488 void
server_unzoom_window(struct window * w)489 server_unzoom_window(struct window *w)
490 {
491 if (window_unzoom(w, 1) == 0)
492 server_redraw_window(w);
493 }
494