Line | Count | Source |
1 | | /* $OpenBSD: screen.c,v 1.107 2026/07/26 09:20:54 nicm Exp $ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include <sys/types.h> |
20 | | |
21 | | #include <stdlib.h> |
22 | | #include <string.h> |
23 | | #include <unistd.h> |
24 | | |
25 | | #include "tmux.h" |
26 | | |
27 | | /* Selected area in screen. */ |
28 | | struct screen_sel { |
29 | | int hidden; |
30 | | int rectangle; |
31 | | int modekeys; |
32 | | |
33 | | u_int sx; |
34 | | u_int sy; |
35 | | |
36 | | u_int ex; |
37 | | u_int ey; |
38 | | |
39 | | u_int clipx; |
40 | | |
41 | | struct grid_cell cell; |
42 | | }; |
43 | | |
44 | | /* Entry on title stack. */ |
45 | | struct screen_title_entry { |
46 | | char *text; |
47 | | |
48 | | TAILQ_ENTRY(screen_title_entry) entry; |
49 | | }; |
50 | | TAILQ_HEAD(screen_titles, screen_title_entry); |
51 | | |
52 | | static void screen_resize_y(struct screen *, u_int, int, u_int *); |
53 | | static void screen_reflow(struct screen *, u_int, u_int *, u_int *, int); |
54 | | |
55 | | /* Free titles stack. */ |
56 | | static void |
57 | | screen_free_titles(struct screen *s) |
58 | 45.6k | { |
59 | 45.6k | struct screen_title_entry *title_entry; |
60 | | |
61 | 45.6k | if (s->titles == NULL) |
62 | 45.6k | return; |
63 | | |
64 | 273 | while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) { |
65 | 221 | TAILQ_REMOVE(s->titles, title_entry, entry); |
66 | 221 | free(title_entry->text); |
67 | 221 | free(title_entry); |
68 | 221 | } |
69 | | |
70 | 52 | free(s->titles); |
71 | 52 | s->titles = NULL; |
72 | 52 | s->ntitles = 0; |
73 | 52 | } |
74 | | |
75 | | /* Create a new screen. */ |
76 | | void |
77 | | screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) |
78 | 22.8k | { |
79 | 22.8k | s->grid = grid_create(sx, sy, hlimit); |
80 | 22.8k | s->saved_grid = NULL; |
81 | | |
82 | 22.8k | s->title = xstrdup(""); |
83 | 22.8k | s->titles = NULL; |
84 | 22.8k | s->ntitles = 0; |
85 | 22.8k | s->path = NULL; |
86 | | |
87 | 22.8k | s->cstyle = SCREEN_CURSOR_DEFAULT; |
88 | 22.8k | s->default_cstyle = SCREEN_CURSOR_DEFAULT; |
89 | 22.8k | s->mode = MODE_CURSOR; |
90 | 22.8k | s->default_mode = 0; |
91 | 22.8k | s->ccolour = -1; |
92 | 22.8k | s->default_ccolour = -1; |
93 | 22.8k | s->tabs = NULL; |
94 | 22.8k | s->sel = NULL; |
95 | | |
96 | | #ifdef ENABLE_SIXEL |
97 | | TAILQ_INIT(&s->images); |
98 | | TAILQ_INIT(&s->saved_images); |
99 | | #endif |
100 | | |
101 | 22.8k | s->write_list = NULL; |
102 | 22.8k | s->hyperlinks = NULL; |
103 | | |
104 | 22.8k | screen_reinit(s, 1); |
105 | 22.8k | } |
106 | | |
107 | | /* Reinitialise screen. */ |
108 | | void |
109 | | screen_reinit(struct screen *s, int check) |
110 | 22.8k | { |
111 | 22.8k | s->cx = 0; |
112 | 22.8k | s->cy = 0; |
113 | | |
114 | 22.8k | s->rupper = 0; |
115 | 22.8k | s->rlower = screen_size_y(s) - 1; |
116 | | |
117 | 22.8k | s->mode = MODE_CURSOR|MODE_WRAP|(s->mode & MODE_CRLF); |
118 | | |
119 | 22.8k | if (options_get_number(global_options, "extended-keys") == 2) |
120 | 0 | s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED; |
121 | | |
122 | 22.8k | if (SCREEN_IS_ALTERNATE(s)) |
123 | 0 | screen_alternate_off(s, NULL, 0); |
124 | 22.8k | s->saved_cx = UINT_MAX; |
125 | 22.8k | s->saved_cy = UINT_MAX; |
126 | | |
127 | 22.8k | screen_reset_tabs(s); |
128 | 22.8k | if (check) |
129 | 22.8k | grid_check_is_clear(s->grid); |
130 | 22.8k | grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8); |
131 | | |
132 | 22.8k | screen_clear_selection(s); |
133 | 22.8k | screen_free_titles(s); |
134 | | |
135 | | #ifdef ENABLE_SIXEL |
136 | | image_free_all(s); |
137 | | #endif |
138 | | |
139 | 22.8k | screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0); |
140 | 22.8k | screen_reset_hyperlinks(s); |
141 | 22.8k | } |
142 | | |
143 | | /* Reset hyperlinks of a screen. */ |
144 | | void |
145 | | screen_reset_hyperlinks(struct screen *s) |
146 | 22.8k | { |
147 | 22.8k | if (s->hyperlinks == NULL) |
148 | 22.8k | s->hyperlinks = hyperlinks_init(); |
149 | 0 | else |
150 | 0 | hyperlinks_reset(s->hyperlinks); |
151 | 22.8k | } |
152 | | |
153 | | /* Destroy a screen. */ |
154 | | void |
155 | | screen_free(struct screen *s) |
156 | 22.8k | { |
157 | | #ifdef ENABLE_SIXEL |
158 | | struct image *im; |
159 | | #endif |
160 | | |
161 | 22.8k | free(s->sel); |
162 | 22.8k | free(s->tabs); |
163 | 22.8k | free(s->path); |
164 | 22.8k | free(s->title); |
165 | | |
166 | 22.8k | if (s->write_list != NULL) |
167 | 11.4k | screen_write_free_list(s); |
168 | | |
169 | 22.8k | if (SCREEN_IS_ALTERNATE(s)) |
170 | 72 | grid_destroy(s->saved_grid); |
171 | 22.8k | grid_destroy(s->grid); |
172 | | |
173 | 22.8k | if (s->hyperlinks != NULL) |
174 | 22.8k | hyperlinks_free(s->hyperlinks); |
175 | 22.8k | screen_free_titles(s); |
176 | | |
177 | | #ifdef ENABLE_SIXEL |
178 | | /* |
179 | | * Images saved when entering the alternate screen stay linked in the |
180 | | * global list; move them back so they are freed and unlinked here, or |
181 | | * a later eviction would write through this freed screen. |
182 | | */ |
183 | | TAILQ_CONCAT(&s->images, &s->saved_images, entry); |
184 | | TAILQ_FOREACH(im, &s->images, entry) |
185 | | im->list = &s->images; |
186 | | image_free_all(s); |
187 | | #endif |
188 | 22.8k | } |
189 | | |
190 | | /* Reset tabs to default, eight spaces apart. */ |
191 | | void |
192 | | screen_reset_tabs(struct screen *s) |
193 | 24.0k | { |
194 | 24.0k | u_int i; |
195 | | |
196 | 24.0k | free(s->tabs); |
197 | | |
198 | 24.0k | if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL) |
199 | 0 | fatal("bit_alloc failed"); |
200 | 137k | for (i = 8; i < screen_size_x(s); i += 8) |
201 | 113k | bit_set(s->tabs, i); |
202 | 24.0k | } |
203 | | |
204 | | /* Set default cursor style and colour from options. */ |
205 | | void |
206 | | screen_set_default_cursor(struct screen *s, struct options *oo) |
207 | 11.4k | { |
208 | 11.4k | struct grid_cell gc; |
209 | 11.4k | int c; |
210 | | |
211 | 11.4k | style_apply(&gc, oo, "cursor-colour", NULL); |
212 | 11.4k | s->default_ccolour = gc.fg; |
213 | | |
214 | 11.4k | c = options_get_number(oo, "cursor-style"); |
215 | 11.4k | s->default_mode = 0; |
216 | 11.4k | screen_set_cursor_style(c, &s->default_cstyle, &s->default_mode); |
217 | 11.4k | } |
218 | | |
219 | | /* Set screen cursor style and mode. */ |
220 | | void |
221 | | screen_set_cursor_style(u_int style, enum screen_cursor_style *cstyle, |
222 | | int *mode) |
223 | 12.2k | { |
224 | 12.2k | switch (style) { |
225 | 11.6k | case 0: |
226 | 11.6k | *cstyle = SCREEN_CURSOR_DEFAULT; |
227 | 11.6k | break; |
228 | 68 | case 1: |
229 | 68 | *cstyle = SCREEN_CURSOR_BLOCK; |
230 | 68 | *mode |= MODE_CURSOR_BLINKING; |
231 | 68 | break; |
232 | 76 | case 2: |
233 | 76 | *cstyle = SCREEN_CURSOR_BLOCK; |
234 | 76 | *mode &= ~MODE_CURSOR_BLINKING; |
235 | 76 | break; |
236 | 70 | case 3: |
237 | 70 | *cstyle = SCREEN_CURSOR_UNDERLINE; |
238 | 70 | *mode |= MODE_CURSOR_BLINKING; |
239 | 70 | break; |
240 | 70 | case 4: |
241 | 70 | *cstyle = SCREEN_CURSOR_UNDERLINE; |
242 | 70 | *mode &= ~MODE_CURSOR_BLINKING; |
243 | 70 | break; |
244 | 70 | case 5: |
245 | 70 | *cstyle = SCREEN_CURSOR_BAR; |
246 | 70 | *mode |= MODE_CURSOR_BLINKING; |
247 | 70 | break; |
248 | 71 | case 6: |
249 | 71 | *cstyle = SCREEN_CURSOR_BAR; |
250 | 71 | *mode &= ~MODE_CURSOR_BLINKING; |
251 | 71 | break; |
252 | 12.2k | } |
253 | 12.2k | } |
254 | | |
255 | | /* Set screen cursor colour. */ |
256 | | void |
257 | | screen_set_cursor_colour(struct screen *s, int colour) |
258 | 185 | { |
259 | 185 | s->ccolour = colour; |
260 | 185 | } |
261 | | |
262 | | /* Set screen title. */ |
263 | | int |
264 | | screen_set_title(struct screen *s, const char *title, int untrusted) |
265 | 14.3k | { |
266 | 14.3k | char *new_title; |
267 | | |
268 | 14.3k | new_title = clean_name(title, untrusted); |
269 | 14.3k | if (new_title == NULL) |
270 | 319 | return (0); |
271 | 14.0k | free(s->title); |
272 | 14.0k | s->title = new_title; |
273 | 14.0k | return (1); |
274 | 14.3k | } |
275 | | |
276 | | /* Set screen path. */ |
277 | | int |
278 | | screen_set_path(struct screen *s, const char *path, int untrusted) |
279 | 269 | { |
280 | 269 | char *new_path; |
281 | | |
282 | 269 | new_path = clean_name(path, untrusted); |
283 | 269 | if (new_path == NULL) |
284 | 0 | return (0); |
285 | 269 | free(s->path); |
286 | 269 | s->path = new_path; |
287 | 269 | return (1); |
288 | 269 | } |
289 | | |
290 | | /* Push the current title onto the stack. */ |
291 | | void |
292 | | screen_push_title(struct screen *s) |
293 | 551 | { |
294 | 551 | struct screen_title_entry *title_entry; |
295 | | |
296 | 551 | log_debug("%s: %u", __func__, s->ntitles); |
297 | | |
298 | 703 | while (s->ntitles >= 10) { |
299 | 152 | title_entry = TAILQ_LAST(s->titles, screen_titles); |
300 | 152 | free(title_entry->text); |
301 | 152 | TAILQ_REMOVE(s->titles, title_entry, entry); |
302 | 152 | free(title_entry); |
303 | 152 | s->ntitles--; |
304 | 152 | } |
305 | | |
306 | 551 | if (s->titles == NULL) { |
307 | 52 | s->titles = xmalloc(sizeof *s->titles); |
308 | 52 | TAILQ_INIT(s->titles); |
309 | 52 | } |
310 | 551 | title_entry = xmalloc(sizeof *title_entry); |
311 | 551 | title_entry->text = xstrdup(s->title); |
312 | 551 | TAILQ_INSERT_HEAD(s->titles, title_entry, entry); |
313 | 551 | s->ntitles++; |
314 | 551 | } |
315 | | |
316 | | /* |
317 | | * Pop a title from the stack and set it as the screen title. If the stack is |
318 | | * empty, do nothing. |
319 | | */ |
320 | | void |
321 | | screen_pop_title(struct screen *s) |
322 | 344 | { |
323 | 344 | struct screen_title_entry *title_entry; |
324 | | |
325 | 344 | if (s->titles == NULL) |
326 | 100 | return; |
327 | 244 | log_debug("%s: %u", __func__, s->ntitles); |
328 | | |
329 | 244 | title_entry = TAILQ_FIRST(s->titles); |
330 | 244 | if (title_entry != NULL) { |
331 | 178 | free(s->title); |
332 | 178 | s->title = title_entry->text; |
333 | 178 | TAILQ_REMOVE(s->titles, title_entry, entry); |
334 | 178 | free(title_entry); |
335 | 178 | s->ntitles--; |
336 | 178 | } |
337 | 244 | } |
338 | | |
339 | | /* |
340 | | * Set the progress bar state and progress. The progress will not be updated |
341 | | * if p is negative. |
342 | | */ |
343 | | void |
344 | | screen_set_progress_bar(struct screen *s, enum progress_bar_state pbs, int p) |
345 | 23.1k | { |
346 | 23.1k | s->progress_bar.state = pbs; |
347 | 23.1k | if (p >= 0 && pbs != PROGRESS_BAR_INDETERMINATE) |
348 | 22.9k | s->progress_bar.progress = p; |
349 | 23.1k | } |
350 | | |
351 | | |
352 | | /* Resize screen with options. */ |
353 | | void |
354 | | screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, |
355 | | int eat_empty, int cursor) |
356 | 636 | { |
357 | 636 | u_int cx = s->cx, cy = s->grid->hsize + s->cy; |
358 | | |
359 | 636 | if (s->write_list != NULL) |
360 | 636 | screen_write_free_list(s); |
361 | | |
362 | 636 | log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)", |
363 | 636 | __func__, sx, sy, screen_size_x(s), screen_size_y(s), s->cx, s->cy, |
364 | 636 | cx, cy); |
365 | | |
366 | 636 | if (sx < 1) |
367 | 0 | sx = 1; |
368 | 636 | if (sy < 1) |
369 | 0 | sy = 1; |
370 | | |
371 | 636 | if (sx != screen_size_x(s)) { |
372 | 0 | s->grid->sx = sx; |
373 | 0 | screen_reset_tabs(s); |
374 | 0 | } else |
375 | 636 | reflow = 0; |
376 | | |
377 | 636 | if (sy != screen_size_y(s)) |
378 | 0 | screen_resize_y(s, sy, eat_empty, &cy); |
379 | | |
380 | | #ifdef ENABLE_SIXEL |
381 | | image_free_all(s); |
382 | | #endif |
383 | | |
384 | 636 | if (reflow) |
385 | 0 | screen_reflow(s, sx, &cx, &cy, cursor); |
386 | | |
387 | 636 | if (cy >= s->grid->hsize) { |
388 | 636 | s->cx = cx; |
389 | 636 | s->cy = cy - s->grid->hsize; |
390 | 636 | } else { |
391 | 0 | s->cx = 0; |
392 | 0 | s->cy = 0; |
393 | 0 | } |
394 | | |
395 | 636 | log_debug("%s: cursor finished at %u,%u = %u,%u", __func__, s->cx, |
396 | 636 | s->cy, cx, cy); |
397 | | |
398 | 636 | if (s->write_list != NULL) |
399 | 636 | screen_write_make_list(s); |
400 | 636 | } |
401 | | |
402 | | /* Resize screen. */ |
403 | | void |
404 | | screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) |
405 | 636 | { |
406 | 636 | screen_resize_cursor(s, sx, sy, reflow, 1, 1); |
407 | 636 | } |
408 | | |
409 | | static void |
410 | | screen_resize_y(struct screen *s, u_int sy, int eat_empty, u_int *cy) |
411 | 0 | { |
412 | 0 | struct grid *gd = s->grid; |
413 | 0 | u_int needed, available, oldy, i; |
414 | |
|
415 | 0 | if (sy == 0) |
416 | 0 | fatalx("zero size"); |
417 | 0 | oldy = screen_size_y(s); |
418 | | |
419 | | /* |
420 | | * When resizing: |
421 | | * |
422 | | * If the height is decreasing, delete lines from the bottom until |
423 | | * hitting the cursor, then push lines from the top into the history. |
424 | | * |
425 | | * When increasing, pull as many lines as possible from scrolled |
426 | | * history (not explicitly cleared from view) to the top, then fill the |
427 | | * remaining with blanks at the bottom. |
428 | | */ |
429 | | |
430 | | /* Size decreasing. */ |
431 | 0 | if (sy < oldy) { |
432 | 0 | needed = oldy - sy; |
433 | | |
434 | | /* Delete as many lines as possible from the bottom. */ |
435 | 0 | if (eat_empty) { |
436 | 0 | available = oldy - 1 - s->cy; |
437 | 0 | if (available > 0) { |
438 | 0 | if (available > needed) |
439 | 0 | available = needed; |
440 | 0 | grid_view_delete_lines(gd, oldy - available, |
441 | 0 | available, 8); |
442 | 0 | } |
443 | 0 | needed -= available; |
444 | 0 | } |
445 | | |
446 | | /* |
447 | | * Now just increase the history size, if possible, to take |
448 | | * over the lines which are left. If history is off, delete |
449 | | * lines from the top. |
450 | | */ |
451 | 0 | available = s->cy; |
452 | 0 | if (gd->flags & GRID_HISTORY) { |
453 | 0 | gd->hscrolled += needed; |
454 | 0 | gd->hsize += needed; |
455 | 0 | } else if (needed > 0 && available > 0) { |
456 | 0 | if (available > needed) |
457 | 0 | available = needed; |
458 | 0 | grid_view_delete_lines(gd, 0, available, 8); |
459 | 0 | (*cy) -= available; |
460 | 0 | } |
461 | 0 | } |
462 | | |
463 | | /* Resize line array. */ |
464 | 0 | grid_adjust_lines(gd, gd->hsize + sy); |
465 | | |
466 | | /* Size increasing. */ |
467 | 0 | if (sy > oldy) { |
468 | 0 | needed = sy - oldy; |
469 | | |
470 | | /* |
471 | | * Try to pull as much as possible out of scrolled history, if |
472 | | * it is enabled. |
473 | | */ |
474 | 0 | available = gd->hscrolled; |
475 | 0 | if (gd->flags & GRID_HISTORY && available > 0) { |
476 | 0 | if (available > needed) |
477 | 0 | available = needed; |
478 | 0 | gd->hscrolled -= available; |
479 | 0 | gd->hsize -= available; |
480 | 0 | } else |
481 | 0 | available = 0; |
482 | 0 | needed -= available; |
483 | | |
484 | | /* Then fill the rest in with blanks. */ |
485 | 0 | for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++) |
486 | 0 | grid_empty_line(gd, i, 8); |
487 | 0 | } |
488 | | |
489 | | /* Set the new size, and reset the scroll region. */ |
490 | 0 | gd->sy = sy; |
491 | 0 | s->rupper = 0; |
492 | 0 | s->rlower = screen_size_y(s) - 1; |
493 | 0 | } |
494 | | |
495 | | /* Set selection. */ |
496 | | void |
497 | | screen_set_selection(struct screen *s, u_int sx, u_int sy, |
498 | | u_int ex, u_int ey, u_int rectangle, u_int clipx, int modekeys, |
499 | | struct grid_cell *gc) |
500 | 0 | { |
501 | 0 | if (s->sel == NULL) |
502 | 0 | s->sel = xcalloc(1, sizeof *s->sel); |
503 | |
|
504 | 0 | memcpy(&s->sel->cell, gc, sizeof s->sel->cell); |
505 | 0 | s->sel->hidden = 0; |
506 | 0 | s->sel->rectangle = rectangle; |
507 | 0 | s->sel->modekeys = modekeys; |
508 | |
|
509 | 0 | s->sel->sx = sx; |
510 | 0 | s->sel->sy = sy; |
511 | 0 | s->sel->ex = ex; |
512 | 0 | s->sel->ey = ey; |
513 | 0 | s->sel->clipx = clipx; |
514 | 0 | } |
515 | | |
516 | | /* Clear selection. */ |
517 | | void |
518 | | screen_clear_selection(struct screen *s) |
519 | 22.8k | { |
520 | 22.8k | free(s->sel); |
521 | 22.8k | s->sel = NULL; |
522 | 22.8k | } |
523 | | |
524 | | /* Hide selection. */ |
525 | | void |
526 | | screen_hide_selection(struct screen *s) |
527 | 0 | { |
528 | 0 | if (s->sel != NULL) |
529 | 0 | s->sel->hidden = 1; |
530 | 0 | } |
531 | | |
532 | | /* Check if cell in selection. */ |
533 | | int |
534 | | screen_check_selection(struct screen *s, u_int px, u_int py) |
535 | 41.6k | { |
536 | 41.6k | struct screen_sel *sel = s->sel; |
537 | 41.6k | u_int xx; |
538 | | |
539 | 41.6k | if (sel == NULL || sel->hidden) |
540 | 41.6k | return (0); |
541 | 0 | if (px < sel->clipx) |
542 | 0 | return (0); |
543 | | |
544 | 0 | if (sel->rectangle) { |
545 | 0 | if (sel->sy < sel->ey) { |
546 | | /* start line < end line -- downward selection. */ |
547 | 0 | if (py < sel->sy || py > sel->ey) |
548 | 0 | return (0); |
549 | 0 | } else if (sel->sy > sel->ey) { |
550 | | /* start line > end line -- upward selection. */ |
551 | 0 | if (py > sel->sy || py < sel->ey) |
552 | 0 | return (0); |
553 | 0 | } else { |
554 | | /* starting line == ending line. */ |
555 | 0 | if (py != sel->sy) |
556 | 0 | return (0); |
557 | 0 | } |
558 | | |
559 | | /* |
560 | | * Need to include the selection start row, but not the cursor |
561 | | * row, which means the selection changes depending on which |
562 | | * one is on the left. |
563 | | */ |
564 | 0 | if (sel->ex < sel->sx) { |
565 | | /* Cursor (ex) is on the left. */ |
566 | 0 | if (px < sel->ex) |
567 | 0 | return (0); |
568 | | |
569 | 0 | if (px > sel->sx) |
570 | 0 | return (0); |
571 | 0 | } else { |
572 | | /* Selection start (sx) is on the left. */ |
573 | 0 | if (px < sel->sx) |
574 | 0 | return (0); |
575 | | |
576 | 0 | if (px > sel->ex) |
577 | 0 | return (0); |
578 | 0 | } |
579 | 0 | } else { |
580 | | /* |
581 | | * Like emacs, keep the top-left-most character, and drop the |
582 | | * bottom-right-most, regardless of copy direction. |
583 | | */ |
584 | 0 | if (sel->sy < sel->ey) { |
585 | | /* starting line < ending line -- downward selection. */ |
586 | 0 | if (py < sel->sy || py > sel->ey) |
587 | 0 | return (0); |
588 | | |
589 | 0 | if (py == sel->sy && px < sel->sx) |
590 | 0 | return (0); |
591 | | |
592 | 0 | if (sel->modekeys == MODEKEY_EMACS) |
593 | 0 | xx = (sel->ex == 0 ? 0 : sel->ex - 1); |
594 | 0 | else |
595 | 0 | xx = sel->ex; |
596 | 0 | if (py == sel->ey && px > xx) |
597 | 0 | return (0); |
598 | 0 | } else if (sel->sy > sel->ey) { |
599 | | /* starting line > ending line -- upward selection. */ |
600 | 0 | if (py > sel->sy || py < sel->ey) |
601 | 0 | return (0); |
602 | | |
603 | 0 | if (py == sel->ey && px < sel->ex) |
604 | 0 | return (0); |
605 | | |
606 | 0 | if (sel->modekeys == MODEKEY_EMACS) |
607 | 0 | xx = sel->sx - 1; |
608 | 0 | else |
609 | 0 | xx = sel->sx; |
610 | 0 | if (py == sel->sy && (sel->sx == 0 || px > xx)) |
611 | 0 | return (0); |
612 | 0 | } else { |
613 | | /* starting line == ending line. */ |
614 | 0 | if (py != sel->sy) |
615 | 0 | return (0); |
616 | | |
617 | 0 | if (sel->ex < sel->sx) { |
618 | | /* cursor (ex) is on the left */ |
619 | 0 | if (sel->modekeys == MODEKEY_EMACS) |
620 | 0 | xx = sel->sx - 1; |
621 | 0 | else |
622 | 0 | xx = sel->sx; |
623 | 0 | if (px > xx || px < sel->ex) |
624 | 0 | return (0); |
625 | 0 | } else { |
626 | | /* selection start (sx) is on the left */ |
627 | 0 | if (sel->modekeys == MODEKEY_EMACS) |
628 | 0 | xx = (sel->ex == 0 ? 0 : sel->ex - 1); |
629 | 0 | else |
630 | 0 | xx = sel->ex; |
631 | 0 | if (px < sel->sx || px > xx) |
632 | 0 | return (0); |
633 | 0 | } |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | 0 | return (1); |
638 | 0 | } |
639 | | |
640 | | /* Get selected grid cell. */ |
641 | | int |
642 | | screen_select_cell(struct screen *s, struct grid_cell *dst, |
643 | | const struct grid_cell *src) |
644 | 0 | { |
645 | 0 | if (s->sel == NULL || s->sel->hidden) |
646 | 0 | return (0); |
647 | | |
648 | 0 | memcpy(dst, &s->sel->cell, sizeof *dst); |
649 | 0 | if (COLOUR_DEFAULT(dst->fg)) |
650 | 0 | dst->fg = src->fg; |
651 | 0 | if (COLOUR_DEFAULT(dst->bg)) |
652 | 0 | dst->bg = src->bg; |
653 | 0 | utf8_copy(&dst->data, &src->data); |
654 | 0 | dst->flags = src->flags; |
655 | |
|
656 | 0 | if (dst->attr & GRID_ATTR_NOATTR) |
657 | 0 | dst->attr |= (src->attr & GRID_ATTR_CHARSET); |
658 | 0 | else |
659 | 0 | dst->attr |= src->attr; |
660 | 0 | return (1); |
661 | 0 | } |
662 | | |
663 | | /* Reflow wrapped lines. */ |
664 | | static void |
665 | | screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor) |
666 | 0 | { |
667 | 0 | u_int wx, wy; |
668 | |
|
669 | 0 | if (cursor) { |
670 | 0 | grid_wrap_position(s->grid, *cx, *cy, &wx, &wy); |
671 | 0 | log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx, |
672 | 0 | wy); |
673 | 0 | } |
674 | |
|
675 | 0 | grid_reflow(s->grid, new_x); |
676 | |
|
677 | 0 | if (cursor) { |
678 | 0 | grid_unwrap_position(s->grid, cx, cy, wx, wy); |
679 | 0 | log_debug("%s: new cursor is %u,%u", __func__, *cx, *cy); |
680 | 0 | } |
681 | 0 | else { |
682 | 0 | *cx = 0; |
683 | 0 | *cy = s->grid->hsize; |
684 | 0 | } |
685 | 0 | } |
686 | | |
687 | | /* |
688 | | * Enter alternative screen mode. A copy of the visible screen is saved and the |
689 | | * history is not updated. |
690 | | */ |
691 | | int |
692 | | screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) |
693 | 755 | { |
694 | 755 | u_int sx, sy; |
695 | | #ifdef ENABLE_SIXEL |
696 | | struct image *im; |
697 | | #endif |
698 | | |
699 | 755 | if (SCREEN_IS_ALTERNATE(s)) |
700 | 365 | return 0; |
701 | 390 | sx = screen_size_x(s); |
702 | 390 | sy = screen_size_y(s); |
703 | | |
704 | 390 | s->saved_grid = grid_create(sx, sy, 0); |
705 | 390 | grid_duplicate_lines(s->saved_grid, 0, s->grid, screen_hsize(s), sy); |
706 | 390 | if (cursor) { |
707 | 94 | s->saved_cx = s->cx; |
708 | 94 | s->saved_cy = s->cy; |
709 | 94 | } |
710 | 390 | memcpy(&s->saved_cell, gc, sizeof s->saved_cell); |
711 | | |
712 | | #ifdef ENABLE_SIXEL |
713 | | TAILQ_CONCAT(&s->saved_images, &s->images, entry); |
714 | | TAILQ_FOREACH(im, &s->saved_images, entry) |
715 | | im->list = &s->saved_images; |
716 | | #endif |
717 | | |
718 | 390 | grid_view_clear(s->grid, 0, 0, sx, sy, 8); |
719 | | |
720 | 390 | s->saved_flags = s->grid->flags; |
721 | 390 | s->grid->flags &= ~GRID_HISTORY; |
722 | | |
723 | 390 | return 1; |
724 | 755 | } |
725 | | |
726 | | /* Exit alternate screen mode and restore the copied grid. */ |
727 | | int |
728 | | screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) |
729 | 788 | { |
730 | 788 | u_int sx = screen_size_x(s), sy = screen_size_y(s); |
731 | | #ifdef ENABLE_SIXEL |
732 | | struct image *im; |
733 | | #endif |
734 | | |
735 | | /* |
736 | | * If the current size is different, temporarily resize to the old size |
737 | | * before copying back. |
738 | | */ |
739 | 788 | if (SCREEN_IS_ALTERNATE(s)) |
740 | 318 | screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 0); |
741 | | |
742 | | /* |
743 | | * Restore the cursor position and cell. This happens even if not |
744 | | * currently in the alternate screen. |
745 | | */ |
746 | 788 | if (cursor && s->saved_cx != UINT_MAX && s->saved_cy != UINT_MAX) { |
747 | 134 | s->cx = s->saved_cx; |
748 | 134 | s->cy = s->saved_cy; |
749 | 134 | if (gc != NULL) |
750 | 134 | memcpy(gc, &s->saved_cell, sizeof *gc); |
751 | 134 | } |
752 | | |
753 | | /* If not in the alternate screen, do nothing more. */ |
754 | 788 | if (!SCREEN_IS_ALTERNATE(s)) { |
755 | 470 | if (s->cx > screen_size_x(s) - 1) |
756 | 66 | s->cx = screen_size_x(s) - 1; |
757 | 470 | if (s->cy > screen_size_y(s) - 1) |
758 | 0 | s->cy = screen_size_y(s) - 1; |
759 | 470 | return 0; |
760 | 470 | } |
761 | | |
762 | | /* Restore the saved grid. */ |
763 | 318 | grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0, |
764 | 318 | s->saved_grid->sy); |
765 | | |
766 | | /* |
767 | | * Turn history back on (so resize can use it) and then resize back to |
768 | | * the current size. |
769 | | */ |
770 | 318 | if (s->saved_flags & GRID_HISTORY) |
771 | 0 | s->grid->flags |= GRID_HISTORY; |
772 | 318 | screen_resize(s, sx, sy, 1); |
773 | | |
774 | 318 | grid_destroy(s->saved_grid); |
775 | 318 | s->saved_grid = NULL; |
776 | | |
777 | | #ifdef ENABLE_SIXEL |
778 | | image_free_all(s); |
779 | | TAILQ_CONCAT(&s->images, &s->saved_images, entry); |
780 | | TAILQ_FOREACH(im, &s->images, entry) |
781 | | im->list = &s->images; |
782 | | #endif |
783 | | |
784 | 318 | if (s->cx > screen_size_x(s) - 1) |
785 | 69 | s->cx = screen_size_x(s) - 1; |
786 | 318 | if (s->cy > screen_size_y(s) - 1) |
787 | 0 | s->cy = screen_size_y(s) - 1; |
788 | | |
789 | 318 | return 1; |
790 | 788 | } |
791 | | |
792 | | /* Get mode as a string. */ |
793 | | const char * |
794 | | screen_mode_to_string(int mode) |
795 | 0 | { |
796 | 0 | static char tmp[1024]; |
797 | |
|
798 | 0 | if (mode == 0) |
799 | 0 | return ("NONE"); |
800 | 0 | if (mode == ALL_MODES) |
801 | 0 | return ("ALL"); |
802 | | |
803 | 0 | *tmp = '\0'; |
804 | 0 | if (mode & MODE_CURSOR) |
805 | 0 | strlcat(tmp, "CURSOR,", sizeof tmp); |
806 | 0 | if (mode & MODE_INSERT) |
807 | 0 | strlcat(tmp, "INSERT,", sizeof tmp); |
808 | 0 | if (mode & MODE_KCURSOR) |
809 | 0 | strlcat(tmp, "KCURSOR,", sizeof tmp); |
810 | 0 | if (mode & MODE_KKEYPAD) |
811 | 0 | strlcat(tmp, "KKEYPAD,", sizeof tmp); |
812 | 0 | if (mode & MODE_WRAP) |
813 | 0 | strlcat(tmp, "WRAP,", sizeof tmp); |
814 | 0 | if (mode & MODE_MOUSE_STANDARD) |
815 | 0 | strlcat(tmp, "MOUSE_STANDARD,", sizeof tmp); |
816 | 0 | if (mode & MODE_MOUSE_BUTTON) |
817 | 0 | strlcat(tmp, "MOUSE_BUTTON,", sizeof tmp); |
818 | 0 | if (mode & MODE_CURSOR_BLINKING) |
819 | 0 | strlcat(tmp, "CURSOR_BLINKING,", sizeof tmp); |
820 | 0 | if (mode & MODE_CURSOR_VERY_VISIBLE) |
821 | 0 | strlcat(tmp, "CURSOR_VERY_VISIBLE,", sizeof tmp); |
822 | 0 | if (mode & MODE_CURSOR_BLINKING_SET) |
823 | 0 | strlcat(tmp, "CURSOR_BLINKING_SET,", sizeof tmp); |
824 | 0 | if (mode & MODE_MOUSE_UTF8) |
825 | 0 | strlcat(tmp, "MOUSE_UTF8,", sizeof tmp); |
826 | 0 | if (mode & MODE_MOUSE_SGR) |
827 | 0 | strlcat(tmp, "MOUSE_SGR,", sizeof tmp); |
828 | 0 | if (mode & MODE_BRACKETPASTE) |
829 | 0 | strlcat(tmp, "BRACKETPASTE,", sizeof tmp); |
830 | 0 | if (mode & MODE_FOCUSON) |
831 | 0 | strlcat(tmp, "FOCUSON,", sizeof tmp); |
832 | 0 | if (mode & MODE_MOUSE_ALL) |
833 | 0 | strlcat(tmp, "MOUSE_ALL,", sizeof tmp); |
834 | 0 | if (mode & MODE_ORIGIN) |
835 | 0 | strlcat(tmp, "ORIGIN,", sizeof tmp); |
836 | 0 | if (mode & MODE_CRLF) |
837 | 0 | strlcat(tmp, "CRLF,", sizeof tmp); |
838 | 0 | if (mode & MODE_KEYS_EXTENDED) |
839 | 0 | strlcat(tmp, "KEYS_EXTENDED,", sizeof tmp); |
840 | 0 | if (mode & MODE_KEYS_EXTENDED_2) |
841 | 0 | strlcat(tmp, "KEYS_EXTENDED_2,", sizeof tmp); |
842 | 0 | if (mode & MODE_THEME_UPDATES) |
843 | 0 | strlcat(tmp, "THEME_UPDATES,", sizeof tmp); |
844 | 0 | if (mode & MODE_SYNC) |
845 | 0 | strlcat(tmp, "SYNC,", sizeof tmp); |
846 | 0 | if (*tmp != '\0') |
847 | 0 | tmp[strlen(tmp) - 1] = '\0'; |
848 | 0 | return (tmp); |
849 | 0 | } |
850 | | |
851 | | /* Convert screen to a string. */ |
852 | | const char * |
853 | | screen_print(struct screen *s, int line) |
854 | 0 | { |
855 | 0 | static char *buf; |
856 | 0 | static size_t len = 16384; |
857 | 0 | const char *acs; |
858 | 0 | u_int x, y; |
859 | 0 | int n; |
860 | 0 | size_t last = 0; |
861 | 0 | struct utf8_data ud; |
862 | 0 | struct grid_line *gl; |
863 | 0 | struct grid_cell_entry *gce; |
864 | |
|
865 | 0 | if (buf == NULL) |
866 | 0 | buf = xmalloc(len); |
867 | |
|
868 | 0 | for (y = 0; y < screen_hsize(s) + s->grid->sy; y++) { |
869 | 0 | if (line >= 0 && y != (u_int)line) |
870 | 0 | continue; |
871 | 0 | n = snprintf(buf + last, len - last, "%.4d \"", y); |
872 | 0 | if (n <= 0 || (u_int)n >= len - last) |
873 | 0 | goto out; |
874 | 0 | last += n; |
875 | |
|
876 | 0 | gl = &s->grid->linedata[y]; |
877 | 0 | for (x = 0; x < gl->cellused; x++) { |
878 | 0 | gce = &gl->celldata[x]; |
879 | 0 | if (gce->flags & GRID_FLAG_PADDING) |
880 | 0 | continue; |
881 | | |
882 | 0 | if (~gce->flags & GRID_FLAG_EXTENDED) { |
883 | 0 | if (last + 2 >= len) |
884 | 0 | goto out; |
885 | 0 | buf[last++] = gce->data.data; |
886 | 0 | } else if (gce->flags & GRID_FLAG_TAB) { |
887 | 0 | if (last + 2 >= len) |
888 | 0 | goto out; |
889 | 0 | buf[last++] = '\t'; |
890 | 0 | } else if (gce->flags & GRID_ATTR_CHARSET) { |
891 | 0 | acs = tty_acs_get(NULL, gce->data.data); |
892 | 0 | if (acs != NULL) |
893 | 0 | n = strlen(acs); |
894 | 0 | else { |
895 | 0 | acs = &gce->data.data; |
896 | 0 | n = 1; |
897 | 0 | } |
898 | 0 | if (last + n + 1 >= len) |
899 | 0 | goto out; |
900 | 0 | memcpy(buf + last, acs, n); |
901 | 0 | last += n; |
902 | 0 | } else { |
903 | 0 | utf8_to_data(gl->extddata[gce->offset].data, |
904 | 0 | &ud); |
905 | 0 | if (ud.size > 0) { |
906 | 0 | if (last + ud.size + 1 >= len) |
907 | 0 | goto out; |
908 | 0 | memcpy(buf + last, ud.data, ud.size); |
909 | 0 | last += ud.size; |
910 | 0 | } |
911 | 0 | } |
912 | 0 | } |
913 | | |
914 | 0 | if (last + 3 >= len) |
915 | 0 | goto out; |
916 | 0 | buf[last++] = '"'; |
917 | 0 | buf[last++] = '\n'; |
918 | 0 | } |
919 | | |
920 | 0 | out: |
921 | 0 | buf[last] = '\0'; |
922 | 0 | return (buf); |
923 | 0 | } |