1 |
|
|
/* $OpenBSD: server-client.c,v 1.245 2017/10/16 19:30:53 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 |
|
|
static void server_client_free(int, short, void *); |
36 |
|
|
static void server_client_check_focus(struct window_pane *); |
37 |
|
|
static void server_client_check_resize(struct window_pane *); |
38 |
|
|
static key_code server_client_check_mouse(struct client *); |
39 |
|
|
static void server_client_repeat_timer(int, short, void *); |
40 |
|
|
static void server_client_click_timer(int, short, void *); |
41 |
|
|
static void server_client_check_exit(struct client *); |
42 |
|
|
static void server_client_check_redraw(struct client *); |
43 |
|
|
static void server_client_set_title(struct client *); |
44 |
|
|
static void server_client_reset_state(struct client *); |
45 |
|
|
static int server_client_assume_paste(struct session *); |
46 |
|
|
|
47 |
|
|
static void server_client_dispatch(struct imsg *, void *); |
48 |
|
|
static void server_client_dispatch_command(struct client *, struct imsg *); |
49 |
|
|
static void server_client_dispatch_identify(struct client *, struct imsg *); |
50 |
|
|
static void server_client_dispatch_shell(struct client *); |
51 |
|
|
|
52 |
|
|
/* Number of attached clients. */ |
53 |
|
|
u_int |
54 |
|
|
server_client_how_many(void) |
55 |
|
|
{ |
56 |
|
|
struct client *c; |
57 |
|
|
u_int n; |
58 |
|
|
|
59 |
|
|
n = 0; |
60 |
|
|
TAILQ_FOREACH(c, &clients, entry) { |
61 |
|
|
if (c->session != NULL && (~c->flags & CLIENT_DETACHING)) |
62 |
|
|
n++; |
63 |
|
|
} |
64 |
|
|
return (n); |
65 |
|
|
} |
66 |
|
|
|
67 |
|
|
/* Identify mode callback. */ |
68 |
|
|
static void |
69 |
|
|
server_client_callback_identify(__unused int fd, __unused short events, |
70 |
|
|
void *data) |
71 |
|
|
{ |
72 |
|
|
server_client_clear_identify(data, NULL); |
73 |
|
|
} |
74 |
|
|
|
75 |
|
|
/* Set identify mode on client. */ |
76 |
|
|
void |
77 |
|
|
server_client_set_identify(struct client *c, u_int delay) |
78 |
|
|
{ |
79 |
|
|
struct timeval tv; |
80 |
|
|
|
81 |
|
|
tv.tv_sec = delay / 1000; |
82 |
|
|
tv.tv_usec = (delay % 1000) * 1000L; |
83 |
|
|
|
84 |
|
|
if (event_initialized(&c->identify_timer)) |
85 |
|
|
evtimer_del(&c->identify_timer); |
86 |
|
|
evtimer_set(&c->identify_timer, server_client_callback_identify, c); |
87 |
|
|
if (delay != 0) |
88 |
|
|
evtimer_add(&c->identify_timer, &tv); |
89 |
|
|
|
90 |
|
|
c->flags |= CLIENT_IDENTIFY; |
91 |
|
|
c->tty.flags |= (TTY_FREEZE|TTY_NOCURSOR); |
92 |
|
|
server_redraw_client(c); |
93 |
|
|
} |
94 |
|
|
|
95 |
|
|
/* Clear identify mode on client. */ |
96 |
|
|
void |
97 |
|
|
server_client_clear_identify(struct client *c, struct window_pane *wp) |
98 |
|
|
{ |
99 |
|
|
if (~c->flags & CLIENT_IDENTIFY) |
100 |
|
|
return; |
101 |
|
|
c->flags &= ~CLIENT_IDENTIFY; |
102 |
|
|
|
103 |
|
|
if (c->identify_callback != NULL) |
104 |
|
|
c->identify_callback(c, wp); |
105 |
|
|
|
106 |
|
|
c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR); |
107 |
|
|
server_redraw_client(c); |
108 |
|
|
} |
109 |
|
|
|
110 |
|
|
/* Check if this client is inside this server. */ |
111 |
|
|
int |
112 |
|
|
server_client_check_nested(struct client *c) |
113 |
|
|
{ |
114 |
|
|
struct environ_entry *envent; |
115 |
|
|
struct window_pane *wp; |
116 |
|
|
|
117 |
|
|
envent = environ_find(c->environ, "TMUX"); |
118 |
|
|
if (envent == NULL || *envent->value == '\0') |
119 |
|
|
return (0); |
120 |
|
|
|
121 |
|
|
RB_FOREACH(wp, window_pane_tree, &all_window_panes) { |
122 |
|
|
if (strcmp(wp->tty, c->ttyname) == 0) |
123 |
|
|
return (1); |
124 |
|
|
} |
125 |
|
|
return (0); |
126 |
|
|
} |
127 |
|
|
|
128 |
|
|
/* Set client key table. */ |
129 |
|
|
void |
130 |
|
|
server_client_set_key_table(struct client *c, const char *name) |
131 |
|
|
{ |
132 |
|
|
if (name == NULL) |
133 |
|
|
name = server_client_get_key_table(c); |
134 |
|
|
|
135 |
|
|
key_bindings_unref_table(c->keytable); |
136 |
|
|
c->keytable = key_bindings_get_table(name, 1); |
137 |
|
|
c->keytable->references++; |
138 |
|
|
} |
139 |
|
|
|
140 |
|
|
/* Get default key table. */ |
141 |
|
|
const char * |
142 |
|
|
server_client_get_key_table(struct client *c) |
143 |
|
|
{ |
144 |
|
|
struct session *s = c->session; |
145 |
|
|
const char *name; |
146 |
|
|
|
147 |
|
|
if (s == NULL) |
148 |
|
|
return ("root"); |
149 |
|
|
|
150 |
|
|
name = options_get_string(s->options, "key-table"); |
151 |
|
|
if (*name == '\0') |
152 |
|
|
return ("root"); |
153 |
|
|
return (name); |
154 |
|
|
} |
155 |
|
|
|
156 |
|
|
/* Is this table the default key table? */ |
157 |
|
|
static int |
158 |
|
|
server_client_is_default_key_table(struct client *c, struct key_table *table) |
159 |
|
|
{ |
160 |
|
|
return (strcmp(table->name, server_client_get_key_table(c)) == 0); |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
/* Create a new client. */ |
164 |
|
|
void |
165 |
|
|
server_client_create(int fd) |
166 |
|
|
{ |
167 |
|
|
struct client *c; |
168 |
|
|
|
169 |
|
|
setblocking(fd, 0); |
170 |
|
|
|
171 |
|
|
c = xcalloc(1, sizeof *c); |
172 |
|
|
c->references = 1; |
173 |
|
|
c->peer = proc_add_peer(server_proc, fd, server_client_dispatch, c); |
174 |
|
|
|
175 |
|
|
if (gettimeofday(&c->creation_time, NULL) != 0) |
176 |
|
|
fatal("gettimeofday failed"); |
177 |
|
|
memcpy(&c->activity_time, &c->creation_time, sizeof c->activity_time); |
178 |
|
|
|
179 |
|
|
c->environ = environ_create(); |
180 |
|
|
|
181 |
|
|
c->fd = -1; |
182 |
|
|
c->cwd = NULL; |
183 |
|
|
|
184 |
|
|
TAILQ_INIT(&c->queue); |
185 |
|
|
|
186 |
|
|
c->stdin_data = evbuffer_new(); |
187 |
|
|
c->stdout_data = evbuffer_new(); |
188 |
|
|
c->stderr_data = evbuffer_new(); |
189 |
|
|
|
190 |
|
|
c->tty.fd = -1; |
191 |
|
|
c->title = NULL; |
192 |
|
|
|
193 |
|
|
c->session = NULL; |
194 |
|
|
c->last_session = NULL; |
195 |
|
|
c->tty.sx = 80; |
196 |
|
|
c->tty.sy = 24; |
197 |
|
|
|
198 |
|
|
screen_init(&c->status, c->tty.sx, 1, 0); |
199 |
|
|
|
200 |
|
|
c->message_string = NULL; |
201 |
|
|
TAILQ_INIT(&c->message_log); |
202 |
|
|
|
203 |
|
|
c->prompt_string = NULL; |
204 |
|
|
c->prompt_buffer = NULL; |
205 |
|
|
c->prompt_index = 0; |
206 |
|
|
|
207 |
|
|
c->flags |= CLIENT_FOCUSED; |
208 |
|
|
|
209 |
|
|
c->keytable = key_bindings_get_table("root", 1); |
210 |
|
|
c->keytable->references++; |
211 |
|
|
|
212 |
|
|
evtimer_set(&c->repeat_timer, server_client_repeat_timer, c); |
213 |
|
|
evtimer_set(&c->click_timer, server_client_click_timer, c); |
214 |
|
|
|
215 |
|
|
TAILQ_INSERT_TAIL(&clients, c, entry); |
216 |
|
|
log_debug("new client %p", c); |
217 |
|
|
} |
218 |
|
|
|
219 |
|
|
/* Open client terminal if needed. */ |
220 |
|
|
int |
221 |
|
|
server_client_open(struct client *c, char **cause) |
222 |
|
|
{ |
223 |
|
|
if (c->flags & CLIENT_CONTROL) |
224 |
|
|
return (0); |
225 |
|
|
|
226 |
|
|
if (strcmp(c->ttyname, "/dev/tty") == 0) { |
227 |
|
|
*cause = xstrdup("can't use /dev/tty"); |
228 |
|
|
return (-1); |
229 |
|
|
} |
230 |
|
|
|
231 |
|
|
if (!(c->flags & CLIENT_TERMINAL)) { |
232 |
|
|
*cause = xstrdup("not a terminal"); |
233 |
|
|
return (-1); |
234 |
|
|
} |
235 |
|
|
|
236 |
|
|
if (tty_open(&c->tty, cause) != 0) |
237 |
|
|
return (-1); |
238 |
|
|
|
239 |
|
|
return (0); |
240 |
|
|
} |
241 |
|
|
|
242 |
|
|
/* Lost a client. */ |
243 |
|
|
void |
244 |
|
|
server_client_lost(struct client *c) |
245 |
|
|
{ |
246 |
|
|
struct message_entry *msg, *msg1; |
247 |
|
|
|
248 |
|
|
c->flags |= CLIENT_DEAD; |
249 |
|
|
|
250 |
|
|
server_client_clear_identify(c, NULL); |
251 |
|
|
status_prompt_clear(c); |
252 |
|
|
status_message_clear(c); |
253 |
|
|
|
254 |
|
|
if (c->stdin_callback != NULL) |
255 |
|
|
c->stdin_callback(c, 1, c->stdin_callback_data); |
256 |
|
|
|
257 |
|
|
TAILQ_REMOVE(&clients, c, entry); |
258 |
|
|
log_debug("lost client %p", c); |
259 |
|
|
|
260 |
|
|
/* |
261 |
|
|
* If CLIENT_TERMINAL hasn't been set, then tty_init hasn't been called |
262 |
|
|
* and tty_free might close an unrelated fd. |
263 |
|
|
*/ |
264 |
|
|
if (c->flags & CLIENT_TERMINAL) |
265 |
|
|
tty_free(&c->tty); |
266 |
|
|
free(c->ttyname); |
267 |
|
|
free(c->term); |
268 |
|
|
|
269 |
|
|
evbuffer_free(c->stdin_data); |
270 |
|
|
evbuffer_free(c->stdout_data); |
271 |
|
|
if (c->stderr_data != c->stdout_data) |
272 |
|
|
evbuffer_free(c->stderr_data); |
273 |
|
|
|
274 |
|
|
if (event_initialized(&c->status_timer)) |
275 |
|
|
evtimer_del(&c->status_timer); |
276 |
|
|
screen_free(&c->status); |
277 |
|
|
if (c->old_status != NULL) { |
278 |
|
|
screen_free(c->old_status); |
279 |
|
|
free(c->old_status); |
280 |
|
|
} |
281 |
|
|
|
282 |
|
|
free(c->title); |
283 |
|
|
free((void *)c->cwd); |
284 |
|
|
|
285 |
|
|
evtimer_del(&c->repeat_timer); |
286 |
|
|
evtimer_del(&c->click_timer); |
287 |
|
|
|
288 |
|
|
key_bindings_unref_table(c->keytable); |
289 |
|
|
|
290 |
|
|
if (event_initialized(&c->identify_timer)) |
291 |
|
|
evtimer_del(&c->identify_timer); |
292 |
|
|
|
293 |
|
|
free(c->message_string); |
294 |
|
|
if (event_initialized(&c->message_timer)) |
295 |
|
|
evtimer_del(&c->message_timer); |
296 |
|
|
TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { |
297 |
|
|
free(msg->msg); |
298 |
|
|
TAILQ_REMOVE(&c->message_log, msg, entry); |
299 |
|
|
free(msg); |
300 |
|
|
} |
301 |
|
|
|
302 |
|
|
free(c->prompt_string); |
303 |
|
|
free(c->prompt_buffer); |
304 |
|
|
|
305 |
|
|
format_lost_client(c); |
306 |
|
|
environ_free(c->environ); |
307 |
|
|
|
308 |
|
|
proc_remove_peer(c->peer); |
309 |
|
|
c->peer = NULL; |
310 |
|
|
|
311 |
|
|
server_client_unref(c); |
312 |
|
|
|
313 |
|
|
server_add_accept(0); /* may be more file descriptors now */ |
314 |
|
|
|
315 |
|
|
recalculate_sizes(); |
316 |
|
|
server_check_unattached(); |
317 |
|
|
server_update_socket(); |
318 |
|
|
} |
319 |
|
|
|
320 |
|
|
/* Remove reference from a client. */ |
321 |
|
|
void |
322 |
|
|
server_client_unref(struct client *c) |
323 |
|
|
{ |
324 |
|
|
log_debug("unref client %p (%d references)", c, c->references); |
325 |
|
|
|
326 |
|
|
c->references--; |
327 |
|
|
if (c->references == 0) |
328 |
|
|
event_once(-1, EV_TIMEOUT, server_client_free, c, NULL); |
329 |
|
|
} |
330 |
|
|
|
331 |
|
|
/* Free dead client. */ |
332 |
|
|
static void |
333 |
|
|
server_client_free(__unused int fd, __unused short events, void *arg) |
334 |
|
|
{ |
335 |
|
|
struct client *c = arg; |
336 |
|
|
|
337 |
|
|
log_debug("free client %p (%d references)", c, c->references); |
338 |
|
|
|
339 |
|
|
if (!TAILQ_EMPTY(&c->queue)) |
340 |
|
|
fatalx("queue not empty"); |
341 |
|
|
|
342 |
|
|
if (c->references == 0) { |
343 |
|
|
free((void *)c->name); |
344 |
|
|
free(c); |
345 |
|
|
} |
346 |
|
|
} |
347 |
|
|
|
348 |
|
|
/* Suspend a client. */ |
349 |
|
|
void |
350 |
|
|
server_client_suspend(struct client *c) |
351 |
|
|
{ |
352 |
|
|
struct session *s = c->session; |
353 |
|
|
|
354 |
|
|
if (s == NULL || (c->flags & CLIENT_DETACHING)) |
355 |
|
|
return; |
356 |
|
|
|
357 |
|
|
tty_stop_tty(&c->tty); |
358 |
|
|
c->flags |= CLIENT_SUSPENDED; |
359 |
|
|
proc_send(c->peer, MSG_SUSPEND, -1, NULL, 0); |
360 |
|
|
} |
361 |
|
|
|
362 |
|
|
/* Detach a client. */ |
363 |
|
|
void |
364 |
|
|
server_client_detach(struct client *c, enum msgtype msgtype) |
365 |
|
|
{ |
366 |
|
|
struct session *s = c->session; |
367 |
|
|
|
368 |
|
|
if (s == NULL || (c->flags & CLIENT_DETACHING)) |
369 |
|
|
return; |
370 |
|
|
|
371 |
|
|
c->flags |= CLIENT_DETACHING; |
372 |
|
|
notify_client("client-detached", c); |
373 |
|
|
proc_send(c->peer, msgtype, -1, s->name, strlen(s->name) + 1); |
374 |
|
|
} |
375 |
|
|
|
376 |
|
|
/* Execute command to replace a client. */ |
377 |
|
|
void |
378 |
|
|
server_client_exec(struct client *c, const char *cmd) |
379 |
|
|
{ |
380 |
|
|
struct session *s = c->session; |
381 |
|
|
char *msg; |
382 |
|
|
const char *shell; |
383 |
|
|
size_t cmdsize, shellsize; |
384 |
|
|
|
385 |
|
|
if (*cmd == '\0') |
386 |
|
|
return; |
387 |
|
|
cmdsize = strlen(cmd) + 1; |
388 |
|
|
|
389 |
|
|
if (s != NULL) |
390 |
|
|
shell = options_get_string(s->options, "default-shell"); |
391 |
|
|
else |
392 |
|
|
shell = options_get_string(global_s_options, "default-shell"); |
393 |
|
|
shellsize = strlen(shell) + 1; |
394 |
|
|
|
395 |
|
|
msg = xmalloc(cmdsize + shellsize); |
396 |
|
|
memcpy(msg, cmd, cmdsize); |
397 |
|
|
memcpy(msg + cmdsize, shell, shellsize); |
398 |
|
|
|
399 |
|
|
proc_send(c->peer, MSG_EXEC, -1, msg, cmdsize + shellsize); |
400 |
|
|
free(msg); |
401 |
|
|
} |
402 |
|
|
|
403 |
|
|
/* Check for mouse keys. */ |
404 |
|
|
static key_code |
405 |
|
|
server_client_check_mouse(struct client *c) |
406 |
|
|
{ |
407 |
|
|
struct session *s = c->session; |
408 |
|
|
struct mouse_event *m = &c->tty.mouse; |
409 |
|
|
struct window *w; |
410 |
|
|
struct window_pane *wp; |
411 |
|
|
u_int x, y, b; |
412 |
|
|
int flag; |
413 |
|
|
key_code key; |
414 |
|
|
struct timeval tv; |
415 |
|
|
enum { NOTYPE, MOVE, DOWN, UP, DRAG, WHEEL, DOUBLE, TRIPLE } type; |
416 |
|
|
enum { NOWHERE, PANE, STATUS, BORDER } where; |
417 |
|
|
|
418 |
|
|
type = NOTYPE; |
419 |
|
|
where = NOWHERE; |
420 |
|
|
|
421 |
|
|
log_debug("mouse %02x at %u,%u (last %u,%u) (%d)", m->b, m->x, m->y, |
422 |
|
|
m->lx, m->ly, c->tty.mouse_drag_flag); |
423 |
|
|
|
424 |
|
|
/* What type of event is this? */ |
425 |
|
|
if ((m->sgr_type != ' ' && |
426 |
|
|
MOUSE_DRAG(m->sgr_b) && |
427 |
|
|
MOUSE_BUTTONS(m->sgr_b) == 3) || |
428 |
|
|
(m->sgr_type == ' ' && |
429 |
|
|
MOUSE_DRAG(m->b) && |
430 |
|
|
MOUSE_BUTTONS(m->b) == 3 && |
431 |
|
|
MOUSE_BUTTONS(m->lb) == 3)) { |
432 |
|
|
type = MOVE; |
433 |
|
|
x = m->x, y = m->y, b = 0; |
434 |
|
|
log_debug("move at %u,%u", x, y); |
435 |
|
|
} else if (MOUSE_DRAG(m->b)) { |
436 |
|
|
type = DRAG; |
437 |
|
|
if (c->tty.mouse_drag_flag) { |
438 |
|
|
x = m->x, y = m->y, b = m->b; |
439 |
|
|
log_debug("drag update at %u,%u", x, y); |
440 |
|
|
} else { |
441 |
|
|
x = m->lx, y = m->ly, b = m->lb; |
442 |
|
|
log_debug("drag start at %u,%u", x, y); |
443 |
|
|
} |
444 |
|
|
} else if (MOUSE_WHEEL(m->b)) { |
445 |
|
|
type = WHEEL; |
446 |
|
|
x = m->x, y = m->y, b = m->b; |
447 |
|
|
log_debug("wheel at %u,%u", x, y); |
448 |
|
|
} else if (MOUSE_RELEASE(m->b)) { |
449 |
|
|
type = UP; |
450 |
|
|
x = m->x, y = m->y, b = m->lb; |
451 |
|
|
log_debug("up at %u,%u", x, y); |
452 |
|
|
} else { |
453 |
|
|
if (c->flags & CLIENT_DOUBLECLICK) { |
454 |
|
|
evtimer_del(&c->click_timer); |
455 |
|
|
c->flags &= ~CLIENT_DOUBLECLICK; |
456 |
|
|
if (m->b == c->click_button) { |
457 |
|
|
type = DOUBLE; |
458 |
|
|
x = m->x, y = m->y, b = m->b; |
459 |
|
|
log_debug("double-click at %u,%u", x, y); |
460 |
|
|
flag = CLIENT_TRIPLECLICK; |
461 |
|
|
goto add_timer; |
462 |
|
|
} |
463 |
|
|
} else if (c->flags & CLIENT_TRIPLECLICK) { |
464 |
|
|
evtimer_del(&c->click_timer); |
465 |
|
|
c->flags &= ~CLIENT_TRIPLECLICK; |
466 |
|
|
if (m->b == c->click_button) { |
467 |
|
|
type = TRIPLE; |
468 |
|
|
x = m->x, y = m->y, b = m->b; |
469 |
|
|
log_debug("triple-click at %u,%u", x, y); |
470 |
|
|
goto have_event; |
471 |
|
|
} |
472 |
|
|
} |
473 |
|
|
|
474 |
|
|
type = DOWN; |
475 |
|
|
x = m->x, y = m->y, b = m->b; |
476 |
|
|
log_debug("down at %u,%u", x, y); |
477 |
|
|
flag = CLIENT_DOUBLECLICK; |
478 |
|
|
|
479 |
|
|
add_timer: |
480 |
|
|
if (KEYC_CLICK_TIMEOUT != 0) { |
481 |
|
|
c->flags |= flag; |
482 |
|
|
c->click_button = m->b; |
483 |
|
|
|
484 |
|
|
tv.tv_sec = KEYC_CLICK_TIMEOUT / 1000; |
485 |
|
|
tv.tv_usec = (KEYC_CLICK_TIMEOUT % 1000) * 1000L; |
486 |
|
|
evtimer_del(&c->click_timer); |
487 |
|
|
evtimer_add(&c->click_timer, &tv); |
488 |
|
|
} |
489 |
|
|
} |
490 |
|
|
|
491 |
|
|
have_event: |
492 |
|
|
if (type == NOTYPE) |
493 |
|
|
return (KEYC_UNKNOWN); |
494 |
|
|
|
495 |
|
|
/* Always save the session. */ |
496 |
|
|
m->s = s->id; |
497 |
|
|
|
498 |
|
|
/* Is this on the status line? */ |
499 |
|
|
m->statusat = status_at_line(c); |
500 |
|
|
if (m->statusat != -1 && y == (u_int)m->statusat) { |
501 |
|
|
w = status_get_window_at(c, x); |
502 |
|
|
if (w == NULL) |
503 |
|
|
return (KEYC_UNKNOWN); |
504 |
|
|
m->w = w->id; |
505 |
|
|
where = STATUS; |
506 |
|
|
} else |
507 |
|
|
m->w = -1; |
508 |
|
|
|
509 |
|
|
/* Not on status line. Adjust position and check for border or pane. */ |
510 |
|
|
if (where == NOWHERE) { |
511 |
|
|
if (m->statusat == 0 && y > 0) |
512 |
|
|
y--; |
513 |
|
|
else if (m->statusat > 0 && y >= (u_int)m->statusat) |
514 |
|
|
y = m->statusat - 1; |
515 |
|
|
|
516 |
|
|
TAILQ_FOREACH(wp, &s->curw->window->panes, entry) { |
517 |
|
|
if ((wp->xoff + wp->sx == x && |
518 |
|
|
wp->yoff <= 1 + y && |
519 |
|
|
wp->yoff + wp->sy >= y) || |
520 |
|
|
(wp->yoff + wp->sy == y && |
521 |
|
|
wp->xoff <= 1 + x && |
522 |
|
|
wp->xoff + wp->sx >= x)) |
523 |
|
|
break; |
524 |
|
|
} |
525 |
|
|
if (wp != NULL) |
526 |
|
|
where = BORDER; |
527 |
|
|
else { |
528 |
|
|
wp = window_get_active_at(s->curw->window, x, y); |
529 |
|
|
if (wp != NULL) { |
530 |
|
|
where = PANE; |
531 |
|
|
log_debug("mouse at %u,%u is on pane %%%u", |
532 |
|
|
x, y, wp->id); |
533 |
|
|
} |
534 |
|
|
} |
535 |
|
|
if (where == NOWHERE) |
536 |
|
|
return (KEYC_UNKNOWN); |
537 |
|
|
m->wp = wp->id; |
538 |
|
|
m->w = wp->window->id; |
539 |
|
|
} else |
540 |
|
|
m->wp = -1; |
541 |
|
|
|
542 |
|
|
/* Stop dragging if needed. */ |
543 |
|
|
if (type != DRAG && type != WHEEL && c->tty.mouse_drag_flag) { |
544 |
|
|
if (c->tty.mouse_drag_release != NULL) |
545 |
|
|
c->tty.mouse_drag_release(c, m); |
546 |
|
|
|
547 |
|
|
c->tty.mouse_drag_update = NULL; |
548 |
|
|
c->tty.mouse_drag_release = NULL; |
549 |
|
|
|
550 |
|
|
/* |
551 |
|
|
* End a mouse drag by passing a MouseDragEnd key corresponding |
552 |
|
|
* to the button that started the drag. |
553 |
|
|
*/ |
554 |
|
|
switch (c->tty.mouse_drag_flag) { |
555 |
|
|
case 1: |
556 |
|
|
if (where == PANE) |
557 |
|
|
key = KEYC_MOUSEDRAGEND1_PANE; |
558 |
|
|
if (where == STATUS) |
559 |
|
|
key = KEYC_MOUSEDRAGEND1_STATUS; |
560 |
|
|
if (where == BORDER) |
561 |
|
|
key = KEYC_MOUSEDRAGEND1_BORDER; |
562 |
|
|
break; |
563 |
|
|
case 2: |
564 |
|
|
if (where == PANE) |
565 |
|
|
key = KEYC_MOUSEDRAGEND2_PANE; |
566 |
|
|
if (where == STATUS) |
567 |
|
|
key = KEYC_MOUSEDRAGEND2_STATUS; |
568 |
|
|
if (where == BORDER) |
569 |
|
|
key = KEYC_MOUSEDRAGEND2_BORDER; |
570 |
|
|
break; |
571 |
|
|
case 3: |
572 |
|
|
if (where == PANE) |
573 |
|
|
key = KEYC_MOUSEDRAGEND3_PANE; |
574 |
|
|
if (where == STATUS) |
575 |
|
|
key = KEYC_MOUSEDRAGEND3_STATUS; |
576 |
|
|
if (where == BORDER) |
577 |
|
|
key = KEYC_MOUSEDRAGEND3_BORDER; |
578 |
|
|
break; |
579 |
|
|
default: |
580 |
|
|
key = KEYC_MOUSE; |
581 |
|
|
break; |
582 |
|
|
} |
583 |
|
|
c->tty.mouse_drag_flag = 0; |
584 |
|
|
|
585 |
|
|
return (key); |
586 |
|
|
} |
587 |
|
|
|
588 |
|
|
/* Convert to a key binding. */ |
589 |
|
|
key = KEYC_UNKNOWN; |
590 |
|
|
switch (type) { |
591 |
|
|
case NOTYPE: |
592 |
|
|
break; |
593 |
|
|
case MOVE: |
594 |
|
|
if (where == PANE) |
595 |
|
|
key = KEYC_MOUSEMOVE_PANE; |
596 |
|
|
if (where == STATUS) |
597 |
|
|
key = KEYC_MOUSEMOVE_STATUS; |
598 |
|
|
if (where == BORDER) |
599 |
|
|
key = KEYC_MOUSEMOVE_BORDER; |
600 |
|
|
break; |
601 |
|
|
case DRAG: |
602 |
|
|
if (c->tty.mouse_drag_update != NULL) |
603 |
|
|
key = KEYC_DRAGGING; |
604 |
|
|
else { |
605 |
|
|
switch (MOUSE_BUTTONS(b)) { |
606 |
|
|
case 0: |
607 |
|
|
if (where == PANE) |
608 |
|
|
key = KEYC_MOUSEDRAG1_PANE; |
609 |
|
|
if (where == STATUS) |
610 |
|
|
key = KEYC_MOUSEDRAG1_STATUS; |
611 |
|
|
if (where == BORDER) |
612 |
|
|
key = KEYC_MOUSEDRAG1_BORDER; |
613 |
|
|
break; |
614 |
|
|
case 1: |
615 |
|
|
if (where == PANE) |
616 |
|
|
key = KEYC_MOUSEDRAG2_PANE; |
617 |
|
|
if (where == STATUS) |
618 |
|
|
key = KEYC_MOUSEDRAG2_STATUS; |
619 |
|
|
if (where == BORDER) |
620 |
|
|
key = KEYC_MOUSEDRAG2_BORDER; |
621 |
|
|
break; |
622 |
|
|
case 2: |
623 |
|
|
if (where == PANE) |
624 |
|
|
key = KEYC_MOUSEDRAG3_PANE; |
625 |
|
|
if (where == STATUS) |
626 |
|
|
key = KEYC_MOUSEDRAG3_STATUS; |
627 |
|
|
if (where == BORDER) |
628 |
|
|
key = KEYC_MOUSEDRAG3_BORDER; |
629 |
|
|
break; |
630 |
|
|
} |
631 |
|
|
} |
632 |
|
|
|
633 |
|
|
/* |
634 |
|
|
* Begin a drag by setting the flag to a non-zero value that |
635 |
|
|
* corresponds to the mouse button in use. |
636 |
|
|
*/ |
637 |
|
|
c->tty.mouse_drag_flag = MOUSE_BUTTONS(b) + 1; |
638 |
|
|
break; |
639 |
|
|
case WHEEL: |
640 |
|
|
if (MOUSE_BUTTONS(b) == MOUSE_WHEEL_UP) { |
641 |
|
|
if (where == PANE) |
642 |
|
|
key = KEYC_WHEELUP_PANE; |
643 |
|
|
if (where == STATUS) |
644 |
|
|
key = KEYC_WHEELUP_STATUS; |
645 |
|
|
if (where == BORDER) |
646 |
|
|
key = KEYC_WHEELUP_BORDER; |
647 |
|
|
} else { |
648 |
|
|
if (where == PANE) |
649 |
|
|
key = KEYC_WHEELDOWN_PANE; |
650 |
|
|
if (where == STATUS) |
651 |
|
|
key = KEYC_WHEELDOWN_STATUS; |
652 |
|
|
if (where == BORDER) |
653 |
|
|
key = KEYC_WHEELDOWN_BORDER; |
654 |
|
|
} |
655 |
|
|
break; |
656 |
|
|
case UP: |
657 |
|
|
switch (MOUSE_BUTTONS(b)) { |
658 |
|
|
case 0: |
659 |
|
|
if (where == PANE) |
660 |
|
|
key = KEYC_MOUSEUP1_PANE; |
661 |
|
|
if (where == STATUS) |
662 |
|
|
key = KEYC_MOUSEUP1_STATUS; |
663 |
|
|
if (where == BORDER) |
664 |
|
|
key = KEYC_MOUSEUP1_BORDER; |
665 |
|
|
break; |
666 |
|
|
case 1: |
667 |
|
|
if (where == PANE) |
668 |
|
|
key = KEYC_MOUSEUP2_PANE; |
669 |
|
|
if (where == STATUS) |
670 |
|
|
key = KEYC_MOUSEUP2_STATUS; |
671 |
|
|
if (where == BORDER) |
672 |
|
|
key = KEYC_MOUSEUP2_BORDER; |
673 |
|
|
break; |
674 |
|
|
case 2: |
675 |
|
|
if (where == PANE) |
676 |
|
|
key = KEYC_MOUSEUP3_PANE; |
677 |
|
|
if (where == STATUS) |
678 |
|
|
key = KEYC_MOUSEUP3_STATUS; |
679 |
|
|
if (where == BORDER) |
680 |
|
|
key = KEYC_MOUSEUP3_BORDER; |
681 |
|
|
break; |
682 |
|
|
} |
683 |
|
|
break; |
684 |
|
|
case DOWN: |
685 |
|
|
switch (MOUSE_BUTTONS(b)) { |
686 |
|
|
case 0: |
687 |
|
|
if (where == PANE) |
688 |
|
|
key = KEYC_MOUSEDOWN1_PANE; |
689 |
|
|
if (where == STATUS) |
690 |
|
|
key = KEYC_MOUSEDOWN1_STATUS; |
691 |
|
|
if (where == BORDER) |
692 |
|
|
key = KEYC_MOUSEDOWN1_BORDER; |
693 |
|
|
break; |
694 |
|
|
case 1: |
695 |
|
|
if (where == PANE) |
696 |
|
|
key = KEYC_MOUSEDOWN2_PANE; |
697 |
|
|
if (where == STATUS) |
698 |
|
|
key = KEYC_MOUSEDOWN2_STATUS; |
699 |
|
|
if (where == BORDER) |
700 |
|
|
key = KEYC_MOUSEDOWN2_BORDER; |
701 |
|
|
break; |
702 |
|
|
case 2: |
703 |
|
|
if (where == PANE) |
704 |
|
|
key = KEYC_MOUSEDOWN3_PANE; |
705 |
|
|
if (where == STATUS) |
706 |
|
|
key = KEYC_MOUSEDOWN3_STATUS; |
707 |
|
|
if (where == BORDER) |
708 |
|
|
key = KEYC_MOUSEDOWN3_BORDER; |
709 |
|
|
break; |
710 |
|
|
} |
711 |
|
|
break; |
712 |
|
|
case DOUBLE: |
713 |
|
|
switch (MOUSE_BUTTONS(b)) { |
714 |
|
|
case 0: |
715 |
|
|
if (where == PANE) |
716 |
|
|
key = KEYC_DOUBLECLICK1_PANE; |
717 |
|
|
if (where == STATUS) |
718 |
|
|
key = KEYC_DOUBLECLICK1_STATUS; |
719 |
|
|
if (where == BORDER) |
720 |
|
|
key = KEYC_DOUBLECLICK1_BORDER; |
721 |
|
|
break; |
722 |
|
|
case 1: |
723 |
|
|
if (where == PANE) |
724 |
|
|
key = KEYC_DOUBLECLICK2_PANE; |
725 |
|
|
if (where == STATUS) |
726 |
|
|
key = KEYC_DOUBLECLICK2_STATUS; |
727 |
|
|
if (where == BORDER) |
728 |
|
|
key = KEYC_DOUBLECLICK2_BORDER; |
729 |
|
|
break; |
730 |
|
|
case 2: |
731 |
|
|
if (where == PANE) |
732 |
|
|
key = KEYC_DOUBLECLICK3_PANE; |
733 |
|
|
if (where == STATUS) |
734 |
|
|
key = KEYC_DOUBLECLICK3_STATUS; |
735 |
|
|
if (where == BORDER) |
736 |
|
|
key = KEYC_DOUBLECLICK3_BORDER; |
737 |
|
|
break; |
738 |
|
|
} |
739 |
|
|
break; |
740 |
|
|
case TRIPLE: |
741 |
|
|
switch (MOUSE_BUTTONS(b)) { |
742 |
|
|
case 0: |
743 |
|
|
if (where == PANE) |
744 |
|
|
key = KEYC_TRIPLECLICK1_PANE; |
745 |
|
|
if (where == STATUS) |
746 |
|
|
key = KEYC_TRIPLECLICK1_STATUS; |
747 |
|
|
if (where == BORDER) |
748 |
|
|
key = KEYC_TRIPLECLICK1_BORDER; |
749 |
|
|
break; |
750 |
|
|
case 1: |
751 |
|
|
if (where == PANE) |
752 |
|
|
key = KEYC_TRIPLECLICK2_PANE; |
753 |
|
|
if (where == STATUS) |
754 |
|
|
key = KEYC_TRIPLECLICK2_STATUS; |
755 |
|
|
if (where == BORDER) |
756 |
|
|
key = KEYC_TRIPLECLICK2_BORDER; |
757 |
|
|
break; |
758 |
|
|
case 2: |
759 |
|
|
if (where == PANE) |
760 |
|
|
key = KEYC_TRIPLECLICK3_PANE; |
761 |
|
|
if (where == STATUS) |
762 |
|
|
key = KEYC_TRIPLECLICK3_STATUS; |
763 |
|
|
if (where == BORDER) |
764 |
|
|
key = KEYC_TRIPLECLICK3_BORDER; |
765 |
|
|
break; |
766 |
|
|
} |
767 |
|
|
break; |
768 |
|
|
} |
769 |
|
|
if (key == KEYC_UNKNOWN) |
770 |
|
|
return (KEYC_UNKNOWN); |
771 |
|
|
|
772 |
|
|
/* Apply modifiers if any. */ |
773 |
|
|
if (b & MOUSE_MASK_META) |
774 |
|
|
key |= KEYC_ESCAPE; |
775 |
|
|
if (b & MOUSE_MASK_CTRL) |
776 |
|
|
key |= KEYC_CTRL; |
777 |
|
|
if (b & MOUSE_MASK_SHIFT) |
778 |
|
|
key |= KEYC_SHIFT; |
779 |
|
|
|
780 |
|
|
return (key); |
781 |
|
|
} |
782 |
|
|
|
783 |
|
|
/* Is this fast enough to probably be a paste? */ |
784 |
|
|
static int |
785 |
|
|
server_client_assume_paste(struct session *s) |
786 |
|
|
{ |
787 |
|
|
struct timeval tv; |
788 |
|
|
int t; |
789 |
|
|
|
790 |
|
|
if ((t = options_get_number(s->options, "assume-paste-time")) == 0) |
791 |
|
|
return (0); |
792 |
|
|
|
793 |
|
|
timersub(&s->activity_time, &s->last_activity_time, &tv); |
794 |
|
|
if (tv.tv_sec == 0 && tv.tv_usec < t * 1000) { |
795 |
|
|
log_debug("session %s pasting (flag %d)", s->name, |
796 |
|
|
!!(s->flags & SESSION_PASTING)); |
797 |
|
|
if (s->flags & SESSION_PASTING) |
798 |
|
|
return (1); |
799 |
|
|
s->flags |= SESSION_PASTING; |
800 |
|
|
return (0); |
801 |
|
|
} |
802 |
|
|
log_debug("session %s not pasting", s->name); |
803 |
|
|
s->flags &= ~SESSION_PASTING; |
804 |
|
|
return (0); |
805 |
|
|
} |
806 |
|
|
|
807 |
|
|
/* Handle data key input from client. */ |
808 |
|
|
void |
809 |
|
|
server_client_handle_key(struct client *c, key_code key) |
810 |
|
|
{ |
811 |
|
|
struct mouse_event *m = &c->tty.mouse; |
812 |
|
|
struct session *s = c->session; |
813 |
|
|
struct window *w; |
814 |
|
|
struct window_pane *wp; |
815 |
|
|
struct timeval tv; |
816 |
|
|
struct key_table *table, *first; |
817 |
|
|
struct key_binding bd_find, *bd; |
818 |
|
|
int xtimeout, flags; |
819 |
|
|
struct cmd_find_state fs; |
820 |
|
|
key_code key0; |
821 |
|
|
|
822 |
|
|
/* Check the client is good to accept input. */ |
823 |
|
|
if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) |
824 |
|
|
return; |
825 |
|
|
w = s->curw->window; |
826 |
|
|
|
827 |
|
|
/* Update the activity timer. */ |
828 |
|
|
if (gettimeofday(&c->activity_time, NULL) != 0) |
829 |
|
|
fatal("gettimeofday failed"); |
830 |
|
|
session_update_activity(s, &c->activity_time); |
831 |
|
|
|
832 |
|
|
/* Number keys jump to pane in identify mode. */ |
833 |
|
|
if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') { |
834 |
|
|
if (c->flags & CLIENT_READONLY) |
835 |
|
|
return; |
836 |
|
|
window_unzoom(w); |
837 |
|
|
wp = window_pane_at_index(w, key - '0'); |
838 |
|
|
if (wp != NULL && !window_pane_visible(wp)) |
839 |
|
|
wp = NULL; |
840 |
|
|
server_client_clear_identify(c, wp); |
841 |
|
|
return; |
842 |
|
|
} |
843 |
|
|
|
844 |
|
|
/* Handle status line. */ |
845 |
|
|
if (!(c->flags & CLIENT_READONLY)) { |
846 |
|
|
status_message_clear(c); |
847 |
|
|
server_client_clear_identify(c, NULL); |
848 |
|
|
} |
849 |
|
|
if (c->prompt_string != NULL) { |
850 |
|
|
if (c->flags & CLIENT_READONLY) |
851 |
|
|
return; |
852 |
|
|
if (status_prompt_key(c, key) == 0) |
853 |
|
|
return; |
854 |
|
|
} |
855 |
|
|
|
856 |
|
|
/* Check for mouse keys. */ |
857 |
|
|
m->valid = 0; |
858 |
|
|
if (key == KEYC_MOUSE) { |
859 |
|
|
if (c->flags & CLIENT_READONLY) |
860 |
|
|
return; |
861 |
|
|
key = server_client_check_mouse(c); |
862 |
|
|
if (key == KEYC_UNKNOWN) |
863 |
|
|
return; |
864 |
|
|
|
865 |
|
|
m->valid = 1; |
866 |
|
|
m->key = key; |
867 |
|
|
|
868 |
|
|
/* |
869 |
|
|
* Mouse drag is in progress, so fire the callback (now that |
870 |
|
|
* the mouse event is valid). |
871 |
|
|
*/ |
872 |
|
|
if (key == KEYC_DRAGGING) { |
873 |
|
|
c->tty.mouse_drag_update(c, m); |
874 |
|
|
return; |
875 |
|
|
} |
876 |
|
|
} else |
877 |
|
|
m->valid = 0; |
878 |
|
|
|
879 |
|
|
/* Find affected pane. */ |
880 |
|
|
if (!KEYC_IS_MOUSE(key) || cmd_find_from_mouse(&fs, m, 0) != 0) |
881 |
|
|
cmd_find_from_session(&fs, s, 0); |
882 |
|
|
wp = fs.wp; |
883 |
|
|
|
884 |
|
|
/* Forward mouse keys if disabled. */ |
885 |
|
|
if (KEYC_IS_MOUSE(key) && !options_get_number(s->options, "mouse")) |
886 |
|
|
goto forward; |
887 |
|
|
|
888 |
|
|
/* Treat everything as a regular key when pasting is detected. */ |
889 |
|
|
if (!KEYC_IS_MOUSE(key) && server_client_assume_paste(s)) |
890 |
|
|
goto forward; |
891 |
|
|
|
892 |
|
|
/* |
893 |
|
|
* Work out the current key table. If the pane is in a mode, use |
894 |
|
|
* the mode table instead of the default key table. |
895 |
|
|
*/ |
896 |
|
|
if (server_client_is_default_key_table(c, c->keytable) && |
897 |
|
|
wp != NULL && |
898 |
|
|
wp->mode != NULL && |
899 |
|
|
wp->mode->key_table != NULL) |
900 |
|
|
table = key_bindings_get_table(wp->mode->key_table(wp), 1); |
901 |
|
|
else |
902 |
|
|
table = c->keytable; |
903 |
|
|
first = table; |
904 |
|
|
|
905 |
|
|
/* |
906 |
|
|
* The prefix always takes precedence and forces a switch to the prefix |
907 |
|
|
* table, unless we are already there. |
908 |
|
|
*/ |
909 |
|
|
key0 = (key & ~KEYC_XTERM); |
910 |
|
|
if ((key0 == (key_code)options_get_number(s->options, "prefix") || |
911 |
|
|
key0 == (key_code)options_get_number(s->options, "prefix2")) && |
912 |
|
|
strcmp(table->name, "prefix") != 0) { |
913 |
|
|
server_client_set_key_table(c, "prefix"); |
914 |
|
|
server_status_client(c); |
915 |
|
|
return; |
916 |
|
|
} |
917 |
|
|
flags = c->flags; |
918 |
|
|
|
919 |
|
|
retry: |
920 |
|
|
/* Log key table. */ |
921 |
|
|
if (wp == NULL) |
922 |
|
|
log_debug("key table %s (no pane)", table->name); |
923 |
|
|
else |
924 |
|
|
log_debug("key table %s (pane %%%u)", table->name, wp->id); |
925 |
|
|
if (c->flags & CLIENT_REPEAT) |
926 |
|
|
log_debug("currently repeating"); |
927 |
|
|
|
928 |
|
|
/* Try to see if there is a key binding in the current table. */ |
929 |
|
|
bd_find.key = key0; |
930 |
|
|
bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find); |
931 |
|
|
if (bd != NULL) { |
932 |
|
|
/* |
933 |
|
|
* Key was matched in this table. If currently repeating but a |
934 |
|
|
* non-repeating binding was found, stop repeating and try |
935 |
|
|
* again in the root table. |
936 |
|
|
*/ |
937 |
|
|
if ((c->flags & CLIENT_REPEAT) && |
938 |
|
|
(~bd->flags & KEY_BINDING_REPEAT)) { |
939 |
|
|
server_client_set_key_table(c, NULL); |
940 |
|
|
c->flags &= ~CLIENT_REPEAT; |
941 |
|
|
server_status_client(c); |
942 |
|
|
table = c->keytable; |
943 |
|
|
goto retry; |
944 |
|
|
} |
945 |
|
|
log_debug("found in key table %s", table->name); |
946 |
|
|
|
947 |
|
|
/* |
948 |
|
|
* Take a reference to this table to make sure the key binding |
949 |
|
|
* doesn't disappear. |
950 |
|
|
*/ |
951 |
|
|
table->references++; |
952 |
|
|
|
953 |
|
|
/* |
954 |
|
|
* If this is a repeating key, start the timer. Otherwise reset |
955 |
|
|
* the client back to the root table. |
956 |
|
|
*/ |
957 |
|
|
xtimeout = options_get_number(s->options, "repeat-time"); |
958 |
|
|
if (xtimeout != 0 && (bd->flags & KEY_BINDING_REPEAT)) { |
959 |
|
|
c->flags |= CLIENT_REPEAT; |
960 |
|
|
|
961 |
|
|
tv.tv_sec = xtimeout / 1000; |
962 |
|
|
tv.tv_usec = (xtimeout % 1000) * 1000L; |
963 |
|
|
evtimer_del(&c->repeat_timer); |
964 |
|
|
evtimer_add(&c->repeat_timer, &tv); |
965 |
|
|
} else { |
966 |
|
|
c->flags &= ~CLIENT_REPEAT; |
967 |
|
|
server_client_set_key_table(c, NULL); |
968 |
|
|
} |
969 |
|
|
server_status_client(c); |
970 |
|
|
|
971 |
|
|
/* Execute the key binding. */ |
972 |
|
|
key_bindings_dispatch(bd, NULL, c, m, &fs); |
973 |
|
|
key_bindings_unref_table(table); |
974 |
|
|
return; |
975 |
|
|
} |
976 |
|
|
|
977 |
|
|
/* |
978 |
|
|
* No match in this table. If not in the root table or if repeating, |
979 |
|
|
* switch the client back to the root table and try again. |
980 |
|
|
*/ |
981 |
|
|
log_debug("not found in key table %s", table->name); |
982 |
|
|
if (!server_client_is_default_key_table(c, table) || |
983 |
|
|
(c->flags & CLIENT_REPEAT)) { |
984 |
|
|
server_client_set_key_table(c, NULL); |
985 |
|
|
c->flags &= ~CLIENT_REPEAT; |
986 |
|
|
server_status_client(c); |
987 |
|
|
table = c->keytable; |
988 |
|
|
goto retry; |
989 |
|
|
} |
990 |
|
|
|
991 |
|
|
/* |
992 |
|
|
* No match in the root table either. If this wasn't the first table |
993 |
|
|
* tried, don't pass the key to the pane. |
994 |
|
|
*/ |
995 |
|
|
if (first != table && (~flags & CLIENT_REPEAT)) { |
996 |
|
|
server_client_set_key_table(c, NULL); |
997 |
|
|
server_status_client(c); |
998 |
|
|
return; |
999 |
|
|
} |
1000 |
|
|
|
1001 |
|
|
forward: |
1002 |
|
|
if (c->flags & CLIENT_READONLY) |
1003 |
|
|
return; |
1004 |
|
|
if (wp != NULL) |
1005 |
|
|
window_pane_key(wp, c, s, key, m); |
1006 |
|
|
} |
1007 |
|
|
|
1008 |
|
|
/* Client functions that need to happen every loop. */ |
1009 |
|
|
void |
1010 |
|
|
server_client_loop(void) |
1011 |
|
|
{ |
1012 |
|
|
struct client *c; |
1013 |
|
|
struct window *w; |
1014 |
|
|
struct window_pane *wp; |
1015 |
|
|
int focus; |
1016 |
|
|
|
1017 |
|
|
TAILQ_FOREACH(c, &clients, entry) { |
1018 |
|
|
server_client_check_exit(c); |
1019 |
|
|
if (c->session != NULL) { |
1020 |
|
|
server_client_check_redraw(c); |
1021 |
|
|
server_client_reset_state(c); |
1022 |
|
|
} |
1023 |
|
|
} |
1024 |
|
|
|
1025 |
|
|
/* |
1026 |
|
|
* Any windows will have been redrawn as part of clients, so clear |
1027 |
|
|
* their flags now. Also check pane focus and resize. |
1028 |
|
|
*/ |
1029 |
|
|
focus = options_get_number(global_options, "focus-events"); |
1030 |
|
|
RB_FOREACH(w, windows, &windows) { |
1031 |
|
|
TAILQ_FOREACH(wp, &w->panes, entry) { |
1032 |
|
|
if (wp->fd != -1) { |
1033 |
|
|
if (focus) |
1034 |
|
|
server_client_check_focus(wp); |
1035 |
|
|
server_client_check_resize(wp); |
1036 |
|
|
} |
1037 |
|
|
wp->flags &= ~PANE_REDRAW; |
1038 |
|
|
} |
1039 |
|
|
check_window_name(w); |
1040 |
|
|
} |
1041 |
|
|
} |
1042 |
|
|
|
1043 |
|
|
/* Check if we need to force a resize. */ |
1044 |
|
|
static int |
1045 |
|
|
server_client_resize_force(struct window_pane *wp) |
1046 |
|
|
{ |
1047 |
|
|
struct timeval tv = { .tv_usec = 100000 }; |
1048 |
|
|
struct winsize ws; |
1049 |
|
|
|
1050 |
|
|
/* |
1051 |
|
|
* If we are resizing to the same size as when we entered the loop |
1052 |
|
|
* (that is, to the same size the application currently thinks it is), |
1053 |
|
|
* tmux may have gone through several resizes internally and thrown |
1054 |
|
|
* away parts of the screen. So we need the application to actually |
1055 |
|
|
* redraw even though its final size has not changed. |
1056 |
|
|
*/ |
1057 |
|
|
|
1058 |
|
|
if (wp->flags & PANE_RESIZEFORCE) { |
1059 |
|
|
wp->flags &= ~PANE_RESIZEFORCE; |
1060 |
|
|
return (0); |
1061 |
|
|
} |
1062 |
|
|
|
1063 |
|
|
if (wp->sx != wp->osx || |
1064 |
|
|
wp->sy != wp->osy || |
1065 |
|
|
wp->sx <= 1 || |
1066 |
|
|
wp->sy <= 1) |
1067 |
|
|
return (0); |
1068 |
|
|
|
1069 |
|
|
memset(&ws, 0, sizeof ws); |
1070 |
|
|
ws.ws_col = wp->sx; |
1071 |
|
|
ws.ws_row = wp->sy - 1; |
1072 |
|
|
if (wp->fd != -1 && ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) |
1073 |
|
|
fatal("ioctl failed"); |
1074 |
|
|
log_debug("%s: %%%u forcing resize", __func__, wp->id); |
1075 |
|
|
|
1076 |
|
|
evtimer_add(&wp->resize_timer, &tv); |
1077 |
|
|
wp->flags |= PANE_RESIZEFORCE; |
1078 |
|
|
return (1); |
1079 |
|
|
} |
1080 |
|
|
|
1081 |
|
|
/* Resize timer event. */ |
1082 |
|
|
static void |
1083 |
|
|
server_client_resize_event(__unused int fd, __unused short events, void *data) |
1084 |
|
|
{ |
1085 |
|
|
struct window_pane *wp = data; |
1086 |
|
|
struct winsize ws; |
1087 |
|
|
|
1088 |
|
|
evtimer_del(&wp->resize_timer); |
1089 |
|
|
|
1090 |
|
|
if (!(wp->flags & PANE_RESIZE)) |
1091 |
|
|
return; |
1092 |
|
|
if (server_client_resize_force(wp)) |
1093 |
|
|
return; |
1094 |
|
|
|
1095 |
|
|
memset(&ws, 0, sizeof ws); |
1096 |
|
|
ws.ws_col = wp->sx; |
1097 |
|
|
ws.ws_row = wp->sy; |
1098 |
|
|
if (wp->fd != -1 && ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) |
1099 |
|
|
fatal("ioctl failed"); |
1100 |
|
|
log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, wp->sy); |
1101 |
|
|
|
1102 |
|
|
wp->flags &= ~PANE_RESIZE; |
1103 |
|
|
|
1104 |
|
|
wp->osx = wp->sx; |
1105 |
|
|
wp->osy = wp->sy; |
1106 |
|
|
} |
1107 |
|
|
|
1108 |
|
|
/* Check if pane should be resized. */ |
1109 |
|
|
static void |
1110 |
|
|
server_client_check_resize(struct window_pane *wp) |
1111 |
|
|
{ |
1112 |
|
|
struct timeval tv = { .tv_usec = 250000 }; |
1113 |
|
|
|
1114 |
|
|
if (!(wp->flags & PANE_RESIZE)) |
1115 |
|
|
return; |
1116 |
|
|
log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, wp->sx, wp->sy); |
1117 |
|
|
|
1118 |
|
|
if (!event_initialized(&wp->resize_timer)) |
1119 |
|
|
evtimer_set(&wp->resize_timer, server_client_resize_event, wp); |
1120 |
|
|
|
1121 |
|
|
/* |
1122 |
|
|
* The first resize should happen immediately, so if the timer is not |
1123 |
|
|
* running, do it now. |
1124 |
|
|
*/ |
1125 |
|
|
if (!evtimer_pending(&wp->resize_timer, NULL)) |
1126 |
|
|
server_client_resize_event(-1, 0, wp); |
1127 |
|
|
|
1128 |
|
|
/* |
1129 |
|
|
* If the pane is in the alternate screen, let the timer expire and |
1130 |
|
|
* resize to give the application a chance to redraw. If not, keep |
1131 |
|
|
* pushing the timer back. |
1132 |
|
|
*/ |
1133 |
|
|
if (wp->saved_grid != NULL && evtimer_pending(&wp->resize_timer, NULL)) |
1134 |
|
|
return; |
1135 |
|
|
evtimer_del(&wp->resize_timer); |
1136 |
|
|
evtimer_add(&wp->resize_timer, &tv); |
1137 |
|
|
} |
1138 |
|
|
|
1139 |
|
|
/* Check whether pane should be focused. */ |
1140 |
|
|
static void |
1141 |
|
|
server_client_check_focus(struct window_pane *wp) |
1142 |
|
|
{ |
1143 |
|
|
struct client *c; |
1144 |
|
|
int push; |
1145 |
|
|
|
1146 |
|
|
/* Do we need to push the focus state? */ |
1147 |
|
|
push = wp->flags & PANE_FOCUSPUSH; |
1148 |
|
|
wp->flags &= ~PANE_FOCUSPUSH; |
1149 |
|
|
|
1150 |
|
|
/* If we don't care about focus, forget it. */ |
1151 |
|
|
if (!(wp->base.mode & MODE_FOCUSON)) |
1152 |
|
|
return; |
1153 |
|
|
|
1154 |
|
|
/* If we're not the active pane in our window, we're not focused. */ |
1155 |
|
|
if (wp->window->active != wp) |
1156 |
|
|
goto not_focused; |
1157 |
|
|
|
1158 |
|
|
/* If we're in a mode, we're not focused. */ |
1159 |
|
|
if (wp->screen != &wp->base) |
1160 |
|
|
goto not_focused; |
1161 |
|
|
|
1162 |
|
|
/* |
1163 |
|
|
* If our window is the current window in any focused clients with an |
1164 |
|
|
* attached session, we're focused. |
1165 |
|
|
*/ |
1166 |
|
|
TAILQ_FOREACH(c, &clients, entry) { |
1167 |
|
|
if (c->session == NULL || !(c->flags & CLIENT_FOCUSED)) |
1168 |
|
|
continue; |
1169 |
|
|
if (c->session->flags & SESSION_UNATTACHED) |
1170 |
|
|
continue; |
1171 |
|
|
|
1172 |
|
|
if (c->session->curw->window == wp->window) |
1173 |
|
|
goto focused; |
1174 |
|
|
} |
1175 |
|
|
|
1176 |
|
|
not_focused: |
1177 |
|
|
if (push || (wp->flags & PANE_FOCUSED)) |
1178 |
|
|
bufferevent_write(wp->event, "\033[O", 3); |
1179 |
|
|
wp->flags &= ~PANE_FOCUSED; |
1180 |
|
|
return; |
1181 |
|
|
|
1182 |
|
|
focused: |
1183 |
|
|
if (push || !(wp->flags & PANE_FOCUSED)) |
1184 |
|
|
bufferevent_write(wp->event, "\033[I", 3); |
1185 |
|
|
wp->flags |= PANE_FOCUSED; |
1186 |
|
|
} |
1187 |
|
|
|
1188 |
|
|
/* |
1189 |
|
|
* Update cursor position and mode settings. The scroll region and attributes |
1190 |
|
|
* are cleared when idle (waiting for an event) as this is the most likely time |
1191 |
|
|
* a user may interrupt tmux, for example with ~^Z in ssh(1). This is a |
1192 |
|
|
* compromise between excessive resets and likelihood of an interrupt. |
1193 |
|
|
* |
1194 |
|
|
* tty_region/tty_reset/tty_update_mode already take care of not resetting |
1195 |
|
|
* things that are already in their default state. |
1196 |
|
|
*/ |
1197 |
|
|
static void |
1198 |
|
|
server_client_reset_state(struct client *c) |
1199 |
|
|
{ |
1200 |
|
|
struct window *w = c->session->curw->window; |
1201 |
|
|
struct window_pane *wp = w->active, *loop; |
1202 |
|
|
struct screen *s = wp->screen; |
1203 |
|
|
struct options *oo = c->session->options; |
1204 |
|
|
int lines, mode; |
1205 |
|
|
|
1206 |
|
|
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) |
1207 |
|
|
return; |
1208 |
|
|
|
1209 |
|
|
tty_region_off(&c->tty); |
1210 |
|
|
tty_margin_off(&c->tty); |
1211 |
|
|
|
1212 |
|
|
if (status_at_line(c) != 0) |
1213 |
|
|
lines = 0; |
1214 |
|
|
else |
1215 |
|
|
lines = status_line_size(c->session); |
1216 |
|
|
if (!window_pane_visible(wp) || wp->yoff + s->cy >= c->tty.sy - lines) |
1217 |
|
|
tty_cursor(&c->tty, 0, 0); |
1218 |
|
|
else |
1219 |
|
|
tty_cursor(&c->tty, wp->xoff + s->cx, lines + wp->yoff + s->cy); |
1220 |
|
|
|
1221 |
|
|
/* |
1222 |
|
|
* Set mouse mode if requested. To support dragging, always use button |
1223 |
|
|
* mode. |
1224 |
|
|
*/ |
1225 |
|
|
mode = s->mode; |
1226 |
|
|
if (options_get_number(oo, "mouse")) { |
1227 |
|
|
mode &= ~ALL_MOUSE_MODES; |
1228 |
|
|
TAILQ_FOREACH(loop, &w->panes, entry) { |
1229 |
|
|
if (loop->screen->mode & MODE_MOUSE_ALL) |
1230 |
|
|
mode |= MODE_MOUSE_ALL; |
1231 |
|
|
} |
1232 |
|
|
if (~mode & MODE_MOUSE_ALL) |
1233 |
|
|
mode |= MODE_MOUSE_BUTTON; |
1234 |
|
|
} |
1235 |
|
|
|
1236 |
|
|
/* Clear bracketed paste mode if at the prompt. */ |
1237 |
|
|
if (c->prompt_string != NULL) |
1238 |
|
|
mode &= ~MODE_BRACKETPASTE; |
1239 |
|
|
|
1240 |
|
|
/* Set the terminal mode and reset attributes. */ |
1241 |
|
|
tty_update_mode(&c->tty, mode, s); |
1242 |
|
|
tty_reset(&c->tty); |
1243 |
|
|
} |
1244 |
|
|
|
1245 |
|
|
/* Repeat time callback. */ |
1246 |
|
|
static void |
1247 |
|
|
server_client_repeat_timer(__unused int fd, __unused short events, void *data) |
1248 |
|
|
{ |
1249 |
|
|
struct client *c = data; |
1250 |
|
|
|
1251 |
|
|
if (c->flags & CLIENT_REPEAT) { |
1252 |
|
|
server_client_set_key_table(c, NULL); |
1253 |
|
|
c->flags &= ~CLIENT_REPEAT; |
1254 |
|
|
server_status_client(c); |
1255 |
|
|
} |
1256 |
|
|
} |
1257 |
|
|
|
1258 |
|
|
/* Double-click callback. */ |
1259 |
|
|
static void |
1260 |
|
|
server_client_click_timer(__unused int fd, __unused short events, void *data) |
1261 |
|
|
{ |
1262 |
|
|
struct client *c = data; |
1263 |
|
|
|
1264 |
|
|
c->flags &= ~(CLIENT_DOUBLECLICK|CLIENT_TRIPLECLICK); |
1265 |
|
|
} |
1266 |
|
|
|
1267 |
|
|
/* Check if client should be exited. */ |
1268 |
|
|
static void |
1269 |
|
|
server_client_check_exit(struct client *c) |
1270 |
|
|
{ |
1271 |
|
|
if (!(c->flags & CLIENT_EXIT)) |
1272 |
|
|
return; |
1273 |
|
|
|
1274 |
|
|
if (EVBUFFER_LENGTH(c->stdin_data) != 0) |
1275 |
|
|
return; |
1276 |
|
|
if (EVBUFFER_LENGTH(c->stdout_data) != 0) |
1277 |
|
|
return; |
1278 |
|
|
if (EVBUFFER_LENGTH(c->stderr_data) != 0) |
1279 |
|
|
return; |
1280 |
|
|
|
1281 |
|
|
proc_send(c->peer, MSG_EXIT, -1, &c->retval, sizeof c->retval); |
1282 |
|
|
c->flags &= ~CLIENT_EXIT; |
1283 |
|
|
} |
1284 |
|
|
|
1285 |
|
|
/* Redraw timer callback. */ |
1286 |
|
|
static void |
1287 |
|
|
server_client_redraw_timer(__unused int fd, __unused short events, |
1288 |
|
|
__unused void* data) |
1289 |
|
|
{ |
1290 |
|
|
log_debug("redraw timer fired"); |
1291 |
|
|
} |
1292 |
|
|
|
1293 |
|
|
/* Check for client redraws. */ |
1294 |
|
|
static void |
1295 |
|
|
server_client_check_redraw(struct client *c) |
1296 |
|
|
{ |
1297 |
|
|
struct session *s = c->session; |
1298 |
|
|
struct tty *tty = &c->tty; |
1299 |
|
|
struct window_pane *wp; |
1300 |
|
|
int needed, flags, masked; |
1301 |
|
|
struct timeval tv = { .tv_usec = 1000 }; |
1302 |
|
|
static struct event ev; |
1303 |
|
|
size_t left; |
1304 |
|
|
|
1305 |
|
|
if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) |
1306 |
|
|
return; |
1307 |
|
|
|
1308 |
|
|
/* |
1309 |
|
|
* If there is outstanding data, defer the redraw until it has been |
1310 |
|
|
* consumed. We can just add a timer to get out of the event loop and |
1311 |
|
|
* end up back here. |
1312 |
|
|
*/ |
1313 |
|
|
needed = 0; |
1314 |
|
|
if (c->flags & CLIENT_REDRAW) |
1315 |
|
|
needed = 1; |
1316 |
|
|
else { |
1317 |
|
|
TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { |
1318 |
|
|
if (wp->flags & PANE_REDRAW) { |
1319 |
|
|
needed = 1; |
1320 |
|
|
break; |
1321 |
|
|
} |
1322 |
|
|
} |
1323 |
|
|
} |
1324 |
|
|
if (needed && (left = EVBUFFER_LENGTH(tty->out)) != 0) { |
1325 |
|
|
log_debug("%s: redraw deferred (%zu left)", c->name, left); |
1326 |
|
|
if (!evtimer_initialized(&ev)) |
1327 |
|
|
evtimer_set(&ev, server_client_redraw_timer, NULL); |
1328 |
|
|
if (!evtimer_pending(&ev, NULL)) { |
1329 |
|
|
log_debug("redraw timer started"); |
1330 |
|
|
evtimer_add(&ev, &tv); |
1331 |
|
|
} |
1332 |
|
|
|
1333 |
|
|
/* |
1334 |
|
|
* We may have got here for a single pane redraw, but force a |
1335 |
|
|
* full redraw next time in case other panes have been updated. |
1336 |
|
|
*/ |
1337 |
|
|
c->flags |= CLIENT_REDRAW; |
1338 |
|
|
return; |
1339 |
|
|
} else if (needed) |
1340 |
|
|
log_debug("%s: redraw needed", c->name); |
1341 |
|
|
|
1342 |
|
|
if (c->flags & (CLIENT_REDRAW|CLIENT_STATUS)) { |
1343 |
|
|
if (options_get_number(s->options, "set-titles")) |
1344 |
|
|
server_client_set_title(c); |
1345 |
|
|
screen_redraw_update(c); /* will adjust flags */ |
1346 |
|
|
} |
1347 |
|
|
|
1348 |
|
|
flags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR); |
1349 |
|
|
tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE)) | TTY_NOCURSOR; |
1350 |
|
|
|
1351 |
|
|
if (c->flags & CLIENT_REDRAW) { |
1352 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
1353 |
|
|
screen_redraw_screen(c, 1, 1, 1); |
1354 |
|
|
c->flags &= ~(CLIENT_STATUS|CLIENT_BORDERS); |
1355 |
|
|
} else { |
1356 |
|
|
TAILQ_FOREACH(wp, &c->session->curw->window->panes, entry) { |
1357 |
|
|
if (wp->flags & PANE_REDRAW) { |
1358 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
1359 |
|
|
screen_redraw_pane(c, wp); |
1360 |
|
|
} |
1361 |
|
|
} |
1362 |
|
|
} |
1363 |
|
|
|
1364 |
|
|
masked = c->flags & (CLIENT_BORDERS|CLIENT_STATUS); |
1365 |
|
|
if (masked != 0) |
1366 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
1367 |
|
|
if (masked == CLIENT_BORDERS) |
1368 |
|
|
screen_redraw_screen(c, 0, 0, 1); |
1369 |
|
|
else if (masked == CLIENT_STATUS) |
1370 |
|
|
screen_redraw_screen(c, 0, 1, 0); |
1371 |
|
|
else if (masked != 0) |
1372 |
|
|
screen_redraw_screen(c, 0, 1, 1); |
1373 |
|
|
|
1374 |
|
|
tty->flags = (tty->flags & ~(TTY_FREEZE|TTY_NOCURSOR)) | flags; |
1375 |
|
|
tty_update_mode(tty, tty->mode, NULL); |
1376 |
|
|
|
1377 |
|
|
c->flags &= ~(CLIENT_REDRAW|CLIENT_BORDERS|CLIENT_STATUS| |
1378 |
|
|
CLIENT_STATUSFORCE); |
1379 |
|
|
|
1380 |
|
|
if (needed) { |
1381 |
|
|
/* |
1382 |
|
|
* We would have deferred the redraw unless the output buffer |
1383 |
|
|
* was empty, so we can record how many bytes the redraw |
1384 |
|
|
* generated. |
1385 |
|
|
*/ |
1386 |
|
|
c->redraw = EVBUFFER_LENGTH(tty->out); |
1387 |
|
|
log_debug("%s: redraw added %zu bytes", c->name, c->redraw); |
1388 |
|
|
} |
1389 |
|
|
} |
1390 |
|
|
|
1391 |
|
|
/* Set client title. */ |
1392 |
|
|
static void |
1393 |
|
|
server_client_set_title(struct client *c) |
1394 |
|
|
{ |
1395 |
|
|
struct session *s = c->session; |
1396 |
|
|
const char *template; |
1397 |
|
|
char *title; |
1398 |
|
|
struct format_tree *ft; |
1399 |
|
|
|
1400 |
|
|
template = options_get_string(s->options, "set-titles-string"); |
1401 |
|
|
|
1402 |
|
|
ft = format_create(c, NULL, FORMAT_NONE, 0); |
1403 |
|
|
format_defaults(ft, c, NULL, NULL, NULL); |
1404 |
|
|
|
1405 |
|
|
title = format_expand_time(ft, template, time(NULL)); |
1406 |
|
|
if (c->title == NULL || strcmp(title, c->title) != 0) { |
1407 |
|
|
free(c->title); |
1408 |
|
|
c->title = xstrdup(title); |
1409 |
|
|
tty_set_title(&c->tty, c->title); |
1410 |
|
|
} |
1411 |
|
|
free(title); |
1412 |
|
|
|
1413 |
|
|
format_free(ft); |
1414 |
|
|
} |
1415 |
|
|
|
1416 |
|
|
/* Dispatch message from client. */ |
1417 |
|
|
static void |
1418 |
|
|
server_client_dispatch(struct imsg *imsg, void *arg) |
1419 |
|
|
{ |
1420 |
|
|
struct client *c = arg; |
1421 |
|
|
struct msg_stdin_data stdindata; |
1422 |
|
|
const char *data; |
1423 |
|
|
ssize_t datalen; |
1424 |
|
|
struct session *s; |
1425 |
|
|
|
1426 |
|
|
if (c->flags & CLIENT_DEAD) |
1427 |
|
|
return; |
1428 |
|
|
|
1429 |
|
|
if (imsg == NULL) { |
1430 |
|
|
server_client_lost(c); |
1431 |
|
|
return; |
1432 |
|
|
} |
1433 |
|
|
|
1434 |
|
|
data = imsg->data; |
1435 |
|
|
datalen = imsg->hdr.len - IMSG_HEADER_SIZE; |
1436 |
|
|
|
1437 |
|
|
switch (imsg->hdr.type) { |
1438 |
|
|
case MSG_IDENTIFY_FLAGS: |
1439 |
|
|
case MSG_IDENTIFY_TERM: |
1440 |
|
|
case MSG_IDENTIFY_TTYNAME: |
1441 |
|
|
case MSG_IDENTIFY_CWD: |
1442 |
|
|
case MSG_IDENTIFY_STDIN: |
1443 |
|
|
case MSG_IDENTIFY_ENVIRON: |
1444 |
|
|
case MSG_IDENTIFY_CLIENTPID: |
1445 |
|
|
case MSG_IDENTIFY_DONE: |
1446 |
|
|
server_client_dispatch_identify(c, imsg); |
1447 |
|
|
break; |
1448 |
|
|
case MSG_COMMAND: |
1449 |
|
|
server_client_dispatch_command(c, imsg); |
1450 |
|
|
break; |
1451 |
|
|
case MSG_STDIN: |
1452 |
|
|
if (datalen != sizeof stdindata) |
1453 |
|
|
fatalx("bad MSG_STDIN size"); |
1454 |
|
|
memcpy(&stdindata, data, sizeof stdindata); |
1455 |
|
|
|
1456 |
|
|
if (c->stdin_callback == NULL) |
1457 |
|
|
break; |
1458 |
|
|
if (stdindata.size <= 0) |
1459 |
|
|
c->stdin_closed = 1; |
1460 |
|
|
else { |
1461 |
|
|
evbuffer_add(c->stdin_data, stdindata.data, |
1462 |
|
|
stdindata.size); |
1463 |
|
|
} |
1464 |
|
|
c->stdin_callback(c, c->stdin_closed, |
1465 |
|
|
c->stdin_callback_data); |
1466 |
|
|
break; |
1467 |
|
|
case MSG_RESIZE: |
1468 |
|
|
if (datalen != 0) |
1469 |
|
|
fatalx("bad MSG_RESIZE size"); |
1470 |
|
|
|
1471 |
|
|
if (c->flags & CLIENT_CONTROL) |
1472 |
|
|
break; |
1473 |
|
|
tty_resize(&c->tty); |
1474 |
|
|
recalculate_sizes(); |
1475 |
|
|
server_redraw_client(c); |
1476 |
|
|
if (c->session != NULL) |
1477 |
|
|
notify_client("client-resized", c); |
1478 |
|
|
break; |
1479 |
|
|
case MSG_EXITING: |
1480 |
|
|
if (datalen != 0) |
1481 |
|
|
fatalx("bad MSG_EXITING size"); |
1482 |
|
|
|
1483 |
|
|
c->session = NULL; |
1484 |
|
|
tty_close(&c->tty); |
1485 |
|
|
proc_send(c->peer, MSG_EXITED, -1, NULL, 0); |
1486 |
|
|
break; |
1487 |
|
|
case MSG_WAKEUP: |
1488 |
|
|
case MSG_UNLOCK: |
1489 |
|
|
if (datalen != 0) |
1490 |
|
|
fatalx("bad MSG_WAKEUP size"); |
1491 |
|
|
|
1492 |
|
|
if (!(c->flags & CLIENT_SUSPENDED)) |
1493 |
|
|
break; |
1494 |
|
|
c->flags &= ~CLIENT_SUSPENDED; |
1495 |
|
|
|
1496 |
|
|
if (c->tty.fd == -1) /* exited in the meantime */ |
1497 |
|
|
break; |
1498 |
|
|
s = c->session; |
1499 |
|
|
|
1500 |
|
|
if (gettimeofday(&c->activity_time, NULL) != 0) |
1501 |
|
|
fatal("gettimeofday failed"); |
1502 |
|
|
|
1503 |
|
|
tty_start_tty(&c->tty); |
1504 |
|
|
server_redraw_client(c); |
1505 |
|
|
recalculate_sizes(); |
1506 |
|
|
|
1507 |
|
|
if (s != NULL) |
1508 |
|
|
session_update_activity(s, &c->activity_time); |
1509 |
|
|
break; |
1510 |
|
|
case MSG_SHELL: |
1511 |
|
|
if (datalen != 0) |
1512 |
|
|
fatalx("bad MSG_SHELL size"); |
1513 |
|
|
|
1514 |
|
|
server_client_dispatch_shell(c); |
1515 |
|
|
break; |
1516 |
|
|
} |
1517 |
|
|
} |
1518 |
|
|
|
1519 |
|
|
/* Callback when command is done. */ |
1520 |
|
|
static enum cmd_retval |
1521 |
|
|
server_client_command_done(struct cmdq_item *item, __unused void *data) |
1522 |
|
|
{ |
1523 |
|
|
struct client *c = item->client; |
1524 |
|
|
|
1525 |
|
|
if (~c->flags & CLIENT_ATTACHED) |
1526 |
|
|
c->flags |= CLIENT_EXIT; |
1527 |
|
|
return (CMD_RETURN_NORMAL); |
1528 |
|
|
} |
1529 |
|
|
|
1530 |
|
|
/* Show an error message. */ |
1531 |
|
|
static enum cmd_retval |
1532 |
|
|
server_client_command_error(struct cmdq_item *item, void *data) |
1533 |
|
|
{ |
1534 |
|
|
char *error = data; |
1535 |
|
|
|
1536 |
|
|
cmdq_error(item, "%s", error); |
1537 |
|
|
free(error); |
1538 |
|
|
|
1539 |
|
|
return (CMD_RETURN_NORMAL); |
1540 |
|
|
} |
1541 |
|
|
|
1542 |
|
|
/* Handle command message. */ |
1543 |
|
|
static void |
1544 |
|
|
server_client_dispatch_command(struct client *c, struct imsg *imsg) |
1545 |
|
|
{ |
1546 |
|
|
struct msg_command_data data; |
1547 |
|
|
char *buf; |
1548 |
|
|
size_t len; |
1549 |
|
|
struct cmd_list *cmdlist = NULL; |
1550 |
|
|
int argc; |
1551 |
|
|
char **argv, *cause; |
1552 |
|
|
|
1553 |
|
|
if (imsg->hdr.len - IMSG_HEADER_SIZE < sizeof data) |
1554 |
|
|
fatalx("bad MSG_COMMAND size"); |
1555 |
|
|
memcpy(&data, imsg->data, sizeof data); |
1556 |
|
|
|
1557 |
|
|
buf = (char *)imsg->data + sizeof data; |
1558 |
|
|
len = imsg->hdr.len - IMSG_HEADER_SIZE - sizeof data; |
1559 |
|
|
if (len > 0 && buf[len - 1] != '\0') |
1560 |
|
|
fatalx("bad MSG_COMMAND string"); |
1561 |
|
|
|
1562 |
|
|
argc = data.argc; |
1563 |
|
|
if (cmd_unpack_argv(buf, len, argc, &argv) != 0) { |
1564 |
|
|
cause = xstrdup("command too long"); |
1565 |
|
|
goto error; |
1566 |
|
|
} |
1567 |
|
|
|
1568 |
|
|
if (argc == 0) { |
1569 |
|
|
argc = 1; |
1570 |
|
|
argv = xcalloc(1, sizeof *argv); |
1571 |
|
|
*argv = xstrdup("new-session"); |
1572 |
|
|
} |
1573 |
|
|
|
1574 |
|
|
if ((cmdlist = cmd_list_parse(argc, argv, NULL, 0, &cause)) == NULL) { |
1575 |
|
|
cmd_free_argv(argc, argv); |
1576 |
|
|
goto error; |
1577 |
|
|
} |
1578 |
|
|
cmd_free_argv(argc, argv); |
1579 |
|
|
|
1580 |
|
|
cmdq_append(c, cmdq_get_command(cmdlist, NULL, NULL, 0)); |
1581 |
|
|
cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); |
1582 |
|
|
cmd_list_free(cmdlist); |
1583 |
|
|
return; |
1584 |
|
|
|
1585 |
|
|
error: |
1586 |
|
|
cmdq_append(c, cmdq_get_callback(server_client_command_error, cause)); |
1587 |
|
|
|
1588 |
|
|
if (cmdlist != NULL) |
1589 |
|
|
cmd_list_free(cmdlist); |
1590 |
|
|
|
1591 |
|
|
c->flags |= CLIENT_EXIT; |
1592 |
|
|
} |
1593 |
|
|
|
1594 |
|
|
/* Handle identify message. */ |
1595 |
|
|
static void |
1596 |
|
|
server_client_dispatch_identify(struct client *c, struct imsg *imsg) |
1597 |
|
|
{ |
1598 |
|
|
const char *data, *home; |
1599 |
|
|
size_t datalen; |
1600 |
|
|
int flags; |
1601 |
|
|
char *name; |
1602 |
|
|
|
1603 |
|
|
if (c->flags & CLIENT_IDENTIFIED) |
1604 |
|
|
fatalx("out-of-order identify message"); |
1605 |
|
|
|
1606 |
|
|
data = imsg->data; |
1607 |
|
|
datalen = imsg->hdr.len - IMSG_HEADER_SIZE; |
1608 |
|
|
|
1609 |
|
|
switch (imsg->hdr.type) { |
1610 |
|
|
case MSG_IDENTIFY_FLAGS: |
1611 |
|
|
if (datalen != sizeof flags) |
1612 |
|
|
fatalx("bad MSG_IDENTIFY_FLAGS size"); |
1613 |
|
|
memcpy(&flags, data, sizeof flags); |
1614 |
|
|
c->flags |= flags; |
1615 |
|
|
log_debug("client %p IDENTIFY_FLAGS %#x", c, flags); |
1616 |
|
|
break; |
1617 |
|
|
case MSG_IDENTIFY_TERM: |
1618 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1619 |
|
|
fatalx("bad MSG_IDENTIFY_TERM string"); |
1620 |
|
|
c->term = xstrdup(data); |
1621 |
|
|
log_debug("client %p IDENTIFY_TERM %s", c, data); |
1622 |
|
|
break; |
1623 |
|
|
case MSG_IDENTIFY_TTYNAME: |
1624 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1625 |
|
|
fatalx("bad MSG_IDENTIFY_TTYNAME string"); |
1626 |
|
|
c->ttyname = xstrdup(data); |
1627 |
|
|
log_debug("client %p IDENTIFY_TTYNAME %s", c, data); |
1628 |
|
|
break; |
1629 |
|
|
case MSG_IDENTIFY_CWD: |
1630 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1631 |
|
|
fatalx("bad MSG_IDENTIFY_CWD string"); |
1632 |
|
|
if (access(data, X_OK) == 0) |
1633 |
|
|
c->cwd = xstrdup(data); |
1634 |
|
|
else if ((home = find_home()) != NULL) |
1635 |
|
|
c->cwd = xstrdup(home); |
1636 |
|
|
else |
1637 |
|
|
c->cwd = xstrdup("/"); |
1638 |
|
|
log_debug("client %p IDENTIFY_CWD %s", c, data); |
1639 |
|
|
break; |
1640 |
|
|
case MSG_IDENTIFY_STDIN: |
1641 |
|
|
if (datalen != 0) |
1642 |
|
|
fatalx("bad MSG_IDENTIFY_STDIN size"); |
1643 |
|
|
c->fd = imsg->fd; |
1644 |
|
|
log_debug("client %p IDENTIFY_STDIN %d", c, imsg->fd); |
1645 |
|
|
break; |
1646 |
|
|
case MSG_IDENTIFY_ENVIRON: |
1647 |
|
|
if (datalen == 0 || data[datalen - 1] != '\0') |
1648 |
|
|
fatalx("bad MSG_IDENTIFY_ENVIRON string"); |
1649 |
|
|
if (strchr(data, '=') != NULL) |
1650 |
|
|
environ_put(c->environ, data); |
1651 |
|
|
log_debug("client %p IDENTIFY_ENVIRON %s", c, data); |
1652 |
|
|
break; |
1653 |
|
|
case MSG_IDENTIFY_CLIENTPID: |
1654 |
|
|
if (datalen != sizeof c->pid) |
1655 |
|
|
fatalx("bad MSG_IDENTIFY_CLIENTPID size"); |
1656 |
|
|
memcpy(&c->pid, data, sizeof c->pid); |
1657 |
|
|
log_debug("client %p IDENTIFY_CLIENTPID %ld", c, (long)c->pid); |
1658 |
|
|
break; |
1659 |
|
|
default: |
1660 |
|
|
break; |
1661 |
|
|
} |
1662 |
|
|
|
1663 |
|
|
if (imsg->hdr.type != MSG_IDENTIFY_DONE) |
1664 |
|
|
return; |
1665 |
|
|
c->flags |= CLIENT_IDENTIFIED; |
1666 |
|
|
|
1667 |
|
|
if (*c->ttyname != '\0') |
1668 |
|
|
name = xstrdup(c->ttyname); |
1669 |
|
|
else |
1670 |
|
|
xasprintf(&name, "client-%ld", (long)c->pid); |
1671 |
|
|
c->name = name; |
1672 |
|
|
log_debug("client %p name is %s", c, c->name); |
1673 |
|
|
|
1674 |
|
|
if (c->flags & CLIENT_CONTROL) { |
1675 |
|
|
c->stdin_callback = control_callback; |
1676 |
|
|
|
1677 |
|
|
evbuffer_free(c->stderr_data); |
1678 |
|
|
c->stderr_data = c->stdout_data; |
1679 |
|
|
|
1680 |
|
|
if (c->flags & CLIENT_CONTROLCONTROL) |
1681 |
|
|
evbuffer_add_printf(c->stdout_data, "\033P1000p"); |
1682 |
|
|
proc_send(c->peer, MSG_STDIN, -1, NULL, 0); |
1683 |
|
|
|
1684 |
|
|
c->tty.fd = -1; |
1685 |
|
|
|
1686 |
|
|
close(c->fd); |
1687 |
|
|
c->fd = -1; |
1688 |
|
|
|
1689 |
|
|
return; |
1690 |
|
|
} |
1691 |
|
|
|
1692 |
|
|
if (c->fd == -1) |
1693 |
|
|
return; |
1694 |
|
|
if (tty_init(&c->tty, c, c->fd, c->term) != 0) { |
1695 |
|
|
close(c->fd); |
1696 |
|
|
c->fd = -1; |
1697 |
|
|
return; |
1698 |
|
|
} |
1699 |
|
|
if (c->flags & CLIENT_UTF8) |
1700 |
|
|
c->tty.flags |= TTY_UTF8; |
1701 |
|
|
if (c->flags & CLIENT_256COLOURS) |
1702 |
|
|
c->tty.term_flags |= TERM_256COLOURS; |
1703 |
|
|
|
1704 |
|
|
tty_resize(&c->tty); |
1705 |
|
|
|
1706 |
|
|
if (!(c->flags & CLIENT_CONTROL)) |
1707 |
|
|
c->flags |= CLIENT_TERMINAL; |
1708 |
|
|
} |
1709 |
|
|
|
1710 |
|
|
/* Handle shell message. */ |
1711 |
|
|
static void |
1712 |
|
|
server_client_dispatch_shell(struct client *c) |
1713 |
|
|
{ |
1714 |
|
|
const char *shell; |
1715 |
|
|
|
1716 |
|
|
shell = options_get_string(global_s_options, "default-shell"); |
1717 |
|
|
if (*shell == '\0' || areshell(shell)) |
1718 |
|
|
shell = _PATH_BSHELL; |
1719 |
|
|
proc_send(c->peer, MSG_SHELL, -1, shell, strlen(shell) + 1); |
1720 |
|
|
|
1721 |
|
|
proc_kill_peer(c->peer); |
1722 |
|
|
} |
1723 |
|
|
|
1724 |
|
|
/* Event callback to push more stdout data if any left. */ |
1725 |
|
|
static void |
1726 |
|
|
server_client_stdout_cb(__unused int fd, __unused short events, void *arg) |
1727 |
|
|
{ |
1728 |
|
|
struct client *c = arg; |
1729 |
|
|
|
1730 |
|
|
if (~c->flags & CLIENT_DEAD) |
1731 |
|
|
server_client_push_stdout(c); |
1732 |
|
|
server_client_unref(c); |
1733 |
|
|
} |
1734 |
|
|
|
1735 |
|
|
/* Push stdout to client if possible. */ |
1736 |
|
|
void |
1737 |
|
|
server_client_push_stdout(struct client *c) |
1738 |
|
|
{ |
1739 |
|
|
struct msg_stdout_data data; |
1740 |
|
|
size_t sent, left; |
1741 |
|
|
|
1742 |
|
|
left = EVBUFFER_LENGTH(c->stdout_data); |
1743 |
|
|
while (left != 0) { |
1744 |
|
|
sent = left; |
1745 |
|
|
if (sent > sizeof data.data) |
1746 |
|
|
sent = sizeof data.data; |
1747 |
|
|
memcpy(data.data, EVBUFFER_DATA(c->stdout_data), sent); |
1748 |
|
|
data.size = sent; |
1749 |
|
|
|
1750 |
|
|
if (proc_send(c->peer, MSG_STDOUT, -1, &data, sizeof data) != 0) |
1751 |
|
|
break; |
1752 |
|
|
evbuffer_drain(c->stdout_data, sent); |
1753 |
|
|
|
1754 |
|
|
left = EVBUFFER_LENGTH(c->stdout_data); |
1755 |
|
|
log_debug("%s: client %p, sent %zu, left %zu", __func__, c, |
1756 |
|
|
sent, left); |
1757 |
|
|
} |
1758 |
|
|
if (left != 0) { |
1759 |
|
|
c->references++; |
1760 |
|
|
event_once(-1, EV_TIMEOUT, server_client_stdout_cb, c, NULL); |
1761 |
|
|
log_debug("%s: client %p, queued", __func__, c); |
1762 |
|
|
} |
1763 |
|
|
} |
1764 |
|
|
|
1765 |
|
|
/* Event callback to push more stderr data if any left. */ |
1766 |
|
|
static void |
1767 |
|
|
server_client_stderr_cb(__unused int fd, __unused short events, void *arg) |
1768 |
|
|
{ |
1769 |
|
|
struct client *c = arg; |
1770 |
|
|
|
1771 |
|
|
if (~c->flags & CLIENT_DEAD) |
1772 |
|
|
server_client_push_stderr(c); |
1773 |
|
|
server_client_unref(c); |
1774 |
|
|
} |
1775 |
|
|
|
1776 |
|
|
/* Push stderr to client if possible. */ |
1777 |
|
|
void |
1778 |
|
|
server_client_push_stderr(struct client *c) |
1779 |
|
|
{ |
1780 |
|
|
struct msg_stderr_data data; |
1781 |
|
|
size_t sent, left; |
1782 |
|
|
|
1783 |
|
|
if (c->stderr_data == c->stdout_data) { |
1784 |
|
|
server_client_push_stdout(c); |
1785 |
|
|
return; |
1786 |
|
|
} |
1787 |
|
|
|
1788 |
|
|
left = EVBUFFER_LENGTH(c->stderr_data); |
1789 |
|
|
while (left != 0) { |
1790 |
|
|
sent = left; |
1791 |
|
|
if (sent > sizeof data.data) |
1792 |
|
|
sent = sizeof data.data; |
1793 |
|
|
memcpy(data.data, EVBUFFER_DATA(c->stderr_data), sent); |
1794 |
|
|
data.size = sent; |
1795 |
|
|
|
1796 |
|
|
if (proc_send(c->peer, MSG_STDERR, -1, &data, sizeof data) != 0) |
1797 |
|
|
break; |
1798 |
|
|
evbuffer_drain(c->stderr_data, sent); |
1799 |
|
|
|
1800 |
|
|
left = EVBUFFER_LENGTH(c->stderr_data); |
1801 |
|
|
log_debug("%s: client %p, sent %zu, left %zu", __func__, c, |
1802 |
|
|
sent, left); |
1803 |
|
|
} |
1804 |
|
|
if (left != 0) { |
1805 |
|
|
c->references++; |
1806 |
|
|
event_once(-1, EV_TIMEOUT, server_client_stderr_cb, c, NULL); |
1807 |
|
|
log_debug("%s: client %p, queued", __func__, c); |
1808 |
|
|
} |
1809 |
|
|
} |
1810 |
|
|
|
1811 |
|
|
/* Add to client message log. */ |
1812 |
|
|
void |
1813 |
|
|
server_client_add_message(struct client *c, const char *fmt, ...) |
1814 |
|
|
{ |
1815 |
|
|
struct message_entry *msg, *msg1; |
1816 |
|
|
char *s; |
1817 |
|
|
va_list ap; |
1818 |
|
|
u_int limit; |
1819 |
|
|
|
1820 |
|
|
va_start(ap, fmt); |
1821 |
|
|
xvasprintf(&s, fmt, ap); |
1822 |
|
|
va_end(ap); |
1823 |
|
|
|
1824 |
|
|
log_debug("message %s (client %p)", s, c); |
1825 |
|
|
|
1826 |
|
|
msg = xcalloc(1, sizeof *msg); |
1827 |
|
|
msg->msg_time = time(NULL); |
1828 |
|
|
msg->msg_num = c->message_next++; |
1829 |
|
|
msg->msg = s; |
1830 |
|
|
TAILQ_INSERT_TAIL(&c->message_log, msg, entry); |
1831 |
|
|
|
1832 |
|
|
limit = options_get_number(global_options, "message-limit"); |
1833 |
|
|
TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) { |
1834 |
|
|
if (msg->msg_num + limit >= c->message_next) |
1835 |
|
|
break; |
1836 |
|
|
free(msg->msg); |
1837 |
|
|
TAILQ_REMOVE(&c->message_log, msg, entry); |
1838 |
|
|
free(msg); |
1839 |
|
|
} |
1840 |
|
|
} |
1841 |
|
|
|
1842 |
|
|
/* Get client working directory. */ |
1843 |
|
|
const char * |
1844 |
|
|
server_client_get_cwd(struct client *c) |
1845 |
|
|
{ |
1846 |
|
|
struct session *s; |
1847 |
|
|
|
1848 |
|
|
if (c != NULL && c->session == NULL && c->cwd != NULL) |
1849 |
|
|
return (c->cwd); |
1850 |
|
|
if (c != NULL && (s = c->session) != NULL && s->cwd != NULL) |
1851 |
|
|
return (s->cwd); |
1852 |
|
|
return ("."); |
1853 |
|
|
} |
1854 |
|
|
|
1855 |
|
|
/* Resolve an absolute path or relative to client working directory. */ |
1856 |
|
|
char * |
1857 |
|
|
server_client_get_path(struct client *c, const char *file) |
1858 |
|
|
{ |
1859 |
|
|
char *path, resolved[PATH_MAX]; |
1860 |
|
|
|
1861 |
|
|
if (*file == '/') |
1862 |
|
|
path = xstrdup(file); |
1863 |
|
|
else |
1864 |
|
|
xasprintf(&path, "%s/%s", server_client_get_cwd(c), file); |
1865 |
|
|
if (realpath(path, resolved) == NULL) |
1866 |
|
|
return (path); |
1867 |
|
|
free(path); |
1868 |
|
|
return (xstrdup(resolved)); |
1869 |
|
|
} |