1 /* $OpenBSD: popup.c,v 1.23 2021/07/21 08:06:36 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/wait.h> 21 22 #include <paths.h> 23 #include <signal.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <unistd.h> 27 28 #include "tmux.h" 29 30 struct popup_data { 31 struct client *c; 32 struct cmdq_item *item; 33 int flags; 34 35 struct screen s; 36 struct job *job; 37 struct input_ctx *ictx; 38 int status; 39 popup_close_cb cb; 40 void *arg; 41 42 /* Current position and size. */ 43 u_int px; 44 u_int py; 45 u_int sx; 46 u_int sy; 47 48 /* Preferred position and size. */ 49 u_int ppx; 50 u_int ppy; 51 u_int psx; 52 u_int psy; 53 54 enum { OFF, MOVE, SIZE } dragging; 55 u_int dx; 56 u_int dy; 57 58 u_int lx; 59 u_int ly; 60 u_int lb; 61 }; 62 63 struct popup_editor { 64 char *path; 65 popup_finish_edit_cb cb; 66 void *arg; 67 }; 68 69 static void 70 popup_redraw_cb(const struct tty_ctx *ttyctx) 71 { 72 struct popup_data *pd = ttyctx->arg; 73 74 pd->c->flags |= CLIENT_REDRAWOVERLAY; 75 } 76 77 static int 78 popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c) 79 { 80 struct popup_data *pd = ttyctx->arg; 81 82 if (c != pd->c) 83 return (0); 84 if (pd->c->flags & CLIENT_REDRAWOVERLAY) 85 return (0); 86 87 ttyctx->bigger = 0; 88 ttyctx->wox = 0; 89 ttyctx->woy = 0; 90 ttyctx->wsx = c->tty.sx; 91 ttyctx->wsy = c->tty.sy; 92 93 ttyctx->xoff = ttyctx->rxoff = pd->px + 1; 94 ttyctx->yoff = ttyctx->ryoff = pd->py + 1; 95 96 return (1); 97 } 98 99 static void 100 popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx) 101 { 102 struct popup_data *pd = ctx->arg; 103 104 ttyctx->redraw_cb = popup_redraw_cb; 105 ttyctx->set_client_cb = popup_set_client_cb; 106 ttyctx->arg = pd; 107 } 108 109 static struct screen * 110 popup_mode_cb(struct client *c, u_int *cx, u_int *cy) 111 { 112 struct popup_data *pd = c->overlay_data; 113 114 *cx = pd->px + 1 + pd->s.cx; 115 *cy = pd->py + 1 + pd->s.cy; 116 return (&pd->s); 117 } 118 119 static int 120 popup_check_cb(struct client *c, u_int px, u_int py) 121 { 122 struct popup_data *pd = c->overlay_data; 123 124 if (px < pd->px || px > pd->px + pd->sx - 1) 125 return (1); 126 if (py < pd->py || py > pd->py + pd->sy - 1) 127 return (1); 128 return (0); 129 } 130 131 static void 132 popup_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0) 133 { 134 struct popup_data *pd = c->overlay_data; 135 struct tty *tty = &c->tty; 136 struct screen s; 137 struct screen_write_ctx ctx; 138 u_int i, px = pd->px, py = pd->py; 139 140 screen_init(&s, pd->sx, pd->sy, 0); 141 screen_write_start(&ctx, &s); 142 screen_write_clearscreen(&ctx, 8); 143 144 /* Skip drawing popup if the terminal is too small. */ 145 if (pd->sx > 2 && pd->sy > 2) { 146 screen_write_box(&ctx, pd->sx, pd->sy); 147 screen_write_cursormove(&ctx, 1, 1, 0); 148 screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2, 149 pd->sy - 2); 150 } 151 screen_write_stop(&ctx); 152 153 c->overlay_check = NULL; 154 for (i = 0; i < pd->sy; i++){ 155 tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, 156 &grid_default_cell, NULL); 157 } 158 c->overlay_check = popup_check_cb; 159 } 160 161 static void 162 popup_free_cb(struct client *c) 163 { 164 struct popup_data *pd = c->overlay_data; 165 struct cmdq_item *item = pd->item; 166 167 if (pd->cb != NULL) 168 pd->cb(pd->status, pd->arg); 169 170 if (item != NULL) { 171 if (cmdq_get_client(item) != NULL && 172 cmdq_get_client(item)->session == NULL) 173 cmdq_get_client(item)->retval = pd->status; 174 cmdq_continue(item); 175 } 176 server_client_unref(pd->c); 177 178 if (pd->job != NULL) 179 job_free(pd->job); 180 input_free(pd->ictx); 181 182 screen_free(&pd->s); 183 free(pd); 184 } 185 186 static void 187 popup_resize_cb(struct client *c) 188 { 189 struct popup_data *pd = c->overlay_data; 190 struct tty *tty = &c->tty; 191 192 if (pd == NULL) 193 return; 194 195 /* Adjust position and size. */ 196 if (pd->psy > tty->sy) 197 pd->sy = tty->sy; 198 else 199 pd->sy = pd->psy; 200 if (pd->psx > tty->sx) 201 pd->sx = tty->sx; 202 else 203 pd->sx = pd->psx; 204 if (pd->ppy + pd->sy > tty->sy) 205 pd->py = tty->sy - pd->sy; 206 else 207 pd->py = pd->ppy; 208 if (pd->ppx + pd->sx > tty->sx) 209 pd->px = tty->sx - pd->sx; 210 else 211 pd->px = pd->ppx; 212 213 /* Avoid zero size screens. */ 214 if (pd->sx > 2 && pd->sy > 2) { 215 screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0); 216 if (pd->job != NULL) 217 job_resize(pd->job, pd->sx - 2, pd->sy - 2); 218 } 219 } 220 221 static void 222 popup_handle_drag(struct client *c, struct popup_data *pd, 223 struct mouse_event *m) 224 { 225 u_int px, py; 226 227 if (!MOUSE_DRAG(m->b)) 228 pd->dragging = OFF; 229 else if (pd->dragging == MOVE) { 230 if (m->x < pd->dx) 231 px = 0; 232 else if (m->x - pd->dx + pd->sx > c->tty.sx) 233 px = c->tty.sx - pd->sx; 234 else 235 px = m->x - pd->dx; 236 if (m->y < pd->dy) 237 py = 0; 238 else if (m->y - pd->dy + pd->sy > c->tty.sy) 239 py = c->tty.sy - pd->sy; 240 else 241 py = m->y - pd->dy; 242 pd->px = px; 243 pd->py = py; 244 pd->dx = m->x - pd->px; 245 pd->dy = m->y - pd->py; 246 pd->ppx = px; 247 pd->ppy = py; 248 server_redraw_client(c); 249 } else if (pd->dragging == SIZE) { 250 if (m->x < pd->px + 3) 251 return; 252 if (m->y < pd->py + 3) 253 return; 254 pd->sx = m->x - pd->px; 255 pd->sy = m->y - pd->py; 256 pd->psx = pd->sx; 257 pd->psy = pd->sy; 258 259 screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0); 260 if (pd->job != NULL) 261 job_resize(pd->job, pd->sx - 2, pd->sy - 2); 262 server_redraw_client(c); 263 } 264 } 265 266 static int 267 popup_key_cb(struct client *c, struct key_event *event) 268 { 269 struct popup_data *pd = c->overlay_data; 270 struct mouse_event *m = &event->m; 271 const char *buf; 272 size_t len; 273 274 if (KEYC_IS_MOUSE(event->key)) { 275 if (pd->dragging != OFF) { 276 popup_handle_drag(c, pd, m); 277 goto out; 278 } 279 if (m->x < pd->px || 280 m->x > pd->px + pd->sx - 1 || 281 m->y < pd->py || 282 m->y > pd->py + pd->sy - 1) { 283 if (MOUSE_BUTTONS (m->b) == 1) 284 return (1); 285 return (0); 286 } 287 if ((m->b & MOUSE_MASK_META) || 288 m->x == pd->px || 289 m->x == pd->px + pd->sx - 1 || 290 m->y == pd->py || 291 m->y == pd->py + pd->sy - 1) { 292 if (!MOUSE_DRAG(m->b)) 293 goto out; 294 if (MOUSE_BUTTONS(m->lb) == 0) 295 pd->dragging = MOVE; 296 else if (MOUSE_BUTTONS(m->lb) == 2) 297 pd->dragging = SIZE; 298 pd->dx = m->lx - pd->px; 299 pd->dy = m->ly - pd->py; 300 goto out; 301 } 302 } 303 304 if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) || 305 pd->job == NULL) && 306 (event->key == '\033' || event->key == '\003')) 307 return (1); 308 if (pd->job != NULL) { 309 if (KEYC_IS_MOUSE(event->key)) { 310 /* Must be inside, checked already. */ 311 if (!input_key_get_mouse(&pd->s, m, m->x - pd->px - 1, 312 m->y - pd->py - 1, &buf, &len)) 313 return (0); 314 bufferevent_write(job_get_event(pd->job), buf, len); 315 return (0); 316 } 317 input_key(&pd->s, job_get_event(pd->job), event->key); 318 } 319 return (0); 320 321 out: 322 pd->lx = m->x; 323 pd->ly = m->y; 324 pd->lb = m->b; 325 return (0); 326 } 327 328 static void 329 popup_job_update_cb(struct job *job) 330 { 331 struct popup_data *pd = job_get_data(job); 332 struct evbuffer *evb = job_get_event(job)->input; 333 struct client *c = pd->c; 334 struct screen *s = &pd->s; 335 void *data = EVBUFFER_DATA(evb); 336 size_t size = EVBUFFER_LENGTH(evb); 337 338 if (size == 0) 339 return; 340 341 c->overlay_check = NULL; 342 c->tty.flags &= ~TTY_FREEZE; 343 344 input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size); 345 346 c->tty.flags |= TTY_FREEZE; 347 c->overlay_check = popup_check_cb; 348 349 evbuffer_drain(evb, size); 350 } 351 352 static void 353 popup_job_complete_cb(struct job *job) 354 { 355 struct popup_data *pd = job_get_data(job); 356 int status; 357 358 status = job_get_status(pd->job); 359 if (WIFEXITED(status)) 360 pd->status = WEXITSTATUS(status); 361 else if (WIFSIGNALED(status)) 362 pd->status = WTERMSIG(status); 363 else 364 pd->status = 0; 365 pd->job = NULL; 366 367 if ((pd->flags & POPUP_CLOSEEXIT) || 368 ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0)) 369 server_client_clear_overlay(pd->c); 370 } 371 372 int 373 popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx, 374 u_int sy, const char *shellcmd, int argc, char **argv, const char *cwd, 375 struct client *c, struct session *s, popup_close_cb cb, void *arg) 376 { 377 struct popup_data *pd; 378 379 if (sx < 3 || sy < 3) 380 return (-1); 381 if (c->tty.sx < sx || c->tty.sy < sy) 382 return (-1); 383 384 pd = xcalloc(1, sizeof *pd); 385 pd->item = item; 386 pd->flags = flags; 387 388 pd->c = c; 389 pd->c->references++; 390 391 pd->cb = cb; 392 pd->arg = arg; 393 pd->status = 128 + SIGHUP; 394 395 screen_init(&pd->s, sx - 2, sy - 2, 0); 396 397 pd->px = px; 398 pd->py = py; 399 pd->sx = sx; 400 pd->sy = sy; 401 402 pd->ppx = px; 403 pd->ppy = py; 404 pd->psx = sx; 405 pd->psy = sy; 406 407 pd->job = job_run(shellcmd, argc, argv, s, cwd, 408 popup_job_update_cb, popup_job_complete_cb, NULL, pd, 409 JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE, pd->sx - 2, pd->sy - 2); 410 pd->ictx = input_init(NULL, job_get_event(pd->job)); 411 412 server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb, 413 popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd); 414 return (0); 415 } 416 417 static void 418 popup_editor_free(struct popup_editor *pe) 419 { 420 unlink(pe->path); 421 free(pe->path); 422 free(pe); 423 } 424 425 static void 426 popup_editor_close_cb(int status, void *arg) 427 { 428 struct popup_editor *pe = arg; 429 FILE *f; 430 char *buf = NULL; 431 off_t len = 0; 432 433 if (status != 0) { 434 pe->cb(NULL, 0, pe->arg); 435 popup_editor_free(pe); 436 return; 437 } 438 439 f = fopen(pe->path, "r"); 440 if (f != NULL) { 441 fseeko(f, 0, SEEK_END); 442 len = ftello(f); 443 fseeko(f, 0, SEEK_SET); 444 445 if (len == 0 || 446 (uintmax_t)len > (uintmax_t)SIZE_MAX || 447 (buf = malloc(len)) == NULL || 448 fread(buf, len, 1, f) != 1) { 449 free(buf); 450 buf = NULL; 451 len = 0; 452 } 453 fclose(f); 454 } 455 pe->cb(buf, len, pe->arg); /* callback now owns buffer */ 456 popup_editor_free(pe); 457 } 458 459 int 460 popup_editor(struct client *c, const char *buf, size_t len, 461 popup_finish_edit_cb cb, void *arg) 462 { 463 struct popup_editor *pe; 464 int fd; 465 FILE *f; 466 char *cmd; 467 char path[] = _PATH_TMP "tmux.XXXXXXXX"; 468 const char *editor; 469 u_int px, py, sx, sy; 470 471 editor = options_get_string(global_options, "editor"); 472 if (*editor == '\0') 473 return (-1); 474 475 fd = mkstemp(path); 476 if (fd == -1) 477 return (-1); 478 f = fdopen(fd, "w"); 479 if (fwrite(buf, len, 1, f) != 1) { 480 fclose(f); 481 return (-1); 482 } 483 fclose(f); 484 485 pe = xcalloc(1, sizeof *pe); 486 pe->path = xstrdup(path); 487 pe->cb = cb; 488 pe->arg = arg; 489 490 sx = c->tty.sx * 9 / 10; 491 sy = c->tty.sy * 9 / 10; 492 px = (c->tty.sx / 2) - (sx / 2); 493 py = (c->tty.sy / 2) - (sy / 2); 494 495 xasprintf(&cmd, "%s %s", editor, path); 496 if (popup_display(POPUP_CLOSEEXIT, NULL, px, py, sx, sy, cmd, 0, NULL, 497 _PATH_TMP, c, NULL, popup_editor_close_cb, pe) != 0) { 498 popup_editor_free(pe); 499 free(cmd); 500 return (-1); 501 } 502 free(cmd); 503 return (0); 504 } 505