1 |
|
|
/* $OpenBSD: server-client.c,v 1.187 2016/06/16 10:55:47 nicm Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2009 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/ioctl.h> |
21 |
|
|
#include <sys/uio.h> |
22 |
|
|
|
23 |
|
|
#include <errno.h> |
24 |
|
|
#include <event.h> |
25 |
|
|
#include <fcntl.h> |
26 |
|
|
#include <imsg.h> |
27 |
|
|
#include <paths.h> |
28 |
|
|
#include <stdlib.h> |
29 |
|
|
#include <string.h> |
30 |
|
|
#include <time.h> |
31 |
|
|
#include <unistd.h> |
32 |
|
|
|
33 |
|
|
#include "tmux.h" |
34 |
|
|
|
35 |
|
|
void server_client_free(int, short, void *); |
36 |
|
|
void server_client_check_focus(struct window_pane *); |
37 |
|
|
void server_client_check_resize(struct window_pane *); |
38 |
|
|
key_code server_client_check_mouse(struct client *); |
39 |
|
|
void server_client_repeat_timer(int, short, void *); |
40 |
|
|
void server_client_check_exit(struct client *); |
41 |
|
|
void server_client_check_redraw(struct client *); |
42 |
|
|
void server_client_set_title(struct client *); |
43 |
|
|
void server_client_reset_state(struct client *); |
44 |
|
|
int server_client_assume_paste(struct session *); |
45 |
|
|
|
46 |
|
|
void server_client_dispatch(struct imsg *, void *); |
47 |
|
|
void server_client_dispatch_command(struct client *, struct imsg *); |
48 |
|
|
void server_client_dispatch_identify(struct client *, struct imsg *); |
49 |
|
|
void server_client_dispatch_shell(struct client *); |
50 |
|
|
|
51 |
|
|
/* Check if this client is inside this server. */ |
52 |
|
|
int |
53 |
|
|
server_client_check_nested(struct client *c) |
54 |
|
|
{ |
55 |
|
|
struct environ_entry *envent; |
56 |
|
|
struct window_pane *wp; |
57 |
|
|
|
58 |
|
|
if (c->tty.path == NULL) |
59 |
|
|
return (0); |
60 |
|
|
|
61 |
|
|
envent = environ_find(c->environ, "TMUX"); |
62 |
|
|
if (envent == NULL || *envent->value == '\0') |
63 |
|
|
return (0); |
64 |
|
|
|
65 |
|
|
RB_FOREACH(wp, window_pane_tree, &all_window_panes) { |
66 |
|
|
if (strcmp(wp->tty, c->tty.path) == 0) |
67 |
|
|
return (1); |
68 |
|
|
} |
69 |
|
|
return (0); |
70 |
|
|
} |
71 |
|
|
|
72 |
|
|
/* Set client key table. */ |
73 |
|
|
void |
74 |
|
|
server_client_set_key_table(struct client *c, const char *name) |
75 |
|
|
{ |
76 |
|
|
if (name == NULL) |
77 |
|
|
name = server_client_get_key_table(c); |
78 |
|
|
|
79 |
|
|
key_bindings_unref_table(c->keytable); |
80 |
|
|
c->keytable = key_bindings_get_table(name, 1); |
81 |
|
|
c->keytable->references++; |
82 |
|
|
} |
83 |
|
|
|
84 |
|
|
/* Get default key table. */ |
85 |
|
|
const char * |
86 |
|
|
server_client_get_key_table(struct client *c) |
87 |
|
|
{ |
88 |
|
|
struct session *s = c->session; |
89 |
|
|
const char *name; |
90 |
|
|
|
91 |
|
|
if (s == NULL) |
92 |
|
|
return ("root"); |
93 |
|
|
|
94 |
|
|
name = options_get_string(s->options, "key-table"); |
95 |
|
|
if (*name == '\0') |
96 |
|
|
return ("root"); |
97 |
|
|
return (name); |
98 |
|
|
} |
99 |
|
|
|
100 |
|
|
/* Create a new client. */ |
101 |
|
|
void |
102 |
|
|
server_client_create(int fd) |
103 |
|
|
{ |
104 |
|
|
struct client *c; |
105 |
|
|
|
106 |
|
|
setblocking(fd, 0); |
107 |
|
|
|
108 |
|
|
c = xcalloc(1, sizeof *c); |
109 |
|
|
c->references = 1; |
110 |
|
|
c->peer = proc_add_peer(server_proc, fd, server_client_dispatch, c); |
111 |
|
|
|
112 |
|
|
if (gettimeofday(&c->creation_time, NULL) != 0) |
113 |
|
|
fatal("gettimeofday failed"); |
114 |
|
|
memcpy(&c->activity_time, &c->creation_time, sizeof c->activity_time); |
115 |
|
|
|
116 |
|
|
c->environ = environ_create(); |
117 |
|
|
|
118 |
|
|
c->fd = -1; |
119 |
|
|
c->cwd = NULL; |
120 |
|
|
|
121 |
|
|
c->cmdq = cmdq_new(c); |
122 |
|
|
c->cmdq->client_exit = 1; |
123 |
|
|
|
124 |
|
|
c->stdin_data = evbuffer_new(); |
125 |
|
|
c->stdout_data = evbuffer_new(); |
126 |
|
|
c->stderr_data = evbuffer_new(); |
127 |
|
|
|
128 |
|
|
c->tty.fd = -1; |
129 |
|
|
c->title = NULL; |
130 |
|
|
|
131 |
|
|
c->session = NULL; |
132 |
|
|
c->last_session = NULL; |
133 |
|
|
c->tty.sx = 80; |
134 |
|
|
c->tty.sy = 24; |
135 |
|
|
|
136 |
|
|
screen_init(&c->status, c->tty.sx, 1, 0); |
137 |
|
|
|
138 |
|
|
c->message_string = NULL; |
139 |
|
|
TAILQ_INIT(&c->message_log); |
140 |
|
|
|
141 |
|
|
c->prompt_string = NULL; |
142 |
|
|
c->prompt_buffer = NULL; |
143 |
|
|
c->prompt_index = 0; |
144 |
|
|
|
145 |
|
|
c->flags |= CLIENT_FOCUSED; |
146 |
|
|
|
147 |
|
|
c->keytable = key_bindings_get_table("root", 1); |
148 |
|
|
c->keytable->references++; |
149 |
|
|
|
150 |
|
|
evtimer_set(&c->repeat_timer, server_client_repeat_timer, c); |
151 |
|
|
|
152 |
|
|
TAILQ_INSERT_TAIL(&clients, c, entry); |
153 |
|
|
log_debug("new client %p", c); |
154 |
|
|
} |
155 |
|
|
|
156 |
|
|
/* Open client terminal if needed. */ |
157 |
|
|
int |
158 |
|
|
server_client_open(struct client *c, char **cause) |
159 |
|
|
{ |
160 |
|
|
if (c->flags & CLIENT_CONTROL) |
161 |
|
|
return (0); |
162 |
|
|
|
163 |
|
|
if (strcmp(c->ttyname, "/dev/tty") == 0) { |
164 |
|
|
*cause = xstrdup("can't use /dev/tty"); |
165 |
|
|
return (-1); |
166 |
|
|
} |
167 |
|
|
|
168 |
|
|
if (!(c->flags & CLIENT_TERMINAL)) { |
169 |
|
|
*cause = xstrdup("not a terminal"); |
170 |
|
|
return (-1); |
171 |
|
|
} |
172 |
|
|
|
173 |
|
|
if (tty_open(&c->tty, cause) != 0) |
174 |
|
|
return (-1); |
175 |
|
|
|
176 |
|
|
return (0); |
177 |
|
|
} |
178 |
|
|
|
179 |
|
|
/* Lost a client. */ |
180 |
|
|
void |
181 |
|
|
server_client_lost(struct client *c) |
182 |
|
|
{ |
183 |
|
|
struct message_entry *msg, *msg1; |
184 |
|
|
|
185 |
|
|
c->flags |= CLIENT_DEAD; |
186 |
|
|
|
187 |
|
|
server_clear_identify(c, NULL); |
188 |
|
|
status_prompt_clear(c); |
189 |
|
|
status_message_clear(c); |
190 |
|
|
|
191 |
|
|
if (c->stdin_callback != NULL) |
192 |
|
|
c->stdin_callback(c, 1, c->stdin_callback_data); |
193 |
|
|
|
194 |
|
|
TAILQ_REMOVE(&clients, c, entry); |
195 |
|
|
log_debug("lost client %p", c); |
196 |
|
|
|
197 |
|
|
/* |
198 |
|
|
* If CLIENT_TERMINAL hasn't been set, then tty_init hasn't been called |
199 |
|
|
* and tty_free might close an unrelated fd. |
200 |
|
|
*/ |
201 |
|
|
if (c->flags & CLIENT_TERMINAL) |
202 |
|
|
tty_free(&c->tty); |
203 |
|
|
free(c->ttyname); |
204 |
|
|
free(c->term); |
205 |
|
|
|
206 |
|
|
evbuffer_free(c->stdin_data); |
207 |
|
|
evbuffer_free(c->stdout_data); |
208 |
|
|
if (c->stderr_data != c->stdout_data) |
209 |
|
|
evbuffer_free(c->stderr_data); |
210 |
|
|
|
211 |
|
|
if (event_initialized(&c->status_timer)) |
212 |
|
|
evtimer_del(&c->status_timer); |
213 |
|
|
screen_free(&c->status); |
214 |
|
|
|
215 |
|
|
free(c->title); |
216 |
|
|
free((void *)c->cwd); |
217 |
|
|
|
218 |
|
|
evtimer_del(&c->repeat_timer); |
219 |
|
|
|
220 |
|
|
key_bindings_unref_table(c->keytable); |
221 |
|
|
|
222 |
|
|
if (event_initialized(&c->identify_timer)) |
223 |
|
|
evtimer_del(&c->identify_timer); |
224 |
|
|
|
225 |
|
|
free(c->message_string); |
226 |
|
|
if (event_initialized(&c->message_timer)) |
227 |
|
|
evtimer_del(&c->message_timer); |
228 |
|
|
TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { |
229 |
|
|
free(msg->msg); |
230 |
|
|
TAILQ_REMOVE(&c->message_log, msg, entry); |
231 |
|
|
free(msg); |
232 |
|
|
} |
233 |
|
|
|
234 |
|
|
free(c->prompt_string); |
235 |
|
|
free(c->prompt_buffer); |
236 |
|
|
|
237 |
|
|
c->cmdq->flags |= CMD_Q_DEAD; |
238 |
|
|
cmdq_free(c->cmdq); |
239 |
|
|
c->cmdq = NULL; |
240 |
|
|
|
241 |
|
|
environ_free(c->environ); |
242 |
|
|
|
243 |
|
|
proc_remove_peer(c->peer); |
244 |
|
|
c->peer = NULL; |
245 |
|
|
|
246 |
|
|
server_client_unref(c); |
247 |
|
|
|
248 |
|
|
server_add_accept(0); /* may be more file descriptors now */ |
249 |
|
|
|
250 |
|
|
recalculate_sizes(); |
251 |
|
|
server_check_unattached(); |
252 |
|
|
server_update_socket(); |
253 |
|
|
} |
254 |
|
|
|
255 |
|
|
/* Remove reference from a client. */ |
256 |
|
|
void |
257 |
|
|
server_client_unref(struct client *c) |
258 |
|
|
{ |
259 |
|
|
log_debug("unref client %p (%d references)", c, c->references); |
260 |
|
|
|
261 |
|
|
c->references--; |
262 |
|
|
if (c->references == 0) |
263 |
|
|
event_once(-1, EV_TIMEOUT, server_client_free, c, NULL); |
264 |
|
|
} |
265 |
|
|
|
266 |
|
|
/* Free dead client. */ |
267 |
|
|
void |
268 |
|
|
server_client_free(__unused int fd, __unused short events, void *arg) |
269 |
|
|
{ |
270 |
|
|
struct client *c = arg; |
271 |
|
|
|
272 |
|
|
log_debug("free client %p (%d references)", c, c->references); |
273 |
|
|
|
274 |
|
|
if (c->references == 0) |
275 |
|
|
free(c); |
276 |
|
|
} |
277 |
|
|
|
278 |
|
|
/* Detach a client. */ |
279 |
|
|
void |
280 |
|
|
server_client_detach(struct client *c, enum msgtype msgtype) |
281 |
|
|
{ |
282 |
|
|
struct session *s = c->session; |
283 |
|
|
|
284 |
|
|
if (s == NULL) |
285 |
|
|
return; |
286 |
|
|
|
287 |
|
|
hooks_run(c->session->hooks, c, NULL, "client-detached"); |
288 |
|
|
proc_send_s(c->peer, msgtype, s->name); |
289 |
|
|
} |
290 |
|
|
|
291 |
|
|
/* Check for mouse keys. */ |
292 |
|
|
key_code |
293 |
|
|
server_client_check_mouse(struct client *c) |
294 |
|
|
{ |
295 |
|
|
struct session *s = c->session; |
296 |
|
|
struct mouse_event *m = &c->tty.mouse; |
297 |
|
|
struct window *w; |
298 |
|
|
struct window_pane *wp; |
299 |
|
|
enum { NOTYPE, DOWN, UP, DRAG, WHEEL } type = NOTYPE; |
300 |
|
|
enum { NOWHERE, PANE, STATUS, BORDER } where = NOWHERE; |
301 |
|
|
u_int x, y, b; |
302 |
|
|
key_code key; |
303 |
|
|
|
304 |
|
|
log_debug("mouse %02x at %u,%u (last %u,%u) (%d)", m->b, m->x, m->y, |
305 |
|
|
m->lx, m->ly, c->tty.mouse_drag_flag); |
306 |
|
|
|
307 |
|
|
/* What type of event is this? */ |
308 |
|
|
if (MOUSE_DRAG(m->b)) { |
309 |
|
|
type = DRAG; |
310 |
|
|
if (c->tty.mouse_drag_flag) { |
311 |
|
|
x = m->x, y = m->y, b = m->b; |
312 |
|
|
log_debug("drag update at %u,%u", x, y); |
313 |
|
|
} else { |
314 |
|
|
x = m->lx, y = m->ly, b = m->lb; |
315 |
|
|
log_debug("drag start at %u,%u", x, y); |
316 |
|
|
} |
317 |
|
|
} else if (MOUSE_WHEEL(m->b)) { |
318 |
|
|
type = WHEEL; |
319 |
|
|
x = m->x, y = m->y, b = m->b; |
320 |
|
|
log_debug("wheel at %u,%u", x, y); |
321 |
|
|
} else if (MOUSE_BUTTONS(m->b) == 3) { |
322 |
|
|
type = UP; |
323 |
|
|
x = m->x, y = m->y, b = m->lb; |
324 |
|
|
log_debug("up at %u,%u", x, y); |
325 |
|
|
} else { |
326 |
|
|
type = DOWN; |
327 |
|
|
x = m->x, y = m->y, b = m->b; |
328 |
|
|
log_debug("down at %u,%u", x, y); |
329 |
|
|
} |
330 |
|
|
if (type == NOTYPE) |
331 |
|
|
return (KEYC_UNKNOWN); |
332 |
|
|
|
333 |
|
|
/* Always save the session. */ |
334 |
|
|
m->s = s->id; |
335 |
|
|
|
336 |
|
|
/* Is this on the status line? */ |
337 |
|
|
m->statusat = status_at_line(c); |
338 |
|
|
if (m->statusat != -1 && y == (u_int)m->statusat) { |
339 |
|
|
w = status_get_window_at(c, x); |
340 |
|
|
if (w == NULL) |
341 |
|
|
return (KEYC_UNKNOWN); |
342 |
|
|
m->w = w->id; |
343 |
|
|
where = STATUS; |
344 |
|
|
} else |
345 |
|
|
m->w = -1; |
346 |
|
|
|
347 |
|
|
/* Not on status line. Adjust position and check for border or pane. */ |
348 |
|
|
if (where == NOWHERE) { |
349 |
|
|
if (m->statusat == 0 && y > 0) |
350 |
|
|
y--; |
351 |
|
|
else if (m->statusat > 0 && y >= (u_int)m->statusat) |
352 |
|
|
y = m->statusat - 1; |
353 |
|
|
|
354 |
|
|
TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { |
355 |
|
|
if ((wp->xoff + wp->sx == x && |
356 |
|
|
wp->yoff <= 1 + y && |
357 |
|
|
wp->yoff + wp->sy >= y) || |
358 |
|
|
(wp->yoff + wp->sy == y && |
359 |
|
|
wp->xoff <= 1 + x && |
360 |
|
|
wp->xoff + wp->sx >= x)) |
361 |
|
|
break; |
362 |
|
|
} |
363 |
|
|
if (wp != NULL) |
364 |
|
|
where = BORDER; |
365 |
|
|
else { |
366 |
|
|
wp = window_get_active_at(s->curw->window, x, y); |
367 |
|
|
if (wp != NULL) { |
368 |
|
|
where = PANE; |
369 |
|
|
log_debug("mouse at %u,%u is on pane %%%u", |
370 |
|
|
x, y, wp->id); |
371 |
|
|
} |
372 |
|
|
} |
373 |
|
|
if (where == NOWHERE) |
374 |
|
|
return (KEYC_UNKNOWN); |
375 |
|
|
m->wp = wp->id; |
376 |
|
|
m->w = wp->window->id; |
377 |
|
|
} else |
378 |
|
|
m->wp = -1; |
379 |
|
|
|
380 |
|
|
/* Stop dragging if needed. */ |
381 |
|
|
if (type != DRAG && c->tty.mouse_drag_flag) { |
382 |
|
|
if (c->tty.mouse_drag_release != NULL) |
383 |
|
|
c->tty.mouse_drag_release(c, m); |
384 |
|
|
|
385 |
|
|
c->tty.mouse_drag_update = NULL; |
386 |
|
|
c->tty.mouse_drag_release = NULL; |
387 |
|
|
|
388 |
|
|
/* |
389 |
|
|
* End a mouse drag by passing a MouseDragEnd key corresponding |
390 |
|
|
* to the button that started the drag. |
391 |
|
|
*/ |
392 |
|
|
switch (c->tty.mouse_drag_flag) { |
393 |
|
|
case 1: |
394 |
|
|
if (where == PANE) |
395 |
|
|
key = KEYC_MOUSEDRAGEND1_PANE; |
396 |
|
|
if (where == STATUS) |
397 |
|
|
key = KEYC_MOUSEDRAGEND1_STATUS; |
398 |
|
|
if (where == BORDER) |
399 |
|
|
key = KEYC_MOUSEDRAGEND1_BORDER; |
400 |
|
|
break; |
401 |
|
|
case 2: |
402 |
|
|
if (where == PANE) |
403 |
|
|
key = KEYC_MOUSEDRAGEND2_PANE; |
404 |
|
|
if (where == STATUS) |
405 |
|
|
key = KEYC_MOUSEDRAGEND2_STATUS; |
406 |
|
|
if (where == BORDER) |
407 |
|
|
key = KEYC_MOUSEDRAGEND2_BORDER; |
408 |
|
|
break; |
409 |
|
|
case 3: |
410 |
|
|
if (where == PANE) |
411 |
|
|
key = KEYC_MOUSEDRAGEND3_PANE; |
412 |
|
|
if (where == STATUS) |
413 |
|
|
key = KEYC_MOUSEDRAGEND3_STATUS; |
414 |
|
|
if (where == BORDER) |
415 |
|
|
key = KEYC_MOUSEDRAGEND3_BORDER; |
416 |
|
|
break; |
417 |
|
|
default: |
418 |
|
|
key = KEYC_MOUSE; |
419 |
|
|
break; |
420 |
|
|
} |
421 |
|
|
c->tty.mouse_drag_flag = 0; |
422 |
|
|
|
423 |
|
|
return (key); |
424 |
|
|
} |
425 |
|
|
|
426 |
|
|
/* Convert to a key binding. */ |
427 |
|
|
key = KEYC_UNKNOWN; |
428 |
|
|
switch (type) { |
429 |
|
|
case NOTYPE: |
430 |
|
|
break; |
431 |
|
|
case DRAG: |
432 |
|
|
if (c->tty.mouse_drag_update != NULL) |
433 |
|
|
c->tty.mouse_drag_update(c, m); |
434 |
|
|
else { |
435 |
|
|
switch (MOUSE_BUTTONS(b)) { |
436 |
|
|
case 0: |
437 |
|
|
if (where == PANE) |
438 |
|
|
key = KEYC_MOUSEDRAG1_PANE; |
439 |
|
|
if (where == STATUS) |
440 |
|
|
key = KEYC_MOUSEDRAG1_STATUS; |
441 |
|
|
if (where == BORDER) |
442 |
|
|
key = KEYC_MOUSEDRAG1_BORDER; |
443 |
|
|
break; |
444 |
|
|
case 1: |
445 |
|
|
if (where == PANE) |
446 |
|
|
key = KEYC_MOUSEDRAG2_PANE; |
447 |
|
|
if (where == STATUS) |
448 |
|
|
key = KEYC_MOUSEDRAG2_STATUS; |
449 |
|
|
if (where == BORDER) |
450 |
|
|
key = KEYC_MOUSEDRAG2_BORDER; |
451 |
|
|
break; |
452 |
|
|
case 2: |
453 |
|
|
if (where == PANE) |
454 |
|
|
key = KEYC_MOUSEDRAG3_PANE; |
455 |
|
|
if (where == STATUS) |
456 |
|
|
key = KEYC_MOUSEDRAG3_STATUS; |
457 |
|
|
if (where == BORDER) |
458 |
|
|
key = KEYC_MOUSEDRAG3_BORDER; |
459 |
|
|
break; |
460 |
|
|
} |
461 |
|
|
} |
462 |
|
|
|
463 |
|
|
/* |
464 |
|
|
* Begin a drag by setting the flag to a non-zero value that |
465 |
|
|
* corresponds to the mouse button in use. |
466 |
|
|
*/ |
467 |
|
|
c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; |
468 |
|
|
break; |
469 |
|
|
case WHEEL: |
470 |
|
|
if (MOUSE_BUTTONS(b) == MOUSE_WHEEL_UP) { |
471 |
|
|
if (where == PANE) |
472 |
|
|
key = KEYC_WHEELUP_PANE; |
473 |
|
|
if (where == STATUS) |
474 |
|
|
key = KEYC_WHEELUP_STATUS; |
475 |
|
|
if (where == BORDER) |
476 |
|
|
key = KEYC_WHEELUP_BORDER; |
477 |
|
|
} else { |
478 |
|
|
if (where == PANE) |
479 |
|
|
key = KEYC_WHEELDOWN_PANE; |
480 |
|
|
if (where == STATUS) |
481 |
|
|
key = KEYC_WHEELDOWN_STATUS; |
482 |
|
|
if (where == BORDER) |
483 |
|
|
key = KEYC_WHEELDOWN_BORDER; |
484 |
|
|
} |
485 |
|
|
break; |
486 |
|
|
case UP: |
487 |
|
|
switch (MOUSE_BUTTONS(b)) { |
488 |
|
|
case 0: |
489 |
|
|
if (where == PANE) |
490 |
|
|
key = KEYC_MOUSEUP1_PANE; |
491 |
|
|
if (where == STATUS) |
492 |
|
|
key = KEYC_MOUSEUP1_STATUS; |
493 |
|
|
if (where == BORDER) |
494 |
|
|
key = KEYC_MOUSEUP1_BORDER; |
495 |
|
|
break; |
496 |
|
|
case 1: |
497 |
|
|
if (where == PANE) |
498 |
|
|
key = KEYC_MOUSEUP2_PANE; |
499 |
|
|
if (where == STATUS) |
500 |
|
|
key = KEYC_MOUSEUP2_STATUS; |
501 |
|
|
if (where == BORDER) |
502 |
|
|
key = KEYC_MOUSEUP2_BORDER; |
503 |
|
|
break; |
504 |
|
|
case 2: |
505 |
|
|
if (where == PANE) |
506 |
|
|
key = KEYC_MOUSEUP3_PANE; |
507 |
|
|
if (where == STATUS) |
508 |
|
|
key = KEYC_MOUSEUP3_STATUS; |
509 |
|
|
if (where == BORDER) |
510 |
|
|
key = KEYC_MOUSEUP3_BORDER; |
511 |
|
|
break; |
512 |
|
|
} |
513 |
|
|
break; |
514 |
|
|
case DOWN: |
515 |
|
|
switch (MOUSE_BUTTONS(b)) { |
516 |
|
|
case 0: |
517 |
|
|
if (where == PANE) |
518 |
|
|
key = KEYC_MOUSEDOWN1_PANE; |
519 |
|
|
if (where == STATUS) |
520 |
|
|
key = KEYC_MOUSEDOWN1_STATUS; |
521 |
|
|
if (where == BORDER) |
522 |
|
|
key = KEYC_MOUSEDOWN1_BORDER; |
523 |
|
|
break; |
524 |
|
|
case 1: |
525 |
|
|
if (where == PANE) |
526 |
|
|
key = KEYC_MOUSEDOWN2_PANE; |
527 |
|
|
if (where == STATUS) |
528 |
|
|
key = KEYC_MOUSEDOWN2_STATUS; |
529 |
|
|
if (where == BORDER) |
530 |
|
|
key = KEYC_MOUSEDOWN2_BORDER; |
531 |
|
|
break; |
532 |
|
|
case 2: |
533 |
|
|
if (where == PANE) |
534 |
|
|
key = KEYC_MOUSEDOWN3_PANE; |
535 |
|
|
if (where == STATUS) |
536 |
|
|
key = KEYC_MOUSEDOWN3_STATUS; |
537 |
|
|
if (where == BORDER) |
538 |
|
|
key = KEYC_MOUSEDOWN3_BORDER; |
539 |
|
|
break; |
540 |
|
|
} |
541 |
|
|
break; |
542 |
|
|
} |
543 |
|
|
if (key == KEYC_UNKNOWN) |
544 |
|
|
return (KEYC_UNKNOWN); |
545 |
|
|
|
546 |
|
|
/* Apply modifiers if any. */ |
547 |
|
|
if (b & MOUSE_MASK_META) |
548 |
|
|
key |= KEYC_ESCAPE; |
549 |
|
|
if (b & MOUSE_MASK_CTRL) |
550 |
|
|
key |= KEYC_CTRL; |
551 |
|
|
if (b & MOUSE_MASK_SHIFT) |
552 |
|
|
key |= KEYC_SHIFT; |
553 |
|
|
|
554 |
|
|
return (key); |
555 |
|
|
} |
556 |
|
|
|
557 |
|
|
/* Is this fast enough to probably be a paste? */ |
558 |
|
|
int |
559 |
|
|
server_client_assume_paste(struct session *s) |
560 |
|
|
{ |
561 |
|
|
struct timeval tv; |
562 |
|
|
int t; |
563 |
|
|
|
564 |
|
|
if ((t = options_get_number(s->options, "assume-paste-time")) == 0) |
565 |
|
|
return (0); |
566 |
|
|
|
567 |
|
|
timersub(&s->activity_time, &s->last_activity_time, &tv); |
568 |
|
|
if (tv.tv_sec == 0 && tv.tv_usec < t * 1000) { |
569 |
|
|
log_debug("session %s pasting (flag %d)", s->name, |
570 |
|
|
!!(s->flags & SESSION_PASTING)); |
571 |
|
|
if (s->flags & SESSION_PASTING) |
572 |
|
|
return (1); |
573 |
|
|
s->flags |= SESSION_PASTING; |
574 |
|
|
return (0); |
575 |
|
|
} |
576 |
|
|
log_debug("session %s not pasting", s->name); |
577 |
|
|
s->flags &= ~SESSION_PASTING; |
578 |
|
|
return (0); |
579 |
|
|
} |
580 |
|
|
|
581 |
|
|
/* Handle data key input from client. */ |
582 |
|
|
void |
583 |
|
|
server_client_handle_key(struct client *c, key_code key) |
584 |
|
|
{ |
585 |
|
|
struct mouse_event *m = &c->tty.mouse; |
586 |
|
|
struct session *s = c->session; |
587 |
|
|
struct window *w; |
588 |
|
|
struct window_pane *wp; |
589 |
|
|
struct timeval tv; |
590 |
|
|
struct key_table *table; |
591 |
|
|
struct key_binding bd_find, *bd; |
592 |
|
|
int xtimeout; |
593 |
|
|
|
594 |
|
|
/* Check the client is good to accept input. */ |
595 |
|
|
if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) |
596 |
|
|
return; |
597 |
|
|
w = s->curw->window; |
598 |
|
|
|
599 |
|
|
/* Update the activity timer. */ |
600 |
|
|
if (gettimeofday(&c->activity_time, NULL) != 0) |
601 |
|
|
fatal("gettimeofday failed"); |
602 |
|
|
session_update_activity(s, &c->activity_time); |
603 |
|
|
|
604 |
|
|
/* Number keys jump to pane in identify mode. */ |
605 |
|
|
if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') { |
606 |
|
|
if (c->flags & CLIENT_READONLY) |
607 |
|
|
return; |
608 |
|
|
window_unzoom(w); |
609 |
|
|
wp = window_pane_at_index(w, key - '0'); |
610 |
|
|
if (wp != NULL && !window_pane_visible(wp)) |
611 |
|
|
wp = NULL; |
612 |
|
|
server_clear_identify(c, wp); |
613 |
|
|
return; |
614 |
|
|
} |
615 |
|
|
|
616 |
|
|
/* Handle status line. */ |
617 |
|
|
if (!(c->flags & CLIENT_READONLY)) { |
618 |
|
|
status_message_clear(c); |
619 |
|
|
server_clear_identify(c, NULL); |
620 |
|
|
} |
621 |
|
|
if (c->prompt_string != NULL) { |
622 |
|
|
if (!(c->flags & CLIENT_READONLY)) |
623 |
|
|
status_prompt_key(c, key); |
624 |
|
|
return; |
625 |
|
|
} |
626 |
|
|
|
627 |
|
|
/* Check for mouse keys. */ |
628 |
|
|
if (key == KEYC_MOUSE) { |
629 |
|
|
if (c->flags & CLIENT_READONLY) |
630 |
|
|
return; |
631 |
|
|
key = server_client_check_mouse(c); |
632 |
|
|
if (key == KEYC_UNKNOWN) |
633 |
|
|
return; |
634 |
|
|
|
635 |
|
|
m->valid = 1; |
636 |
|
|
m->key = key; |
637 |
|
|
|
638 |
|
|
if (!options_get_number(s->options, "mouse")) |
639 |
|
|
goto forward; |
640 |
|
|
} else |
641 |
|
|
m->valid = 0; |
642 |
|
|
|
643 |
|
|
/* Treat everything as a regular key when pasting is detected. */ |
644 |
|
|
if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s)) |
645 |
|
|
goto forward; |
646 |
|
|
|
647 |
|
|
retry: |
648 |
|
|
/* Try to see if there is a key binding in the current table. */ |
649 |
|
|
bd_find.key = key; |
650 |
|
|
bd = RB_FIND(key_bindings, &c->keytable->key_bindings, &bd_find); |
651 |
|
|
if (bd != NULL) { |
652 |
|
|
/* |
653 |
|
|
* Key was matched in this table. If currently repeating but a |
654 |
|
|
* non-repeating binding was found, stop repeating and try |
655 |
|
|
* again in the root table. |
656 |
|
|
*/ |
657 |
|
|
if ((c->flags & CLIENT_REPEAT) && !bd->can_repeat) { |
658 |
|
|
server_client_set_key_table(c, NULL); |
659 |
|
|
c->flags &= ~CLIENT_REPEAT; |
660 |
|
|
server_status_client(c); |
661 |
|
|
goto retry; |
662 |
|
|
} |
663 |
|
|
|
664 |
|
|
/* |
665 |
|
|
* Take a reference to this table to make sure the key binding |
666 |
|
|
* doesn't disappear. |
667 |
|
|
*/ |
668 |
|
|
table = c->keytable; |
669 |
|
|
table->references++; |
670 |
|
|
|
671 |
|
|
/* |
672 |
|
|
* If this is a repeating key, start the timer. Otherwise reset |
673 |
|
|
* the client back to the root table. |
674 |
|
|
*/ |
675 |
|
|
xtimeout = options_get_number(s->options, "repeat-time"); |
676 |
|
|
if (xtimeout != 0 && bd->can_repeat) { |
677 |
|
|
c->flags |= CLIENT_REPEAT; |
678 |
|
|
|
679 |
|
|
tv.tv_sec = xtimeout / 1000; |
680 |
|
|
tv.tv_usec = (xtimeout % 1000) * 1000L; |
681 |
|
|
evtimer_del(&c->repeat_timer); |
682 |
|
|
evtimer_add(&c->repeat_timer, &tv); |
683 |
|
|
} else { |
684 |
|
|
c->flags &= ~CLIENT_REPEAT; |
685 |
|
|
server_client_set_key_table(c, NULL); |
686 |
|
|
} |
687 |
|
|
server_status_client(c); |
688 |
|
|
|
689 |
|
|
/* Dispatch the key binding. */ |
690 |
|
|
key_bindings_dispatch(bd, c, m); |
691 |
|
|
key_bindings_unref_table(table); |
692 |
|
|
return; |
693 |
|
|
} |
694 |
|
|
|
695 |
|
|
/* |
696 |
|
|
* No match in this table. If repeating, switch the client back to the |
697 |
|
|
* root table and try again. |
698 |
|
|
*/ |
699 |
|
|
if (c->flags & CLIENT_REPEAT) { |
700 |
|
|
server_client_set_key_table(c, NULL); |
701 |
|
|
c->flags &= ~CLIENT_REPEAT; |
702 |
|
|
server_status_client(c); |
703 |
|
|
goto retry; |
704 |
|
|
} |
705 |
|
|
|
706 |
|
|
/* If no match and we're not in the root table, that's it. */ |
707 |
|
|
if (strcmp(c->keytable->name, server_client_get_key_table(c)) != 0) { |
708 |
|
|
server_client_set_key_table(c, NULL); |
709 |
|
|
server_status_client(c); |
710 |
|
|
return; |
711 |
|
|
} |
712 |
|
|
|
713 |
|
|
/* |
714 |
|
|
* No match, but in the root table. Prefix switches to the prefix table |
715 |
|
|
* and everything else is passed through. |
716 |
|
|
*/ |
717 |
|
|
if (key == (key_code)options_get_number(s->options, "prefix") || |
718 |
|
|
key == (key_code)options_get_number(s->options, "prefix2")) { |
719 |
|
|
server_client_set_key_table(c, "prefix"); |
720 |
|
|
server_status_client(c); |
721 |
|
|
return; |
722 |
|
|
} |
723 |
|
|
|
724 |
|
|
forward: |
725 |
|
|
if (c->flags & CLIENT_READONLY) |
726 |
|
|
return; |
727 |
|
|
if (KEYC_IS_MOUSE(key)) |
728 |
|
|
wp = cmd_mouse_pane(m, NULL, NULL); |
729 |
|
|
else |
730 |
|
|
wp = w->active; |
731 |
|
|
if (wp != NULL) |
732 |
|
|
window_pane_key(wp, c, s, key, m); |
733 |
|
|
} |
734 |
|
|
|
735 |
|
|
/* Client functions that need to happen every loop. */ |
736 |
|
|
void |
737 |
|
|
server_client_loop(void) |
738 |
|
|
{ |
739 |
|
|
struct client *c; |
740 |
|
|
struct window *w; |
741 |
|
|
struct window_pane *wp; |
742 |
|
|
|
743 |
|
|
TAILQ_FOREACH(c, &clients, entry) { |
744 |
|
|
server_client_check_exit(c); |
745 |
|
|
if (c->session != NULL) { |
746 |
|
|
server_client_check_redraw(c); |
747 |
|
|
server_client_reset_state(c); |
748 |
|
|
} |
749 |
|
|
} |
750 |
|
|
|
751 |
|
|
/* |
752 |
|
|
* Any windows will have been redrawn as part of clients, so clear |
753 |
|
|
* their flags now. Also check pane focus and resize. |
754 |
|
|
*/ |
755 |
|
|
RB_FOREACH(w, windows, &windows) { |
756 |
|
|
w->flags &= ~WINDOW_REDRAW; |
757 |
|
|
TAILQ_FOREACH(wp, &w->panes, entry) { |
758 |
|
|
if (wp->fd != -1) { |
759 |
|
|
server_client_check_focus(wp); |
760 |
|
|
server_client_check_resize(wp); |
761 |
|
|
} |
762 |
|
|
wp->flags &= ~PANE_REDRAW; |
763 |
|
|
} |
764 |
|
|
check_window_name(w); |
765 |
|
|
} |
766 |
|
|
} |
767 |
|
|
|
768 |
|
|
/* Check if pane should be resized. */ |
769 |
|
|
void |
770 |
|
|
server_client_check_resize(struct window_pane *wp) |
771 |
|
|
{ |
772 |
|
|
struct winsize ws; |
773 |
|
|
|
774 |
|
|
if (!(wp->flags & PANE_RESIZE)) |
775 |
|
|
return; |
776 |
|
|
|
777 |
|
|
memset(&ws, 0, sizeof ws); |
778 |
|
|
ws.ws_col = wp->sx; |
779 |
|
|
ws.ws_row = wp->sy; |
780 |
|
|
|
781 |
|
|
if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) |
782 |
|
|
fatal("ioctl failed"); |
783 |
|
|
|
784 |
|
|
wp->flags &= ~PANE_RESIZE; |
785 |
|
|
} |
786 |
|
|
|
787 |
|
|
/* Check whether pane should be focused. */ |
788 |
|
|
void |
789 |
|
|
server_client_check_focus(struct window_pane *wp) |
790 |
|
|
{ |
791 |
|
|
struct client *c; |
792 |
|
|
int push; |
793 |
|
|
|
794 |
|
|
/* Are focus events off? */ |
795 |
|
|
if (!options_get_number(global_options, "focus-events")) |
796 |
|
|
return; |
797 |
|
|
|
798 |
|
|
/* Do we need to push the focus state? */ |
799 |
|
|
push = wp->flags & PANE_FOCUSPUSH; |
800 |
|
|
wp->flags &= ~PANE_FOCUSPUSH; |
801 |
|
|
|
802 |
|
|
/* If we don't care about focus, forget it. */ |
803 |
|
|
if (!(wp->base.mode & MODE_FOCUSON)) |
804 |
|
|
return; |
805 |
|
|
|
806 |
|
|
/* If we're not the active pane in our window, we're not focused. */ |
807 |
|
|
if (wp->window->active != wp) |
808 |
|
|
goto not_focused; |
809 |
|
|
|
810 |
|
|
/* If we're in a mode, we're not focused. */ |
811 |
|
|
if (wp->screen != &wp->base) |
812 |
|
|
goto not_focused; |
813 |
|
|
|
814 |
|
|
/* |
815 |
|
|
* If our window is the current window in any focused clients with an |
816 |
|
|
* attached session, we're focused. |
817 |
|
|
*/ |
818 |
|
|
TAILQ_FOREACH(c, &clients, entry) { |
819 |
|
|
if (c->session == NULL || !(c->flags & CLIENT_FOCUSED)) |
820 |
|
|
continue; |
821 |
|
|
if (c->session->flags & SESSION_UNATTACHED) |
822 |
|
|
continue; |
823 |
|
|
|
824 |
|
|
if (c->session->curw->window == wp->window) |
825 |
|
|
goto focused; |
826 |
|
|
} |
827 |
|
|
|
828 |
|
|
not_focused: |
829 |
|
|
if (push || (wp->flags & PANE_FOCUSED)) |
830 |
|
|
bufferevent_write(wp->event, "\033[O", 3); |
831 |
|
|
wp->flags &= ~PANE_FOCUSED; |
832 |
|
|
return; |
833 |
|
|
|
834 |
|
|
focused: |
835 |
|
|
if (push || !(wp->flags & PANE_FOCUSED)) |
836 |
|
|
bufferevent_write(wp->event, "\033[I", 3); |
837 |
|
|
wp->flags |= PANE_FOCUSED; |
838 |
|
|
} |
839 |
|
|
|
840 |
|
|
/* |
841 |
|
|
* Update cursor position and mode settings. The scroll region and attributes |
842 |
|
|
* are cleared when idle (waiting for an event) as this is the most likely time |
843 |
|
|
* a user may interrupt tmux, for example with ~^Z in ssh(1). This is a |
844 |
|
|
* compromise between excessive resets and likelihood of an interrupt. |
845 |
|
|
* |
846 |
|
|
* tty_region/tty_reset/tty_update_mode already take care of not resetting |
847 |
|
|
* things that are already in their default state. |
848 |
|
|
*/ |
849 |
|
|
void |
850 |
|
|
server_client_reset_state(struct client *c) |
851 |
|
|
{ |
852 |
|
|
struct window *w = c->session->curw->window; |
853 |
|
|
struct window_pane *wp = w->active; |
854 |
|
|
struct screen *s = wp->screen; |
855 |
|
|
struct options *oo = c->session->options; |
856 |
|
|
int status, mode, o; |
857 |
|
|
|
858 |
|
|
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) |
859 |
|
|
return; |
860 |
|
|
|
861 |
|
|
tty_region(&c->tty, 0, c->tty.sy - 1); |
862 |
|
|
|
863 |
|
|
status = options_get_number(oo, "status"); |
864 |
|
|
if (!window_pane_visible(wp) || wp->yoff + s->cy >= c->tty.sy - status) |
865 |
|
|
tty_cursor(&c->tty, 0, 0); |
866 |
|
|
else { |
867 |
|
|
o = status && options_get_number(oo, "status-position") == 0; |
868 |
|
|
tty_cursor(&c->tty, wp->xoff + s->cx, o + wp->yoff + s->cy); |
869 |
|
|
} |
870 |
|
|
|
871 |
|
|
/* |
872 |
|
|
* Set mouse mode if requested. To support dragging, always use button |
873 |
|
|
* mode. |
874 |
|
|
*/ |
875 |
|
|
mode = s->mode; |
876 |
|
|
if (options_get_number(oo, "mouse")) |
877 |
|
|
mode = (mode & ~ALL_MOUSE_MODES) | MODE_MOUSE_BUTTON; |
878 |
|
|
|
879 |
|
|
/* Set the terminal mode and reset attributes. */ |
880 |
|
|
tty_update_mode(&c->tty, mode, s); |
881 |
|
|
tty_reset(&c->tty); |
882 |
|
|
} |
883 |
|
|
|
884 |
|
|
/* Repeat time callback. */ |
885 |
|
|
void |
886 |
|
|
server_client_repeat_timer(__unused int fd, __unused short events, void *data) |
887 |
|
|
{ |
888 |
|
|
struct client *c = data; |
889 |
|
|
|
890 |
|
|
if (c->flags & CLIENT_REPEAT) { |
891 |
|
|
server_client_set_key_table(c, NULL); |
892 |
|
|
c->flags &= ~CLIENT_REPEAT; |
893 |
|
|
server_status_client(c); |
894 |
|
|
} |
895 |
|
|
} |
896 |
|
|
|
897 |
|
|
/* Check if client should be exited. */ |
898 |
|
|
void |
899 |
|
|
server_client_check_exit(struct client *c) |
900 |
|
|
{ |
901 |
|
|
if (!(c->flags & CLIENT_EXIT)) |
902 |
|
|
return; |
903 |
|
|
|
904 |
|
|
if (EVBUFFER_LENGTH(c->stdin_data) != 0) |
905 |
|
|
return; |
906 |
|
|
if (EVBUFFER_LENGTH(c->stdout_data) != 0) |
907 |
|
|
return; |
908 |
|
|
if (EVBUFFER_LENGTH(c->stderr_data) != 0) |
909 |
|
|
return; |
910 |
|
|
|
911 |
|
|
proc_send(c->peer, MSG_EXIT, -1, &c->retval, sizeof c->retval); |
912 |
|
|
c->flags &= ~CLIENT_EXIT; |
913 |
|
|
} |
914 |
|
|
|
915 |
|
|
/* Check for client redraws. */ |
916 |
|
|
void |
917 |
|
|
server_client_check_redraw(struct client *c) |
918 |
|
|
{ |
919 |
|
|
struct session *s = c->session; |
920 |
|
|
struct tty *tty = &c->tty; |
921 |
|
|
struct window_pane *wp; |
922 |
|
|
int flags, masked, redraw; |
923 |
|
|
|
924 |
|
|
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) |
925 |
|
|
return; |
926 |
|
|
|
927 |
|
|
if (c->flags & (CLIENT_REDRAW|CLIENT_STATUS)) { |
928 |
|
|
if (options_get_number(s->options, "set-titles")) |
929 |
|
|
server_client_set_title(c); |
930 |
|
|
|
931 |
|
|
if (c->message_string != NULL) |
932 |
|
|
redraw = status_message_redraw(c); |
933 |
|
|
else if (c->prompt_string != NULL) |
934 |
|
|
redraw = status_prompt_redraw(c); |
935 |
|
|
else |
936 |
|
|
redraw = status_redraw(c); |
937 |
|
|
if (!redraw) |
938 |
|
|
c->flags &= ~CLIENT_STATUS; |
939 |
|
|
} |
940 |
|
|
|
941 |
|
|
flags = tty->flags & (TTY_FREEZE|TTY_NOCURSOR); |
942 |
|
|
tty->flags = (tty->flags & ~TTY_FREEZE) | TTY_NOCURSOR; |
943 |
|
|
|
944 |
|
|
if (c->flags & CLIENT_REDRAW) { |
945 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
946 |
|
|
screen_redraw_screen(c, 1, 1, 1); |
947 |
|
|
c->flags &= ~(CLIENT_STATUS|CLIENT_BORDERS); |
948 |
|
|
} else if (c->flags & CLIENT_REDRAWWINDOW) { |
949 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
950 |
|
|
TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) |
951 |
|
|
screen_redraw_pane(c, wp); |
952 |
|
|
c->flags &= ~CLIENT_REDRAWWINDOW; |
953 |
|
|
} else { |
954 |
|
|
TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { |
955 |
|
|
if (wp->flags & PANE_REDRAW) { |
956 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
957 |
|
|
screen_redraw_pane(c, wp); |
958 |
|
|
} |
959 |
|
|
} |
960 |
|
|
} |
961 |
|
|
|
962 |
|
|
masked = c->flags & (CLIENT_BORDERS|CLIENT_STATUS); |
963 |
|
|
if (masked != 0) |
964 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
965 |
|
|
if (masked == CLIENT_BORDERS) |
966 |
|
|
screen_redraw_screen(c, 0, 0, 1); |
967 |
|
|
else if (masked == CLIENT_STATUS) |
968 |
|
|
screen_redraw_screen(c, 0, 1, 0); |
969 |
|
|
else if (masked != 0) |
970 |
|
|
screen_redraw_screen(c, 0, 1, 1); |
971 |
|
|
|
972 |
|
|
tty->flags = (tty->flags & ~(TTY_FREEZE|TTY_NOCURSOR)) | flags; |
973 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
974 |
|
|
|
975 |
|
|
c->flags &= ~(CLIENT_REDRAW|CLIENT_BORDERS|CLIENT_STATUS| |
976 |
|
|
CLIENT_STATUSFORCE); |
977 |
|
|
} |
978 |
|
|
|
979 |
|
|
/* Set client title. */ |
980 |
|
|
void |
981 |
|
|
server_client_set_title(struct client *c) |
982 |
|
|
{ |
983 |
|
|
struct session *s = c->session; |
984 |
|
|
const char *template; |
985 |
|
|
char *title; |
986 |
|
|
struct format_tree *ft; |
987 |
|
|
|
988 |
|
|
template = options_get_string(s->options, "set-titles-string"); |
989 |
|
|
|
990 |
|
|
ft = format_create(NULL, 0); |
991 |
|
|
format_defaults(ft, c, NULL, NULL, NULL); |
992 |
|
|
|
993 |
|
|
title = format_expand_time(ft, template, time(NULL)); |
994 |
|
|
if (c->title == NULL || strcmp(title, c->title) != 0) { |
995 |
|
|
free(c->title); |
996 |
|
|
c->title = xstrdup(title); |
997 |
|
|
tty_set_title(&c->tty, c->title); |
998 |
|
|
} |
999 |
|
|
free(title); |
1000 |
|
|
|
1001 |
|
|
format_free(ft); |
1002 |
|
|
} |
1003 |
|
|
|
1004 |
|
|
/* Dispatch message from client. */ |
1005 |
|
|
void |
1006 |
|
|
server_client_dispatch(struct imsg *imsg, void *arg) |
1007 |
|
|
{ |
1008 |
|
|
struct client *c = arg; |
1009 |
|
|
struct msg_stdin_data stdindata; |
1010 |
|
|
const char *data; |
1011 |
|
|
ssize_t datalen; |
1012 |
|
|
struct session *s; |
1013 |
|
|
|
1014 |
|
|
if (c->flags & CLIENT_DEAD) |
1015 |
|
|
return; |
1016 |
|
|
|
1017 |
|
|
if (imsg == NULL) { |
1018 |
|
|
server_client_lost(c); |
1019 |
|
|
return; |
1020 |
|
|
} |
1021 |
|
|
|
1022 |
|
|
data = imsg->data; |
1023 |
|
|
datalen = imsg->hdr.len - IMSG_HEADER_SIZE; |
1024 |
|
|
|
1025 |
|
|
switch (imsg->hdr.type) { |
1026 |
|
|
case MSG_IDENTIFY_FLAGS: |
1027 |
|
|
case MSG_IDENTIFY_TERM: |
1028 |
|
|
case MSG_IDENTIFY_TTYNAME: |
1029 |
|
|
case MSG_IDENTIFY_CWD: |
1030 |
|
|
case MSG_IDENTIFY_STDIN: |
1031 |
|
|
case MSG_IDENTIFY_ENVIRON: |
1032 |
|
|
case MSG_IDENTIFY_CLIENTPID: |
1033 |
|
|
case MSG_IDENTIFY_DONE: |
1034 |
|
|
server_client_dispatch_identify(c, imsg); |
1035 |
|
|
break; |
1036 |
|
|
case MSG_COMMAND: |
1037 |
|
|
server_client_dispatch_command(c, imsg); |
1038 |
|
|
break; |
1039 |
|
|
case MSG_STDIN: |
1040 |
|
|
if (datalen != sizeof stdindata) |
1041 |
|
|
fatalx("bad MSG_STDIN size"); |
1042 |
|
|
memcpy(&stdindata, data, sizeof stdindata); |
1043 |
|
|
|
1044 |
|
|
if (c->stdin_callback == NULL) |
1045 |
|
|
break; |
1046 |
|
|
if (stdindata.size <= 0) |
1047 |
|
|
c->stdin_closed = 1; |
1048 |
|
|
else { |
1049 |
|
|
evbuffer_add(c->stdin_data, stdindata.data, |
1050 |
|
|
stdindata.size); |
1051 |
|
|
} |
1052 |
|
|
c->stdin_callback(c, c->stdin_closed, |
1053 |
|
|
c->stdin_callback_data); |
1054 |
|
|
break; |
1055 |
|
|
case MSG_RESIZE: |
1056 |
|
|
if (datalen != 0) |
1057 |
|
|
fatalx("bad MSG_RESIZE size"); |
1058 |
|
|
|
1059 |
|
|
if (c->flags & CLIENT_CONTROL) |
1060 |
|
|
break; |
1061 |
|
|
if (tty_resize(&c->tty)) { |
1062 |
|
|
recalculate_sizes(); |
1063 |
|
|
server_redraw_client(c); |
1064 |
|
|
} |
1065 |
|
|
if (c->session != NULL) |
1066 |
|
|
hooks_run(c->session->hooks, c, NULL, "client-resized"); |
1067 |
|
|
break; |
1068 |
|
|
case MSG_EXITING: |
1069 |
|
|
if (datalen != 0) |
1070 |
|
|
fatalx("bad MSG_EXITING size"); |
1071 |
|
|
|
1072 |
|
|
c->session = NULL; |
1073 |
|
|
tty_close(&c->tty); |
1074 |
|
|
proc_send(c->peer, MSG_EXITED, -1, NULL, 0); |
1075 |
|
|
break; |
1076 |
|
|
case MSG_WAKEUP: |
1077 |
|
|
case MSG_UNLOCK: |
1078 |
|
|
if (datalen != 0) |
1079 |
|
|
fatalx("bad MSG_WAKEUP size"); |
1080 |
|
|
|
1081 |
|
|
if (!(c->flags & CLIENT_SUSPENDED)) |
1082 |
|
|
break; |
1083 |
|
|
c->flags &= ~CLIENT_SUSPENDED; |
1084 |
|
|
|
1085 |
|
|
if (c->tty.fd == -1) /* exited in the meantime */ |
1086 |
|
|
break; |
1087 |
|
|
s = c->session; |
1088 |
|
|
|
1089 |
|
|
if (gettimeofday(&c->activity_time, NULL) != 0) |
1090 |
|
|
fatal("gettimeofday failed"); |
1091 |
|
|
|
1092 |
|
|
tty_start_tty(&c->tty); |
1093 |
|
|
server_redraw_client(c); |
1094 |
|
|
recalculate_sizes(); |
1095 |
|
|
|
1096 |
|
|
if (s != NULL) |
1097 |
|
|
session_update_activity(s, &c->activity_time); |
1098 |
|
|
break; |
1099 |
|
|
case MSG_SHELL: |
1100 |
|
|
if (datalen != 0) |
1101 |
|
|
fatalx("bad MSG_SHELL size"); |
1102 |
|
|
|
1103 |
|
|
server_client_dispatch_shell(c); |
1104 |
|
|
break; |
1105 |
|
|
} |
1106 |
|
|
} |
1107 |
|
|
|
1108 |
|
|
/* Handle command message. */ |
1109 |
|
|
void |
1110 |
|
|
server_client_dispatch_command(struct client *c, struct imsg *imsg) |
1111 |
|
|
{ |
1112 |
|
|
struct msg_command_data data; |
1113 |
|
|
char *buf; |
1114 |
|
|
size_t len; |
1115 |
|
|
struct cmd_list *cmdlist = NULL; |
1116 |
|
|
int argc; |
1117 |
|
|
char **argv, *cause; |
1118 |
|
|
|
1119 |
|
|
if (imsg->hdr.len - IMSG_HEADER_SIZE < sizeof data) |
1120 |
|
|
fatalx("bad MSG_COMMAND size"); |
1121 |
|
|
memcpy(&data, imsg->data, sizeof data); |
1122 |
|
|
|
1123 |
|
|
buf = (char *)imsg->data + sizeof data; |
1124 |
|
|
len = imsg->hdr.len - IMSG_HEADER_SIZE - sizeof data; |
1125 |
|
|
if (len > 0 && buf[len - 1] != '\0') |
1126 |
|
|
fatalx("bad MSG_COMMAND string"); |
1127 |
|
|
|
1128 |
|
|
argc = data.argc; |
1129 |
|
|
if (cmd_unpack_argv(buf, len, argc, &argv) != 0) { |
1130 |
|
|
cmdq_error(c->cmdq, "command too long"); |
1131 |
|
|
goto error; |
1132 |
|
|
} |
1133 |
|
|
|
1134 |
|
|
if (argc == 0) { |
1135 |
|
|
argc = 1; |
1136 |
|
|
argv = xcalloc(1, sizeof *argv); |
1137 |
|
|
*argv = xstrdup("new-session"); |
1138 |
|
|
} |
1139 |
|
|
|
1140 |
|
|
if ((cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause)) == NULL) { |
1141 |
|
|
cmdq_error(c->cmdq, "%s", cause); |
1142 |
|
|
cmd_free_argv(argc, argv); |
1143 |
|
|
goto error; |
1144 |
|
|
} |
1145 |
|
|
cmd_free_argv(argc, argv); |
1146 |
|
|
|
1147 |
|
|
if (c != cfg_client || cfg_finished) |
1148 |
|
|
cmdq_run(c->cmdq, cmdlist, NULL); |
1149 |
|
|
else |
1150 |
|
|
cmdq_append(c->cmdq, cmdlist, NULL); |
1151 |
|
|
cmd_list_free(cmdlist); |
1152 |
|
|
return; |
1153 |
|
|
|
1154 |
|
|
error: |
1155 |
|
|
if (cmdlist != NULL) |
1156 |
|
|
cmd_list_free(cmdlist); |
1157 |
|
|
|
1158 |
|
|
c->flags |= CLIENT_EXIT; |
1159 |
|
|
} |
1160 |
|
|
|
1161 |
|
|
/* Handle identify message. */ |
1162 |
|
|
void |
1163 |
|
|
server_client_dispatch_identify(struct client *c, struct imsg *imsg) |
1164 |
|
|
{ |
1165 |
|
|
const char *data, *home; |
1166 |
|
|
size_t datalen; |
1167 |
|
|
int flags; |
1168 |
|
|
|
1169 |
|
|
if (c->flags & CLIENT_IDENTIFIED) |
1170 |
|
|
fatalx("out-of-order identify message"); |
1171 |
|
|
|
1172 |
|
|
data = imsg->data; |
1173 |
|
|
datalen = imsg->hdr.len - IMSG_HEADER_SIZE; |
1174 |
|
|
|
1175 |
|
|
switch (imsg->hdr.type) { |
1176 |
|
|
case MSG_IDENTIFY_FLAGS: |
1177 |
|
|
if (datalen != sizeof flags) |
1178 |
|
|
fatalx("bad MSG_IDENTIFY_FLAGS size"); |
1179 |
|
|
memcpy(&flags, data, sizeof flags); |
1180 |
|
|
c->flags |= flags; |
1181 |
|
|
log_debug("client %p IDENTIFY_FLAGS %#x", c, flags); |
1182 |
|
|
break; |
1183 |
|
|
case MSG_IDENTIFY_TERM: |
1184 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1185 |
|
|
fatalx("bad MSG_IDENTIFY_TERM string"); |
1186 |
|
|
c->term = xstrdup(data); |
1187 |
|
|
log_debug("client %p IDENTIFY_TERM %s", c, data); |
1188 |
|
|
break; |
1189 |
|
|
case MSG_IDENTIFY_TTYNAME: |
1190 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1191 |
|
|
fatalx("bad MSG_IDENTIFY_TTYNAME string"); |
1192 |
|
|
c->ttyname = xstrdup(data); |
1193 |
|
|
log_debug("client %p IDENTIFY_TTYNAME %s", c, data); |
1194 |
|
|
break; |
1195 |
|
|
case MSG_IDENTIFY_CWD: |
1196 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1197 |
|
|
fatalx("bad MSG_IDENTIFY_CWD string"); |
1198 |
|
|
if (access(data, X_OK) == 0) |
1199 |
|
|
c->cwd = xstrdup(data); |
1200 |
|
|
else if ((home = find_home()) != NULL) |
1201 |
|
|
c->cwd = xstrdup(home); |
1202 |
|
|
else |
1203 |
|
|
c->cwd = xstrdup("/"); |
1204 |
|
|
log_debug("client %p IDENTIFY_CWD %s", c, data); |
1205 |
|
|
break; |
1206 |
|
|
case MSG_IDENTIFY_STDIN: |
1207 |
|
|
if (datalen != 0) |
1208 |
|
|
fatalx("bad MSG_IDENTIFY_STDIN size"); |
1209 |
|
|
c->fd = imsg->fd; |
1210 |
|
|
log_debug("client %p IDENTIFY_STDIN %d", c, imsg->fd); |
1211 |
|
|
break; |
1212 |
|
|
case MSG_IDENTIFY_ENVIRON: |
1213 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1214 |
|
|
fatalx("bad MSG_IDENTIFY_ENVIRON string"); |
1215 |
|
|
if (strchr(data, '=') != NULL) |
1216 |
|
|
environ_put(c->environ, data); |
1217 |
|
|
log_debug("client %p IDENTIFY_ENVIRON %s", c, data); |
1218 |
|
|
break; |
1219 |
|
|
case MSG_IDENTIFY_CLIENTPID: |
1220 |
|
|
if (datalen != sizeof c->pid) |
1221 |
|
|
fatalx("bad MSG_IDENTIFY_CLIENTPID size"); |
1222 |
|
|
memcpy(&c->pid, data, sizeof c->pid); |
1223 |
|
|
log_debug("client %p IDENTIFY_CLIENTPID %ld", c, (long)c->pid); |
1224 |
|
|
break; |
1225 |
|
|
default: |
1226 |
|
|
break; |
1227 |
|
|
} |
1228 |
|
|
|
1229 |
|
|
if (imsg->hdr.type != MSG_IDENTIFY_DONE) |
1230 |
|
|
return; |
1231 |
|
|
c->flags |= CLIENT_IDENTIFIED; |
1232 |
|
|
|
1233 |
|
|
if (c->flags & CLIENT_CONTROL) { |
1234 |
|
|
c->stdin_callback = control_callback; |
1235 |
|
|
|
1236 |
|
|
evbuffer_free(c->stderr_data); |
1237 |
|
|
c->stderr_data = c->stdout_data; |
1238 |
|
|
|
1239 |
|
|
if (c->flags & CLIENT_CONTROLCONTROL) |
1240 |
|
|
evbuffer_add_printf(c->stdout_data, "\033P1000p"); |
1241 |
|
|
proc_send(c->peer, MSG_STDIN, -1, NULL, 0); |
1242 |
|
|
|
1243 |
|
|
c->tty.fd = -1; |
1244 |
|
|
|
1245 |
|
|
close(c->fd); |
1246 |
|
|
c->fd = -1; |
1247 |
|
|
|
1248 |
|
|
return; |
1249 |
|
|
} |
1250 |
|
|
|
1251 |
|
|
if (c->fd == -1) |
1252 |
|
|
return; |
1253 |
|
|
if (tty_init(&c->tty, c, c->fd, c->term) != 0) { |
1254 |
|
|
close(c->fd); |
1255 |
|
|
c->fd = -1; |
1256 |
|
|
return; |
1257 |
|
|
} |
1258 |
|
|
if (c->flags & CLIENT_UTF8) |
1259 |
|
|
c->tty.flags |= TTY_UTF8; |
1260 |
|
|
if (c->flags & CLIENT_256COLOURS) |
1261 |
|
|
c->tty.term_flags |= TERM_256COLOURS; |
1262 |
|
|
|
1263 |
|
|
tty_resize(&c->tty); |
1264 |
|
|
|
1265 |
|
|
if (!(c->flags & CLIENT_CONTROL)) |
1266 |
|
|
c->flags |= CLIENT_TERMINAL; |
1267 |
|
|
} |
1268 |
|
|
|
1269 |
|
|
/* Handle shell message. */ |
1270 |
|
|
void |
1271 |
|
|
server_client_dispatch_shell(struct client *c) |
1272 |
|
|
{ |
1273 |
|
|
const char *shell; |
1274 |
|
|
|
1275 |
|
|
shell = options_get_string(global_s_options, "default-shell"); |
1276 |
|
|
if (*shell == '\0' || areshell(shell)) |
1277 |
|
|
shell = _PATH_BSHELL; |
1278 |
|
|
proc_send_s(c->peer, MSG_SHELL, shell); |
1279 |
|
|
|
1280 |
|
|
proc_kill_peer(c->peer); |
1281 |
|
|
} |
1282 |
|
|
|
1283 |
|
|
/* Event callback to push more stdout data if any left. */ |
1284 |
|
|
static void |
1285 |
|
|
server_client_stdout_cb(__unused int fd, __unused short events, void *arg) |
1286 |
|
|
{ |
1287 |
|
|
struct client *c = arg; |
1288 |
|
|
|
1289 |
|
|
if (~c->flags & CLIENT_DEAD) |
1290 |
|
|
server_client_push_stdout(c); |
1291 |
|
|
server_client_unref(c); |
1292 |
|
|
} |
1293 |
|
|
|
1294 |
|
|
/* Push stdout to client if possible. */ |
1295 |
|
|
void |
1296 |
|
|
server_client_push_stdout(struct client *c) |
1297 |
|
|
{ |
1298 |
|
|
struct msg_stdout_data data; |
1299 |
|
|
size_t sent, left; |
1300 |
|
|
|
1301 |
|
|
left = EVBUFFER_LENGTH(c->stdout_data); |
1302 |
|
|
while (left != 0) { |
1303 |
|
|
sent = left; |
1304 |
|
|
if (sent > sizeof data.data) |
1305 |
|
|
sent = sizeof data.data; |
1306 |
|
|
memcpy(data.data, EVBUFFER_DATA(c->stdout_data), sent); |
1307 |
|
|
data.size = sent; |
1308 |
|
|
|
1309 |
|
|
if (proc_send(c->peer, MSG_STDOUT, -1, &data, sizeof data) != 0) |
1310 |
|
|
break; |
1311 |
|
|
evbuffer_drain(c->stdout_data, sent); |
1312 |
|
|
|
1313 |
|
|
left = EVBUFFER_LENGTH(c->stdout_data); |
1314 |
|
|
log_debug("%s: client %p, sent %zu, left %zu", __func__, c, |
1315 |
|
|
sent, left); |
1316 |
|
|
} |
1317 |
|
|
if (left != 0) { |
1318 |
|
|
c->references++; |
1319 |
|
|
event_once(-1, EV_TIMEOUT, server_client_stdout_cb, c, NULL); |
1320 |
|
|
log_debug("%s: client %p, queued", __func__, c); |
1321 |
|
|
} |
1322 |
|
|
} |
1323 |
|
|
|
1324 |
|
|
/* Event callback to push more stderr data if any left. */ |
1325 |
|
|
static void |
1326 |
|
|
server_client_stderr_cb(__unused int fd, __unused short events, void *arg) |
1327 |
|
|
{ |
1328 |
|
|
struct client *c = arg; |
1329 |
|
|
|
1330 |
|
|
if (~c->flags & CLIENT_DEAD) |
1331 |
|
|
server_client_push_stderr(c); |
1332 |
|
|
server_client_unref(c); |
1333 |
|
|
} |
1334 |
|
|
|
1335 |
|
|
/* Push stderr to client if possible. */ |
1336 |
|
|
void |
1337 |
|
|
server_client_push_stderr(struct client *c) |
1338 |
|
|
{ |
1339 |
|
|
struct msg_stderr_data data; |
1340 |
|
|
size_t sent, left; |
1341 |
|
|
|
1342 |
|
|
if (c->stderr_data == c->stdout_data) { |
1343 |
|
|
server_client_push_stdout(c); |
1344 |
|
|
return; |
1345 |
|
|
} |
1346 |
|
|
|
1347 |
|
|
left = EVBUFFER_LENGTH(c->stderr_data); |
1348 |
|
|
while (left != 0) { |
1349 |
|
|
sent = left; |
1350 |
|
|
if (sent > sizeof data.data) |
1351 |
|
|
sent = sizeof data.data; |
1352 |
|
|
memcpy(data.data, EVBUFFER_DATA(c->stderr_data), sent); |
1353 |
|
|
data.size = sent; |
1354 |
|
|
|
1355 |
|
|
if (proc_send(c->peer, MSG_STDERR, -1, &data, sizeof data) != 0) |
1356 |
|
|
break; |
1357 |
|
|
evbuffer_drain(c->stderr_data, sent); |
1358 |
|
|
|
1359 |
|
|
left = EVBUFFER_LENGTH(c->stderr_data); |
1360 |
|
|
log_debug("%s: client %p, sent %zu, left %zu", __func__, c, |
1361 |
|
|
sent, left); |
1362 |
|
|
} |
1363 |
|
|
if (left != 0) { |
1364 |
|
|
c->references++; |
1365 |
|
|
event_once(-1, EV_TIMEOUT, server_client_stderr_cb, c, NULL); |
1366 |
|
|
log_debug("%s: client %p, queued", __func__, c); |
1367 |
|
|
} |
1368 |
|
|
} |