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