server-fn.c revision 1.8
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/uio.h>
21
22#include <stdlib.h>
23#include <string.h>
24#include <time.h>
25#include <unistd.h>
26
27#include "tmux.h"
28
29static struct session	*server_next_session(struct session *);
30static void		 server_destroy_session_group(struct session *);
31
32void
33server_redraw_client(struct client *c)
34{
35	c->flags |= CLIENT_REDRAW;
36}
37
38void
39server_status_client(struct client *c)
40{
41	c->flags |= CLIENT_STATUS;
42}
43
44void
45server_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
55void
56server_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
68void
69server_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
79void
80server_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
92void
93server_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
103void
104server_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_BORDERS;
111	}
112}
113
114void
115server_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
131void
132server_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
142void
143server_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
153void
154server_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
177void
178server_kill_window(struct window *w)
179{
180	struct session		*s, *next_s, *target_s;
181	struct session_group	*sg;
182	struct winlink		*wl;
183
184	next_s = RB_MIN(sessions, &sessions);
185	while (next_s != NULL) {
186		s = next_s;
187		next_s = RB_NEXT(sessions, &sessions, s);
188
189		if (!session_has(s, w))
190			continue;
191		server_unzoom_window(w);
192		while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
193			if (session_detach(s, wl)) {
194				server_destroy_session_group(s);
195				break;
196			} else
197				server_redraw_session_group(s);
198		}
199
200		if (options_get_number(s->options, "renumber-windows")) {
201			if ((sg = session_group_contains(s)) != NULL) {
202				TAILQ_FOREACH(target_s, &sg->sessions, gentry)
203					session_renumber_windows(target_s);
204			} else
205				session_renumber_windows(s);
206		}
207	}
208	recalculate_sizes();
209}
210
211int
212server_link_window(struct session *src, struct winlink *srcwl,
213    struct session *dst, int dstidx, int killflag, int selectflag,
214    char **cause)
215{
216	struct winlink		*dstwl;
217	struct session_group	*srcsg, *dstsg;
218
219	srcsg = session_group_contains(src);
220	dstsg = session_group_contains(dst);
221	if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
222		xasprintf(cause, "sessions are grouped");
223		return (-1);
224	}
225
226	dstwl = NULL;
227	if (dstidx != -1)
228		dstwl = winlink_find_by_index(&dst->windows, dstidx);
229	if (dstwl != NULL) {
230		if (dstwl->window == srcwl->window) {
231			xasprintf(cause, "same index: %d", dstidx);
232			return (-1);
233		}
234		if (killflag) {
235			/*
236			 * Can't use session_detach as it will destroy session
237			 * if this makes it empty.
238			 */
239			notify_session_window("window-unlinked", dst,
240			    dstwl->window);
241			dstwl->flags &= ~WINLINK_ALERTFLAGS;
242			winlink_stack_remove(&dst->lastw, dstwl);
243			winlink_remove(&dst->windows, dstwl);
244
245			/* Force select/redraw if current. */
246			if (dstwl == dst->curw) {
247				selectflag = 1;
248				dst->curw = NULL;
249			}
250		}
251	}
252
253	if (dstidx == -1)
254		dstidx = -1 - options_get_number(dst->options, "base-index");
255	dstwl = session_attach(dst, srcwl->window, dstidx, cause);
256	if (dstwl == NULL)
257		return (-1);
258
259	if (selectflag)
260		session_select(dst, dstwl->idx);
261	server_redraw_session_group(dst);
262
263	return (0);
264}
265
266void
267server_unlink_window(struct session *s, struct winlink *wl)
268{
269	if (session_detach(s, wl))
270		server_destroy_session_group(s);
271	else
272		server_redraw_session_group(s);
273}
274
275void
276server_destroy_pane(struct window_pane *wp, int notify)
277{
278	struct window		*w = wp->window;
279	int			 old_fd;
280	struct screen_write_ctx	 ctx;
281	struct grid_cell	 gc;
282
283	old_fd = wp->fd;
284	if (wp->fd != -1) {
285#ifdef HAVE_UTEMPTER
286		utempter_remove_record(wp->fd);
287#endif
288		bufferevent_free(wp->event);
289		close(wp->fd);
290		wp->fd = -1;
291	}
292
293	if (options_get_number(w->options, "remain-on-exit")) {
294		if (old_fd == -1)
295			return;
296
297		if (notify)
298			notify_pane("pane-died", wp);
299
300		screen_write_start(&ctx, wp, &wp->base);
301		screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1);
302		screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1);
303		screen_write_linefeed(&ctx, 1, 8);
304		memcpy(&gc, &grid_default_cell, sizeof gc);
305		gc.attr |= GRID_ATTR_BRIGHT;
306		screen_write_puts(&ctx, &gc, "Pane is dead");
307		screen_write_stop(&ctx);
308		wp->flags |= PANE_REDRAW;
309
310		return;
311	}
312
313	if (notify)
314		notify_pane("pane-exited", wp);
315
316	server_unzoom_window(w);
317	layout_close_pane(wp);
318	window_remove_pane(w, wp);
319
320	if (TAILQ_EMPTY(&w->panes))
321		server_kill_window(w);
322	else
323		server_redraw_window(w);
324}
325
326static void
327server_destroy_session_group(struct session *s)
328{
329	struct session_group	*sg;
330	struct session		*s1;
331
332	if ((sg = session_group_contains(s)) == NULL)
333		server_destroy_session(s);
334	else {
335		TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
336			server_destroy_session(s);
337			session_destroy(s, __func__);
338		}
339	}
340}
341
342static struct session *
343server_next_session(struct session *s)
344{
345	struct session *s_loop, *s_out;
346
347	s_out = NULL;
348	RB_FOREACH(s_loop, sessions, &sessions) {
349		if (s_loop == s)
350			continue;
351		if (s_out == NULL ||
352		    timercmp(&s_loop->activity_time, &s_out->activity_time, <))
353			s_out = s_loop;
354	}
355	return (s_out);
356}
357
358void
359server_destroy_session(struct session *s)
360{
361	struct client	*c;
362	struct session	*s_new;
363
364	if (!options_get_number(s->options, "detach-on-destroy"))
365		s_new = server_next_session(s);
366	else
367		s_new = NULL;
368
369	TAILQ_FOREACH(c, &clients, entry) {
370		if (c->session != s)
371			continue;
372		if (s_new == NULL) {
373			c->session = NULL;
374			c->flags |= CLIENT_EXIT;
375		} else {
376			c->last_session = NULL;
377			c->session = s_new;
378			server_client_set_key_table(c, NULL);
379			status_timer_start(c);
380			notify_client("client-session-changed", c);
381			session_update_activity(s_new, NULL);
382			gettimeofday(&s_new->last_attached_time, NULL);
383			server_redraw_client(c);
384			alerts_check_session(s_new);
385		}
386	}
387	recalculate_sizes();
388}
389
390void
391server_check_unattached(void)
392{
393	struct session	*s;
394
395	/*
396	 * If any sessions are no longer attached and have destroy-unattached
397	 * set, collect them.
398	 */
399	RB_FOREACH(s, sessions, &sessions) {
400		if (!(s->flags & SESSION_UNATTACHED))
401			continue;
402		if (options_get_number (s->options, "destroy-unattached"))
403			session_destroy(s, __func__);
404	}
405}
406
407/* Set stdin callback. */
408int
409server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int,
410    void *), void *cb_data, char **cause)
411{
412	if (c == NULL || c->session != NULL) {
413		*cause = xstrdup("no client with stdin");
414		return (-1);
415	}
416	if (c->flags & CLIENT_TERMINAL) {
417		*cause = xstrdup("stdin is a tty");
418		return (-1);
419	}
420	if (c->stdin_callback != NULL) {
421		*cause = xstrdup("stdin in use");
422		return (-1);
423	}
424
425	c->stdin_callback_data = cb_data;
426	c->stdin_callback = cb;
427
428	c->references++;
429
430	if (c->stdin_closed)
431		c->stdin_callback(c, 1, c->stdin_callback_data);
432
433	proc_send(c->peer, MSG_STDIN, -1, NULL, 0);
434
435	return (0);
436}
437
438void
439server_unzoom_window(struct window *w)
440{
441	if (window_unzoom(w) == 0) {
442		server_redraw_window(w);
443		server_status_window(w);
444	}
445}
446