1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2009, 2013 The FreeBSD Foundation
5 *
6 * This software was developed by Ed Schouten under sponsorship from the
7 * FreeBSD Foundation.
8 *
9 * Portions of this software were developed by Oleksandr Rybalko
10 * under sponsorship from the FreeBSD Foundation.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/kernel.h>
37#include <sys/lock.h>
38#include <sys/malloc.h>
39#include <sys/mutex.h>
40#include <sys/reboot.h>
41
42#include <dev/vt/vt.h>
43
44static MALLOC_DEFINE(M_VTBUF, "vtbuf", "vt buffer");
45
46#define	VTBUF_LOCK(vb)		mtx_lock_spin(&(vb)->vb_lock)
47#define	VTBUF_UNLOCK(vb)	mtx_unlock_spin(&(vb)->vb_lock)
48
49#define POS_INDEX(c, r) (((r) << 12) + (c))
50#define	POS_COPY(d, s)	do {	\
51	(d).tp_col = (s).tp_col;	\
52	(d).tp_row = (s).tp_row;	\
53} while (0)
54
55#ifndef SC_NO_CUTPASTE
56static int vtbuf_htw(const struct vt_buf *vb, int row);
57static int vtbuf_wth(const struct vt_buf *vb, int row);
58static int vtbuf_in_this_range(int begin, int test, int end, int sz);
59#endif
60
61/*
62 * line4
63 * line5 <--- curroffset (terminal output to that line)
64 * line0
65 * line1                  <--- roffset (history display from that point)
66 * line2
67 * line3
68 */
69int
70vthistory_seek(struct vt_buf *vb, int offset, int whence)
71{
72	int diff, top, bottom, roffset;
73
74	/* No scrolling if not enabled. */
75	if ((vb->vb_flags & VBF_SCROLL) == 0) {
76		if (vb->vb_roffset != vb->vb_curroffset) {
77			vb->vb_roffset = vb->vb_curroffset;
78			return (0xffff);
79		}
80		return (0); /* No changes */
81	}
82
83	/* "top" may be a negative integer. */
84	bottom = vb->vb_curroffset;
85	top = (vb->vb_flags & VBF_HISTORY_FULL) ?
86	    bottom + vb->vb_scr_size.tp_row - vb->vb_history_size :
87	    0;
88
89	roffset = 0; /* Make gcc happy. */
90	switch (whence) {
91	case VHS_SET:
92		if (offset < 0)
93			offset = 0;
94		roffset = top + offset;
95		break;
96	case VHS_CUR:
97		/*
98		 * Operate on copy of offset value, since it temporary
99		 * can be bigger than amount of rows in buffer.
100		 */
101		roffset = vb->vb_roffset;
102		if (roffset >= bottom + vb->vb_scr_size.tp_row)
103			roffset -= vb->vb_history_size;
104
105		roffset += offset;
106		roffset = MAX(roffset, top);
107		roffset = MIN(roffset, bottom);
108
109		if (roffset < 0)
110			roffset = vb->vb_history_size + roffset;
111
112		break;
113	case VHS_END:
114		/* Go to current offset. */
115		roffset = vb->vb_curroffset;
116		break;
117	}
118
119	diff = vb->vb_roffset != roffset;
120	vb->vb_roffset = roffset;
121
122	return (diff);
123}
124
125void
126vthistory_addlines(struct vt_buf *vb, int offset)
127{
128#ifndef SC_NO_CUTPASTE
129	int cur, sz;
130#endif
131
132	vb->vb_curroffset += offset;
133	if (vb->vb_curroffset + vb->vb_scr_size.tp_row >= vb->vb_history_size) {
134		vb->vb_flags |= VBF_HISTORY_FULL;
135		vb->vb_curroffset %= vb->vb_history_size;
136	}
137	if ((vb->vb_flags & VBF_SCROLL) == 0) {
138		vb->vb_roffset = vb->vb_curroffset;
139	}
140
141#ifndef SC_NO_CUTPASTE
142	sz = vb->vb_history_size;
143	cur = vb->vb_roffset + vb->vb_scr_size.tp_row + sz - 1;
144	if (vtbuf_in_this_range(cur, vb->vb_mark_start.tp_row, cur + offset, sz) ||
145	    vtbuf_in_this_range(cur, vb->vb_mark_end.tp_row, cur + offset, sz)) {
146		/* clear screen selection */
147		vb->vb_mark_start.tp_row = vb->vb_mark_end.tp_row;
148		vb->vb_mark_start.tp_col = vb->vb_mark_end.tp_col;
149	}
150#endif
151}
152
153void
154vthistory_getpos(const struct vt_buf *vb, unsigned int *offset)
155{
156
157	*offset = vb->vb_roffset;
158}
159
160#ifndef SC_NO_CUTPASTE	/* Only mouse support use it now. */
161/* Translate history row to current view row number. */
162static int
163vtbuf_htw(const struct vt_buf *vb, int row)
164{
165
166	/*
167	 * total 1000 rows.
168	 * History offset	roffset	winrow
169	 *	205		200	((205 - 200 + 1000) % 1000) = 5
170	 *	90		990	((90 - 990 + 1000) % 1000) = 100
171	 */
172	return ((row - vb->vb_roffset + vb->vb_history_size) %
173	    vb->vb_history_size);
174}
175
176/* Translate current view row number to history row. */
177static int
178vtbuf_wth(const struct vt_buf *vb, int row)
179{
180
181	return ((vb->vb_roffset + row) % vb->vb_history_size);
182}
183
184/*
185 * Test if an index in a circular buffer is within a range.
186 *
187 * begin - start index
188 * end - end index
189 * test - test index
190 * sz - size of circular buffer when it turns over
191 */
192static int
193vtbuf_in_this_range(int begin, int test, int end, int sz)
194{
195
196	begin %= sz;
197	end %= sz;
198
199	/* check for inversion */
200	if (begin > end)
201		return (test >= begin || test < end);
202	else
203		return (test >= begin && test < end);
204}
205#endif
206
207int
208vtbuf_iscursor(const struct vt_buf *vb, int row, int col)
209{
210#ifndef SC_NO_CUTPASTE
211	int sc, sr, sz, ec, er, tmp;
212#endif
213
214	if ((vb->vb_flags & (VBF_CURSOR|VBF_SCROLL)) == VBF_CURSOR &&
215	    (vb->vb_cursor.tp_row == row) && (vb->vb_cursor.tp_col == col))
216		return (1);
217
218#ifndef SC_NO_CUTPASTE
219	/* Mark cut/paste region. */
220	if (vb->vb_mark_start.tp_col == vb->vb_mark_end.tp_col &&
221	    vb->vb_mark_start.tp_row == vb->vb_mark_end.tp_row)
222		return (0);
223
224	sc = vb->vb_mark_start.tp_col;
225	sr = vb->vb_mark_start.tp_row;
226	ec = vb->vb_mark_end.tp_col;
227	er = vb->vb_mark_end.tp_row;
228
229	/*
230	 * Information about if the selection was made bottom-top or
231	 * top-bottom is lost due to modulo arithmetics and needs to
232	 * be recovered:
233	 */
234	sz = vb->vb_history_size;
235	tmp = (sz + er - sr) % sz;
236	row = vtbuf_wth(vb, row);
237
238	/* Swap start and end if start > end */
239	if ((2 * tmp) > sz || (tmp == 0 && sc > ec)) {
240		tmp = sc; sc = ec; ec = tmp;
241		tmp = sr; sr = er; er = tmp;
242	}
243
244	if (vtbuf_in_this_range(POS_INDEX(sc, sr), POS_INDEX(col, row),
245	    POS_INDEX(ec, er), POS_INDEX(0, sz)))
246		return (1);
247#endif
248
249	return (0);
250}
251
252void
253vtbuf_lock(struct vt_buf *vb)
254{
255
256	VTBUF_LOCK(vb);
257}
258
259void
260vtbuf_unlock(struct vt_buf *vb)
261{
262
263	VTBUF_UNLOCK(vb);
264}
265
266void
267vtbuf_dirty(struct vt_buf *vb, const term_rect_t *area)
268{
269
270	if (vb->vb_dirtyrect.tr_begin.tp_row > area->tr_begin.tp_row)
271		vb->vb_dirtyrect.tr_begin.tp_row = area->tr_begin.tp_row;
272	if (vb->vb_dirtyrect.tr_begin.tp_col > area->tr_begin.tp_col)
273		vb->vb_dirtyrect.tr_begin.tp_col = area->tr_begin.tp_col;
274	if (vb->vb_dirtyrect.tr_end.tp_row < area->tr_end.tp_row)
275		vb->vb_dirtyrect.tr_end.tp_row = area->tr_end.tp_row;
276	if (vb->vb_dirtyrect.tr_end.tp_col < area->tr_end.tp_col)
277		vb->vb_dirtyrect.tr_end.tp_col = area->tr_end.tp_col;
278}
279
280static inline void
281vtbuf_dirty_cell(struct vt_buf *vb, const term_pos_t *p)
282{
283	term_rect_t area;
284
285	area.tr_begin = *p;
286	area.tr_end.tp_row = p->tp_row + 1;
287	area.tr_end.tp_col = p->tp_col + 1;
288	vtbuf_dirty(vb, &area);
289}
290
291static void
292vtbuf_make_undirty(struct vt_buf *vb)
293{
294
295	vb->vb_dirtyrect.tr_begin = vb->vb_scr_size;
296	vb->vb_dirtyrect.tr_end.tp_row = vb->vb_dirtyrect.tr_end.tp_col = 0;
297}
298
299void
300vtbuf_undirty(struct vt_buf *vb, term_rect_t *r)
301{
302
303	*r = vb->vb_dirtyrect;
304	vtbuf_make_undirty(vb);
305}
306
307void
308vtbuf_copy(struct vt_buf *vb, const term_rect_t *r, const term_pos_t *p2)
309{
310	const term_pos_t *p1 = &r->tr_begin;
311	term_rect_t area;
312	unsigned int rows, cols;
313	int pr, rdiff;
314
315	KASSERT(r->tr_begin.tp_row < vb->vb_scr_size.tp_row,
316	    ("vtbuf_copy begin.tp_row %d must be less than screen width %d",
317		r->tr_begin.tp_row, vb->vb_scr_size.tp_row));
318	KASSERT(r->tr_begin.tp_col < vb->vb_scr_size.tp_col,
319	    ("vtbuf_copy begin.tp_col %d must be less than screen height %d",
320		r->tr_begin.tp_col, vb->vb_scr_size.tp_col));
321
322	KASSERT(r->tr_end.tp_row <= vb->vb_scr_size.tp_row,
323	    ("vtbuf_copy end.tp_row %d must be less than screen width %d",
324		r->tr_end.tp_row, vb->vb_scr_size.tp_row));
325	KASSERT(r->tr_end.tp_col <= vb->vb_scr_size.tp_col,
326	    ("vtbuf_copy end.tp_col %d must be less than screen height %d",
327		r->tr_end.tp_col, vb->vb_scr_size.tp_col));
328
329	KASSERT(p2->tp_row < vb->vb_scr_size.tp_row,
330	    ("vtbuf_copy tp_row %d must be less than screen width %d",
331		p2->tp_row, vb->vb_scr_size.tp_row));
332	KASSERT(p2->tp_col < vb->vb_scr_size.tp_col,
333	    ("vtbuf_copy tp_col %d must be less than screen height %d",
334		p2->tp_col, vb->vb_scr_size.tp_col));
335
336	rows = r->tr_end.tp_row - r->tr_begin.tp_row;
337	rdiff = r->tr_begin.tp_row - p2->tp_row;
338	cols = r->tr_end.tp_col - r->tr_begin.tp_col;
339	if (r->tr_begin.tp_row > p2->tp_row && r->tr_begin.tp_col == 0 &&
340	    r->tr_end.tp_col == vb->vb_scr_size.tp_col && /* Full row. */
341	    (rows + rdiff) == vb->vb_scr_size.tp_row && /* Whole screen. */
342	    rdiff > 0) { /* Only forward direction. Do not eat history. */
343		vthistory_addlines(vb, rdiff);
344	} else if (p2->tp_row < p1->tp_row) {
345		/* Handle overlapping copies of line segments. */
346		/* Move data up. */
347		for (pr = 0; pr < rows; pr++)
348			memmove(
349			    &VTBUF_FIELD(vb, p2->tp_row + pr, p2->tp_col),
350			    &VTBUF_FIELD(vb, p1->tp_row + pr, p1->tp_col),
351			    cols * sizeof(term_char_t));
352	} else {
353		/* Move data down. */
354		for (pr = rows - 1; pr >= 0; pr--)
355			memmove(
356			    &VTBUF_FIELD(vb, p2->tp_row + pr, p2->tp_col),
357			    &VTBUF_FIELD(vb, p1->tp_row + pr, p1->tp_col),
358			    cols * sizeof(term_char_t));
359	}
360
361	area.tr_begin = *p2;
362	area.tr_end.tp_row = MIN(p2->tp_row + rows, vb->vb_scr_size.tp_row);
363	area.tr_end.tp_col = MIN(p2->tp_col + cols, vb->vb_scr_size.tp_col);
364	vtbuf_dirty(vb, &area);
365}
366
367static void
368vtbuf_do_fill(struct vt_buf *vb, const term_rect_t *r, term_char_t c)
369{
370	unsigned int pr, pc;
371	term_char_t *row;
372
373	for (pr = r->tr_begin.tp_row; pr < r->tr_end.tp_row; pr++) {
374		row = vb->vb_rows[(vb->vb_curroffset + pr) %
375		    VTBUF_MAX_HEIGHT(vb)];
376		for (pc = r->tr_begin.tp_col; pc < r->tr_end.tp_col; pc++) {
377			row[pc] = c;
378		}
379	}
380}
381
382void
383vtbuf_fill(struct vt_buf *vb, const term_rect_t *r, term_char_t c)
384{
385
386	KASSERT(r->tr_begin.tp_row < vb->vb_scr_size.tp_row,
387	    ("vtbuf_fill begin.tp_row %d must be < screen height %d",
388		r->tr_begin.tp_row, vb->vb_scr_size.tp_row));
389	KASSERT(r->tr_begin.tp_col < vb->vb_scr_size.tp_col,
390	    ("vtbuf_fill begin.tp_col %d must be < screen width %d",
391		r->tr_begin.tp_col, vb->vb_scr_size.tp_col));
392
393	KASSERT(r->tr_end.tp_row <= vb->vb_scr_size.tp_row,
394	    ("vtbuf_fill end.tp_row %d must be <= screen height %d",
395		r->tr_end.tp_row, vb->vb_scr_size.tp_row));
396	KASSERT(r->tr_end.tp_col <= vb->vb_scr_size.tp_col,
397	    ("vtbuf_fill end.tp_col %d must be <= screen width %d",
398		r->tr_end.tp_col, vb->vb_scr_size.tp_col));
399
400	vtbuf_do_fill(vb, r, c);
401	vtbuf_dirty(vb, r);
402}
403
404static void
405vtbuf_init_rows(struct vt_buf *vb)
406{
407	int r;
408
409	vb->vb_history_size = MAX(vb->vb_history_size, vb->vb_scr_size.tp_row);
410
411	for (r = 0; r < vb->vb_history_size; r++)
412		vb->vb_rows[r] = &vb->vb_buffer[r * vb->vb_scr_size.tp_col];
413}
414
415static void
416vtbuf_do_clearhistory(struct vt_buf *vb)
417{
418	term_rect_t rect;
419	const teken_attr_t *a;
420	term_char_t ch;
421
422	a = teken_get_curattr(&vb->vb_terminal->tm_emulator);
423	ch = TCOLOR_FG(a->ta_fgcolor) | TCOLOR_BG(a->ta_bgcolor);
424
425	rect.tr_begin.tp_row = rect.tr_begin.tp_col = 0;
426	rect.tr_end.tp_col = vb->vb_scr_size.tp_col;
427	rect.tr_end.tp_row = vb->vb_history_size;
428
429	vtbuf_do_fill(vb, &rect, VTBUF_SPACE_CHAR(ch));
430}
431
432static void
433vtbuf_reset_scrollback(struct vt_buf *vb)
434{
435	vb->vb_roffset = 0;
436	vb->vb_curroffset = 0;
437	vb->vb_mark_start.tp_row = 0;
438	vb->vb_mark_start.tp_col = 0;
439	vb->vb_mark_end.tp_row = 0;
440	vb->vb_mark_end.tp_col = 0;
441}
442
443void
444vtbuf_init_early(struct vt_buf *vb)
445{
446	vb->vb_flags |= VBF_CURSOR;
447	vtbuf_reset_scrollback(vb);
448	vtbuf_init_rows(vb);
449	vtbuf_do_clearhistory(vb);
450	vtbuf_make_undirty(vb);
451	if ((vb->vb_flags & VBF_MTX_INIT) == 0) {
452		mtx_init(&vb->vb_lock, "vtbuf", NULL, MTX_SPIN);
453		vb->vb_flags |= VBF_MTX_INIT;
454	}
455}
456
457void
458vtbuf_init(struct vt_buf *vb, const term_pos_t *p)
459{
460	int sz;
461
462	vb->vb_scr_size = *p;
463	vb->vb_history_size = VBF_DEFAULT_HISTORY_SIZE;
464
465	if ((vb->vb_flags & VBF_STATIC) == 0) {
466		sz = vb->vb_history_size * p->tp_col * sizeof(term_char_t);
467		vb->vb_buffer = malloc(sz, M_VTBUF, M_WAITOK | M_ZERO);
468
469		sz = vb->vb_history_size * sizeof(term_char_t *);
470		vb->vb_rows = malloc(sz, M_VTBUF, M_WAITOK | M_ZERO);
471	}
472
473	vtbuf_init_early(vb);
474}
475
476void
477vtbuf_clearhistory(struct vt_buf *vb)
478{
479	VTBUF_LOCK(vb);
480	vtbuf_do_clearhistory(vb);
481	vtbuf_reset_scrollback(vb);
482	vb->vb_flags &= ~VBF_HISTORY_FULL;
483	VTBUF_UNLOCK(vb);
484}
485
486void
487vtbuf_sethistory_size(struct vt_buf *vb, unsigned int size)
488{
489	term_pos_t p;
490
491	/* With same size */
492	p.tp_row = vb->vb_scr_size.tp_row;
493	p.tp_col = vb->vb_scr_size.tp_col;
494	vtbuf_grow(vb, &p, size);
495}
496
497void
498vtbuf_grow(struct vt_buf *vb, const term_pos_t *p, unsigned int history_size)
499{
500	term_char_t *old, *new, **rows, **oldrows, **copyrows, *row, *oldrow;
501	unsigned int w, h, c, r, old_history_size;
502	size_t bufsize, rowssize;
503	int history_full;
504	const teken_attr_t *a;
505	term_char_t ch;
506
507	a = teken_get_curattr(&vb->vb_terminal->tm_emulator);
508	ch = TCOLOR_FG(a->ta_fgcolor) | TCOLOR_BG(a->ta_bgcolor);
509
510	history_size = MAX(history_size, p->tp_row);
511
512	/* Allocate new buffer. */
513	bufsize = history_size * p->tp_col * sizeof(term_char_t);
514	new = malloc(bufsize, M_VTBUF, M_WAITOK | M_ZERO);
515	rowssize = history_size * sizeof(term_pos_t *);
516	rows = malloc(rowssize, M_VTBUF, M_WAITOK | M_ZERO);
517
518	/* Toggle it. */
519	VTBUF_LOCK(vb);
520	old = vb->vb_flags & VBF_STATIC ? NULL : vb->vb_buffer;
521	oldrows = vb->vb_flags & VBF_STATIC ? NULL : vb->vb_rows;
522	copyrows = vb->vb_rows;
523
524	w = vb->vb_scr_size.tp_col;
525	h = vb->vb_scr_size.tp_row;
526	old_history_size = vb->vb_history_size;
527	history_full = vb->vb_flags & VBF_HISTORY_FULL ||
528	    vb->vb_curroffset + h >= history_size;
529
530	vb->vb_history_size = history_size;
531	vb->vb_buffer = new;
532	vb->vb_rows = rows;
533	vb->vb_flags &= ~VBF_STATIC;
534	vb->vb_scr_size = *p;
535	vtbuf_init_rows(vb);
536
537	/*
538	 * Copy rows to the new buffer. The first row in the history
539	 * is back to index 0, ie. the new buffer doesn't cycle.
540	 */
541	if (history_size > old_history_size) {
542		for (r = 0; r < old_history_size; r++) {
543			row = rows[r];
544
545			/* Compute the corresponding row in the old buffer. */
546			if (history_full)
547				/*
548				 * The buffer is full, the "top" row is
549				 * the one just after the viewable area
550				 * (curroffset + viewable height) in the
551				 * cycling buffer. The corresponding row
552				 * is computed from this top row.
553				 */
554				oldrow = copyrows[
555				    (vb->vb_curroffset + h + r) %
556				    old_history_size];
557			else
558				/*
559				 * The buffer is not full, therefore,
560				 * we didn't cycle already. The
561				 * corresponding rows are the same in
562				 * both buffers.
563				 */
564				oldrow = copyrows[r];
565
566			memmove(row, oldrow,
567			    MIN(p->tp_col, w) * sizeof(term_char_t));
568
569			/*
570			 * XXX VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR) will
571			 * extended lines of kernel text using the wrong
572			 * background color.
573			 */
574			for (c = MIN(p->tp_col, w); c < p->tp_col; c++) {
575				row[c] = VTBUF_SPACE_CHAR(ch);
576			}
577		}
578
579		/* Fill remaining rows. */
580		for (r = old_history_size; r < history_size; r++) {
581			row = rows[r];
582			for (c = MIN(p->tp_col, w); c < p->tp_col; c++) {
583				row[c] = VTBUF_SPACE_CHAR(ch);
584			}
585		}
586
587		vb->vb_flags &= ~VBF_HISTORY_FULL;
588
589		/*
590		 * If the screen is already filled (there are non-visible lines
591		 * above the current viewable area), adjust curroffset to the
592		 * new viewable area.
593		 *
594		 * If the old buffer was full, set curroffset to the
595		 * <h>th most recent line of history in the new, non-cycled
596		 * buffer. Otherwise, it didn't cycle, so the old curroffset
597		 * is the same in the new buffer.
598		 */
599		if (history_full)
600			vb->vb_curroffset = old_history_size - h;
601	} else {
602		/*
603		 * (old_history_size - history_size) lines of history are
604		 * dropped.
605		 */
606		for (r = 0; r < history_size; r++) {
607			row = rows[r];
608
609			/*
610			 * Compute the corresponding row in the old buffer.
611			 *
612			 * See the equivalent if{} block above for an
613			 * explanation.
614			 */
615			if (history_full)
616				oldrow = copyrows[
617				    (vb->vb_curroffset + h + r +
618				     (old_history_size - history_size)) %
619				    old_history_size];
620			else
621				oldrow = copyrows[r];
622
623			memmove(row, oldrow,
624			    MIN(p->tp_col, w) * sizeof(term_char_t));
625
626			/*
627			 * XXX VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR) will
628			 * extended lines of kernel text using the wrong
629			 * background color.
630			 */
631			for (c = MIN(p->tp_col, w); c < p->tp_col; c++) {
632				row[c] = VTBUF_SPACE_CHAR(ch);
633			}
634		}
635
636		if (history_full) {
637			vb->vb_curroffset = history_size - h;
638			vb->vb_flags |= VBF_HISTORY_FULL;
639		}
640	}
641
642	vb->vb_roffset = vb->vb_curroffset;
643
644	/* Adjust cursor position. */
645	if (vb->vb_cursor.tp_col > p->tp_col - 1)
646		/*
647		 * Move cursor to the last column, in case its previous
648		 * position is outside of the new screen area.
649		 */
650		vb->vb_cursor.tp_col = p->tp_col - 1;
651
652	if (vb->vb_curroffset > 0 || vb->vb_cursor.tp_row > p->tp_row - 1)
653		/* Move cursor to the last line on the screen. */
654		vb->vb_cursor.tp_row = p->tp_row - 1;
655
656	VTBUF_UNLOCK(vb);
657
658	/* Deallocate old buffer. */
659	free(old, M_VTBUF);
660	free(oldrows, M_VTBUF);
661}
662
663void
664vtbuf_putchar(struct vt_buf *vb, const term_pos_t *p, term_char_t c)
665{
666	term_char_t *row;
667
668	KASSERT(p->tp_row < vb->vb_scr_size.tp_row,
669	    ("vtbuf_putchar tp_row %d must be less than screen width %d",
670		p->tp_row, vb->vb_scr_size.tp_row));
671	KASSERT(p->tp_col < vb->vb_scr_size.tp_col,
672	    ("vtbuf_putchar tp_col %d must be less than screen height %d",
673		p->tp_col, vb->vb_scr_size.tp_col));
674
675	row = vb->vb_rows[(vb->vb_curroffset + p->tp_row) %
676	    VTBUF_MAX_HEIGHT(vb)];
677	if (row[p->tp_col] != c) {
678		row[p->tp_col] = c;
679		vtbuf_dirty_cell(vb, p);
680	}
681}
682
683void
684vtbuf_cursor_position(struct vt_buf *vb, const term_pos_t *p)
685{
686	if (vb->vb_flags & VBF_CURSOR) {
687		vtbuf_dirty_cell(vb, &vb->vb_cursor);
688		vb->vb_cursor = *p;
689		vtbuf_dirty_cell(vb, &vb->vb_cursor);
690	} else {
691		vb->vb_cursor = *p;
692	}
693}
694
695#ifndef SC_NO_CUTPASTE
696static void
697vtbuf_flush_mark(struct vt_buf *vb)
698{
699	term_rect_t area;
700	int s, e;
701
702	/* Notify renderer to update marked region. */
703	if ((vb->vb_mark_start.tp_col != vb->vb_mark_end.tp_col) ||
704	    (vb->vb_mark_start.tp_row != vb->vb_mark_end.tp_row)) {
705		s = vtbuf_htw(vb, vb->vb_mark_start.tp_row);
706		e = vtbuf_htw(vb, vb->vb_mark_end.tp_row);
707
708		area.tr_begin.tp_col = 0;
709		area.tr_begin.tp_row = MIN(s, e);
710
711		area.tr_end.tp_col = vb->vb_scr_size.tp_col;
712		area.tr_end.tp_row = MAX(s, e) + 1;
713
714		VTBUF_LOCK(vb);
715		vtbuf_dirty(vb, &area);
716		VTBUF_UNLOCK(vb);
717	}
718}
719
720int
721vtbuf_get_marked_len(struct vt_buf *vb)
722{
723	int ei, si, sz;
724	term_pos_t s, e;
725
726	/* Swap according to window coordinates. */
727	if (POS_INDEX(vtbuf_htw(vb, vb->vb_mark_start.tp_row),
728	    vb->vb_mark_start.tp_col) >
729	    POS_INDEX(vtbuf_htw(vb, vb->vb_mark_end.tp_row),
730	    vb->vb_mark_end.tp_col)) {
731		POS_COPY(e, vb->vb_mark_start);
732		POS_COPY(s, vb->vb_mark_end);
733	} else {
734		POS_COPY(s, vb->vb_mark_start);
735		POS_COPY(e, vb->vb_mark_end);
736	}
737
738	si = s.tp_row * vb->vb_scr_size.tp_col + s.tp_col;
739	ei = e.tp_row * vb->vb_scr_size.tp_col + e.tp_col;
740
741	/* Number symbols and number of rows to inject \r */
742	sz = ei - si + (1 + e.tp_row - s.tp_row);
743
744	return (sz * sizeof(term_char_t));
745}
746
747static bool
748tchar_is_word_separator(term_char_t ch)
749{
750	/* List of unicode word separator characters: */
751	switch (TCHAR_CHARACTER(ch)) {
752	case 0x0020: /* SPACE */
753	case 0x180E: /* MONGOLIAN VOWEL SEPARATOR */
754	case 0x2002: /* EN SPACE (nut) */
755	case 0x2003: /* EM SPACE (mutton) */
756	case 0x2004: /* THREE-PER-EM SPACE (thick space) */
757	case 0x2005: /* FOUR-PER-EM SPACE (mid space) */
758	case 0x2006: /* SIX-PER-EM SPACE */
759	case 0x2008: /* PUNCTUATION SPACE */
760	case 0x2009: /* THIN SPACE */
761	case 0x200A: /* HAIR SPACE */
762	case 0x200B: /* ZERO WIDTH SPACE */
763	case 0x3000: /* IDEOGRAPHIC SPACE */
764		return (true);
765	default:
766		return (false);
767	}
768}
769
770void
771vtbuf_extract_marked(struct vt_buf *vb, term_char_t *buf, int sz, int mark)
772{
773	int i, j, r, c, cs, ce;
774	term_pos_t s, e;
775
776	/* Swap according to window coordinates. */
777	if (POS_INDEX(vtbuf_htw(vb, vb->vb_mark_start.tp_row),
778	    vb->vb_mark_start.tp_col) >
779	    POS_INDEX(vtbuf_htw(vb, vb->vb_mark_end.tp_row),
780	    vb->vb_mark_end.tp_col)) {
781		POS_COPY(e, vb->vb_mark_start);
782		POS_COPY(s, vb->vb_mark_end);
783	} else {
784		POS_COPY(s, vb->vb_mark_start);
785		POS_COPY(e, vb->vb_mark_end);
786	}
787
788	i = 0;
789	for (r = s.tp_row; r <= e.tp_row; r++) {
790		cs = (r == s.tp_row)?s.tp_col:0;
791		ce = (r == e.tp_row)?e.tp_col:vb->vb_scr_size.tp_col;
792
793		/* Copy characters from terminal window. */
794		j = i;
795		for (c = cs; c < ce; c++)
796			buf[i++] = vb->vb_rows[r][c];
797
798		/* For all rows, but the last one. */
799		if (r != e.tp_row || mark == VTB_MARK_ROW) {
800			/* Trim trailing word separators, if any. */
801			for (; i != j; i--) {
802				if (!tchar_is_word_separator(buf[i - 1]))
803					break;
804			}
805			/* Add newline character as expected by TTY. */
806			buf[i++] = '\r';
807		}
808	}
809	/* Zero rest of expected buffer size, if any. */
810	while ((i * sizeof(buf[0])) < sz)
811		buf[i++] = '\0';
812
813	MPASS((i * sizeof(buf[0])) == sz);
814}
815
816int
817vtbuf_set_mark(struct vt_buf *vb, int type, int col, int row)
818{
819	term_char_t *r;
820	int i;
821
822	switch (type) {
823	case VTB_MARK_END:	/* B1 UP */
824		if (vb->vb_mark_last != VTB_MARK_MOVE)
825			return (0);
826		/* FALLTHROUGH */
827	case VTB_MARK_MOVE:
828	case VTB_MARK_EXTEND:
829		vtbuf_flush_mark(vb); /* Clean old mark. */
830		vb->vb_mark_end.tp_col = col;
831		vb->vb_mark_end.tp_row = vtbuf_wth(vb, row);
832		break;
833	case VTB_MARK_START:
834		vtbuf_flush_mark(vb); /* Clean old mark. */
835		vb->vb_mark_start.tp_col = col;
836		vb->vb_mark_start.tp_row = vtbuf_wth(vb, row);
837		/* Start again, so clear end point. */
838		vb->vb_mark_end.tp_col = col;
839		vb->vb_mark_end.tp_row = vtbuf_wth(vb, row);
840		break;
841	case VTB_MARK_WORD:
842		vtbuf_flush_mark(vb); /* Clean old mark. */
843		vb->vb_mark_start.tp_row = vb->vb_mark_end.tp_row =
844		    vtbuf_wth(vb, row);
845		r = vb->vb_rows[vb->vb_mark_start.tp_row];
846		for (i = col; i >= 0; i --) {
847			if (tchar_is_word_separator(r[i])) {
848				vb->vb_mark_start.tp_col = i + 1;
849				break;
850			}
851		}
852		/* No space - word extends to beginning of line. */
853		if (i == -1)
854			vb->vb_mark_start.tp_col = 0;
855		for (i = col; i < vb->vb_scr_size.tp_col; i++) {
856			if (tchar_is_word_separator(r[i])) {
857				vb->vb_mark_end.tp_col = i;
858				break;
859			}
860		}
861		/* No space - word extends to end of line. */
862		if (i == vb->vb_scr_size.tp_col)
863			vb->vb_mark_end.tp_col = i;
864
865		if (vb->vb_mark_start.tp_col > vb->vb_mark_end.tp_col)
866			vb->vb_mark_start.tp_col = vb->vb_mark_end.tp_col;
867		break;
868	case VTB_MARK_ROW:
869		vtbuf_flush_mark(vb); /* Clean old mark. */
870		vb->vb_mark_start.tp_col = 0;
871		vb->vb_mark_end.tp_col = vb->vb_scr_size.tp_col;
872		vb->vb_mark_start.tp_row = vb->vb_mark_end.tp_row =
873		    vtbuf_wth(vb, row);
874		break;
875	case VTB_MARK_NONE:
876		vb->vb_mark_last = type;
877		/* FALLTHROUGH */
878	default:
879		/* panic? */
880		return (0);
881	}
882
883	vb->vb_mark_last = type;
884	/* Draw new marked region. */
885	vtbuf_flush_mark(vb);
886	return (1);
887}
888#endif
889
890void
891vtbuf_cursor_visibility(struct vt_buf *vb, int yes)
892{
893	int oflags, nflags;
894
895	oflags = vb->vb_flags;
896	if (yes)
897		vb->vb_flags |= VBF_CURSOR;
898	else
899		vb->vb_flags &= ~VBF_CURSOR;
900	nflags = vb->vb_flags;
901
902	if (oflags != nflags)
903		vtbuf_dirty_cell(vb, &vb->vb_cursor);
904}
905
906void
907vtbuf_scroll_mode(struct vt_buf *vb, int yes)
908{
909	int oflags, nflags;
910
911	VTBUF_LOCK(vb);
912	oflags = vb->vb_flags;
913	if (yes)
914		vb->vb_flags |= VBF_SCROLL;
915	else
916		vb->vb_flags &= ~VBF_SCROLL;
917	nflags = vb->vb_flags;
918
919	if (oflags != nflags)
920		vtbuf_dirty_cell(vb, &vb->vb_cursor);
921	VTBUF_UNLOCK(vb);
922}
923