Index: head/sys/dev/vt/vt.h =================================================================== --- head/sys/dev/vt/vt.h (revision 333668) +++ head/sys/dev/vt/vt.h (revision 333669) @@ -1,465 +1,467 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009, 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Portions of this software were developed by Oleksandr Rybalko * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _DEV_VT_VT_H_ #define _DEV_VT_VT_H_ #include #include #include #include #include #include #include #include #include #include #include #include "opt_syscons.h" #include "opt_splash.h" #ifndef VT_MAXWINDOWS #ifdef MAXCONS #define VT_MAXWINDOWS MAXCONS #else #define VT_MAXWINDOWS 12 #endif #endif #ifndef VT_ALT_TO_ESC_HACK #define VT_ALT_TO_ESC_HACK 1 #endif #define VT_CONSWINDOW 0 #if defined(SC_TWOBUTTON_MOUSE) || defined(VT_TWOBUTTON_MOUSE) #define VT_MOUSE_PASTEBUTTON MOUSE_BUTTON3DOWN /* right button */ #define VT_MOUSE_EXTENDBUTTON MOUSE_BUTTON2DOWN /* not really used */ #else #define VT_MOUSE_PASTEBUTTON MOUSE_BUTTON2DOWN /* middle button */ #define VT_MOUSE_EXTENDBUTTON MOUSE_BUTTON3DOWN /* right button */ #endif /* defined(SC_TWOBUTTON_MOUSE) || defined(VT_TWOBUTTON_MOUSE) */ #define SC_DRIVER_NAME "vt" #ifdef VT_DEBUG #define DPRINTF(_l, ...) if (vt_debug > (_l)) printf( __VA_ARGS__ ) #define VT_CONSOLECTL_DEBUG #define VT_SYSMOUSE_DEBUG #else #define DPRINTF(_l, ...) do {} while (0) #endif #define ISSIGVALID(sig) ((sig) > 0 && (sig) < NSIG) #define VT_SYSCTL_INT(_name, _default, _descr) \ int vt_##_name = (_default); \ SYSCTL_INT(_kern_vt, OID_AUTO, _name, CTLFLAG_RWTUN, &vt_##_name, 0, _descr) struct vt_driver; void vt_allocate(const struct vt_driver *, void *); void vt_deallocate(const struct vt_driver *, void *); typedef unsigned int vt_axis_t; /* * List of locks * (d) locked by vd_lock * (b) locked by vb_lock * (G) locked by Giant * (u) unlocked, locked by higher levels * (c) const until freeing * (?) yet to be determined */ /* * Per-device datastructure. */ #ifndef SC_NO_CUTPASTE struct vt_mouse_cursor; #endif struct vt_pastebuf { term_char_t *vpb_buf; /* Copy-paste buffer. */ unsigned int vpb_bufsz; /* Buffer size. */ unsigned int vpb_len; /* Length of a last selection. */ }; struct vt_device { struct vt_window *vd_windows[VT_MAXWINDOWS]; /* (c) Windows. */ struct vt_window *vd_curwindow; /* (d) Current window. */ struct vt_window *vd_savedwindow;/* (?) Saved for suspend. */ struct vt_pastebuf vd_pastebuf; /* (?) Copy/paste buf. */ const struct vt_driver *vd_driver; /* (c) Graphics driver. */ void *vd_softc; /* (u) Driver data. */ const struct vt_driver *vd_prev_driver;/* (?) Previous driver. */ void *vd_prev_softc; /* (?) Previous driver data. */ device_t vd_video_dev; /* (?) Video adapter. */ #ifndef SC_NO_CUTPASTE struct vt_mouse_cursor *vd_mcursor; /* (?) Cursor bitmap. */ term_color_t vd_mcursor_fg; /* (?) Cursor fg color. */ term_color_t vd_mcursor_bg; /* (?) Cursor bg color. */ vt_axis_t vd_mx_drawn; /* (?) Mouse X and Y */ vt_axis_t vd_my_drawn; /* as of last redraw. */ int vd_mshown; /* (?) Mouse shown during */ #endif /* last redrawn. */ uint16_t vd_mx; /* (?) Current mouse X. */ uint16_t vd_my; /* (?) current mouse Y. */ uint32_t vd_mstate; /* (?) Mouse state. */ vt_axis_t vd_width; /* (?) Screen width. */ vt_axis_t vd_height; /* (?) Screen height. */ size_t vd_transpose; /* (?) Screen offset in FB */ struct mtx vd_lock; /* Per-device lock. */ struct cv vd_winswitch; /* (d) Window switch notify. */ struct callout vd_timer; /* (d) Display timer. */ volatile unsigned int vd_timer_armed;/* (?) Display timer started.*/ int vd_flags; /* (d) Device flags. */ #define VDF_TEXTMODE 0x01 /* Do text mode rendering. */ #define VDF_SPLASH 0x02 /* Splash screen active. */ #define VDF_ASYNC 0x04 /* vt_timer() running. */ #define VDF_INVALID 0x08 /* Entire screen should be re-rendered. */ #define VDF_DEAD 0x10 /* Early probing found nothing. */ #define VDF_INITIALIZED 0x20 /* vtterm_cnprobe already done. */ #define VDF_MOUSECURSOR 0x40 /* Mouse cursor visible. */ #define VDF_QUIET_BELL 0x80 /* Disable bell. */ #define VDF_DOWNGRADE 0x8000 /* The driver is being downgraded. */ int vd_keyboard; /* (G) Keyboard index. */ unsigned int vd_kbstate; /* (?) Device unit. */ unsigned int vd_unit; /* (c) Device unit. */ int vd_altbrk; /* (?) Alt break seq. state */ }; #define VD_PASTEBUF(vd) ((vd)->vd_pastebuf.vpb_buf) #define VD_PASTEBUFSZ(vd) ((vd)->vd_pastebuf.vpb_bufsz) #define VD_PASTEBUFLEN(vd) ((vd)->vd_pastebuf.vpb_len) #define VT_LOCK(vd) mtx_lock(&(vd)->vd_lock) #define VT_UNLOCK(vd) mtx_unlock(&(vd)->vd_lock) #define VT_LOCK_ASSERT(vd, what) mtx_assert(&(vd)->vd_lock, what) void vt_resume(struct vt_device *vd); void vt_resume_flush_timer(struct vt_device *vd, int ms); void vt_suspend(struct vt_device *vd); /* * Per-window terminal screen buffer. * * Because redrawing is performed asynchronously, the buffer keeps track * of a rectangle that needs to be redrawn (vb_dirtyrect). Because this * approach seemed to cause suboptimal performance (when the top left * and the bottom right of the screen are modified), it also uses a set * of bitmasks to keep track of the rows and columns (mod 64) that have * been modified. */ struct vt_buf { struct mtx vb_lock; /* Buffer lock. */ term_pos_t vb_scr_size; /* (b) Screen dimensions. */ int vb_flags; /* (b) Flags. */ #define VBF_CURSOR 0x1 /* Cursor visible. */ #define VBF_STATIC 0x2 /* Buffer is statically allocated. */ #define VBF_MTX_INIT 0x4 /* Mutex initialized. */ #define VBF_SCROLL 0x8 /* scroll locked mode. */ #define VBF_HISTORY_FULL 0x10 /* All rows filled. */ unsigned int vb_history_size; unsigned int vb_roffset; /* (b) History rows offset. */ unsigned int vb_curroffset; /* (b) Saved rows offset. */ term_pos_t vb_cursor; /* (u) Cursor position. */ term_pos_t vb_mark_start; /* (b) Copy region start. */ term_pos_t vb_mark_end; /* (b) Copy region end. */ int vb_mark_last; /* Last mouse event. */ term_rect_t vb_dirtyrect; /* (b) Dirty rectangle. */ term_char_t *vb_buffer; /* (u) Data buffer. */ term_char_t **vb_rows; /* (u) Array of rows */ }; #ifdef SC_HISTORY_SIZE #define VBF_DEFAULT_HISTORY_SIZE SC_HISTORY_SIZE #else #define VBF_DEFAULT_HISTORY_SIZE 500 #endif +void vtbuf_lock(struct vt_buf *); +void vtbuf_unlock(struct vt_buf *); void vtbuf_copy(struct vt_buf *, const term_rect_t *, const term_pos_t *); -void vtbuf_fill_locked(struct vt_buf *, const term_rect_t *, term_char_t); +void vtbuf_fill(struct vt_buf *, const term_rect_t *, term_char_t); void vtbuf_init_early(struct vt_buf *); void vtbuf_init(struct vt_buf *, const term_pos_t *); void vtbuf_grow(struct vt_buf *, const term_pos_t *, unsigned int); void vtbuf_putchar(struct vt_buf *, const term_pos_t *, term_char_t); void vtbuf_cursor_position(struct vt_buf *, const term_pos_t *); void vtbuf_scroll_mode(struct vt_buf *vb, int yes); void vtbuf_dirty(struct vt_buf *vb, const term_rect_t *area); void vtbuf_undirty(struct vt_buf *, term_rect_t *); void vtbuf_sethistory_size(struct vt_buf *, unsigned int); int vtbuf_iscursor(const struct vt_buf *vb, int row, int col); void vtbuf_cursor_visibility(struct vt_buf *, int); #ifndef SC_NO_CUTPASTE int vtbuf_set_mark(struct vt_buf *vb, int type, int col, int row); int vtbuf_get_marked_len(struct vt_buf *vb); void vtbuf_extract_marked(struct vt_buf *vb, term_char_t *buf, int sz); #endif #define VTB_MARK_NONE 0 #define VTB_MARK_END 1 #define VTB_MARK_START 2 #define VTB_MARK_WORD 3 #define VTB_MARK_ROW 4 #define VTB_MARK_EXTEND 5 #define VTB_MARK_MOVE 6 #define VTBUF_SLCK_ENABLE(vb) vtbuf_scroll_mode((vb), 1) #define VTBUF_SLCK_DISABLE(vb) vtbuf_scroll_mode((vb), 0) #define VTBUF_MAX_HEIGHT(vb) \ ((vb)->vb_history_size) #define VTBUF_GET_ROW(vb, r) \ ((vb)->vb_rows[((vb)->vb_roffset + (r)) % VTBUF_MAX_HEIGHT(vb)]) #define VTBUF_GET_FIELD(vb, r, c) \ ((vb)->vb_rows[((vb)->vb_roffset + (r)) % VTBUF_MAX_HEIGHT(vb)][(c)]) #define VTBUF_FIELD(vb, r, c) \ ((vb)->vb_rows[((vb)->vb_curroffset + (r)) % VTBUF_MAX_HEIGHT(vb)][(c)]) #define VTBUF_ISCURSOR(vb, r, c) \ vtbuf_iscursor((vb), (r), (c)) #define VTBUF_DIRTYROW(mask, row) \ ((mask)->vbm_row & ((uint64_t)1 << ((row) % 64))) #define VTBUF_DIRTYCOL(mask, col) \ ((mask)->vbm_col & ((uint64_t)1 << ((col) % 64))) #define VTBUF_SPACE_CHAR(attr) (' ' | (attr)) #define VHS_SET 0 #define VHS_CUR 1 #define VHS_END 2 int vthistory_seek(struct vt_buf *, int offset, int whence); void vthistory_addlines(struct vt_buf *vb, int offset); void vthistory_getpos(const struct vt_buf *, unsigned int *offset); /* * Per-window datastructure. */ struct vt_window { struct vt_device *vw_device; /* (c) Device. */ struct terminal *vw_terminal; /* (c) Terminal. */ struct vt_buf vw_buf; /* (u) Screen buffer. */ struct vt_font *vw_font; /* (d) Graphical font. */ term_rect_t vw_draw_area; /* (?) Drawable area. */ unsigned int vw_number; /* (c) Window number. */ int vw_kbdmode; /* (?) Keyboard mode. */ int vw_prev_kbdmode;/* (?) Previous mode. */ int vw_kbdstate; /* (?) Keyboard state. */ int vw_grabbed; /* (?) Grab count. */ char *vw_kbdsq; /* Escape sequence queue*/ unsigned int vw_flags; /* (d) Per-window flags. */ int vw_mouse_level;/* Mouse op mode. */ #define VWF_BUSY 0x1 /* Busy reconfiguring device. */ #define VWF_OPENED 0x2 /* TTY in use. */ #define VWF_SCROLL 0x4 /* Keys influence scrollback. */ #define VWF_CONSOLE 0x8 /* Kernel message console window. */ #define VWF_VTYLOCK 0x10 /* Prevent window switch. */ #define VWF_MOUSE_HIDE 0x20 /* Disable mouse events processing. */ #define VWF_READY 0x40 /* Window fully initialized. */ #define VWF_GRAPHICS 0x80 /* Window in graphics mode (KDSETMODE). */ #define VWF_SWWAIT_REL 0x10000 /* Program wait for VT acquire is done. */ #define VWF_SWWAIT_ACQ 0x20000 /* Program wait for VT release is done. */ pid_t vw_pid; /* Terminal holding process */ struct proc *vw_proc; struct vt_mode vw_smode; /* switch mode */ struct callout vw_proc_dead_timer; struct vt_window *vw_switch_to; }; #define VT_AUTO 0 /* switching is automatic */ #define VT_PROCESS 1 /* switching controlled by prog */ #define VT_KERNEL 255 /* switching controlled in kernel */ #define IS_VT_PROC_MODE(vw) ((vw)->vw_smode.mode == VT_PROCESS) /* * Per-device driver routines. */ typedef int vd_init_t(struct vt_device *vd); typedef int vd_probe_t(struct vt_device *vd); typedef void vd_fini_t(struct vt_device *vd, void *softc); typedef void vd_postswitch_t(struct vt_device *vd); typedef void vd_blank_t(struct vt_device *vd, term_color_t color); typedef void vd_bitblt_text_t(struct vt_device *vd, const struct vt_window *vw, const term_rect_t *area); typedef void vd_bitblt_bmp_t(struct vt_device *vd, const struct vt_window *vw, const uint8_t *pattern, const uint8_t *mask, unsigned int width, unsigned int height, unsigned int x, unsigned int y, term_color_t fg, term_color_t bg); typedef int vd_fb_ioctl_t(struct vt_device *, u_long, caddr_t, struct thread *); typedef int vd_fb_mmap_t(struct vt_device *, vm_ooffset_t, vm_paddr_t *, int, vm_memattr_t *); typedef void vd_drawrect_t(struct vt_device *, int, int, int, int, int, term_color_t); typedef void vd_setpixel_t(struct vt_device *, int, int, term_color_t); typedef void vd_suspend_t(struct vt_device *); typedef void vd_resume_t(struct vt_device *); struct vt_driver { char vd_name[16]; /* Console attachment. */ vd_probe_t *vd_probe; vd_init_t *vd_init; vd_fini_t *vd_fini; /* Drawing. */ vd_blank_t *vd_blank; vd_drawrect_t *vd_drawrect; vd_setpixel_t *vd_setpixel; vd_bitblt_text_t *vd_bitblt_text; vd_bitblt_bmp_t *vd_bitblt_bmp; /* Framebuffer ioctls, if present. */ vd_fb_ioctl_t *vd_fb_ioctl; /* Framebuffer mmap, if present. */ vd_fb_mmap_t *vd_fb_mmap; /* Update display setting on vt switch. */ vd_postswitch_t *vd_postswitch; /* Suspend/resume handlers. */ vd_suspend_t *vd_suspend; vd_resume_t *vd_resume; /* Priority to know which one can override */ int vd_priority; #define VD_PRIORITY_DUMB 10 #define VD_PRIORITY_GENERIC 100 #define VD_PRIORITY_SPECIFIC 1000 }; /* * Console device madness. * * Utility macro to make early vt(4) instances work. */ extern struct vt_device vt_consdev; extern struct terminal vt_consterm; extern const struct terminal_class vt_termclass; void vt_upgrade(struct vt_device *vd); #define PIXEL_WIDTH(w) ((w) / 8) #define PIXEL_HEIGHT(h) ((h) / 16) #ifndef VT_FB_MAX_WIDTH #define VT_FB_MAX_WIDTH 4096 #endif #ifndef VT_FB_MAX_HEIGHT #define VT_FB_MAX_HEIGHT 2400 #endif /* name argument is not used yet. */ #define VT_DRIVER_DECLARE(name, drv) DATA_SET(vt_drv_set, drv) /* * Fonts. * * Remapping tables are used to map Unicode points to glyphs. They need * to be sorted, because vtfont_lookup() performs a binary search. Each * font has two remapping tables, for normal and bold. When a character * is not present in bold, it uses a normal glyph. When no glyph is * available, it uses glyph 0, which is normally equal to U+FFFD. */ struct vt_font_map { uint32_t vfm_src; uint16_t vfm_dst; uint16_t vfm_len; }; struct vt_font { struct vt_font_map *vf_map[VFNT_MAPS]; uint8_t *vf_bytes; unsigned int vf_height, vf_width; unsigned int vf_map_count[VFNT_MAPS]; unsigned int vf_refcount; }; #ifndef SC_NO_CUTPASTE struct vt_mouse_cursor { uint8_t map[64 * 64 / 8]; uint8_t mask[64 * 64 / 8]; uint8_t width; uint8_t height; }; #endif const uint8_t *vtfont_lookup(const struct vt_font *vf, term_char_t c); struct vt_font *vtfont_ref(struct vt_font *vf); void vtfont_unref(struct vt_font *vf); int vtfont_load(vfnt_t *f, struct vt_font **ret); /* Sysmouse. */ void sysmouse_process_event(mouse_info_t *mi); #ifndef SC_NO_CUTPASTE void vt_mouse_event(int type, int x, int y, int event, int cnt, int mlevel); void vt_mouse_state(int show); #endif #define VT_MOUSE_SHOW 1 #define VT_MOUSE_HIDE 0 /* Utilities. */ void vt_compute_drawable_area(struct vt_window *); void vt_determine_colors(term_char_t c, int cursor, term_color_t *fg, term_color_t *bg); int vt_is_cursor_in_area(const struct vt_device *vd, const term_rect_t *area); void vt_termsize(struct vt_device *, struct vt_font *, term_pos_t *); void vt_winsize(struct vt_device *, struct vt_font *, struct winsize *); /* Logos-on-boot. */ #define VT_LOGOS_DRAW_BEASTIE 0 #define VT_LOGOS_DRAW_ALT_BEASTIE 1 #define VT_LOGOS_DRAW_ORB 2 extern int vt_draw_logo_cpus; extern int vt_splash_cpu; extern int vt_splash_ncpu; extern int vt_splash_cpu_style; extern int vt_splash_cpu_duration; extern const unsigned int vt_logo_sprite_height; extern const unsigned int vt_logo_sprite_width; void vtterm_draw_cpu_logos(struct vt_device *); #endif /* !_DEV_VT_VT_H_ */ Index: head/sys/dev/vt/vt_buf.c =================================================================== --- head/sys/dev/vt/vt_buf.c (revision 333668) +++ head/sys/dev/vt/vt_buf.c (revision 333669) @@ -1,856 +1,852 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009, 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Portions of this software were developed by Oleksandr Rybalko * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_VTBUF, "vtbuf", "vt buffer"); #define VTBUF_LOCK(vb) mtx_lock_spin(&(vb)->vb_lock) #define VTBUF_UNLOCK(vb) mtx_unlock_spin(&(vb)->vb_lock) #define POS_INDEX(c, r) (((r) << 12) + (c)) #define POS_COPY(d, s) do { \ (d).tp_col = (s).tp_col; \ (d).tp_row = (s).tp_row; \ } while (0) #ifndef SC_NO_CUTPASTE static int vtbuf_htw(const struct vt_buf *vb, int row); static int vtbuf_wth(const struct vt_buf *vb, int row); static int vtbuf_in_this_range(int begin, int test, int end, int sz); #endif /* * line4 * line5 <--- curroffset (terminal output to that line) * line0 * line1 <--- roffset (history display from that point) * line2 * line3 */ int vthistory_seek(struct vt_buf *vb, int offset, int whence) { int diff, top, bottom, roffset; /* No scrolling if not enabled. */ if ((vb->vb_flags & VBF_SCROLL) == 0) { if (vb->vb_roffset != vb->vb_curroffset) { vb->vb_roffset = vb->vb_curroffset; return (0xffff); } return (0); /* No changes */ } /* "top" may be a negative integer. */ bottom = vb->vb_curroffset; top = (vb->vb_flags & VBF_HISTORY_FULL) ? bottom + vb->vb_scr_size.tp_row - vb->vb_history_size : 0; roffset = 0; /* Make gcc happy. */ switch (whence) { case VHS_SET: if (offset < 0) offset = 0; roffset = top + offset; break; case VHS_CUR: /* * Operate on copy of offset value, since it temporary * can be bigger than amount of rows in buffer. */ roffset = vb->vb_roffset; if (roffset >= bottom + vb->vb_scr_size.tp_row) roffset -= vb->vb_history_size; roffset += offset; roffset = MAX(roffset, top); roffset = MIN(roffset, bottom); if (roffset < 0) roffset = vb->vb_history_size + roffset; break; case VHS_END: /* Go to current offset. */ roffset = vb->vb_curroffset; break; } diff = vb->vb_roffset != roffset; vb->vb_roffset = roffset; return (diff); } void vthistory_addlines(struct vt_buf *vb, int offset) { #ifndef SC_NO_CUTPASTE int cur, sz; #endif vb->vb_curroffset += offset; if (vb->vb_curroffset + vb->vb_scr_size.tp_row >= vb->vb_history_size) { vb->vb_flags |= VBF_HISTORY_FULL; vb->vb_curroffset %= vb->vb_history_size; } if ((vb->vb_flags & VBF_SCROLL) == 0) { vb->vb_roffset = vb->vb_curroffset; } #ifndef SC_NO_CUTPASTE sz = vb->vb_history_size; cur = vb->vb_roffset + vb->vb_scr_size.tp_row + sz - 1; if (vtbuf_in_this_range(cur, vb->vb_mark_start.tp_row, cur + offset, sz) || vtbuf_in_this_range(cur, vb->vb_mark_end.tp_row, cur + offset, sz)) { /* clear screen selection */ vb->vb_mark_start.tp_row = vb->vb_mark_end.tp_row; vb->vb_mark_start.tp_col = vb->vb_mark_end.tp_col; } #endif } void vthistory_getpos(const struct vt_buf *vb, unsigned int *offset) { *offset = vb->vb_roffset; } #ifndef SC_NO_CUTPASTE /* Only mouse support use it now. */ /* Translate history row to current view row number. */ static int vtbuf_htw(const struct vt_buf *vb, int row) { /* * total 1000 rows. * History offset roffset winrow * 205 200 ((205 - 200 + 1000) % 1000) = 5 * 90 990 ((90 - 990 + 1000) % 1000) = 100 */ return ((row - vb->vb_roffset + vb->vb_history_size) % vb->vb_history_size); } /* Translate current view row number to history row. */ static int vtbuf_wth(const struct vt_buf *vb, int row) { return ((vb->vb_roffset + row) % vb->vb_history_size); } /* * Test if an index in a circular buffer is within a range. * * begin - start index * end - end index * test - test index * sz - size of circular buffer when it turns over */ static int vtbuf_in_this_range(int begin, int test, int end, int sz) { begin %= sz; end %= sz; /* check for inversion */ if (begin > end) return (test >= begin || test < end); else return (test >= begin && test < end); } #endif int vtbuf_iscursor(const struct vt_buf *vb, int row, int col) { #ifndef SC_NO_CUTPASTE int sc, sr, sz, ec, er, tmp; #endif if ((vb->vb_flags & (VBF_CURSOR|VBF_SCROLL)) == VBF_CURSOR && (vb->vb_cursor.tp_row == row) && (vb->vb_cursor.tp_col == col)) return (1); #ifndef SC_NO_CUTPASTE /* Mark cut/paste region. */ if (vb->vb_mark_start.tp_col == vb->vb_mark_end.tp_col && vb->vb_mark_start.tp_row == vb->vb_mark_end.tp_row) return (0); sc = vb->vb_mark_start.tp_col; sr = vb->vb_mark_start.tp_row; ec = vb->vb_mark_end.tp_col; er = vb->vb_mark_end.tp_row; /* * Information about if the selection was made bottom-top or * top-bottom is lost due to modulo arithmetics and needs to * be recovered: */ sz = vb->vb_history_size; tmp = (sz + er - sr) % sz; row = vtbuf_wth(vb, row); /* Swap start and end if start > end */ if ((2 * tmp) > sz || (tmp == 0 && sc > ec)) { tmp = sc; sc = ec; ec = tmp; tmp = sr; sr = er; er = tmp; } if (vtbuf_in_this_range(POS_INDEX(sc, sr), POS_INDEX(col, row), POS_INDEX(ec, er), POS_INDEX(0, sz))) return (1); #endif return (0); } -static inline void -vtbuf_dirty_locked(struct vt_buf *vb, const term_rect_t *area) +void +vtbuf_lock(struct vt_buf *vb) { + VTBUF_LOCK(vb); +} + +void +vtbuf_unlock(struct vt_buf *vb) +{ + + VTBUF_UNLOCK(vb); +} + +void +vtbuf_dirty(struct vt_buf *vb, const term_rect_t *area) +{ + if (vb->vb_dirtyrect.tr_begin.tp_row > area->tr_begin.tp_row) vb->vb_dirtyrect.tr_begin.tp_row = area->tr_begin.tp_row; if (vb->vb_dirtyrect.tr_begin.tp_col > area->tr_begin.tp_col) vb->vb_dirtyrect.tr_begin.tp_col = area->tr_begin.tp_col; if (vb->vb_dirtyrect.tr_end.tp_row < area->tr_end.tp_row) vb->vb_dirtyrect.tr_end.tp_row = area->tr_end.tp_row; if (vb->vb_dirtyrect.tr_end.tp_col < area->tr_end.tp_col) vb->vb_dirtyrect.tr_end.tp_col = area->tr_end.tp_col; } -void -vtbuf_dirty(struct vt_buf *vb, const term_rect_t *area) -{ - - VTBUF_LOCK(vb); - vtbuf_dirty_locked(vb, area); - VTBUF_UNLOCK(vb); -} - static inline void -vtbuf_dirty_cell_locked(struct vt_buf *vb, const term_pos_t *p) +vtbuf_dirty_cell(struct vt_buf *vb, const term_pos_t *p) { term_rect_t area; area.tr_begin = *p; area.tr_end.tp_row = p->tp_row + 1; area.tr_end.tp_col = p->tp_col + 1; - vtbuf_dirty_locked(vb, &area); + vtbuf_dirty(vb, &area); } static void vtbuf_make_undirty(struct vt_buf *vb) { vb->vb_dirtyrect.tr_begin = vb->vb_scr_size; vb->vb_dirtyrect.tr_end.tp_row = vb->vb_dirtyrect.tr_end.tp_col = 0; } void vtbuf_undirty(struct vt_buf *vb, term_rect_t *r) { - VTBUF_LOCK(vb); *r = vb->vb_dirtyrect; vtbuf_make_undirty(vb); - VTBUF_UNLOCK(vb); } void vtbuf_copy(struct vt_buf *vb, const term_rect_t *r, const term_pos_t *p2) { const term_pos_t *p1 = &r->tr_begin; term_rect_t area; unsigned int rows, cols; int pr, rdiff; KASSERT(r->tr_begin.tp_row < vb->vb_scr_size.tp_row, ("vtbuf_copy begin.tp_row %d must be less than screen width %d", r->tr_begin.tp_row, vb->vb_scr_size.tp_row)); KASSERT(r->tr_begin.tp_col < vb->vb_scr_size.tp_col, ("vtbuf_copy begin.tp_col %d must be less than screen height %d", r->tr_begin.tp_col, vb->vb_scr_size.tp_col)); KASSERT(r->tr_end.tp_row <= vb->vb_scr_size.tp_row, ("vtbuf_copy end.tp_row %d must be less than screen width %d", r->tr_end.tp_row, vb->vb_scr_size.tp_row)); KASSERT(r->tr_end.tp_col <= vb->vb_scr_size.tp_col, ("vtbuf_copy end.tp_col %d must be less than screen height %d", r->tr_end.tp_col, vb->vb_scr_size.tp_col)); KASSERT(p2->tp_row < vb->vb_scr_size.tp_row, ("vtbuf_copy tp_row %d must be less than screen width %d", p2->tp_row, vb->vb_scr_size.tp_row)); KASSERT(p2->tp_col < vb->vb_scr_size.tp_col, ("vtbuf_copy tp_col %d must be less than screen height %d", p2->tp_col, vb->vb_scr_size.tp_col)); rows = r->tr_end.tp_row - r->tr_begin.tp_row; rdiff = r->tr_begin.tp_row - p2->tp_row; cols = r->tr_end.tp_col - r->tr_begin.tp_col; if (r->tr_begin.tp_row > p2->tp_row && r->tr_begin.tp_col == 0 && r->tr_end.tp_col == vb->vb_scr_size.tp_col && /* Full row. */ (rows + rdiff) == vb->vb_scr_size.tp_row && /* Whole screen. */ rdiff > 0) { /* Only forward direction. Do not eat history. */ vthistory_addlines(vb, rdiff); } else if (p2->tp_row < p1->tp_row) { /* Handle overlapping copies of line segments. */ /* Move data up. */ for (pr = 0; pr < rows; pr++) memmove( &VTBUF_FIELD(vb, p2->tp_row + pr, p2->tp_col), &VTBUF_FIELD(vb, p1->tp_row + pr, p1->tp_col), cols * sizeof(term_char_t)); } else { /* Move data down. */ for (pr = rows - 1; pr >= 0; pr--) memmove( &VTBUF_FIELD(vb, p2->tp_row + pr, p2->tp_col), &VTBUF_FIELD(vb, p1->tp_row + pr, p1->tp_col), cols * sizeof(term_char_t)); } area.tr_begin = *p2; area.tr_end.tp_row = MIN(p2->tp_row + rows, vb->vb_scr_size.tp_row); area.tr_end.tp_col = MIN(p2->tp_col + cols, vb->vb_scr_size.tp_col); vtbuf_dirty(vb, &area); } static void -vtbuf_fill(struct vt_buf *vb, const term_rect_t *r, term_char_t c) +vtbuf_do_fill(struct vt_buf *vb, const term_rect_t *r, term_char_t c) { unsigned int pr, pc; term_char_t *row; for (pr = r->tr_begin.tp_row; pr < r->tr_end.tp_row; pr++) { row = vb->vb_rows[(vb->vb_curroffset + pr) % VTBUF_MAX_HEIGHT(vb)]; for (pc = r->tr_begin.tp_col; pc < r->tr_end.tp_col; pc++) { row[pc] = c; } } } void -vtbuf_fill_locked(struct vt_buf *vb, const term_rect_t *r, term_char_t c) +vtbuf_fill(struct vt_buf *vb, const term_rect_t *r, term_char_t c) { + KASSERT(r->tr_begin.tp_row < vb->vb_scr_size.tp_row, - ("vtbuf_fill_locked begin.tp_row %d must be < screen height %d", + ("vtbuf_fill begin.tp_row %d must be < screen height %d", r->tr_begin.tp_row, vb->vb_scr_size.tp_row)); KASSERT(r->tr_begin.tp_col < vb->vb_scr_size.tp_col, - ("vtbuf_fill_locked begin.tp_col %d must be < screen width %d", + ("vtbuf_fill begin.tp_col %d must be < screen width %d", r->tr_begin.tp_col, vb->vb_scr_size.tp_col)); KASSERT(r->tr_end.tp_row <= vb->vb_scr_size.tp_row, - ("vtbuf_fill_locked end.tp_row %d must be <= screen height %d", + ("vtbuf_fill end.tp_row %d must be <= screen height %d", r->tr_end.tp_row, vb->vb_scr_size.tp_row)); KASSERT(r->tr_end.tp_col <= vb->vb_scr_size.tp_col, - ("vtbuf_fill_locked end.tp_col %d must be <= screen width %d", + ("vtbuf_fill end.tp_col %d must be <= screen width %d", r->tr_end.tp_col, vb->vb_scr_size.tp_col)); - VTBUF_LOCK(vb); - vtbuf_fill(vb, r, c); - vtbuf_dirty_locked(vb, r); - VTBUF_UNLOCK(vb); + vtbuf_do_fill(vb, r, c); + vtbuf_dirty(vb, r); } static void vtbuf_init_rows(struct vt_buf *vb) { int r; vb->vb_history_size = MAX(vb->vb_history_size, vb->vb_scr_size.tp_row); for (r = 0; r < vb->vb_history_size; r++) vb->vb_rows[r] = &vb->vb_buffer[r * vb->vb_scr_size.tp_col]; } void vtbuf_init_early(struct vt_buf *vb) { term_rect_t rect; vb->vb_flags |= VBF_CURSOR; vb->vb_roffset = 0; vb->vb_curroffset = 0; vb->vb_mark_start.tp_row = 0; vb->vb_mark_start.tp_col = 0; vb->vb_mark_end.tp_row = 0; vb->vb_mark_end.tp_col = 0; vtbuf_init_rows(vb); rect.tr_begin.tp_row = rect.tr_begin.tp_col = 0; rect.tr_end.tp_col = vb->vb_scr_size.tp_col; rect.tr_end.tp_row = vb->vb_history_size; - vtbuf_fill(vb, &rect, VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR)); + vtbuf_do_fill(vb, &rect, VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR)); vtbuf_make_undirty(vb); if ((vb->vb_flags & VBF_MTX_INIT) == 0) { mtx_init(&vb->vb_lock, "vtbuf", NULL, MTX_SPIN); vb->vb_flags |= VBF_MTX_INIT; } } void vtbuf_init(struct vt_buf *vb, const term_pos_t *p) { int sz; vb->vb_scr_size = *p; vb->vb_history_size = VBF_DEFAULT_HISTORY_SIZE; if ((vb->vb_flags & VBF_STATIC) == 0) { sz = vb->vb_history_size * p->tp_col * sizeof(term_char_t); vb->vb_buffer = malloc(sz, M_VTBUF, M_WAITOK | M_ZERO); sz = vb->vb_history_size * sizeof(term_char_t *); vb->vb_rows = malloc(sz, M_VTBUF, M_WAITOK | M_ZERO); } vtbuf_init_early(vb); } void vtbuf_sethistory_size(struct vt_buf *vb, unsigned int size) { term_pos_t p; /* With same size */ p.tp_row = vb->vb_scr_size.tp_row; p.tp_col = vb->vb_scr_size.tp_col; vtbuf_grow(vb, &p, size); } void vtbuf_grow(struct vt_buf *vb, const term_pos_t *p, unsigned int history_size) { term_char_t *old, *new, **rows, **oldrows, **copyrows, *row, *oldrow; unsigned int w, h, c, r, old_history_size; size_t bufsize, rowssize; int history_full; history_size = MAX(history_size, p->tp_row); /* Allocate new buffer. */ bufsize = history_size * p->tp_col * sizeof(term_char_t); new = malloc(bufsize, M_VTBUF, M_WAITOK | M_ZERO); rowssize = history_size * sizeof(term_pos_t *); rows = malloc(rowssize, M_VTBUF, M_WAITOK | M_ZERO); /* Toggle it. */ VTBUF_LOCK(vb); old = vb->vb_flags & VBF_STATIC ? NULL : vb->vb_buffer; oldrows = vb->vb_flags & VBF_STATIC ? NULL : vb->vb_rows; copyrows = vb->vb_rows; w = vb->vb_scr_size.tp_col; h = vb->vb_scr_size.tp_row; old_history_size = vb->vb_history_size; history_full = vb->vb_flags & VBF_HISTORY_FULL || vb->vb_curroffset + h >= history_size; vb->vb_history_size = history_size; vb->vb_buffer = new; vb->vb_rows = rows; vb->vb_flags &= ~VBF_STATIC; vb->vb_scr_size = *p; vtbuf_init_rows(vb); /* * Copy rows to the new buffer. The first row in the history * is back to index 0, ie. the new buffer doesn't cycle. */ if (history_size > old_history_size) { for (r = 0; r < old_history_size; r ++) { row = rows[r]; /* Compute the corresponding row in the old buffer. */ if (history_full) /* * The buffer is full, the "top" row is * the one just after the viewable area * (curroffset + viewable height) in the * cycling buffer. The corresponding row * is computed from this top row. */ oldrow = copyrows[ (vb->vb_curroffset + h + r) % old_history_size]; else /* * The buffer is not full, therefore, * we didn't cycle already. The * corresponding rows are the same in * both buffers. */ oldrow = copyrows[r]; memmove(row, oldrow, MIN(p->tp_col, w) * sizeof(term_char_t)); /* * XXX VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR) will * extended lines of kernel text using the wrong * background color. */ for (c = MIN(p->tp_col, w); c < p->tp_col; c++) { row[c] = VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR); } } /* Fill remaining rows. */ for (r = old_history_size; r < history_size; r++) { row = rows[r]; for (c = MIN(p->tp_col, w); c < p->tp_col; c++) { row[c] = VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR); } } vb->vb_flags &= ~VBF_HISTORY_FULL; /* * If the screen is already filled (there are non-visible lines * above the current viewable area), adjust curroffset to the * new viewable area. * * If the old buffer was full, set curroffset to the * th most recent line of history in the new, non-cycled * buffer. Otherwise, it didn't cycle, so the old curroffset * is the same in the new buffer. */ if (history_full) vb->vb_curroffset = old_history_size - h; } else { /* * (old_history_size - history_size) lines of history are * dropped. */ for (r = 0; r < history_size; r ++) { row = rows[r]; /* * Compute the corresponding row in the old buffer. * * See the equivalent if{} block above for an * explanation. */ if (history_full) oldrow = copyrows[ (vb->vb_curroffset + h + r + (old_history_size - history_size)) % old_history_size]; else oldrow = copyrows[r]; memmove(row, oldrow, MIN(p->tp_col, w) * sizeof(term_char_t)); /* * XXX VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR) will * extended lines of kernel text using the wrong * background color. */ for (c = MIN(p->tp_col, w); c < p->tp_col; c++) { row[c] = VTBUF_SPACE_CHAR(TERMINAL_NORM_ATTR); } } if (history_full) { vb->vb_curroffset = history_size - h; vb->vb_flags |= VBF_HISTORY_FULL; } } vb->vb_roffset = vb->vb_curroffset; /* Adjust cursor position. */ if (vb->vb_cursor.tp_col > p->tp_col - 1) /* * Move cursor to the last column, in case its previous * position is outside of the new screen area. */ vb->vb_cursor.tp_col = p->tp_col - 1; if (vb->vb_curroffset > 0 || vb->vb_cursor.tp_row > p->tp_row - 1) /* Move cursor to the last line on the screen. */ vb->vb_cursor.tp_row = p->tp_row - 1; VTBUF_UNLOCK(vb); /* Deallocate old buffer. */ free(old, M_VTBUF); free(oldrows, M_VTBUF); } void vtbuf_putchar(struct vt_buf *vb, const term_pos_t *p, term_char_t c) { term_char_t *row; KASSERT(p->tp_row < vb->vb_scr_size.tp_row, ("vtbuf_putchar tp_row %d must be less than screen width %d", p->tp_row, vb->vb_scr_size.tp_row)); KASSERT(p->tp_col < vb->vb_scr_size.tp_col, ("vtbuf_putchar tp_col %d must be less than screen height %d", p->tp_col, vb->vb_scr_size.tp_col)); row = vb->vb_rows[(vb->vb_curroffset + p->tp_row) % VTBUF_MAX_HEIGHT(vb)]; if (row[p->tp_col] != c) { - VTBUF_LOCK(vb); row[p->tp_col] = c; - vtbuf_dirty_cell_locked(vb, p); - VTBUF_UNLOCK(vb); + vtbuf_dirty_cell(vb, p); } } void vtbuf_cursor_position(struct vt_buf *vb, const term_pos_t *p) { - if (vb->vb_flags & VBF_CURSOR) { - VTBUF_LOCK(vb); - vtbuf_dirty_cell_locked(vb, &vb->vb_cursor); + vtbuf_dirty_cell(vb, &vb->vb_cursor); vb->vb_cursor = *p; - vtbuf_dirty_cell_locked(vb, &vb->vb_cursor); - VTBUF_UNLOCK(vb); + vtbuf_dirty_cell(vb, &vb->vb_cursor); } else { vb->vb_cursor = *p; } } #ifndef SC_NO_CUTPASTE static void vtbuf_flush_mark(struct vt_buf *vb) { term_rect_t area; int s, e; /* Notify renderer to update marked region. */ if ((vb->vb_mark_start.tp_col != vb->vb_mark_end.tp_col) || (vb->vb_mark_start.tp_row != vb->vb_mark_end.tp_row)) { s = vtbuf_htw(vb, vb->vb_mark_start.tp_row); e = vtbuf_htw(vb, vb->vb_mark_end.tp_row); area.tr_begin.tp_col = 0; area.tr_begin.tp_row = MIN(s, e); area.tr_end.tp_col = vb->vb_scr_size.tp_col; area.tr_end.tp_row = MAX(s, e) + 1; + VTBUF_LOCK(vb); vtbuf_dirty(vb, &area); + VTBUF_UNLOCK(vb); } } int vtbuf_get_marked_len(struct vt_buf *vb) { int ei, si, sz; term_pos_t s, e; /* Swap according to window coordinates. */ if (POS_INDEX(vtbuf_htw(vb, vb->vb_mark_start.tp_row), vb->vb_mark_start.tp_col) > POS_INDEX(vtbuf_htw(vb, vb->vb_mark_end.tp_row), vb->vb_mark_end.tp_col)) { POS_COPY(e, vb->vb_mark_start); POS_COPY(s, vb->vb_mark_end); } else { POS_COPY(s, vb->vb_mark_start); POS_COPY(e, vb->vb_mark_end); } si = s.tp_row * vb->vb_scr_size.tp_col + s.tp_col; ei = e.tp_row * vb->vb_scr_size.tp_col + e.tp_col; /* Number symbols and number of rows to inject \n */ sz = ei - si + ((e.tp_row - s.tp_row) * 2); return (sz * sizeof(term_char_t)); } void vtbuf_extract_marked(struct vt_buf *vb, term_char_t *buf, int sz) { int i, r, c, cs, ce; term_pos_t s, e; /* Swap according to window coordinates. */ if (POS_INDEX(vtbuf_htw(vb, vb->vb_mark_start.tp_row), vb->vb_mark_start.tp_col) > POS_INDEX(vtbuf_htw(vb, vb->vb_mark_end.tp_row), vb->vb_mark_end.tp_col)) { POS_COPY(e, vb->vb_mark_start); POS_COPY(s, vb->vb_mark_end); } else { POS_COPY(s, vb->vb_mark_start); POS_COPY(e, vb->vb_mark_end); } i = 0; for (r = s.tp_row; r <= e.tp_row; r ++) { cs = (r == s.tp_row)?s.tp_col:0; ce = (r == e.tp_row)?e.tp_col:vb->vb_scr_size.tp_col; for (c = cs; c < ce; c ++) { buf[i++] = vb->vb_rows[r][c]; } /* Add new line for all rows, but not for last one. */ if (r != e.tp_row) { buf[i++] = '\r'; buf[i++] = '\n'; } } } int vtbuf_set_mark(struct vt_buf *vb, int type, int col, int row) { term_char_t *r; int i; switch (type) { case VTB_MARK_END: /* B1 UP */ if (vb->vb_mark_last != VTB_MARK_MOVE) return (0); /* FALLTHROUGH */ case VTB_MARK_MOVE: case VTB_MARK_EXTEND: vtbuf_flush_mark(vb); /* Clean old mark. */ vb->vb_mark_end.tp_col = col; vb->vb_mark_end.tp_row = vtbuf_wth(vb, row); break; case VTB_MARK_START: vtbuf_flush_mark(vb); /* Clean old mark. */ vb->vb_mark_start.tp_col = col; vb->vb_mark_start.tp_row = vtbuf_wth(vb, row); /* Start again, so clear end point. */ vb->vb_mark_end.tp_col = col; vb->vb_mark_end.tp_row = vtbuf_wth(vb, row); break; case VTB_MARK_WORD: vtbuf_flush_mark(vb); /* Clean old mark. */ vb->vb_mark_start.tp_row = vb->vb_mark_end.tp_row = vtbuf_wth(vb, row); r = vb->vb_rows[vb->vb_mark_start.tp_row]; for (i = col; i >= 0; i --) { if (TCHAR_CHARACTER(r[i]) == ' ') { vb->vb_mark_start.tp_col = i + 1; break; } } for (i = col; i < vb->vb_scr_size.tp_col; i ++) { if (TCHAR_CHARACTER(r[i]) == ' ') { vb->vb_mark_end.tp_col = i; break; } } if (vb->vb_mark_start.tp_col > vb->vb_mark_end.tp_col) vb->vb_mark_start.tp_col = vb->vb_mark_end.tp_col; break; case VTB_MARK_ROW: vtbuf_flush_mark(vb); /* Clean old mark. */ vb->vb_mark_start.tp_col = 0; vb->vb_mark_end.tp_col = vb->vb_scr_size.tp_col; vb->vb_mark_start.tp_row = vb->vb_mark_end.tp_row = vtbuf_wth(vb, row); break; case VTB_MARK_NONE: vb->vb_mark_last = type; /* FALLTHROUGH */ default: /* panic? */ return (0); } vb->vb_mark_last = type; /* Draw new marked region. */ vtbuf_flush_mark(vb); return (1); } #endif void vtbuf_cursor_visibility(struct vt_buf *vb, int yes) { int oflags, nflags; - VTBUF_LOCK(vb); oflags = vb->vb_flags; if (yes) vb->vb_flags |= VBF_CURSOR; else vb->vb_flags &= ~VBF_CURSOR; nflags = vb->vb_flags; if (oflags != nflags) - vtbuf_dirty_cell_locked(vb, &vb->vb_cursor); - VTBUF_UNLOCK(vb); + vtbuf_dirty_cell(vb, &vb->vb_cursor); } void vtbuf_scroll_mode(struct vt_buf *vb, int yes) { int oflags, nflags; VTBUF_LOCK(vb); oflags = vb->vb_flags; if (yes) vb->vb_flags |= VBF_SCROLL; else vb->vb_flags &= ~VBF_SCROLL; nflags = vb->vb_flags; if (oflags != nflags) - vtbuf_dirty_cell_locked(vb, &vb->vb_cursor); + vtbuf_dirty_cell(vb, &vb->vb_cursor); VTBUF_UNLOCK(vb); } - Index: head/sys/dev/vt/vt_core.c =================================================================== --- head/sys/dev/vt/vt_core.c (revision 333668) +++ head/sys/dev/vt/vt_core.c (revision 333669) @@ -1,2887 +1,2916 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009, 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Portions of this software were developed by Oleksandr Rybalko * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__i386__) || defined(__amd64__) #include #include #endif static tc_bell_t vtterm_bell; static tc_cursor_t vtterm_cursor; static tc_putchar_t vtterm_putchar; static tc_fill_t vtterm_fill; static tc_copy_t vtterm_copy; +static tc_pre_input_t vtterm_pre_input; +static tc_post_input_t vtterm_post_input; static tc_param_t vtterm_param; static tc_done_t vtterm_done; static tc_cnprobe_t vtterm_cnprobe; static tc_cngetc_t vtterm_cngetc; static tc_cngrab_t vtterm_cngrab; static tc_cnungrab_t vtterm_cnungrab; static tc_opened_t vtterm_opened; static tc_ioctl_t vtterm_ioctl; static tc_mmap_t vtterm_mmap; const struct terminal_class vt_termclass = { .tc_bell = vtterm_bell, .tc_cursor = vtterm_cursor, .tc_putchar = vtterm_putchar, .tc_fill = vtterm_fill, .tc_copy = vtterm_copy, + .tc_pre_input = vtterm_pre_input, + .tc_post_input = vtterm_post_input, .tc_param = vtterm_param, .tc_done = vtterm_done, .tc_cnprobe = vtterm_cnprobe, .tc_cngetc = vtterm_cngetc, .tc_cngrab = vtterm_cngrab, .tc_cnungrab = vtterm_cnungrab, .tc_opened = vtterm_opened, .tc_ioctl = vtterm_ioctl, .tc_mmap = vtterm_mmap, }; /* * Use a constant timer of 25 Hz to redraw the screen. * * XXX: In theory we should only fire up the timer when there is really * activity. Unfortunately we cannot always start timers. We really * don't want to process kernel messages synchronously, because it * really slows down the system. */ #define VT_TIMERFREQ 25 /* Bell pitch/duration. */ #define VT_BELLDURATION ((5 * hz + 99) / 100) #define VT_BELLPITCH 800 #define VT_UNIT(vw) ((vw)->vw_device->vd_unit * VT_MAXWINDOWS + \ (vw)->vw_number) static SYSCTL_NODE(_kern, OID_AUTO, vt, CTLFLAG_RD, 0, "vt(9) parameters"); static VT_SYSCTL_INT(enable_altgr, 1, "Enable AltGr key (Do not assume R.Alt as Alt)"); static VT_SYSCTL_INT(enable_bell, 1, "Enable bell"); static VT_SYSCTL_INT(debug, 0, "vt(9) debug level"); static VT_SYSCTL_INT(deadtimer, 15, "Time to wait busy process in VT_PROCESS mode"); static VT_SYSCTL_INT(suspendswitch, 1, "Switch to VT0 before suspend"); /* Allow to disable some keyboard combinations. */ static VT_SYSCTL_INT(kbd_halt, 1, "Enable halt keyboard combination. " "See kbdmap(5) to configure."); static VT_SYSCTL_INT(kbd_poweroff, 1, "Enable Power Off keyboard combination. " "See kbdmap(5) to configure."); static VT_SYSCTL_INT(kbd_reboot, 1, "Enable reboot keyboard combination. " "See kbdmap(5) to configure (typically Ctrl-Alt-Delete)."); static VT_SYSCTL_INT(kbd_debug, 1, "Enable key combination to enter debugger. " "See kbdmap(5) to configure (typically Ctrl-Alt-Esc)."); static VT_SYSCTL_INT(kbd_panic, 0, "Enable request to panic. " "See kbdmap(5) to configure."); /* Used internally, not a tunable. */ int vt_draw_logo_cpus; VT_SYSCTL_INT(splash_cpu, 0, "Show logo CPUs during boot"); VT_SYSCTL_INT(splash_ncpu, 0, "Override number of logos displayed " "(0 = do not override)"); VT_SYSCTL_INT(splash_cpu_style, 2, "Draw logo style " "(0 = Alternate beastie, 1 = Beastie, 2 = Orb)"); VT_SYSCTL_INT(splash_cpu_duration, 10, "Hide logos after (seconds)"); static unsigned int vt_unit = 0; static MALLOC_DEFINE(M_VT, "vt", "vt device"); struct vt_device *main_vd = &vt_consdev; /* Boot logo. */ extern unsigned int vt_logo_width; extern unsigned int vt_logo_height; extern unsigned int vt_logo_depth; extern unsigned char vt_logo_image[]; #ifndef DEV_SPLASH #define vtterm_draw_cpu_logos(...) do {} while (0) const unsigned int vt_logo_sprite_height; #endif /* Font. */ extern struct vt_font vt_font_default; #ifndef SC_NO_CUTPASTE extern struct vt_mouse_cursor vt_default_mouse_pointer; #endif static int signal_vt_rel(struct vt_window *); static int signal_vt_acq(struct vt_window *); static int finish_vt_rel(struct vt_window *, int, int *); static int finish_vt_acq(struct vt_window *); static int vt_window_switch(struct vt_window *); static int vt_late_window_switch(struct vt_window *); static int vt_proc_alive(struct vt_window *); static void vt_resize(struct vt_device *); static void vt_update_static(void *); #ifndef SC_NO_CUTPASTE static void vt_mouse_paste(void); #endif static void vt_suspend_handler(void *priv); static void vt_resume_handler(void *priv); SET_DECLARE(vt_drv_set, struct vt_driver); #define _VTDEFH MAX(100, PIXEL_HEIGHT(VT_FB_MAX_HEIGHT)) #define _VTDEFW MAX(200, PIXEL_WIDTH(VT_FB_MAX_WIDTH)) struct terminal vt_consterm; static struct vt_window vt_conswindow; struct vt_device vt_consdev = { .vd_driver = NULL, .vd_softc = NULL, .vd_prev_driver = NULL, .vd_prev_softc = NULL, .vd_flags = VDF_INVALID, .vd_windows = { [VT_CONSWINDOW] = &vt_conswindow, }, .vd_curwindow = &vt_conswindow, .vd_kbstate = 0, #ifndef SC_NO_CUTPASTE .vd_pastebuf = { .vpb_buf = NULL, .vpb_bufsz = 0, .vpb_len = 0 }, .vd_mcursor = &vt_default_mouse_pointer, .vd_mcursor_fg = TC_WHITE, .vd_mcursor_bg = TC_BLACK, #endif }; static term_char_t vt_constextbuf[(_VTDEFW) * (VBF_DEFAULT_HISTORY_SIZE)]; static term_char_t *vt_constextbufrows[VBF_DEFAULT_HISTORY_SIZE]; static struct vt_window vt_conswindow = { .vw_number = VT_CONSWINDOW, .vw_flags = VWF_CONSOLE, .vw_buf = { .vb_buffer = &vt_constextbuf[0], .vb_rows = &vt_constextbufrows[0], .vb_history_size = VBF_DEFAULT_HISTORY_SIZE, .vb_curroffset = 0, .vb_roffset = 0, .vb_flags = VBF_STATIC, .vb_mark_start = {.tp_row = 0, .tp_col = 0,}, .vb_mark_end = {.tp_row = 0, .tp_col = 0,}, .vb_scr_size = { .tp_row = _VTDEFH, .tp_col = _VTDEFW, }, }, .vw_device = &vt_consdev, .vw_terminal = &vt_consterm, .vw_kbdmode = K_XLATE, .vw_grabbed = 0, }; struct terminal vt_consterm = { .tm_class = &vt_termclass, .tm_softc = &vt_conswindow, .tm_flags = TF_CONS, }; static struct consdev vt_consterm_consdev = { .cn_ops = &termcn_cnops, .cn_arg = &vt_consterm, .cn_name = "ttyv0", }; /* Add to set of consoles. */ DATA_SET(cons_set, vt_consterm_consdev); /* * Right after kmem is done to allow early drivers to use locking and allocate * memory. */ SYSINIT(vt_update_static, SI_SUB_KMEM, SI_ORDER_ANY, vt_update_static, &vt_consdev); /* Delay until all devices attached, to not waste time. */ SYSINIT(vt_early_cons, SI_SUB_INT_CONFIG_HOOKS, SI_ORDER_ANY, vt_upgrade, &vt_consdev); /* Initialize locks/mem depended members. */ static void vt_update_static(void *dummy) { if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_driver != NULL) printf("VT(%s): %s %ux%u\n", main_vd->vd_driver->vd_name, (main_vd->vd_flags & VDF_TEXTMODE) ? "text" : "resolution", main_vd->vd_width, main_vd->vd_height); else printf("VT: init without driver.\n"); mtx_init(&main_vd->vd_lock, "vtdev", NULL, MTX_DEF); cv_init(&main_vd->vd_winswitch, "vtwswt"); } static void vt_schedule_flush(struct vt_device *vd, int ms) { if (ms <= 0) /* Default to initial value. */ ms = 1000 / VT_TIMERFREQ; callout_schedule(&vd->vd_timer, hz / (1000 / ms)); } void vt_resume_flush_timer(struct vt_device *vd, int ms) { if (!(vd->vd_flags & VDF_ASYNC) || !atomic_cmpset_int(&vd->vd_timer_armed, 0, 1)) return; vt_schedule_flush(vd, ms); } static void vt_suspend_flush_timer(struct vt_device *vd) { /* * As long as this function is called locked, callout_stop() * has the same effect like callout_drain() with regard to * preventing the callback function from executing. */ VT_LOCK_ASSERT(vd, MA_OWNED); if (!(vd->vd_flags & VDF_ASYNC) || !atomic_cmpset_int(&vd->vd_timer_armed, 1, 0)) return; callout_stop(&vd->vd_timer); } static void vt_switch_timer(void *arg) { vt_late_window_switch((struct vt_window *)arg); } static int vt_save_kbd_mode(struct vt_window *vw, keyboard_t *kbd) { int mode, ret; mode = 0; ret = kbdd_ioctl(kbd, KDGKBMODE, (caddr_t)&mode); if (ret == ENOIOCTL) ret = ENODEV; if (ret != 0) return (ret); vw->vw_kbdmode = mode; return (0); } static int vt_update_kbd_mode(struct vt_window *vw, keyboard_t *kbd) { int ret; ret = kbdd_ioctl(kbd, KDSKBMODE, (caddr_t)&vw->vw_kbdmode); if (ret == ENOIOCTL) ret = ENODEV; return (ret); } static int vt_save_kbd_state(struct vt_window *vw, keyboard_t *kbd) { int state, ret; state = 0; ret = kbdd_ioctl(kbd, KDGKBSTATE, (caddr_t)&state); if (ret == ENOIOCTL) ret = ENODEV; if (ret != 0) return (ret); vw->vw_kbdstate &= ~LOCK_MASK; vw->vw_kbdstate |= state & LOCK_MASK; return (0); } static int vt_update_kbd_state(struct vt_window *vw, keyboard_t *kbd) { int state, ret; state = vw->vw_kbdstate & LOCK_MASK; ret = kbdd_ioctl(kbd, KDSKBSTATE, (caddr_t)&state); if (ret == ENOIOCTL) ret = ENODEV; return (ret); } static int vt_save_kbd_leds(struct vt_window *vw, keyboard_t *kbd) { int leds, ret; leds = 0; ret = kbdd_ioctl(kbd, KDGETLED, (caddr_t)&leds); if (ret == ENOIOCTL) ret = ENODEV; if (ret != 0) return (ret); vw->vw_kbdstate &= ~LED_MASK; vw->vw_kbdstate |= leds & LED_MASK; return (0); } static int vt_update_kbd_leds(struct vt_window *vw, keyboard_t *kbd) { int leds, ret; leds = vw->vw_kbdstate & LED_MASK; ret = kbdd_ioctl(kbd, KDSETLED, (caddr_t)&leds); if (ret == ENOIOCTL) ret = ENODEV; return (ret); } static int vt_window_preswitch(struct vt_window *vw, struct vt_window *curvw) { DPRINTF(40, "%s\n", __func__); curvw->vw_switch_to = vw; /* Set timer to allow switch in case when process hang. */ callout_reset(&vw->vw_proc_dead_timer, hz * vt_deadtimer, vt_switch_timer, (void *)vw); /* Notify process about vt switch attempt. */ DPRINTF(30, "%s: Notify process.\n", __func__); signal_vt_rel(curvw); return (0); } static int vt_window_postswitch(struct vt_window *vw) { signal_vt_acq(vw); return (0); } /* vt_late_window_switch will done VT switching for regular case. */ static int vt_late_window_switch(struct vt_window *vw) { int ret; callout_stop(&vw->vw_proc_dead_timer); ret = vt_window_switch(vw); if (ret) return (ret); /* Notify owner process about terminal availability. */ if (vw->vw_smode.mode == VT_PROCESS) { ret = vt_window_postswitch(vw); } return (ret); } /* Switch window. */ static int vt_proc_window_switch(struct vt_window *vw) { struct vt_window *curvw; struct vt_device *vd; int ret; /* Prevent switching to NULL */ if (vw == NULL) { DPRINTF(30, "%s: Cannot switch: vw is NULL.", __func__); return (EINVAL); } vd = vw->vw_device; curvw = vd->vd_curwindow; /* Check if virtual terminal is locked */ if (curvw->vw_flags & VWF_VTYLOCK) return (EBUSY); /* Check if switch already in progress */ if (curvw->vw_flags & VWF_SWWAIT_REL) { /* Check if switching to same window */ if (curvw->vw_switch_to == vw) { DPRINTF(30, "%s: Switch in progress to same vw.", __func__); return (0); /* success */ } DPRINTF(30, "%s: Switch in progress to different vw.", __func__); return (EBUSY); } /* Avoid switching to already selected window */ if (vw == curvw) { DPRINTF(30, "%s: Cannot switch: vw == curvw.", __func__); return (0); /* success */ } /* Ask current process permission to switch away. */ if (curvw->vw_smode.mode == VT_PROCESS) { DPRINTF(30, "%s: VT_PROCESS ", __func__); if (vt_proc_alive(curvw) == FALSE) { DPRINTF(30, "Dead. Cleaning."); /* Dead */ } else { DPRINTF(30, "%s: Signaling process.\n", __func__); /* Alive, try to ask him. */ ret = vt_window_preswitch(vw, curvw); /* Wait for process answer or timeout. */ return (ret); } DPRINTF(30, "\n"); } ret = vt_late_window_switch(vw); return (ret); } /* Switch window ignoring process locking. */ static int vt_window_switch(struct vt_window *vw) { struct vt_device *vd = vw->vw_device; struct vt_window *curvw = vd->vd_curwindow; keyboard_t *kbd; if (kdb_active) { /* * When grabbing the console for the debugger, avoid * locks as that can result in deadlock. While this * could use try locks, that wouldn't really make a * difference as there are sufficient barriers in * debugger entry/exit to be equivalent to * successfully try-locking here. */ if (curvw == vw) return (0); if (!(vw->vw_flags & (VWF_OPENED|VWF_CONSOLE))) return (EINVAL); vd->vd_curwindow = vw; vd->vd_flags |= VDF_INVALID; if (vd->vd_driver->vd_postswitch) vd->vd_driver->vd_postswitch(vd); return (0); } VT_LOCK(vd); if (curvw == vw) { /* Nothing to do. */ VT_UNLOCK(vd); return (0); } if (!(vw->vw_flags & (VWF_OPENED|VWF_CONSOLE))) { VT_UNLOCK(vd); return (EINVAL); } vt_suspend_flush_timer(vd); vd->vd_curwindow = vw; vd->vd_flags |= VDF_INVALID; cv_broadcast(&vd->vd_winswitch); VT_UNLOCK(vd); if (vd->vd_driver->vd_postswitch) vd->vd_driver->vd_postswitch(vd); vt_resume_flush_timer(vd, 0); /* Restore per-window keyboard mode. */ mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) { if (curvw->vw_kbdmode == K_XLATE) vt_save_kbd_state(curvw, kbd); vt_update_kbd_mode(vw, kbd); vt_update_kbd_state(vw, kbd); } mtx_unlock(&Giant); DPRINTF(10, "%s(ttyv%d) done\n", __func__, vw->vw_number); return (0); } void vt_termsize(struct vt_device *vd, struct vt_font *vf, term_pos_t *size) { size->tp_row = vd->vd_height; if (vt_draw_logo_cpus) size->tp_row -= vt_logo_sprite_height; size->tp_col = vd->vd_width; if (vf != NULL) { size->tp_row /= vf->vf_height; size->tp_col /= vf->vf_width; } } static inline void vt_termrect(struct vt_device *vd, struct vt_font *vf, term_rect_t *rect) { rect->tr_begin.tp_row = rect->tr_begin.tp_col = 0; if (vt_draw_logo_cpus) rect->tr_begin.tp_row = vt_logo_sprite_height; rect->tr_end.tp_row = vd->vd_height; rect->tr_end.tp_col = vd->vd_width; if (vf != NULL) { rect->tr_begin.tp_row = howmany(rect->tr_begin.tp_row, vf->vf_height); rect->tr_end.tp_row /= vf->vf_height; rect->tr_end.tp_col /= vf->vf_width; } } void vt_winsize(struct vt_device *vd, struct vt_font *vf, struct winsize *size) { size->ws_ypixel = vd->vd_height; if (vt_draw_logo_cpus) size->ws_ypixel -= vt_logo_sprite_height; size->ws_row = size->ws_ypixel; size->ws_col = size->ws_xpixel = vd->vd_width; if (vf != NULL) { size->ws_row /= vf->vf_height; size->ws_col /= vf->vf_width; } } void vt_compute_drawable_area(struct vt_window *vw) { struct vt_device *vd; struct vt_font *vf; vt_axis_t height; vd = vw->vw_device; if (vw->vw_font == NULL) { vw->vw_draw_area.tr_begin.tp_col = 0; vw->vw_draw_area.tr_begin.tp_row = 0; if (vt_draw_logo_cpus) vw->vw_draw_area.tr_begin.tp_row = vt_logo_sprite_height; vw->vw_draw_area.tr_end.tp_col = vd->vd_width; vw->vw_draw_area.tr_end.tp_row = vd->vd_height; return; } vf = vw->vw_font; /* * Compute the drawable area, so that the text is centered on * the screen. */ height = vd->vd_height; if (vt_draw_logo_cpus) height -= vt_logo_sprite_height; vw->vw_draw_area.tr_begin.tp_col = (vd->vd_width % vf->vf_width) / 2; vw->vw_draw_area.tr_begin.tp_row = (height % vf->vf_height) / 2; if (vt_draw_logo_cpus) vw->vw_draw_area.tr_begin.tp_row += vt_logo_sprite_height; vw->vw_draw_area.tr_end.tp_col = vw->vw_draw_area.tr_begin.tp_col + rounddown(vd->vd_width, vf->vf_width); vw->vw_draw_area.tr_end.tp_row = vw->vw_draw_area.tr_begin.tp_row + rounddown(height, vf->vf_height); } static void vt_scroll(struct vt_window *vw, int offset, int whence) { int diff; term_pos_t size; if ((vw->vw_flags & VWF_SCROLL) == 0) return; vt_termsize(vw->vw_device, vw->vw_font, &size); diff = vthistory_seek(&vw->vw_buf, offset, whence); if (diff) vw->vw_device->vd_flags |= VDF_INVALID; vt_resume_flush_timer(vw->vw_device, 0); } static int vt_machine_kbdevent(int c) { switch (c) { case SPCLKEY | DBG: /* kbdmap(5) keyword `debug`. */ if (vt_kbd_debug) kdb_enter(KDB_WHY_BREAK, "manual escape to debugger"); return (1); case SPCLKEY | HALT: /* kbdmap(5) keyword `halt`. */ if (vt_kbd_halt) shutdown_nice(RB_HALT); return (1); case SPCLKEY | PASTE: /* kbdmap(5) keyword `paste`. */ #ifndef SC_NO_CUTPASTE /* Insert text from cut-paste buffer. */ vt_mouse_paste(); #endif break; case SPCLKEY | PDWN: /* kbdmap(5) keyword `pdwn`. */ if (vt_kbd_poweroff) shutdown_nice(RB_HALT|RB_POWEROFF); return (1); case SPCLKEY | PNC: /* kbdmap(5) keyword `panic`. */ /* * Request to immediate panic if sysctl * kern.vt.enable_panic_key allow it. */ if (vt_kbd_panic) panic("Forced by the panic key"); return (1); case SPCLKEY | RBT: /* kbdmap(5) keyword `boot`. */ if (vt_kbd_reboot) shutdown_nice(RB_AUTOBOOT); return (1); case SPCLKEY | SPSC: /* kbdmap(5) keyword `spsc`. */ /* Force activatation/deactivation of the screen saver. */ /* TODO */ return (1); case SPCLKEY | STBY: /* XXX Not present in kbdcontrol parser. */ /* Put machine into Stand-By mode. */ power_pm_suspend(POWER_SLEEP_STATE_STANDBY); return (1); case SPCLKEY | SUSP: /* kbdmap(5) keyword `susp`. */ /* Suspend machine. */ power_pm_suspend(POWER_SLEEP_STATE_SUSPEND); return (1); } return (0); } static void vt_scrollmode_kbdevent(struct vt_window *vw, int c, int console) { struct vt_device *vd; term_pos_t size; vd = vw->vw_device; /* Only special keys handled in ScrollLock mode */ if ((c & SPCLKEY) == 0) return; c &= ~SPCLKEY; if (console == 0) { if (c >= F_SCR && c <= MIN(L_SCR, F_SCR + VT_MAXWINDOWS - 1)) { vw = vd->vd_windows[c - F_SCR]; vt_proc_window_switch(vw); return; } VT_LOCK(vd); } switch (c) { case SLK: { /* Turn scrolling off. */ vt_scroll(vw, 0, VHS_END); VTBUF_SLCK_DISABLE(&vw->vw_buf); vw->vw_flags &= ~VWF_SCROLL; break; } case FKEY | F(49): /* Home key. */ vt_scroll(vw, 0, VHS_SET); break; case FKEY | F(50): /* Arrow up. */ vt_scroll(vw, -1, VHS_CUR); break; case FKEY | F(51): /* Page up. */ vt_termsize(vd, vw->vw_font, &size); vt_scroll(vw, -size.tp_row, VHS_CUR); break; case FKEY | F(57): /* End key. */ vt_scroll(vw, 0, VHS_END); break; case FKEY | F(58): /* Arrow down. */ vt_scroll(vw, 1, VHS_CUR); break; case FKEY | F(59): /* Page down. */ vt_termsize(vd, vw->vw_font, &size); vt_scroll(vw, size.tp_row, VHS_CUR); break; } if (console == 0) VT_UNLOCK(vd); } static int vt_processkey(keyboard_t *kbd, struct vt_device *vd, int c) { struct vt_window *vw = vd->vd_curwindow; random_harvest_queue(&c, sizeof(c), 1, RANDOM_KEYBOARD); #if VT_ALT_TO_ESC_HACK if (c & RELKEY) { switch (c & ~RELKEY) { case (SPCLKEY | RALT): if (vt_enable_altgr != 0) break; case (SPCLKEY | LALT): vd->vd_kbstate &= ~ALKED; } /* Other keys ignored for RELKEY event. */ return (0); } else { switch (c & ~RELKEY) { case (SPCLKEY | RALT): if (vt_enable_altgr != 0) break; case (SPCLKEY | LALT): vd->vd_kbstate |= ALKED; } } #else if (c & RELKEY) /* Other keys ignored for RELKEY event. */ return (0); #endif if (vt_machine_kbdevent(c)) return (0); if (vw->vw_flags & VWF_SCROLL) { vt_scrollmode_kbdevent(vw, c, 0/* Not a console */); /* Scroll mode keys handled, nothing to do more. */ return (0); } if (c & SPCLKEY) { c &= ~SPCLKEY; if (c >= F_SCR && c <= MIN(L_SCR, F_SCR + VT_MAXWINDOWS - 1)) { vw = vd->vd_windows[c - F_SCR]; vt_proc_window_switch(vw); return (0); } switch (c) { case NEXT: /* Switch to next VT. */ c = (vw->vw_number + 1) % VT_MAXWINDOWS; vw = vd->vd_windows[c]; vt_proc_window_switch(vw); return (0); case PREV: /* Switch to previous VT. */ c = (vw->vw_number + VT_MAXWINDOWS - 1) % VT_MAXWINDOWS; vw = vd->vd_windows[c]; vt_proc_window_switch(vw); return (0); case SLK: { vt_save_kbd_state(vw, kbd); VT_LOCK(vd); if (vw->vw_kbdstate & SLKED) { /* Turn scrolling on. */ vw->vw_flags |= VWF_SCROLL; VTBUF_SLCK_ENABLE(&vw->vw_buf); } else { /* Turn scrolling off. */ vw->vw_flags &= ~VWF_SCROLL; VTBUF_SLCK_DISABLE(&vw->vw_buf); vt_scroll(vw, 0, VHS_END); } VT_UNLOCK(vd); break; } case FKEY | F(1): case FKEY | F(2): case FKEY | F(3): case FKEY | F(4): case FKEY | F(5): case FKEY | F(6): case FKEY | F(7): case FKEY | F(8): case FKEY | F(9): case FKEY | F(10): case FKEY | F(11): case FKEY | F(12): /* F1 through F12 keys. */ terminal_input_special(vw->vw_terminal, TKEY_F1 + c - (FKEY | F(1))); break; case FKEY | F(49): /* Home key. */ terminal_input_special(vw->vw_terminal, TKEY_HOME); break; case FKEY | F(50): /* Arrow up. */ terminal_input_special(vw->vw_terminal, TKEY_UP); break; case FKEY | F(51): /* Page up. */ terminal_input_special(vw->vw_terminal, TKEY_PAGE_UP); break; case FKEY | F(53): /* Arrow left. */ terminal_input_special(vw->vw_terminal, TKEY_LEFT); break; case FKEY | F(55): /* Arrow right. */ terminal_input_special(vw->vw_terminal, TKEY_RIGHT); break; case FKEY | F(57): /* End key. */ terminal_input_special(vw->vw_terminal, TKEY_END); break; case FKEY | F(58): /* Arrow down. */ terminal_input_special(vw->vw_terminal, TKEY_DOWN); break; case FKEY | F(59): /* Page down. */ terminal_input_special(vw->vw_terminal, TKEY_PAGE_DOWN); break; case FKEY | F(60): /* Insert key. */ terminal_input_special(vw->vw_terminal, TKEY_INSERT); break; case FKEY | F(61): /* Delete key. */ terminal_input_special(vw->vw_terminal, TKEY_DELETE); break; } } else if (KEYFLAGS(c) == 0) { /* Don't do UTF-8 conversion when doing raw mode. */ if (vw->vw_kbdmode == K_XLATE) { #if VT_ALT_TO_ESC_HACK if (vd->vd_kbstate & ALKED) { /* * Prepend ESC sequence if one of ALT keys down. */ terminal_input_char(vw->vw_terminal, 0x1b); } #endif #if defined(KDB) kdb_alt_break(c, &vd->vd_altbrk); #endif terminal_input_char(vw->vw_terminal, KEYCHAR(c)); } else terminal_input_raw(vw->vw_terminal, c); } return (0); } static int vt_kbdevent(keyboard_t *kbd, int event, void *arg) { struct vt_device *vd = arg; int c; switch (event) { case KBDIO_KEYINPUT: break; case KBDIO_UNLOADING: mtx_lock(&Giant); vd->vd_keyboard = -1; kbd_release(kbd, (void *)vd); mtx_unlock(&Giant); return (0); default: return (EINVAL); } while ((c = kbdd_read_char(kbd, 0)) != NOKEY) vt_processkey(kbd, vd, c); return (0); } static int vt_allocate_keyboard(struct vt_device *vd) { int idx0, idx; keyboard_t *k0, *k; keyboard_info_t ki; idx0 = kbd_allocate("kbdmux", -1, vd, vt_kbdevent, vd); if (idx0 >= 0) { DPRINTF(20, "%s: kbdmux allocated, idx = %d\n", __func__, idx0); k0 = kbd_get_keyboard(idx0); for (idx = kbd_find_keyboard2("*", -1, 0); idx != -1; idx = kbd_find_keyboard2("*", -1, idx + 1)) { k = kbd_get_keyboard(idx); if (idx == idx0 || KBD_IS_BUSY(k)) continue; bzero(&ki, sizeof(ki)); strncpy(ki.kb_name, k->kb_name, sizeof(ki.kb_name)); ki.kb_name[sizeof(ki.kb_name) - 1] = '\0'; ki.kb_unit = k->kb_unit; kbdd_ioctl(k0, KBADDKBD, (caddr_t) &ki); } } else { DPRINTF(20, "%s: no kbdmux allocated\n", __func__); idx0 = kbd_allocate("*", -1, vd, vt_kbdevent, vd); if (idx0 < 0) { DPRINTF(10, "%s: No keyboard found.\n", __func__); return (-1); } } vd->vd_keyboard = idx0; DPRINTF(20, "%s: vd_keyboard = %d\n", __func__, vd->vd_keyboard); return (idx0); } static void vtterm_bell(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; if (!vt_enable_bell) return; if (vd->vd_flags & VDF_QUIET_BELL) return; sysbeep(1193182 / VT_BELLPITCH, VT_BELLDURATION); } static void vtterm_beep(struct terminal *tm, u_int param) { u_int freq, period; if (!vt_enable_bell) return; if ((param == 0) || ((param & 0xffff) == 0)) { vtterm_bell(tm); return; } period = ((param >> 16) & 0xffff) * hz / 1000; freq = 1193182 / (param & 0xffff); sysbeep(freq, period); } static void vtterm_cursor(struct terminal *tm, const term_pos_t *p) { struct vt_window *vw = tm->tm_softc; vtbuf_cursor_position(&vw->vw_buf, p); vt_resume_flush_timer(vw->vw_device, 0); } static void vtterm_putchar(struct terminal *tm, const term_pos_t *p, term_char_t c) { struct vt_window *vw = tm->tm_softc; vtbuf_putchar(&vw->vw_buf, p, c); vt_resume_flush_timer(vw->vw_device, 0); } static void vtterm_fill(struct terminal *tm, const term_rect_t *r, term_char_t c) { struct vt_window *vw = tm->tm_softc; - vtbuf_fill_locked(&vw->vw_buf, r, c); + vtbuf_fill(&vw->vw_buf, r, c); vt_resume_flush_timer(vw->vw_device, 0); } static void vtterm_copy(struct terminal *tm, const term_rect_t *r, const term_pos_t *p) { struct vt_window *vw = tm->tm_softc; vtbuf_copy(&vw->vw_buf, r, p); vt_resume_flush_timer(vw->vw_device, 0); } static void vtterm_param(struct terminal *tm, int cmd, unsigned int arg) { struct vt_window *vw = tm->tm_softc; switch (cmd) { case TP_SETLOCALCURSOR: /* * 0 means normal (usually block), 1 means hidden, and * 2 means blinking (always block) for compatibility with * syscons. We don't support any changes except hiding, * so must map 2 to 0. */ arg = (arg == 1) ? 0 : 1; /* FALLTHROUGH */ case TP_SHOWCURSOR: vtbuf_cursor_visibility(&vw->vw_buf, arg); vt_resume_flush_timer(vw->vw_device, 0); break; case TP_MOUSE: vw->vw_mouse_level = arg; break; } } void vt_determine_colors(term_char_t c, int cursor, term_color_t *fg, term_color_t *bg) { term_color_t tmp; int invert; invert = 0; *fg = TCHAR_FGCOLOR(c); if (TCHAR_FORMAT(c) & TF_BOLD) *fg = TCOLOR_LIGHT(*fg); *bg = TCHAR_BGCOLOR(c); if (TCHAR_FORMAT(c) & TF_BLINK) *bg = TCOLOR_LIGHT(*bg); if (TCHAR_FORMAT(c) & TF_REVERSE) invert ^= 1; if (cursor) invert ^= 1; if (invert) { tmp = *fg; *fg = *bg; *bg = tmp; } } #ifndef SC_NO_CUTPASTE int vt_is_cursor_in_area(const struct vt_device *vd, const term_rect_t *area) { unsigned int mx, my; /* * We use the cursor position saved during the current refresh, * in case the cursor moved since. */ mx = vd->vd_mx_drawn + vd->vd_curwindow->vw_draw_area.tr_begin.tp_col; my = vd->vd_my_drawn + vd->vd_curwindow->vw_draw_area.tr_begin.tp_row; if (mx >= area->tr_end.tp_col || mx + vd->vd_mcursor->width <= area->tr_begin.tp_col || my >= area->tr_end.tp_row || my + vd->vd_mcursor->height <= area->tr_begin.tp_row) return (0); return (1); } static void -vt_mark_mouse_position_as_dirty(struct vt_device *vd) +vt_mark_mouse_position_as_dirty(struct vt_device *vd, int locked) { term_rect_t area; struct vt_window *vw; struct vt_font *vf; int x, y; vw = vd->vd_curwindow; vf = vw->vw_font; x = vd->vd_mx_drawn; y = vd->vd_my_drawn; if (vf != NULL) { area.tr_begin.tp_col = x / vf->vf_width; area.tr_begin.tp_row = y / vf->vf_height; area.tr_end.tp_col = ((x + vd->vd_mcursor->width) / vf->vf_width) + 1; area.tr_end.tp_row = ((y + vd->vd_mcursor->height) / vf->vf_height) + 1; } else { /* * No font loaded (ie. vt_vga operating in textmode). * * FIXME: This fake area needs to be revisited once the * mouse cursor is supported in vt_vga's textmode. */ area.tr_begin.tp_col = x; area.tr_begin.tp_row = y; area.tr_end.tp_col = x + 2; area.tr_end.tp_row = y + 2; } + if (!locked) + vtbuf_lock(&vw->vw_buf); vtbuf_dirty(&vw->vw_buf, &area); + if (!locked) + vtbuf_unlock(&vw->vw_buf); } #endif static void vt_set_border(struct vt_device *vd, const term_rect_t *area, const term_color_t c) { vd_drawrect_t *drawrect = vd->vd_driver->vd_drawrect; if (drawrect == NULL) return; /* Top bar */ if (area->tr_begin.tp_row > 0) drawrect(vd, 0, 0, vd->vd_width - 1, area->tr_begin.tp_row - 1, 1, c); /* Left bar */ if (area->tr_begin.tp_col > 0) drawrect(vd, 0, area->tr_begin.tp_row, area->tr_begin.tp_col - 1, area->tr_end.tp_row - 1, 1, c); /* Right bar */ if (area->tr_end.tp_col < vd->vd_width) drawrect(vd, area->tr_end.tp_col, area->tr_begin.tp_row, vd->vd_width - 1, area->tr_end.tp_row - 1, 1, c); /* Bottom bar */ if (area->tr_end.tp_row < vd->vd_height) drawrect(vd, 0, area->tr_end.tp_row, vd->vd_width - 1, vd->vd_height - 1, 1, c); } static int vt_flush(struct vt_device *vd) { struct vt_window *vw; struct vt_font *vf; term_rect_t tarea; #ifndef SC_NO_CUTPASTE int cursor_was_shown, cursor_moved; #endif vw = vd->vd_curwindow; if (vw == NULL) return (0); if (vd->vd_flags & VDF_SPLASH || vw->vw_flags & VWF_BUSY) return (0); vf = vw->vw_font; if (((vd->vd_flags & VDF_TEXTMODE) == 0) && (vf == NULL)) return (0); + vtbuf_lock(&vw->vw_buf); + #ifndef SC_NO_CUTPASTE cursor_was_shown = vd->vd_mshown; cursor_moved = (vd->vd_mx != vd->vd_mx_drawn || vd->vd_my != vd->vd_my_drawn); /* Check if the cursor should be displayed or not. */ if ((vd->vd_flags & VDF_MOUSECURSOR) && /* Mouse support enabled. */ !(vw->vw_flags & VWF_MOUSE_HIDE) && /* Cursor displayed. */ !kdb_active && panicstr == NULL) { /* DDB inactive. */ vd->vd_mshown = 1; } else { vd->vd_mshown = 0; } /* * If the cursor changed display state or moved, we must mark * the old position as dirty, so that it's erased. */ if (cursor_was_shown != vd->vd_mshown || (vd->vd_mshown && cursor_moved)) - vt_mark_mouse_position_as_dirty(vd); + vt_mark_mouse_position_as_dirty(vd, true); /* * Save position of the mouse cursor. It's used by backends to * know where to draw the cursor and during the next refresh to * erase the previous position. */ vd->vd_mx_drawn = vd->vd_mx; vd->vd_my_drawn = vd->vd_my; /* * If the cursor is displayed and has moved since last refresh, * mark the new position as dirty. */ if (vd->vd_mshown && cursor_moved) - vt_mark_mouse_position_as_dirty(vd); + vt_mark_mouse_position_as_dirty(vd, true); #endif vtbuf_undirty(&vw->vw_buf, &tarea); /* Force a full redraw when the screen contents are invalid. */ if (vd->vd_flags & VDF_INVALID) { vd->vd_flags &= ~VDF_INVALID; vt_set_border(vd, &vw->vw_draw_area, TC_BLACK); vt_termrect(vd, vf, &tarea); if (vt_draw_logo_cpus) vtterm_draw_cpu_logos(vd); } if (tarea.tr_begin.tp_col < tarea.tr_end.tp_col) { vd->vd_driver->vd_bitblt_text(vd, vw, &tarea); + vtbuf_unlock(&vw->vw_buf); return (1); } + vtbuf_unlock(&vw->vw_buf); return (0); } static void vt_timer(void *arg) { struct vt_device *vd; int changed; vd = arg; /* Update screen if required. */ changed = vt_flush(vd); /* Schedule for next update. */ if (changed) vt_schedule_flush(vd, 0); else vd->vd_timer_armed = 0; } static void +vtterm_pre_input(struct terminal *tm) +{ + struct vt_window *vw = tm->tm_softc; + + vtbuf_lock(&vw->vw_buf); +} + +static void +vtterm_post_input(struct terminal *tm) +{ + struct vt_window *vw = tm->tm_softc; + + vtbuf_unlock(&vw->vw_buf); + vt_resume_flush_timer(vw->vw_device, 0); +} + +static void vtterm_done(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; if (kdb_active || panicstr != NULL) { /* Switch to the debugger. */ if (vd->vd_curwindow != vw) { vd->vd_curwindow = vw; vd->vd_flags |= VDF_INVALID; if (vd->vd_driver->vd_postswitch) vd->vd_driver->vd_postswitch(vd); } vd->vd_flags &= ~VDF_SPLASH; vt_flush(vd); } else if (!(vd->vd_flags & VDF_ASYNC)) { vt_flush(vd); } } #ifdef DEV_SPLASH static void vtterm_splash(struct vt_device *vd) { vt_axis_t top, left; /* Display a nice boot splash. */ if (!(vd->vd_flags & VDF_TEXTMODE) && (boothowto & RB_MUTE)) { top = (vd->vd_height - vt_logo_height) / 2; left = (vd->vd_width - vt_logo_width) / 2; switch (vt_logo_depth) { case 1: /* XXX: Unhardcode colors! */ vd->vd_driver->vd_bitblt_bmp(vd, vd->vd_curwindow, vt_logo_image, NULL, vt_logo_width, vt_logo_height, left, top, TC_WHITE, TC_BLACK); } vd->vd_flags |= VDF_SPLASH; } } #endif static void vtterm_cnprobe(struct terminal *tm, struct consdev *cp) { struct vt_driver *vtd, **vtdlist, *vtdbest = NULL; struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; struct winsize wsz; term_attr_t attr; term_char_t c; if (!vty_enabled(VTY_VT)) return; if (vd->vd_flags & VDF_INITIALIZED) /* Initialization already done. */ return; SET_FOREACH(vtdlist, vt_drv_set) { vtd = *vtdlist; if (vtd->vd_probe == NULL) continue; if (vtd->vd_probe(vd) == CN_DEAD) continue; if ((vtdbest == NULL) || (vtd->vd_priority > vtdbest->vd_priority)) vtdbest = vtd; } if (vtdbest == NULL) { cp->cn_pri = CN_DEAD; vd->vd_flags |= VDF_DEAD; } else { vd->vd_driver = vtdbest; cp->cn_pri = vd->vd_driver->vd_init(vd); } /* Check if driver's vt_init return CN_DEAD. */ if (cp->cn_pri == CN_DEAD) { vd->vd_flags |= VDF_DEAD; } /* Initialize any early-boot keyboard drivers */ kbd_configure(KB_CONF_PROBE_ONLY); vd->vd_unit = atomic_fetchadd_int(&vt_unit, 1); vd->vd_windows[VT_CONSWINDOW] = vw; sprintf(cp->cn_name, "ttyv%r", VT_UNIT(vw)); /* Attach default font if not in TEXTMODE. */ if ((vd->vd_flags & VDF_TEXTMODE) == 0) { vw->vw_font = vtfont_ref(&vt_font_default); vt_compute_drawable_area(vw); } /* * The original screen size was faked (_VTDEFW x _VTDEFH). Now * that we have the real viewable size, fix it in the static * buffer. */ if (vd->vd_width != 0 && vd->vd_height != 0) vt_termsize(vd, vw->vw_font, &vw->vw_buf.vb_scr_size); vtbuf_init_early(&vw->vw_buf); vt_winsize(vd, vw->vw_font, &wsz); c = (boothowto & RB_MUTE) == 0 ? TERMINAL_KERN_ATTR : TERMINAL_NORM_ATTR; attr.ta_format = TCHAR_FORMAT(c); attr.ta_fgcolor = TCHAR_FGCOLOR(c); attr.ta_bgcolor = TCHAR_BGCOLOR(c); terminal_set_winsize_blank(tm, &wsz, 1, &attr); if (vtdbest != NULL) { #ifdef DEV_SPLASH if (!vt_splash_cpu) vtterm_splash(vd); #endif vd->vd_flags |= VDF_INITIALIZED; } } static int vtterm_cngetc(struct terminal *tm) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; keyboard_t *kbd; u_int c; if (vw->vw_kbdsq && *vw->vw_kbdsq) return (*vw->vw_kbdsq++); /* Make sure the splash screen is not there. */ if (vd->vd_flags & VDF_SPLASH) { /* Remove splash */ vd->vd_flags &= ~VDF_SPLASH; /* Mark screen as invalid to force update */ vd->vd_flags |= VDF_INVALID; vt_flush(vd); } /* Stripped down keyboard handler. */ kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd == NULL) return (-1); /* Force keyboard input mode to K_XLATE */ vw->vw_kbdmode = K_XLATE; vt_update_kbd_mode(vw, kbd); /* Switch the keyboard to polling to make it work here. */ kbdd_poll(kbd, TRUE); c = kbdd_read_char(kbd, 0); kbdd_poll(kbd, FALSE); if (c & RELKEY) return (-1); if (vw->vw_flags & VWF_SCROLL) { vt_scrollmode_kbdevent(vw, c, 1/* Console mode */); vt_flush(vd); return (-1); } /* Stripped down handling of vt_kbdevent(), without locking, etc. */ if (c & SPCLKEY) { switch (c) { case SPCLKEY | SLK: vt_save_kbd_state(vw, kbd); if (vw->vw_kbdstate & SLKED) { /* Turn scrolling on. */ vw->vw_flags |= VWF_SCROLL; VTBUF_SLCK_ENABLE(&vw->vw_buf); } else { /* Turn scrolling off. */ vt_scroll(vw, 0, VHS_END); vw->vw_flags &= ~VWF_SCROLL; VTBUF_SLCK_DISABLE(&vw->vw_buf); } break; /* XXX: KDB can handle history. */ case SPCLKEY | FKEY | F(50): /* Arrow up. */ vw->vw_kbdsq = "\x1b[A"; break; case SPCLKEY | FKEY | F(58): /* Arrow down. */ vw->vw_kbdsq = "\x1b[B"; break; case SPCLKEY | FKEY | F(55): /* Arrow right. */ vw->vw_kbdsq = "\x1b[C"; break; case SPCLKEY | FKEY | F(53): /* Arrow left. */ vw->vw_kbdsq = "\x1b[D"; break; } /* Force refresh to make scrollback work. */ vt_flush(vd); } else if (KEYFLAGS(c) == 0) { return (KEYCHAR(c)); } if (vw->vw_kbdsq && *vw->vw_kbdsq) return (*vw->vw_kbdsq++); return (-1); } static void vtterm_cngrab(struct terminal *tm) { struct vt_device *vd; struct vt_window *vw; keyboard_t *kbd; vw = tm->tm_softc; vd = vw->vw_device; if (!cold) vt_window_switch(vw); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd == NULL) return; if (vw->vw_grabbed++ > 0) return; /* * Make sure the keyboard is accessible even when the kbd device * driver is disabled. */ kbdd_enable(kbd); /* We shall always use the keyboard in the XLATE mode here. */ vw->vw_prev_kbdmode = vw->vw_kbdmode; vw->vw_kbdmode = K_XLATE; vt_update_kbd_mode(vw, kbd); kbdd_poll(kbd, TRUE); } static void vtterm_cnungrab(struct terminal *tm) { struct vt_device *vd; struct vt_window *vw; keyboard_t *kbd; vw = tm->tm_softc; vd = vw->vw_device; kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd == NULL) return; if (--vw->vw_grabbed > 0) return; kbdd_poll(kbd, FALSE); vw->vw_kbdmode = vw->vw_prev_kbdmode; vt_update_kbd_mode(vw, kbd); kbdd_disable(kbd); } static void vtterm_opened(struct terminal *tm, int opened) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; VT_LOCK(vd); vd->vd_flags &= ~VDF_SPLASH; if (opened) vw->vw_flags |= VWF_OPENED; else { vw->vw_flags &= ~VWF_OPENED; /* TODO: finish ACQ/REL */ } VT_UNLOCK(vd); } static int vt_change_font(struct vt_window *vw, struct vt_font *vf) { struct vt_device *vd = vw->vw_device; struct terminal *tm = vw->vw_terminal; term_pos_t size; struct winsize wsz; /* * Changing fonts. * * Changing fonts is a little tricky. We must prevent * simultaneous access to the device, so we must stop * the display timer and the terminal from accessing. * We need to switch fonts and grow our screen buffer. * * XXX: Right now the code uses terminal_mute() to * prevent data from reaching the console driver while * resizing the screen buffer. This isn't elegant... */ VT_LOCK(vd); if (vw->vw_flags & VWF_BUSY) { /* Another process is changing the font. */ VT_UNLOCK(vd); return (EBUSY); } vw->vw_flags |= VWF_BUSY; VT_UNLOCK(vd); vt_termsize(vd, vf, &size); vt_winsize(vd, vf, &wsz); /* Grow the screen buffer and terminal. */ terminal_mute(tm, 1); vtbuf_grow(&vw->vw_buf, &size, vw->vw_buf.vb_history_size); terminal_set_winsize_blank(tm, &wsz, 0, NULL); terminal_set_cursor(tm, &vw->vw_buf.vb_cursor); terminal_mute(tm, 0); /* Actually apply the font to the current window. */ VT_LOCK(vd); if (vw->vw_font != vf && vw->vw_font != NULL && vf != NULL) { /* * In case vt_change_font called to update size we don't need * to update font link. */ vtfont_unref(vw->vw_font); vw->vw_font = vtfont_ref(vf); } /* * Compute the drawable area and move the mouse cursor inside * it, in case the new area is smaller than the previous one. */ vt_compute_drawable_area(vw); vd->vd_mx = min(vd->vd_mx, vw->vw_draw_area.tr_end.tp_col - vw->vw_draw_area.tr_begin.tp_col - 1); vd->vd_my = min(vd->vd_my, vw->vw_draw_area.tr_end.tp_row - vw->vw_draw_area.tr_begin.tp_row - 1); /* Force a full redraw the next timer tick. */ if (vd->vd_curwindow == vw) { vd->vd_flags |= VDF_INVALID; vt_resume_flush_timer(vw->vw_device, 0); } vw->vw_flags &= ~VWF_BUSY; VT_UNLOCK(vd); return (0); } static int vt_proc_alive(struct vt_window *vw) { struct proc *p; if (vw->vw_smode.mode != VT_PROCESS) return (FALSE); if (vw->vw_proc) { if ((p = pfind(vw->vw_pid)) != NULL) PROC_UNLOCK(p); if (vw->vw_proc == p) return (TRUE); vw->vw_proc = NULL; vw->vw_smode.mode = VT_AUTO; DPRINTF(1, "vt controlling process %d died\n", vw->vw_pid); vw->vw_pid = 0; } return (FALSE); } static int signal_vt_rel(struct vt_window *vw) { if (vw->vw_smode.mode != VT_PROCESS) return (FALSE); if (vw->vw_proc == NULL || vt_proc_alive(vw) == FALSE) { vw->vw_proc = NULL; vw->vw_pid = 0; return (TRUE); } vw->vw_flags |= VWF_SWWAIT_REL; PROC_LOCK(vw->vw_proc); kern_psignal(vw->vw_proc, vw->vw_smode.relsig); PROC_UNLOCK(vw->vw_proc); DPRINTF(1, "sending relsig to %d\n", vw->vw_pid); return (TRUE); } static int signal_vt_acq(struct vt_window *vw) { if (vw->vw_smode.mode != VT_PROCESS) return (FALSE); if (vw == vw->vw_device->vd_windows[VT_CONSWINDOW]) cnavailable(vw->vw_terminal->consdev, FALSE); if (vw->vw_proc == NULL || vt_proc_alive(vw) == FALSE) { vw->vw_proc = NULL; vw->vw_pid = 0; return (TRUE); } vw->vw_flags |= VWF_SWWAIT_ACQ; PROC_LOCK(vw->vw_proc); kern_psignal(vw->vw_proc, vw->vw_smode.acqsig); PROC_UNLOCK(vw->vw_proc); DPRINTF(1, "sending acqsig to %d\n", vw->vw_pid); return (TRUE); } static int finish_vt_rel(struct vt_window *vw, int release, int *s) { if (vw->vw_flags & VWF_SWWAIT_REL) { vw->vw_flags &= ~VWF_SWWAIT_REL; if (release) { callout_drain(&vw->vw_proc_dead_timer); vt_late_window_switch(vw->vw_switch_to); } return (0); } return (EINVAL); } static int finish_vt_acq(struct vt_window *vw) { if (vw->vw_flags & VWF_SWWAIT_ACQ) { vw->vw_flags &= ~VWF_SWWAIT_ACQ; return (0); } return (EINVAL); } #ifndef SC_NO_CUTPASTE static void vt_mouse_terminput_button(struct vt_device *vd, int button) { struct vt_window *vw; struct vt_font *vf; char mouseb[6] = "\x1B[M"; int i, x, y; vw = vd->vd_curwindow; vf = vw->vw_font; /* Translate to char position. */ x = vd->vd_mx / vf->vf_width; y = vd->vd_my / vf->vf_height; /* Avoid overflow. */ x = MIN(x, 255 - '!'); y = MIN(y, 255 - '!'); mouseb[3] = ' ' + button; mouseb[4] = '!' + x; mouseb[5] = '!' + y; for (i = 0; i < sizeof(mouseb); i++) terminal_input_char(vw->vw_terminal, mouseb[i]); } static void vt_mouse_terminput(struct vt_device *vd, int type, int x, int y, int event, int cnt) { switch (type) { case MOUSE_BUTTON_EVENT: if (cnt > 0) { /* Mouse button pressed. */ if (event & MOUSE_BUTTON1DOWN) vt_mouse_terminput_button(vd, 0); if (event & MOUSE_BUTTON2DOWN) vt_mouse_terminput_button(vd, 1); if (event & MOUSE_BUTTON3DOWN) vt_mouse_terminput_button(vd, 2); } else { /* Mouse button released. */ vt_mouse_terminput_button(vd, 3); } break; #ifdef notyet case MOUSE_MOTION_EVENT: if (mouse->u.data.z < 0) { /* Scroll up. */ sc_mouse_input_button(vd, 64); } else if (mouse->u.data.z > 0) { /* Scroll down. */ sc_mouse_input_button(vd, 65); } break; #endif } } static void vt_mouse_paste() { term_char_t *buf; int i, len; len = VD_PASTEBUFLEN(main_vd); buf = VD_PASTEBUF(main_vd); len /= sizeof(term_char_t); for (i = 0; i < len; i++) { if (buf[i] == '\0') continue; terminal_input_char(main_vd->vd_curwindow->vw_terminal, buf[i]); } } void vt_mouse_event(int type, int x, int y, int event, int cnt, int mlevel) { struct vt_device *vd; struct vt_window *vw; struct vt_font *vf; term_pos_t size; int len, mark; vd = main_vd; vw = vd->vd_curwindow; vf = vw->vw_font; mark = 0; if (vw->vw_flags & (VWF_MOUSE_HIDE | VWF_GRAPHICS)) /* * Either the mouse is disabled, or the window is in * "graphics mode". The graphics mode is usually set by * an X server, using the KDSETMODE ioctl. */ return; if (vf == NULL) /* Text mode. */ return; /* * TODO: add flag about pointer position changed, to not redraw chars * under mouse pointer when nothing changed. */ if (vw->vw_mouse_level > 0) vt_mouse_terminput(vd, type, x, y, event, cnt); switch (type) { case MOUSE_ACTION: case MOUSE_MOTION_EVENT: /* Movement */ x += vd->vd_mx; y += vd->vd_my; vt_termsize(vd, vf, &size); /* Apply limits. */ x = MAX(x, 0); y = MAX(y, 0); x = MIN(x, (size.tp_col * vf->vf_width) - 1); y = MIN(y, (size.tp_row * vf->vf_height) - 1); vd->vd_mx = x; vd->vd_my = y; if (vd->vd_mstate & MOUSE_BUTTON1DOWN) vtbuf_set_mark(&vw->vw_buf, VTB_MARK_MOVE, vd->vd_mx / vf->vf_width, vd->vd_my / vf->vf_height); vt_resume_flush_timer(vw->vw_device, 0); return; /* Done */ case MOUSE_BUTTON_EVENT: /* Buttons */ break; default: return; /* Done */ } switch (event) { case MOUSE_BUTTON1DOWN: switch (cnt % 4) { case 0: /* up */ mark = VTB_MARK_END; break; case 1: /* single click: start cut operation */ mark = VTB_MARK_START; break; case 2: /* double click: cut a word */ mark = VTB_MARK_WORD; break; case 3: /* triple click: cut a line */ mark = VTB_MARK_ROW; break; } break; case VT_MOUSE_PASTEBUTTON: switch (cnt) { case 0: /* up */ break; default: vt_mouse_paste(); break; } return; /* Done */ case VT_MOUSE_EXTENDBUTTON: switch (cnt) { case 0: /* up */ if (!(vd->vd_mstate & MOUSE_BUTTON1DOWN)) mark = VTB_MARK_EXTEND; else mark = 0; break; default: mark = VTB_MARK_EXTEND; break; } break; default: return; /* Done */ } /* Save buttons state. */ if (cnt > 0) vd->vd_mstate |= event; else vd->vd_mstate &= ~event; if (vtbuf_set_mark(&vw->vw_buf, mark, vd->vd_mx / vf->vf_width, vd->vd_my / vf->vf_height) == 1) { /* * We have something marked to copy, so update pointer to * window with selection. */ vt_resume_flush_timer(vw->vw_device, 0); switch (mark) { case VTB_MARK_END: case VTB_MARK_WORD: case VTB_MARK_ROW: case VTB_MARK_EXTEND: break; default: /* Other types of mark do not require to copy data. */ return; } /* Get current selection size in bytes. */ len = vtbuf_get_marked_len(&vw->vw_buf); if (len <= 0) return; /* Reallocate buffer only if old one is too small. */ if (len > VD_PASTEBUFSZ(vd)) { VD_PASTEBUF(vd) = realloc(VD_PASTEBUF(vd), len, M_VT, M_WAITOK | M_ZERO); /* Update buffer size. */ VD_PASTEBUFSZ(vd) = len; } /* Request copy/paste buffer data, no more than `len' */ vtbuf_extract_marked(&vw->vw_buf, VD_PASTEBUF(vd), VD_PASTEBUFSZ(vd)); VD_PASTEBUFLEN(vd) = len; /* XXX VD_PASTEBUF(vd) have to be freed on shutdown/unload. */ } } void vt_mouse_state(int show) { struct vt_device *vd; struct vt_window *vw; vd = main_vd; vw = vd->vd_curwindow; switch (show) { case VT_MOUSE_HIDE: vw->vw_flags |= VWF_MOUSE_HIDE; break; case VT_MOUSE_SHOW: vw->vw_flags &= ~VWF_MOUSE_HIDE; break; } /* Mark mouse position as dirty. */ - vt_mark_mouse_position_as_dirty(vd); + vt_mark_mouse_position_as_dirty(vd, false); vt_resume_flush_timer(vw->vw_device, 0); } #endif static int vtterm_mmap(struct terminal *tm, vm_ooffset_t offset, vm_paddr_t * paddr, int nprot, vm_memattr_t *memattr) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; if (vd->vd_driver->vd_fb_mmap) return (vd->vd_driver->vd_fb_mmap(vd, offset, paddr, nprot, memattr)); return (ENXIO); } static int vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data, struct thread *td) { struct vt_window *vw = tm->tm_softc; struct vt_device *vd = vw->vw_device; keyboard_t *kbd; int error, i, s; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) int ival; switch (cmd) { case _IO('v', 4): cmd = VT_RELDISP; break; case _IO('v', 5): cmd = VT_ACTIVATE; break; case _IO('v', 6): cmd = VT_WAITACTIVE; break; case _IO('K', 20): cmd = KDSKBSTATE; break; case _IO('K', 67): cmd = KDSETRAD; break; case _IO('K', 7): cmd = KDSKBMODE; break; case _IO('K', 8): cmd = KDMKTONE; break; case _IO('K', 63): cmd = KIOCSOUND; break; case _IO('K', 66): cmd = KDSETLED; break; case _IO('c', 110): cmd = CONS_SETKBD; break; default: goto skip_thunk; } ival = IOCPARM_IVAL(data); data = (caddr_t)&ival; skip_thunk: #endif switch (cmd) { case KDSETRAD: /* set keyboard repeat & delay rates (old) */ if (*(int *)data & ~0x7f) return (EINVAL); /* FALLTHROUGH */ case GIO_KEYMAP: case PIO_KEYMAP: case GIO_DEADKEYMAP: case PIO_DEADKEYMAP: case GETFKEY: case SETFKEY: case KDGKBINFO: case KDGKBTYPE: case KDGETREPEAT: /* get keyboard repeat & delay rates */ case KDSETREPEAT: /* set keyboard repeat & delay rates (new) */ case KBADDKBD: /* add/remove keyboard to/from mux */ case KBRELKBD: { error = 0; mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) error = kbdd_ioctl(kbd, cmd, data); mtx_unlock(&Giant); if (error == ENOIOCTL) { if (cmd == KDGKBTYPE) { /* always return something? XXX */ *(int *)data = 0; } else { return (ENODEV); } } return (error); } case KDGKBSTATE: { /* get keyboard state (locks) */ error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) error = vt_save_kbd_state(vw, kbd); mtx_unlock(&Giant); if (error != 0) return (error); } *(int *)data = vw->vw_kbdstate & LOCK_MASK; return (error); } case KDSKBSTATE: { /* set keyboard state (locks) */ int state; state = *(int *)data; if (state & ~LOCK_MASK) return (EINVAL); vw->vw_kbdstate &= ~LOCK_MASK; vw->vw_kbdstate |= state; error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) error = vt_update_kbd_state(vw, kbd); mtx_unlock(&Giant); } return (error); } case KDGETLED: { /* get keyboard LED status */ error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) error = vt_save_kbd_leds(vw, kbd); mtx_unlock(&Giant); if (error != 0) return (error); } *(int *)data = vw->vw_kbdstate & LED_MASK; return (error); } case KDSETLED: { /* set keyboard LED status */ int leds; leds = *(int *)data; if (leds & ~LED_MASK) return (EINVAL); vw->vw_kbdstate &= ~LED_MASK; vw->vw_kbdstate |= leds; error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) error = vt_update_kbd_leds(vw, kbd); mtx_unlock(&Giant); } return (error); } case KDGETMODE: *(int *)data = (vw->vw_flags & VWF_GRAPHICS) ? KD_GRAPHICS : KD_TEXT; return (0); case KDGKBMODE: { error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) error = vt_save_kbd_mode(vw, kbd); mtx_unlock(&Giant); if (error != 0) return (error); } *(int *)data = vw->vw_kbdmode; return (error); } case KDSKBMODE: { int mode; mode = *(int *)data; switch (mode) { case K_XLATE: case K_RAW: case K_CODE: vw->vw_kbdmode = mode; error = 0; if (vw == vd->vd_curwindow) { mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) error = vt_update_kbd_mode(vw, kbd); mtx_unlock(&Giant); } return (error); default: return (EINVAL); } } case FBIOGTYPE: case FBIO_GETWINORG: /* get frame buffer window origin */ case FBIO_GETDISPSTART: /* get display start address */ case FBIO_GETLINEWIDTH: /* get scan line width in bytes */ case FBIO_BLANK: /* blank display */ if (vd->vd_driver->vd_fb_ioctl) return (vd->vd_driver->vd_fb_ioctl(vd, cmd, data, td)); break; case CONS_BLANKTIME: /* XXX */ return (0); case CONS_HISTORY: if (*(int *)data < 0) return EINVAL; if (*(int *)data != vd->vd_curwindow->vw_buf.vb_history_size) vtbuf_sethistory_size(&vd->vd_curwindow->vw_buf, *(int *)data); return 0; case CONS_GET: /* XXX */ *(int *)data = M_CG640x480; return (0); case CONS_BELLTYPE: /* set bell type sound */ if ((*(int *)data) & CONS_QUIET_BELL) vd->vd_flags |= VDF_QUIET_BELL; else vd->vd_flags &= ~VDF_QUIET_BELL; return (0); case CONS_GETINFO: { vid_info_t *vi = (vid_info_t *)data; if (vi->size != sizeof(struct vid_info)) return (EINVAL); if (vw == vd->vd_curwindow) { mtx_lock(&Giant); kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd != NULL) vt_save_kbd_state(vw, kbd); mtx_unlock(&Giant); } vi->m_num = vd->vd_curwindow->vw_number + 1; vi->mk_keylock = vw->vw_kbdstate & LOCK_MASK; /* XXX: other fields! */ return (0); } case CONS_GETVERS: *(int *)data = 0x200; return (0); case CONS_MODEINFO: /* XXX */ return (0); case CONS_MOUSECTL: { mouse_info_t *mouse = (mouse_info_t*)data; /* * All the commands except MOUSE_SHOW nd MOUSE_HIDE * should not be applied to individual TTYs, but only to * consolectl. */ switch (mouse->operation) { case MOUSE_HIDE: if (vd->vd_flags & VDF_MOUSECURSOR) { vd->vd_flags &= ~VDF_MOUSECURSOR; #ifndef SC_NO_CUTPASTE vt_mouse_state(VT_MOUSE_HIDE); #endif } return (0); case MOUSE_SHOW: if (!(vd->vd_flags & VDF_MOUSECURSOR)) { vd->vd_flags |= VDF_MOUSECURSOR; vd->vd_mx = vd->vd_width / 2; vd->vd_my = vd->vd_height / 2; #ifndef SC_NO_CUTPASTE vt_mouse_state(VT_MOUSE_SHOW); #endif } return (0); default: return (EINVAL); } } case PIO_VFONT: { struct vt_font *vf; if (vd->vd_flags & VDF_TEXTMODE) return (ENOTSUP); error = vtfont_load((void *)data, &vf); if (error != 0) return (error); error = vt_change_font(vw, vf); vtfont_unref(vf); return (error); } case PIO_VFONT_DEFAULT: { /* Reset to default font. */ error = vt_change_font(vw, &vt_font_default); return (error); } case GIO_SCRNMAP: { scrmap_t *sm = (scrmap_t *)data; /* We don't have screen maps, so return a handcrafted one. */ for (i = 0; i < 256; i++) sm->scrmap[i] = i; return (0); } case KDSETMODE: /* * FIXME: This implementation is incomplete compared to * syscons. */ switch (*(int *)data) { case KD_TEXT: case KD_TEXT1: case KD_PIXEL: vw->vw_flags &= ~VWF_GRAPHICS; break; case KD_GRAPHICS: vw->vw_flags |= VWF_GRAPHICS; break; } return (0); case KDENABIO: /* allow io operations */ error = priv_check(td, PRIV_IO); if (error != 0) return (error); error = securelevel_gt(td->td_ucred, 0); if (error != 0) return (error); #if defined(__i386__) td->td_frame->tf_eflags |= PSL_IOPL; #elif defined(__amd64__) td->td_frame->tf_rflags |= PSL_IOPL; #endif return (0); case KDDISABIO: /* disallow io operations (default) */ #if defined(__i386__) td->td_frame->tf_eflags &= ~PSL_IOPL; #elif defined(__amd64__) td->td_frame->tf_rflags &= ~PSL_IOPL; #endif return (0); case KDMKTONE: /* sound the bell */ vtterm_beep(tm, *(u_int *)data); return (0); case KIOCSOUND: /* make tone (*data) hz */ /* TODO */ return (0); case CONS_SETKBD: /* set the new keyboard */ mtx_lock(&Giant); error = 0; if (vd->vd_keyboard != *(int *)data) { kbd = kbd_get_keyboard(*(int *)data); if (kbd == NULL) { mtx_unlock(&Giant); return (EINVAL); } i = kbd_allocate(kbd->kb_name, kbd->kb_unit, (void *)vd, vt_kbdevent, vd); if (i >= 0) { if (vd->vd_keyboard != -1) { kbd = kbd_get_keyboard(vd->vd_keyboard); vt_save_kbd_state(vd->vd_curwindow, kbd); kbd_release(kbd, (void *)vd); } kbd = kbd_get_keyboard(i); vd->vd_keyboard = i; vt_update_kbd_mode(vd->vd_curwindow, kbd); vt_update_kbd_state(vd->vd_curwindow, kbd); } else { error = EPERM; /* XXX */ } } mtx_unlock(&Giant); return (error); case CONS_RELKBD: /* release the current keyboard */ mtx_lock(&Giant); error = 0; if (vd->vd_keyboard != -1) { kbd = kbd_get_keyboard(vd->vd_keyboard); if (kbd == NULL) { mtx_unlock(&Giant); return (EINVAL); } vt_save_kbd_state(vd->vd_curwindow, kbd); error = kbd_release(kbd, (void *)vd); if (error == 0) { vd->vd_keyboard = -1; } } mtx_unlock(&Giant); return (error); case VT_ACTIVATE: { int win; win = *(int *)data - 1; DPRINTF(5, "%s%d: VT_ACTIVATE ttyv%d ", SC_DRIVER_NAME, VT_UNIT(vw), win); if ((win >= VT_MAXWINDOWS) || (win < 0)) return (EINVAL); return (vt_proc_window_switch(vd->vd_windows[win])); } case VT_GETACTIVE: *(int *)data = vd->vd_curwindow->vw_number + 1; return (0); case VT_GETINDEX: *(int *)data = vw->vw_number + 1; return (0); case VT_LOCKSWITCH: /* TODO: Check current state, switching can be in progress. */ if ((*(int *)data) == 0x01) vw->vw_flags |= VWF_VTYLOCK; else if ((*(int *)data) == 0x02) vw->vw_flags &= ~VWF_VTYLOCK; else return (EINVAL); return (0); case VT_OPENQRY: VT_LOCK(vd); for (i = 0; i < VT_MAXWINDOWS; i++) { vw = vd->vd_windows[i]; if (vw == NULL) continue; if (!(vw->vw_flags & VWF_OPENED)) { *(int *)data = vw->vw_number + 1; VT_UNLOCK(vd); return (0); } } VT_UNLOCK(vd); return (EINVAL); case VT_WAITACTIVE: { unsigned int idx; error = 0; idx = *(unsigned int *)data; if (idx > VT_MAXWINDOWS) return (EINVAL); if (idx > 0) vw = vd->vd_windows[idx - 1]; VT_LOCK(vd); while (vd->vd_curwindow != vw && error == 0) error = cv_wait_sig(&vd->vd_winswitch, &vd->vd_lock); VT_UNLOCK(vd); return (error); } case VT_SETMODE: { /* set screen switcher mode */ struct vt_mode *mode; struct proc *p1; mode = (struct vt_mode *)data; DPRINTF(5, "%s%d: VT_SETMODE ", SC_DRIVER_NAME, VT_UNIT(vw)); if (vw->vw_smode.mode == VT_PROCESS) { p1 = pfind(vw->vw_pid); if (vw->vw_proc == p1 && vw->vw_proc != td->td_proc) { if (p1) PROC_UNLOCK(p1); DPRINTF(5, "error EPERM\n"); return (EPERM); } if (p1) PROC_UNLOCK(p1); } if (mode->mode == VT_AUTO) { vw->vw_smode.mode = VT_AUTO; vw->vw_proc = NULL; vw->vw_pid = 0; DPRINTF(5, "VT_AUTO, "); if (vw == vw->vw_device->vd_windows[VT_CONSWINDOW]) cnavailable(vw->vw_terminal->consdev, TRUE); /* were we in the middle of the vty switching process? */ if (finish_vt_rel(vw, TRUE, &s) == 0) DPRINTF(5, "reset WAIT_REL, "); if (finish_vt_acq(vw) == 0) DPRINTF(5, "reset WAIT_ACQ, "); return (0); } else if (mode->mode == VT_PROCESS) { if (!ISSIGVALID(mode->relsig) || !ISSIGVALID(mode->acqsig) || !ISSIGVALID(mode->frsig)) { DPRINTF(5, "error EINVAL\n"); return (EINVAL); } DPRINTF(5, "VT_PROCESS %d, ", td->td_proc->p_pid); bcopy(data, &vw->vw_smode, sizeof(struct vt_mode)); vw->vw_proc = td->td_proc; vw->vw_pid = vw->vw_proc->p_pid; if (vw == vw->vw_device->vd_windows[VT_CONSWINDOW]) cnavailable(vw->vw_terminal->consdev, FALSE); } else { DPRINTF(5, "VT_SETMODE failed, unknown mode %d\n", mode->mode); return (EINVAL); } DPRINTF(5, "\n"); return (0); } case VT_GETMODE: /* get screen switcher mode */ bcopy(&vw->vw_smode, data, sizeof(struct vt_mode)); return (0); case VT_RELDISP: /* screen switcher ioctl */ /* * This must be the current vty which is in the VT_PROCESS * switching mode... */ if ((vw != vd->vd_curwindow) || (vw->vw_smode.mode != VT_PROCESS)) { return (EINVAL); } /* ...and this process is controlling it. */ if (vw->vw_proc != td->td_proc) { return (EPERM); } error = EINVAL; switch(*(int *)data) { case VT_FALSE: /* user refuses to release screen, abort */ if ((error = finish_vt_rel(vw, FALSE, &s)) == 0) DPRINTF(5, "%s%d: VT_RELDISP: VT_FALSE\n", SC_DRIVER_NAME, VT_UNIT(vw)); break; case VT_TRUE: /* user has released screen, go on */ /* finish_vt_rel(..., TRUE, ...) should not be locked */ if (vw->vw_flags & VWF_SWWAIT_REL) { if ((error = finish_vt_rel(vw, TRUE, &s)) == 0) DPRINTF(5, "%s%d: VT_RELDISP: VT_TRUE\n", SC_DRIVER_NAME, VT_UNIT(vw)); } else { error = EINVAL; } return (error); case VT_ACKACQ: /* acquire acknowledged, switch completed */ if ((error = finish_vt_acq(vw)) == 0) DPRINTF(5, "%s%d: VT_RELDISP: VT_ACKACQ\n", SC_DRIVER_NAME, VT_UNIT(vw)); break; default: break; } return (error); } return (ENOIOCTL); } static struct vt_window * vt_allocate_window(struct vt_device *vd, unsigned int window) { struct vt_window *vw; struct terminal *tm; term_pos_t size; struct winsize wsz; vw = malloc(sizeof *vw, M_VT, M_WAITOK|M_ZERO); vw->vw_device = vd; vw->vw_number = window; vw->vw_kbdmode = K_XLATE; if ((vd->vd_flags & VDF_TEXTMODE) == 0) { vw->vw_font = vtfont_ref(&vt_font_default); vt_compute_drawable_area(vw); } vt_termsize(vd, vw->vw_font, &size); vt_winsize(vd, vw->vw_font, &wsz); vtbuf_init(&vw->vw_buf, &size); tm = vw->vw_terminal = terminal_alloc(&vt_termclass, vw); terminal_set_winsize(tm, &wsz); vd->vd_windows[window] = vw; callout_init(&vw->vw_proc_dead_timer, 0); return (vw); } void vt_upgrade(struct vt_device *vd) { struct vt_window *vw; unsigned int i; int register_handlers; if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_driver == NULL) return; for (i = 0; i < VT_MAXWINDOWS; i++) { vw = vd->vd_windows[i]; if (vw == NULL) { /* New window. */ vw = vt_allocate_window(vd, i); } if (!(vw->vw_flags & VWF_READY)) { callout_init(&vw->vw_proc_dead_timer, 0); terminal_maketty(vw->vw_terminal, "v%r", VT_UNIT(vw)); vw->vw_flags |= VWF_READY; if (vw->vw_flags & VWF_CONSOLE) { /* For existing console window. */ EVENTHANDLER_REGISTER(shutdown_pre_sync, vt_window_switch, vw, SHUTDOWN_PRI_DEFAULT); } } } VT_LOCK(vd); if (vd->vd_curwindow == NULL) vd->vd_curwindow = vd->vd_windows[VT_CONSWINDOW]; register_handlers = 0; if (!(vd->vd_flags & VDF_ASYNC)) { /* Attach keyboard. */ vt_allocate_keyboard(vd); /* Init 25 Hz timer. */ callout_init_mtx(&vd->vd_timer, &vd->vd_lock, 0); /* * Start timer when everything ready. * Note that the operations here are purposefully ordered. * We need to ensure vd_timer_armed is non-zero before we set * the VDF_ASYNC flag. That prevents this function from * racing with vt_resume_flush_timer() to update the * callout structure. */ atomic_add_acq_int(&vd->vd_timer_armed, 1); vd->vd_flags |= VDF_ASYNC; callout_reset(&vd->vd_timer, hz / VT_TIMERFREQ, vt_timer, vd); register_handlers = 1; } VT_UNLOCK(vd); /* Refill settings with new sizes. */ vt_resize(vd); if (register_handlers) { /* Register suspend/resume handlers. */ EVENTHANDLER_REGISTER(power_suspend_early, vt_suspend_handler, vd, EVENTHANDLER_PRI_ANY); EVENTHANDLER_REGISTER(power_resume, vt_resume_handler, vd, EVENTHANDLER_PRI_ANY); } } static void vt_resize(struct vt_device *vd) { struct vt_window *vw; int i; for (i = 0; i < VT_MAXWINDOWS; i++) { vw = vd->vd_windows[i]; VT_LOCK(vd); /* Assign default font to window, if not textmode. */ if (!(vd->vd_flags & VDF_TEXTMODE) && vw->vw_font == NULL) vw->vw_font = vtfont_ref(&vt_font_default); VT_UNLOCK(vd); /* Resize terminal windows */ while (vt_change_font(vw, vw->vw_font) == EBUSY) { DPRINTF(100, "%s: vt_change_font() is busy, " "window %d\n", __func__, i); } } } static void vt_replace_backend(const struct vt_driver *drv, void *softc) { struct vt_device *vd; vd = main_vd; if (vd->vd_flags & VDF_ASYNC) { /* Stop vt_flush periodic task. */ VT_LOCK(vd); vt_suspend_flush_timer(vd); VT_UNLOCK(vd); /* * Mute current terminal until we done. vt_change_font (called * from vt_resize) will unmute it. */ terminal_mute(vd->vd_curwindow->vw_terminal, 1); } /* * Reset VDF_TEXTMODE flag, driver who require that flag (vt_vga) will * set it. */ VT_LOCK(vd); vd->vd_flags &= ~VDF_TEXTMODE; if (drv != NULL) { /* * We want to upgrade from the current driver to the * given driver. */ vd->vd_prev_driver = vd->vd_driver; vd->vd_prev_softc = vd->vd_softc; vd->vd_driver = drv; vd->vd_softc = softc; vd->vd_driver->vd_init(vd); } else if (vd->vd_prev_driver != NULL && vd->vd_prev_softc != NULL) { /* * No driver given: we want to downgrade to the previous * driver. */ const struct vt_driver *old_drv; void *old_softc; old_drv = vd->vd_driver; old_softc = vd->vd_softc; vd->vd_driver = vd->vd_prev_driver; vd->vd_softc = vd->vd_prev_softc; vd->vd_prev_driver = NULL; vd->vd_prev_softc = NULL; vd->vd_flags |= VDF_DOWNGRADE; vd->vd_driver->vd_init(vd); if (old_drv->vd_fini) old_drv->vd_fini(vd, old_softc); vd->vd_flags &= ~VDF_DOWNGRADE; } VT_UNLOCK(vd); /* Update windows sizes and initialize last items. */ vt_upgrade(vd); #ifdef DEV_SPLASH if (vd->vd_flags & VDF_SPLASH) vtterm_splash(vd); #endif if (vd->vd_flags & VDF_ASYNC) { /* Allow to put chars now. */ terminal_mute(vd->vd_curwindow->vw_terminal, 0); /* Rerun timer for screen updates. */ vt_resume_flush_timer(vd, 0); } /* * Register as console. If it already registered, cnadd() will ignore * it. */ termcn_cnregister(vd->vd_windows[VT_CONSWINDOW]->vw_terminal); } static void vt_suspend_handler(void *priv) { struct vt_device *vd; vd = priv; if (vd->vd_driver != NULL && vd->vd_driver->vd_suspend != NULL) vd->vd_driver->vd_suspend(vd); } static void vt_resume_handler(void *priv) { struct vt_device *vd; vd = priv; if (vd->vd_driver != NULL && vd->vd_driver->vd_resume != NULL) vd->vd_driver->vd_resume(vd); } void vt_allocate(const struct vt_driver *drv, void *softc) { if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_driver == NULL) { main_vd->vd_driver = drv; printf("VT: initialize with new VT driver \"%s\".\n", drv->vd_name); } else { /* * Check if have rights to replace current driver. For example: * it is bad idea to replace KMS driver with generic VGA one. */ if (drv->vd_priority <= main_vd->vd_driver->vd_priority) { printf("VT: Driver priority %d too low. Current %d\n ", drv->vd_priority, main_vd->vd_driver->vd_priority); return; } printf("VT: Replacing driver \"%s\" with new \"%s\".\n", main_vd->vd_driver->vd_name, drv->vd_name); } vt_replace_backend(drv, softc); } void vt_deallocate(const struct vt_driver *drv, void *softc) { if (!vty_enabled(VTY_VT)) return; if (main_vd->vd_prev_driver == NULL || main_vd->vd_driver != drv || main_vd->vd_softc != softc) return; printf("VT: Switching back from \"%s\" to \"%s\".\n", main_vd->vd_driver->vd_name, main_vd->vd_prev_driver->vd_name); vt_replace_backend(NULL, NULL); } void vt_suspend(struct vt_device *vd) { int error; if (vt_suspendswitch == 0) return; /* Save current window. */ vd->vd_savedwindow = vd->vd_curwindow; /* Ask holding process to free window and switch to console window */ vt_proc_window_switch(vd->vd_windows[VT_CONSWINDOW]); /* Wait for the window switch to complete. */ error = 0; VT_LOCK(vd); while (vd->vd_curwindow != vd->vd_windows[VT_CONSWINDOW] && error == 0) error = cv_wait_sig(&vd->vd_winswitch, &vd->vd_lock); VT_UNLOCK(vd); } void vt_resume(struct vt_device *vd) { if (vt_suspendswitch == 0) return; /* Switch back to saved window, if any */ vt_proc_window_switch(vd->vd_savedwindow); vd->vd_savedwindow = NULL; } Index: head/sys/kern/subr_terminal.c =================================================================== --- head/sys/kern/subr_terminal.c (revision 333668) +++ head/sys/kern/subr_terminal.c (revision 333669) @@ -1,660 +1,680 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009 The FreeBSD Foundation * All rights reserved. * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_TERMINAL, "terminal", "terminal device"); /* * Locking. * * Normally we don't need to lock down the terminal emulator, because * the TTY lock is already held when calling teken_input(). * Unfortunately this is not the case when the terminal acts as a * console device, because cnputc() can be called at the same time. * This means terminals may need to be locked down using a spin lock. */ #define TERMINAL_LOCK(tm) do { \ if ((tm)->tm_flags & TF_CONS) \ mtx_lock_spin(&(tm)->tm_mtx); \ else if ((tm)->tm_tty != NULL) \ tty_lock((tm)->tm_tty); \ } while (0) #define TERMINAL_UNLOCK(tm) do { \ if ((tm)->tm_flags & TF_CONS) \ mtx_unlock_spin(&(tm)->tm_mtx); \ else if ((tm)->tm_tty != NULL) \ tty_unlock((tm)->tm_tty); \ } while (0) #define TERMINAL_LOCK_TTY(tm) do { \ if ((tm)->tm_flags & TF_CONS) \ mtx_lock_spin(&(tm)->tm_mtx); \ } while (0) #define TERMINAL_UNLOCK_TTY(tm) do { \ if ((tm)->tm_flags & TF_CONS) \ mtx_unlock_spin(&(tm)->tm_mtx); \ } while (0) #define TERMINAL_LOCK_CONS(tm) mtx_lock_spin(&(tm)->tm_mtx) #define TERMINAL_UNLOCK_CONS(tm) mtx_unlock_spin(&(tm)->tm_mtx) /* * TTY routines. */ static tsw_open_t termtty_open; static tsw_close_t termtty_close; static tsw_outwakeup_t termtty_outwakeup; static tsw_ioctl_t termtty_ioctl; static tsw_mmap_t termtty_mmap; static struct ttydevsw terminal_tty_class = { .tsw_open = termtty_open, .tsw_close = termtty_close, .tsw_outwakeup = termtty_outwakeup, .tsw_ioctl = termtty_ioctl, .tsw_mmap = termtty_mmap, }; /* * Terminal emulator routines. */ static tf_bell_t termteken_bell; static tf_cursor_t termteken_cursor; static tf_putchar_t termteken_putchar; static tf_fill_t termteken_fill; static tf_copy_t termteken_copy; +static tf_pre_input_t termteken_pre_input; +static tf_post_input_t termteken_post_input; static tf_param_t termteken_param; static tf_respond_t termteken_respond; static teken_funcs_t terminal_drawmethods = { .tf_bell = termteken_bell, .tf_cursor = termteken_cursor, .tf_putchar = termteken_putchar, .tf_fill = termteken_fill, .tf_copy = termteken_copy, + .tf_pre_input = termteken_pre_input, + .tf_post_input = termteken_post_input, .tf_param = termteken_param, .tf_respond = termteken_respond, }; /* Kernel message formatting. */ static const teken_attr_t kernel_message = { .ta_fgcolor = TCHAR_FGCOLOR(TERMINAL_KERN_ATTR), .ta_bgcolor = TCHAR_BGCOLOR(TERMINAL_KERN_ATTR), .ta_format = TCHAR_FORMAT(TERMINAL_KERN_ATTR) }; static const teken_attr_t default_message = { .ta_fgcolor = TCHAR_FGCOLOR(TERMINAL_NORM_ATTR), .ta_bgcolor = TCHAR_BGCOLOR(TERMINAL_NORM_ATTR), .ta_format = TCHAR_FORMAT(TERMINAL_NORM_ATTR) }; /* Fudge fg brightness as TF_BOLD (shifted). */ #define TCOLOR_FG_FUDGED(color) __extension__ ({ \ teken_color_t _c; \ \ _c = (color); \ TCOLOR_FG(_c & 7) | ((_c & 8) << 18); \ }) /* Fudge bg brightness as TF_BLINK (shifted). */ #define TCOLOR_BG_FUDGED(color) __extension__ ({ \ teken_color_t _c; \ \ _c = (color); \ TCOLOR_BG(_c & 7) | ((_c & 8) << 20); \ }) #define TCOLOR_256TO16(color) __extension__ ({ \ teken_color_t _c; \ \ _c = (color); \ if (_c >= 16) \ _c = teken_256to16(_c); \ _c; \ }) #define TCHAR_CREATE(c, a) ((c) | TFORMAT((a)->ta_format) | \ TCOLOR_FG_FUDGED(TCOLOR_256TO16((a)->ta_fgcolor)) | \ TCOLOR_BG_FUDGED(TCOLOR_256TO16((a)->ta_bgcolor))) static void terminal_init(struct terminal *tm) { if (tm->tm_flags & TF_CONS) mtx_init(&tm->tm_mtx, "trmlck", NULL, MTX_SPIN); teken_init(&tm->tm_emulator, &terminal_drawmethods, tm); teken_set_defattr(&tm->tm_emulator, &default_message); } struct terminal * terminal_alloc(const struct terminal_class *tc, void *softc) { struct terminal *tm; tm = malloc(sizeof(struct terminal), M_TERMINAL, M_WAITOK|M_ZERO); terminal_init(tm); tm->tm_class = tc; tm->tm_softc = softc; return (tm); } static void terminal_sync_ttysize(struct terminal *tm) { struct tty *tp; tp = tm->tm_tty; if (tp == NULL) return; tty_lock(tp); tty_set_winsize(tp, &tm->tm_winsize); tty_unlock(tp); } void terminal_maketty(struct terminal *tm, const char *fmt, ...) { struct tty *tp; char name[8]; va_list ap; va_start(ap, fmt); vsnrprintf(name, sizeof name, 32, fmt, ap); va_end(ap); tp = tty_alloc(&terminal_tty_class, tm); tty_makedev(tp, NULL, "%s", name); tm->tm_tty = tp; terminal_sync_ttysize(tm); } void terminal_set_cursor(struct terminal *tm, const term_pos_t *pos) { teken_set_cursor(&tm->tm_emulator, pos); } void terminal_set_winsize_blank(struct terminal *tm, const struct winsize *size, int blank, const term_attr_t *attr) { term_rect_t r; tm->tm_winsize = *size; r.tr_begin.tp_row = r.tr_begin.tp_col = 0; r.tr_end.tp_row = size->ws_row; r.tr_end.tp_col = size->ws_col; TERMINAL_LOCK(tm); if (blank == 0) teken_set_winsize_noreset(&tm->tm_emulator, &r.tr_end); else teken_set_winsize(&tm->tm_emulator, &r.tr_end); TERMINAL_UNLOCK(tm); if ((blank != 0) && !(tm->tm_flags & TF_MUTE)) tm->tm_class->tc_fill(tm, &r, TCHAR_CREATE((teken_char_t)' ', attr)); terminal_sync_ttysize(tm); } void terminal_set_winsize(struct terminal *tm, const struct winsize *size) { terminal_set_winsize_blank(tm, size, 1, (const term_attr_t *)&default_message); } /* * XXX: This function is a kludge. Drivers like vt(4) need to * temporarily stop input when resizing, etc. This should ideally be * handled within the driver. */ void terminal_mute(struct terminal *tm, int yes) { TERMINAL_LOCK(tm); if (yes) tm->tm_flags |= TF_MUTE; else tm->tm_flags &= ~TF_MUTE; TERMINAL_UNLOCK(tm); } void terminal_input_char(struct terminal *tm, term_char_t c) { struct tty *tp; tp = tm->tm_tty; if (tp == NULL) return; /* * Strip off any attributes. Also ignore input of second part of * CJK fullwidth characters, as we don't want to return these * characters twice. */ if (TCHAR_FORMAT(c) & TF_CJK_RIGHT) return; c = TCHAR_CHARACTER(c); tty_lock(tp); /* * Conversion to UTF-8. */ if (c < 0x80) { ttydisc_rint(tp, c, 0); } else if (c < 0x800) { char str[2] = { 0xc0 | (c >> 6), 0x80 | (c & 0x3f) }; ttydisc_rint_simple(tp, str, sizeof str); } else if (c < 0x10000) { char str[3] = { 0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f) }; ttydisc_rint_simple(tp, str, sizeof str); } else { char str[4] = { 0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f) }; ttydisc_rint_simple(tp, str, sizeof str); } ttydisc_rint_done(tp); tty_unlock(tp); } void terminal_input_raw(struct terminal *tm, char c) { struct tty *tp; tp = tm->tm_tty; if (tp == NULL) return; tty_lock(tp); ttydisc_rint(tp, c, 0); ttydisc_rint_done(tp); tty_unlock(tp); } void terminal_input_special(struct terminal *tm, unsigned int k) { struct tty *tp; const char *str; tp = tm->tm_tty; if (tp == NULL) return; str = teken_get_sequence(&tm->tm_emulator, k); if (str == NULL) return; tty_lock(tp); ttydisc_rint_simple(tp, str, strlen(str)); ttydisc_rint_done(tp); tty_unlock(tp); } /* * Binding with the TTY layer. */ static int termtty_open(struct tty *tp) { struct terminal *tm = tty_softc(tp); tm->tm_class->tc_opened(tm, 1); return (0); } static void termtty_close(struct tty *tp) { struct terminal *tm = tty_softc(tp); tm->tm_class->tc_opened(tm, 0); } static void termtty_outwakeup(struct tty *tp) { struct terminal *tm = tty_softc(tp); char obuf[128]; size_t olen; unsigned int flags = 0; while ((olen = ttydisc_getc(tp, obuf, sizeof obuf)) > 0) { TERMINAL_LOCK_TTY(tm); if (!(tm->tm_flags & TF_MUTE)) { tm->tm_flags &= ~TF_BELL; teken_input(&tm->tm_emulator, obuf, olen); flags |= tm->tm_flags; } TERMINAL_UNLOCK_TTY(tm); } TERMINAL_LOCK_TTY(tm); if (!(tm->tm_flags & TF_MUTE)) tm->tm_class->tc_done(tm); TERMINAL_UNLOCK_TTY(tm); if (flags & TF_BELL) tm->tm_class->tc_bell(tm); } static int termtty_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) { struct terminal *tm = tty_softc(tp); int error; switch (cmd) { case CONS_GETINFO: { vid_info_t *vi = (vid_info_t *)data; const teken_pos_t *p; int fg, bg; if (vi->size != sizeof(vid_info_t)) return (EINVAL); /* Already help the console driver by filling in some data. */ p = teken_get_cursor(&tm->tm_emulator); vi->mv_row = p->tp_row; vi->mv_col = p->tp_col; p = teken_get_winsize(&tm->tm_emulator); vi->mv_rsz = p->tp_row; vi->mv_csz = p->tp_col; teken_get_defattr_cons25(&tm->tm_emulator, &fg, &bg); vi->mv_norm.fore = fg; vi->mv_norm.back = bg; /* XXX: keep vidcontrol happy; bold backgrounds. */ vi->mv_rev.fore = bg; vi->mv_rev.back = fg & 0x7; break; } } /* * Unlike various other drivers, this driver will never * deallocate TTYs. This means it's safe to temporarily unlock * the TTY when handling ioctls. */ tty_unlock(tp); error = tm->tm_class->tc_ioctl(tm, cmd, data, td); tty_lock(tp); return (error); } static int termtty_mmap(struct tty *tp, vm_ooffset_t offset, vm_paddr_t * paddr, int nprot, vm_memattr_t *memattr) { struct terminal *tm = tty_softc(tp); return (tm->tm_class->tc_mmap(tm, offset, paddr, nprot, memattr)); } /* * Binding with the kernel and debug console. */ static cn_probe_t termcn_cnprobe; static cn_init_t termcn_cninit; static cn_term_t termcn_cnterm; static cn_getc_t termcn_cngetc; static cn_putc_t termcn_cnputc; static cn_grab_t termcn_cngrab; static cn_ungrab_t termcn_cnungrab; const struct consdev_ops termcn_cnops = { .cn_probe = termcn_cnprobe, .cn_init = termcn_cninit, .cn_term = termcn_cnterm, .cn_getc = termcn_cngetc, .cn_putc = termcn_cnputc, .cn_grab = termcn_cngrab, .cn_ungrab = termcn_cnungrab, }; void termcn_cnregister(struct terminal *tm) { struct consdev *cp; cp = tm->consdev; if (cp == NULL) { cp = malloc(sizeof(struct consdev), M_TERMINAL, M_WAITOK|M_ZERO); cp->cn_ops = &termcn_cnops; cp->cn_arg = tm; cp->cn_pri = CN_INTERNAL; sprintf(cp->cn_name, "ttyv0"); tm->tm_flags = TF_CONS; tm->consdev = cp; terminal_init(tm); } /* Attach terminal as console. */ cnadd(cp); } static void termcn_cngrab(struct consdev *cp) { struct terminal *tm = cp->cn_arg; tm->tm_class->tc_cngrab(tm); } static void termcn_cnungrab(struct consdev *cp) { struct terminal *tm = cp->cn_arg; tm->tm_class->tc_cnungrab(tm); } static void termcn_cnprobe(struct consdev *cp) { struct terminal *tm = cp->cn_arg; if (tm == NULL) { cp->cn_pri = CN_DEAD; return; } tm->consdev = cp; terminal_init(tm); tm->tm_class->tc_cnprobe(tm, cp); } static void termcn_cninit(struct consdev *cp) { } static void termcn_cnterm(struct consdev *cp) { } static int termcn_cngetc(struct consdev *cp) { struct terminal *tm = cp->cn_arg; return (tm->tm_class->tc_cngetc(tm)); } static void termcn_cnputc(struct consdev *cp, int c) { struct terminal *tm = cp->cn_arg; teken_attr_t backup; char cv = c; TERMINAL_LOCK_CONS(tm); if (!(tm->tm_flags & TF_MUTE)) { backup = *teken_get_curattr(&tm->tm_emulator); teken_set_curattr(&tm->tm_emulator, &kernel_message); teken_input(&tm->tm_emulator, &cv, 1); teken_set_curattr(&tm->tm_emulator, &backup); tm->tm_class->tc_done(tm); } TERMINAL_UNLOCK_CONS(tm); } /* * Binding with the terminal emulator. */ static void termteken_bell(void *softc) { struct terminal *tm = softc; tm->tm_flags |= TF_BELL; } static void termteken_cursor(void *softc, const teken_pos_t *p) { struct terminal *tm = softc; tm->tm_class->tc_cursor(tm, p); } static void termteken_putchar(void *softc, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { struct terminal *tm = softc; tm->tm_class->tc_putchar(tm, p, TCHAR_CREATE(c, a)); } static void termteken_fill(void *softc, const teken_rect_t *r, teken_char_t c, const teken_attr_t *a) { struct terminal *tm = softc; tm->tm_class->tc_fill(tm, r, TCHAR_CREATE(c, a)); } static void termteken_copy(void *softc, const teken_rect_t *r, const teken_pos_t *p) { struct terminal *tm = softc; tm->tm_class->tc_copy(tm, r, p); +} + +static void +termteken_pre_input(void *softc) +{ + struct terminal *tm = softc; + + tm->tm_class->tc_pre_input(tm); +} + +static void +termteken_post_input(void *softc) +{ + struct terminal *tm = softc; + + tm->tm_class->tc_post_input(tm); } static void termteken_param(void *softc, int cmd, unsigned int arg) { struct terminal *tm = softc; tm->tm_class->tc_param(tm, cmd, arg); } static void termteken_respond(void *softc, const void *buf, size_t len) { #if 0 struct terminal *tm = softc; struct tty *tp; /* * Only inject a response into the TTY if the data actually * originated from the TTY. * * XXX: This cannot be done right now. The TTY could pick up * other locks. It could also in theory cause loops, when the * TTY performs echoing of a command that generates even more * input. */ tp = tm->tm_tty; if (tp == NULL) return; ttydisc_rint_simple(tp, buf, len); ttydisc_rint_done(tp); #endif } Index: head/sys/sys/terminal.h =================================================================== --- head/sys/sys/terminal.h (revision 333668) +++ head/sys/sys/terminal.h (revision 333669) @@ -1,236 +1,240 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009 The FreeBSD Foundation * All rights reserved. * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _SYS_TERMINAL_H_ #define _SYS_TERMINAL_H_ #include #include #include #include #include #include #include #include "opt_syscons.h" #include "opt_teken.h" struct terminal; struct thread; struct tty; /* * The terminal layer is an abstraction on top of the TTY layer and the * console interface. It can be used by system console drivers to * easily interact with the kernel console and TTYs. * * Terminals contain terminal emulators, which means console drivers * don't need to implement their own terminal emulator. The terminal * emulator deals with UTF-8 exclusively. This means that term_char_t, * the data type used to store input/output characters will always * contain Unicode codepoints. * * To save memory usage, the top bits of term_char_t will contain other * attributes, like colors. Right now term_char_t is composed as * follows: * * Bits Meaning * 0-20: Character value * 21-25: Bold, underline, blink, reverse, right part of CJK fullwidth character * 26-28: Foreground color * 29-31: Background color */ typedef uint32_t term_char_t; #define TCHAR_CHARACTER(c) ((c) & 0x1fffff) #define TCHAR_FORMAT(c) (((c) >> 21) & 0x1f) #define TCHAR_FGCOLOR(c) (((c) >> 26) & 0x7) #define TCHAR_BGCOLOR(c) (((c) >> 29) & 0x7) typedef teken_attr_t term_attr_t; typedef teken_color_t term_color_t; #define TCOLOR_FG(c) (((c) & 0x7) << 26) #define TCOLOR_BG(c) (((c) & 0x7) << 29) #define TCOLOR_LIGHT(c) ((c) | 0x8) #define TCOLOR_DARK(c) ((c) & ~0x8) #define TFORMAT(c) (((c) & 0x1f) << 21) /* syscons(4) compatible color attributes for foreground text */ #define FG_BLACK TCOLOR_FG(TC_BLACK) #define FG_BLUE TCOLOR_FG(TC_BLUE) #define FG_GREEN TCOLOR_FG(TC_GREEN) #define FG_CYAN TCOLOR_FG(TC_CYAN) #define FG_RED TCOLOR_FG(TC_RED) #define FG_MAGENTA TCOLOR_FG(TC_MAGENTA) #define FG_BROWN TCOLOR_FG(TC_BROWN) #define FG_LIGHTGREY TCOLOR_FG(TC_WHITE) #define FG_DARKGREY (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_BLACK)) #define FG_LIGHTBLUE (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_BLUE)) #define FG_LIGHTGREEN (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_GREEN)) #define FG_LIGHTCYAN (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_CYAN)) #define FG_LIGHTRED (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_RED)) #define FG_LIGHTMAGENTA (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_MAGENTA)) #define FG_YELLOW (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_BROWN)) #define FG_WHITE (TFORMAT(TF_BOLD) | TCOLOR_FG(TC_WHITE)) #define FG_BLINK TFORMAT(TF_BLINK) /* syscons(4) compatible color attributes for text background */ #define BG_BLACK TCOLOR_BG(TC_BLACK) #define BG_BLUE TCOLOR_BG(TC_BLUE) #define BG_GREEN TCOLOR_BG(TC_GREEN) #define BG_CYAN TCOLOR_BG(TC_CYAN) #define BG_RED TCOLOR_BG(TC_RED) #define BG_MAGENTA TCOLOR_BG(TC_MAGENTA) #define BG_BROWN TCOLOR_BG(TC_BROWN) #define BG_LIGHTGREY TCOLOR_BG(TC_WHITE) #define BG_DARKGREY (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_BLACK)) #define BG_LIGHTBLUE (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_BLUE)) #define BG_LIGHTGREEN (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_GREEN)) #define BG_LIGHTCYAN (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_CYAN)) #define BG_LIGHTRED (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_RED)) #define BG_LIGHTMAGENTA (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_MAGENTA)) #define BG_YELLOW (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_BROWN)) #define BG_WHITE (TFORMAT(TF_BOLD) | TCOLOR_BG(TC_WHITE)) #ifndef TERMINAL_NORM_ATTR #ifdef SC_NORM_ATTR #define TERMINAL_NORM_ATTR SC_NORM_ATTR #else #define TERMINAL_NORM_ATTR (FG_LIGHTGREY | BG_BLACK) #endif #endif #ifndef TERMINAL_KERN_ATTR #ifdef SC_KERNEL_CONS_ATTR #define TERMINAL_KERN_ATTR SC_KERNEL_CONS_ATTR #else #define TERMINAL_KERN_ATTR (FG_WHITE | BG_BLACK) #endif #endif typedef teken_pos_t term_pos_t; typedef teken_rect_t term_rect_t; typedef void tc_cursor_t(struct terminal *tm, const term_pos_t *p); typedef void tc_putchar_t(struct terminal *tm, const term_pos_t *p, term_char_t c); typedef void tc_fill_t(struct terminal *tm, const term_rect_t *r, term_char_t c); typedef void tc_copy_t(struct terminal *tm, const term_rect_t *r, const term_pos_t *p); +typedef void tc_pre_input_t(struct terminal *tm); +typedef void tc_post_input_t(struct terminal *tm); typedef void tc_param_t(struct terminal *tm, int cmd, unsigned int arg); typedef void tc_done_t(struct terminal *tm); typedef void tc_cnprobe_t(struct terminal *tm, struct consdev *cd); typedef int tc_cngetc_t(struct terminal *tm); typedef void tc_cngrab_t(struct terminal *tm); typedef void tc_cnungrab_t(struct terminal *tm); typedef void tc_opened_t(struct terminal *tm, int opened); typedef int tc_ioctl_t(struct terminal *tm, u_long cmd, caddr_t data, struct thread *td); typedef int tc_mmap_t(struct terminal *tm, vm_ooffset_t offset, vm_paddr_t * paddr, int nprot, vm_memattr_t *memattr); typedef void tc_bell_t(struct terminal *tm); struct terminal_class { /* Terminal emulator. */ tc_cursor_t *tc_cursor; tc_putchar_t *tc_putchar; tc_fill_t *tc_fill; tc_copy_t *tc_copy; + tc_pre_input_t *tc_pre_input; + tc_post_input_t *tc_post_input; tc_param_t *tc_param; tc_done_t *tc_done; /* Low-level console interface. */ tc_cnprobe_t *tc_cnprobe; tc_cngetc_t *tc_cngetc; /* DDB & panic handling. */ tc_cngrab_t *tc_cngrab; tc_cnungrab_t *tc_cnungrab; /* Misc. */ tc_opened_t *tc_opened; tc_ioctl_t *tc_ioctl; tc_mmap_t *tc_mmap; tc_bell_t *tc_bell; }; struct terminal { const struct terminal_class *tm_class; void *tm_softc; struct mtx tm_mtx; struct tty *tm_tty; teken_t tm_emulator; struct winsize tm_winsize; unsigned int tm_flags; #define TF_MUTE 0x1 /* Drop incoming data. */ #define TF_BELL 0x2 /* Bell needs to be sent. */ #define TF_CONS 0x4 /* Console device (needs spinlock). */ struct consdev *consdev; }; #ifdef _KERNEL struct terminal *terminal_alloc(const struct terminal_class *tc, void *softc); void terminal_maketty(struct terminal *tm, const char *fmt, ...); void terminal_set_cursor(struct terminal *tm, const term_pos_t *pos); void terminal_set_winsize_blank(struct terminal *tm, const struct winsize *size, int blank, const term_attr_t *attr); void terminal_set_winsize(struct terminal *tm, const struct winsize *size); void terminal_mute(struct terminal *tm, int yes); void terminal_input_char(struct terminal *tm, term_char_t c); void terminal_input_raw(struct terminal *tm, char c); void terminal_input_special(struct terminal *tm, unsigned int k); void termcn_cnregister(struct terminal *tm); /* Kernel console helper interface. */ extern const struct consdev_ops termcn_cnops; #define TERMINAL_DECLARE_EARLY(name, class, softc) \ static struct terminal name = { \ .tm_class = &class, \ .tm_softc = softc, \ .tm_flags = TF_CONS, \ }; \ CONSOLE_DEVICE(name ## _consdev, termcn_cnops, &name) #endif /* _KERNEL */ #endif /* !_SYS_TERMINAL_H_ */ Index: head/sys/teken/teken.c =================================================================== --- head/sys/teken/teken.c (revision 333668) +++ head/sys/teken/teken.c (revision 333669) @@ -1,723 +1,741 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008-2009 Ed Schouten * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #if defined(__FreeBSD__) && defined(_KERNEL) #include #include #include #include #define teken_assert(x) MPASS(x) #else /* !(__FreeBSD__ && _KERNEL) */ #include #include #include #include #include #include #define teken_assert(x) assert(x) #endif /* __FreeBSD__ && _KERNEL */ /* debug messages */ #define teken_printf(x,...) /* Private flags for t_stateflags. */ #define TS_FIRSTDIGIT 0x0001 /* First numeric digit in escape sequence. */ #define TS_INSERT 0x0002 /* Insert mode. */ #define TS_AUTOWRAP 0x0004 /* Autowrap. */ #define TS_ORIGIN 0x0008 /* Origin mode. */ #define TS_WRAPPED 0x0010 /* Next character should be printed on col 0. */ #define TS_8BIT 0x0020 /* UTF-8 disabled. */ #define TS_CONS25 0x0040 /* cons25 emulation. */ #define TS_INSTRING 0x0080 /* Inside string. */ #define TS_CURSORKEYS 0x0100 /* Cursor keys mode. */ /* Character that blanks a cell. */ #define BLANK ' ' #include "teken.h" #include "teken_wcwidth.h" #include "teken_scs.h" static teken_state_t teken_state_init; /* * Wrappers for hooks. */ static inline void teken_funcs_bell(const teken_t *t) { teken_assert(t->t_funcs->tf_bell != NULL); t->t_funcs->tf_bell(t->t_softc); } static inline void teken_funcs_cursor(const teken_t *t) { teken_assert(t->t_cursor.tp_row < t->t_winsize.tp_row); teken_assert(t->t_cursor.tp_col < t->t_winsize.tp_col); teken_assert(t->t_funcs->tf_cursor != NULL); t->t_funcs->tf_cursor(t->t_softc, &t->t_cursor); } static inline void teken_funcs_putchar(const teken_t *t, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { teken_assert(p->tp_row < t->t_winsize.tp_row); teken_assert(p->tp_col < t->t_winsize.tp_col); teken_assert(t->t_funcs->tf_putchar != NULL); t->t_funcs->tf_putchar(t->t_softc, p, c, a); } static inline void teken_funcs_fill(const teken_t *t, const teken_rect_t *r, const teken_char_t c, const teken_attr_t *a) { teken_assert(r->tr_end.tp_row > r->tr_begin.tp_row); teken_assert(r->tr_end.tp_row <= t->t_winsize.tp_row); teken_assert(r->tr_end.tp_col > r->tr_begin.tp_col); teken_assert(r->tr_end.tp_col <= t->t_winsize.tp_col); teken_assert(t->t_funcs->tf_fill != NULL); t->t_funcs->tf_fill(t->t_softc, r, c, a); } static inline void teken_funcs_copy(const teken_t *t, const teken_rect_t *r, const teken_pos_t *p) { teken_assert(r->tr_end.tp_row > r->tr_begin.tp_row); teken_assert(r->tr_end.tp_row <= t->t_winsize.tp_row); teken_assert(r->tr_end.tp_col > r->tr_begin.tp_col); teken_assert(r->tr_end.tp_col <= t->t_winsize.tp_col); teken_assert(p->tp_row + (r->tr_end.tp_row - r->tr_begin.tp_row) <= t->t_winsize.tp_row); teken_assert(p->tp_col + (r->tr_end.tp_col - r->tr_begin.tp_col) <= t->t_winsize.tp_col); teken_assert(t->t_funcs->tf_copy != NULL); t->t_funcs->tf_copy(t->t_softc, r, p); } static inline void +teken_funcs_pre_input(const teken_t *t) +{ + + teken_assert(t->t_funcs->tf_pre_input != NULL); + t->t_funcs->tf_pre_input(t->t_softc); +} + +static inline void +teken_funcs_post_input(const teken_t *t) +{ + + teken_assert(t->t_funcs->tf_post_input != NULL); + t->t_funcs->tf_post_input(t->t_softc); +} + +static inline void teken_funcs_param(const teken_t *t, int cmd, unsigned int value) { teken_assert(t->t_funcs->tf_param != NULL); t->t_funcs->tf_param(t->t_softc, cmd, value); } static inline void teken_funcs_respond(const teken_t *t, const void *buf, size_t len) { teken_assert(t->t_funcs->tf_respond != NULL); t->t_funcs->tf_respond(t->t_softc, buf, len); } #include "teken_subr.h" #include "teken_subr_compat.h" /* * Programming interface. */ void teken_init(teken_t *t, const teken_funcs_t *tf, void *softc) { teken_pos_t tp = { .tp_row = 24, .tp_col = 80 }; t->t_funcs = tf; t->t_softc = softc; t->t_nextstate = teken_state_init; t->t_stateflags = 0; t->t_utf8_left = 0; t->t_defattr.ta_format = 0; t->t_defattr.ta_fgcolor = TC_WHITE; t->t_defattr.ta_bgcolor = TC_BLACK; teken_subr_do_reset(t); teken_set_winsize(t, &tp); } static void teken_input_char(teken_t *t, teken_char_t c) { /* * There is no support for DCS and OSC. Just discard strings * until we receive characters that may indicate string * termination. */ if (t->t_stateflags & TS_INSTRING) { switch (c) { case '\x1B': t->t_stateflags &= ~TS_INSTRING; break; case '\a': t->t_stateflags &= ~TS_INSTRING; return; default: return; } } switch (c) { case '\0': break; case '\a': teken_subr_bell(t); break; case '\b': teken_subr_backspace(t); break; case '\n': case '\x0B': teken_subr_newline(t); break; case '\x0C': teken_subr_newpage(t); break; case '\x0E': if (t->t_stateflags & TS_CONS25) t->t_nextstate(t, c); else t->t_curscs = 1; break; case '\x0F': if (t->t_stateflags & TS_CONS25) t->t_nextstate(t, c); else t->t_curscs = 0; break; case '\r': teken_subr_carriage_return(t); break; case '\t': teken_subr_horizontal_tab(t); break; default: t->t_nextstate(t, c); break; } /* Post-processing assertions. */ teken_assert(t->t_cursor.tp_row >= t->t_originreg.ts_begin); teken_assert(t->t_cursor.tp_row < t->t_originreg.ts_end); teken_assert(t->t_cursor.tp_row < t->t_winsize.tp_row); teken_assert(t->t_cursor.tp_col < t->t_winsize.tp_col); teken_assert(t->t_saved_cursor.tp_row < t->t_winsize.tp_row); teken_assert(t->t_saved_cursor.tp_col < t->t_winsize.tp_col); teken_assert(t->t_scrollreg.ts_end <= t->t_winsize.tp_row); teken_assert(t->t_scrollreg.ts_begin < t->t_scrollreg.ts_end); /* Origin region has to be window size or the same as scrollreg. */ teken_assert((t->t_originreg.ts_begin == t->t_scrollreg.ts_begin && t->t_originreg.ts_end == t->t_scrollreg.ts_end) || (t->t_originreg.ts_begin == 0 && t->t_originreg.ts_end == t->t_winsize.tp_row)); } static void teken_input_byte(teken_t *t, unsigned char c) { /* * UTF-8 handling. */ if ((c & 0x80) == 0x00 || t->t_stateflags & TS_8BIT) { /* One-byte sequence. */ t->t_utf8_left = 0; teken_input_char(t, c); } else if ((c & 0xe0) == 0xc0) { /* Two-byte sequence. */ t->t_utf8_left = 1; t->t_utf8_partial = c & 0x1f; } else if ((c & 0xf0) == 0xe0) { /* Three-byte sequence. */ t->t_utf8_left = 2; t->t_utf8_partial = c & 0x0f; } else if ((c & 0xf8) == 0xf0) { /* Four-byte sequence. */ t->t_utf8_left = 3; t->t_utf8_partial = c & 0x07; } else if ((c & 0xc0) == 0x80) { if (t->t_utf8_left == 0) return; t->t_utf8_left--; t->t_utf8_partial = (t->t_utf8_partial << 6) | (c & 0x3f); if (t->t_utf8_left == 0) { teken_printf("Got UTF-8 char %x\n", t->t_utf8_partial); teken_input_char(t, t->t_utf8_partial); } } } void teken_input(teken_t *t, const void *buf, size_t len) { const char *c = buf; + teken_funcs_pre_input(t); while (len-- > 0) teken_input_byte(t, *c++); + teken_funcs_post_input(t); } const teken_pos_t * teken_get_cursor(const teken_t *t) { return (&t->t_cursor); } void teken_set_cursor(teken_t *t, const teken_pos_t *p) { /* XXX: bounds checking with originreg! */ teken_assert(p->tp_row < t->t_winsize.tp_row); teken_assert(p->tp_col < t->t_winsize.tp_col); t->t_cursor = *p; } const teken_attr_t * teken_get_curattr(const teken_t *t) { return (&t->t_curattr); } void teken_set_curattr(teken_t *t, const teken_attr_t *a) { t->t_curattr = *a; } const teken_attr_t * teken_get_defattr(const teken_t *t) { return (&t->t_defattr); } void teken_set_defattr(teken_t *t, const teken_attr_t *a) { t->t_curattr = t->t_saved_curattr = t->t_defattr = *a; } const teken_pos_t * teken_get_winsize(const teken_t *t) { return (&t->t_winsize); } static void teken_trim_cursor_pos(teken_t *t, const teken_pos_t *new) { const teken_pos_t *cur; cur = &t->t_winsize; if (cur->tp_row < new->tp_row || cur->tp_col < new->tp_col) return; if (t->t_cursor.tp_row >= new->tp_row) t->t_cursor.tp_row = new->tp_row - 1; if (t->t_cursor.tp_col >= new->tp_col) t->t_cursor.tp_col = new->tp_col - 1; } void teken_set_winsize(teken_t *t, const teken_pos_t *p) { teken_trim_cursor_pos(t, p); t->t_winsize = *p; teken_subr_do_reset(t); } void teken_set_winsize_noreset(teken_t *t, const teken_pos_t *p) { teken_trim_cursor_pos(t, p); t->t_winsize = *p; teken_subr_do_resize(t); } void teken_set_8bit(teken_t *t) { t->t_stateflags |= TS_8BIT; } void teken_set_cons25(teken_t *t) { t->t_stateflags |= TS_CONS25; } /* * State machine. */ static void teken_state_switch(teken_t *t, teken_state_t *s) { t->t_nextstate = s; t->t_curnum = 0; t->t_stateflags |= TS_FIRSTDIGIT; } static int teken_state_numbers(teken_t *t, teken_char_t c) { teken_assert(t->t_curnum < T_NUMSIZE); if (c >= '0' && c <= '9') { if (t->t_stateflags & TS_FIRSTDIGIT) { /* First digit. */ t->t_stateflags &= ~TS_FIRSTDIGIT; t->t_nums[t->t_curnum] = c - '0'; } else if (t->t_nums[t->t_curnum] < UINT_MAX / 100) { /* * There is no need to continue parsing input * once the value exceeds the size of the * terminal. It would only allow for integer * overflows when performing arithmetic on the * cursor position. * * Ignore any further digits if the value is * already UINT_MAX / 100. */ t->t_nums[t->t_curnum] = t->t_nums[t->t_curnum] * 10 + c - '0'; } return (1); } else if (c == ';') { if (t->t_stateflags & TS_FIRSTDIGIT) t->t_nums[t->t_curnum] = 0; /* Only allow a limited set of arguments. */ if (++t->t_curnum == T_NUMSIZE) { teken_state_switch(t, teken_state_init); return (1); } t->t_stateflags |= TS_FIRSTDIGIT; return (1); } else { if (t->t_stateflags & TS_FIRSTDIGIT && t->t_curnum > 0) { /* Finish off the last empty argument. */ t->t_nums[t->t_curnum] = 0; t->t_curnum++; } else if ((t->t_stateflags & TS_FIRSTDIGIT) == 0) { /* Also count the last argument. */ t->t_curnum++; } } return (0); } #define k TC_BLACK #define b TC_BLUE #define y TC_BROWN #define c TC_CYAN #define g TC_GREEN #define m TC_MAGENTA #define r TC_RED #define w TC_WHITE #define K (TC_BLACK | TC_LIGHT) #define B (TC_BLUE | TC_LIGHT) #define Y (TC_BROWN | TC_LIGHT) #define C (TC_CYAN | TC_LIGHT) #define G (TC_GREEN | TC_LIGHT) #define M (TC_MAGENTA | TC_LIGHT) #define R (TC_RED | TC_LIGHT) #define W (TC_WHITE | TC_LIGHT) /** * The xterm-256 color map has steps of 0x28 (in the range 0-0xff), except * for the first step which is 0x5f. Scale to the range 0-6 by dividing * by 0x28 and rounding down. The range of 0-5 cannot represent the * larger first step. * * This table is generated by the follow rules: * - if all components are equal, the result is black for (0, 0, 0) and * (2, 2, 2), else white; otherwise: * - subtract the smallest component from all components * - if this gives only one nonzero component, then that is the color * - else if one component is 2 or more larger than the other nonzero one, * then that component gives the color * - else there are 2 nonzero components. The color is that of a small * equal mixture of these components (cyan, yellow or magenta). E.g., * (0, 5, 6) (Turquoise2) is a much purer cyan than (0, 2, 3) * (DeepSkyBlue4), but we map both to cyan since we can't represent * delicate shades of either blue or cyan and blue would be worse. * Here it is important that components of 1 never occur. Blue would * be twice as large as green in (0, 1, 2). */ static const teken_color_t teken_256to8tab[] = { /* xterm normal colors: */ k, r, g, y, b, m, c, w, /* xterm bright colors: */ k, r, g, y, b, m, c, w, /* Red0 submap. */ k, b, b, b, b, b, g, c, c, b, b, b, g, c, c, c, b, b, g, g, c, c, c, b, g, g, g, c, c, c, g, g, g, g, c, c, /* Red2 submap. */ r, m, m, b, b, b, y, k, b, b, b, b, y, g, c, c, b, b, g, g, c, c, c, b, g, g, g, c, c, c, g, g, g, g, c, c, /* Red3 submap. */ r, m, m, m, b, b, y, r, m, m, b, b, y, y, w, b, b, b, y, y, g, c, c, b, g, g, g, c, c, c, g, g, g, g, c, c, /* Red4 submap. */ r, r, m, m, m, b, r, r, m, m, m, b, y, y, r, m, m, b, y, y, y, w, b, b, y, y, y, g, c, c, g, g, g, g, c, c, /* Red5 submap. */ r, r, r, m, m, m, r, r, r, m, m, m, r, r, r, m, m, m, y, y, y, r, m, m, y, y, y, y, w, b, y, y, y, y, g, c, /* Red6 submap. */ r, r, r, r, m, m, r, r, r, r, m, m, r, r, r, r, m, m, r, r, r, r, m, m, y, y, y, y, r, m, y, y, y, y, y, w, /* Grey submap. */ k, k, k, k, k, k, k, k, k, k, k, k, w, w, w, w, w, w, w, w, w, w, w, w, }; /* * This table is generated from the previous one by setting TC_LIGHT for * entries whose luminosity in the xterm256 color map is 60% or larger. * Thus the previous table is currently not really needed. It will be * used for different fine tuning of the tables. */ static const teken_color_t teken_256to16tab[] = { /* xterm normal colors: */ k, r, g, y, b, m, c, w, /* xterm bright colors: */ K, R, G, Y, B, M, C, W, /* Red0 submap. */ k, b, b, b, b, b, g, c, c, b, b, b, g, c, c, c, b, b, g, g, c, c, c, b, g, g, g, c, c, c, g, g, g, g, c, c, /* Red2 submap. */ r, m, m, b, b, b, y, K, b, b, B, B, y, g, c, c, B, B, g, g, c, c, C, B, g, G, G, C, C, C, g, G, G, G, C, C, /* Red3 submap. */ r, m, m, m, b, b, y, r, m, m, B, B, y, y, w, B, B, B, y, y, G, C, C, B, g, G, G, C, C, C, g, G, G, G, C, C, /* Red4 submap. */ r, r, m, m, m, b, r, r, m, m, M, B, y, y, R, M, M, B, y, y, Y, W, B, B, y, Y, Y, G, C, C, g, G, G, G, C, C, /* Red5 submap. */ r, r, r, m, m, m, r, R, R, M, M, M, r, R, R, M, M, M, y, Y, Y, R, M, M, y, Y, Y, Y, W, B, y, Y, Y, Y, G, C, /* Red6 submap. */ r, r, r, r, m, m, r, R, R, R, M, M, r, R, R, R, M, M, r, R, R, R, M, M, y, Y, Y, Y, R, M, y, Y, Y, Y, Y, W, /* Grey submap. */ k, k, k, k, k, k, K, K, K, K, K, K, w, w, w, w, w, w, W, W, W, W, W, W, }; #undef k #undef b #undef y #undef c #undef g #undef m #undef r #undef w #undef K #undef B #undef Y #undef C #undef G #undef M #undef R #undef W teken_color_t teken_256to8(teken_color_t c) { return (teken_256to8tab[c % 256]); } teken_color_t teken_256to16(teken_color_t c) { return (teken_256to16tab[c % 256]); } static const char * const special_strings_cons25[] = { [TKEY_UP] = "\x1B[A", [TKEY_DOWN] = "\x1B[B", [TKEY_LEFT] = "\x1B[D", [TKEY_RIGHT] = "\x1B[C", [TKEY_HOME] = "\x1B[H", [TKEY_END] = "\x1B[F", [TKEY_INSERT] = "\x1B[L", [TKEY_DELETE] = "\x7F", [TKEY_PAGE_UP] = "\x1B[I", [TKEY_PAGE_DOWN] = "\x1B[G", [TKEY_F1] = "\x1B[M", [TKEY_F2] = "\x1B[N", [TKEY_F3] = "\x1B[O", [TKEY_F4] = "\x1B[P", [TKEY_F5] = "\x1B[Q", [TKEY_F6] = "\x1B[R", [TKEY_F7] = "\x1B[S", [TKEY_F8] = "\x1B[T", [TKEY_F9] = "\x1B[U", [TKEY_F10] = "\x1B[V", [TKEY_F11] = "\x1B[W", [TKEY_F12] = "\x1B[X", }; static const char * const special_strings_ckeys[] = { [TKEY_UP] = "\x1BOA", [TKEY_DOWN] = "\x1BOB", [TKEY_LEFT] = "\x1BOD", [TKEY_RIGHT] = "\x1BOC", [TKEY_HOME] = "\x1BOH", [TKEY_END] = "\x1BOF", }; static const char * const special_strings_normal[] = { [TKEY_UP] = "\x1B[A", [TKEY_DOWN] = "\x1B[B", [TKEY_LEFT] = "\x1B[D", [TKEY_RIGHT] = "\x1B[C", [TKEY_HOME] = "\x1B[H", [TKEY_END] = "\x1B[F", [TKEY_INSERT] = "\x1B[2~", [TKEY_DELETE] = "\x1B[3~", [TKEY_PAGE_UP] = "\x1B[5~", [TKEY_PAGE_DOWN] = "\x1B[6~", [TKEY_F1] = "\x1BOP", [TKEY_F2] = "\x1BOQ", [TKEY_F3] = "\x1BOR", [TKEY_F4] = "\x1BOS", [TKEY_F5] = "\x1B[15~", [TKEY_F6] = "\x1B[17~", [TKEY_F7] = "\x1B[18~", [TKEY_F8] = "\x1B[19~", [TKEY_F9] = "\x1B[20~", [TKEY_F10] = "\x1B[21~", [TKEY_F11] = "\x1B[23~", [TKEY_F12] = "\x1B[24~", }; const char * teken_get_sequence(const teken_t *t, unsigned int k) { /* Cons25 mode. */ if (t->t_stateflags & TS_CONS25 && k < sizeof special_strings_cons25 / sizeof(char *)) return (special_strings_cons25[k]); /* Cursor keys mode. */ if (t->t_stateflags & TS_CURSORKEYS && k < sizeof special_strings_ckeys / sizeof(char *)) return (special_strings_ckeys[k]); /* Default xterm sequences. */ if (k < sizeof special_strings_normal / sizeof(char *)) return (special_strings_normal[k]); return (NULL); } #include "teken_state.h" Index: head/sys/teken/teken.h =================================================================== --- head/sys/teken/teken.h (revision 333668) +++ head/sys/teken/teken.h (revision 333669) @@ -1,215 +1,219 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008-2009 Ed Schouten * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _TEKEN_H_ #define _TEKEN_H_ #include /* * libteken: terminal emulation library. * * This library converts an UTF-8 stream of bytes to terminal drawing * commands. */ typedef uint32_t teken_char_t; typedef unsigned short teken_unit_t; typedef unsigned char teken_format_t; #define TF_BOLD 0x01 /* Bold character. */ #define TF_UNDERLINE 0x02 /* Underline character. */ #define TF_BLINK 0x04 /* Blinking character. */ #define TF_REVERSE 0x08 /* Reverse rendered character. */ #define TF_CJK_RIGHT 0x10 /* Right-hand side of CJK character. */ typedef unsigned char teken_color_t; #define TC_BLACK 0 #define TC_RED 1 #define TC_GREEN 2 #define TC_BROWN 3 #define TC_BLUE 4 #define TC_MAGENTA 5 #define TC_CYAN 6 #define TC_WHITE 7 #define TC_NCOLORS 8 #define TC_LIGHT 8 /* ORed with the others. */ typedef struct { teken_unit_t tp_row; teken_unit_t tp_col; } teken_pos_t; typedef struct { teken_pos_t tr_begin; teken_pos_t tr_end; } teken_rect_t; typedef struct { teken_format_t ta_format; teken_color_t ta_fgcolor; teken_color_t ta_bgcolor; } teken_attr_t; typedef struct { teken_unit_t ts_begin; teken_unit_t ts_end; } teken_span_t; typedef struct __teken teken_t; typedef void teken_state_t(teken_t *, teken_char_t); /* * Drawing routines supplied by the user. */ typedef void tf_bell_t(void *); typedef void tf_cursor_t(void *, const teken_pos_t *); typedef void tf_putchar_t(void *, const teken_pos_t *, teken_char_t, const teken_attr_t *); typedef void tf_fill_t(void *, const teken_rect_t *, teken_char_t, const teken_attr_t *); typedef void tf_copy_t(void *, const teken_rect_t *, const teken_pos_t *); +typedef void tf_pre_input_t(void *); +typedef void tf_post_input_t(void *); typedef void tf_param_t(void *, int, unsigned int); #define TP_SHOWCURSOR 0 #define TP_KEYPADAPP 1 #define TP_AUTOREPEAT 2 #define TP_SWITCHVT 3 #define TP_132COLS 4 #define TP_SETBELLPD 5 #define TP_SETBELLPD_PITCH(pd) ((pd) >> 16) #define TP_SETBELLPD_DURATION(pd) ((pd) & 0xffff) #define TP_MOUSE 6 #define TP_SETBORDER 7 #define TP_SETLOCALCURSOR 8 #define TP_SETGLOBALCURSOR 9 typedef void tf_respond_t(void *, const void *, size_t); typedef struct { tf_bell_t *tf_bell; tf_cursor_t *tf_cursor; tf_putchar_t *tf_putchar; tf_fill_t *tf_fill; tf_copy_t *tf_copy; + tf_pre_input_t *tf_pre_input; + tf_post_input_t *tf_post_input; tf_param_t *tf_param; tf_respond_t *tf_respond; } teken_funcs_t; typedef teken_char_t teken_scs_t(const teken_t *, teken_char_t); /* * Terminal state. */ struct __teken { const teken_funcs_t *t_funcs; void *t_softc; teken_state_t *t_nextstate; unsigned int t_stateflags; #define T_NUMSIZE 8 unsigned int t_nums[T_NUMSIZE]; unsigned int t_curnum; teken_pos_t t_cursor; teken_attr_t t_curattr; teken_pos_t t_saved_cursor; teken_attr_t t_saved_curattr; teken_attr_t t_defattr; teken_pos_t t_winsize; /* For DECSTBM. */ teken_span_t t_scrollreg; /* For DECOM. */ teken_span_t t_originreg; #define T_NUMCOL 160 unsigned int t_tabstops[T_NUMCOL / (sizeof(unsigned int) * 8)]; unsigned int t_utf8_left; teken_char_t t_utf8_partial; unsigned int t_curscs; teken_scs_t *t_saved_curscs; teken_scs_t *t_scs[2]; }; /* Initialize teken structure. */ void teken_init(teken_t *, const teken_funcs_t *, void *); /* Deliver character input. */ void teken_input(teken_t *, const void *, size_t); /* Get/set teken attributes. */ const teken_pos_t *teken_get_cursor(const teken_t *); const teken_attr_t *teken_get_curattr(const teken_t *); const teken_attr_t *teken_get_defattr(const teken_t *); void teken_get_defattr_cons25(const teken_t *, int *, int *); const teken_pos_t *teken_get_winsize(const teken_t *); void teken_set_cursor(teken_t *, const teken_pos_t *); void teken_set_curattr(teken_t *, const teken_attr_t *); void teken_set_defattr(teken_t *, const teken_attr_t *); void teken_set_winsize(teken_t *, const teken_pos_t *); void teken_set_winsize_noreset(teken_t *, const teken_pos_t *); /* Key input escape sequences. */ #define TKEY_UP 0x00 #define TKEY_DOWN 0x01 #define TKEY_LEFT 0x02 #define TKEY_RIGHT 0x03 #define TKEY_HOME 0x04 #define TKEY_END 0x05 #define TKEY_INSERT 0x06 #define TKEY_DELETE 0x07 #define TKEY_PAGE_UP 0x08 #define TKEY_PAGE_DOWN 0x09 #define TKEY_F1 0x0a #define TKEY_F2 0x0b #define TKEY_F3 0x0c #define TKEY_F4 0x0d #define TKEY_F5 0x0e #define TKEY_F6 0x0f #define TKEY_F7 0x10 #define TKEY_F8 0x11 #define TKEY_F9 0x12 #define TKEY_F10 0x13 #define TKEY_F11 0x14 #define TKEY_F12 0x15 const char *teken_get_sequence(const teken_t *, unsigned int); /* Legacy features. */ void teken_set_8bit(teken_t *); void teken_set_cons25(teken_t *); /* Color conversion. */ teken_color_t teken_256to16(teken_color_t); teken_color_t teken_256to8(teken_color_t); #endif /* !_TEKEN_H_ */