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