/src/mosh/src/terminal/terminalframebuffer.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | Mosh: the mobile shell |
3 | | Copyright 2012 Keith Winstein |
4 | | |
5 | | This program is free software: you can redistribute it and/or modify |
6 | | it under the terms of the GNU General Public License as published by |
7 | | the Free Software Foundation, either version 3 of the License, or |
8 | | (at your option) any later version. |
9 | | |
10 | | This program is distributed in the hope that it will be useful, |
11 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | GNU General Public License for more details. |
14 | | |
15 | | You should have received a copy of the GNU General Public License |
16 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | In addition, as a special exception, the copyright holders give |
19 | | permission to link the code of portions of this program with the |
20 | | OpenSSL library under certain conditions as described in each |
21 | | individual source file, and distribute linked combinations including |
22 | | the two. |
23 | | |
24 | | You must obey the GNU General Public License in all respects for all |
25 | | of the code used other than OpenSSL. If you modify file(s) with this |
26 | | exception, you may extend this exception to your version of the |
27 | | file(s), but you are not obligated to do so. If you do not wish to do |
28 | | so, delete this exception statement from your version. If you delete |
29 | | this exception statement from all source files in the program, then |
30 | | also delete it here. |
31 | | */ |
32 | | |
33 | | #ifndef TERMINALFB_HPP |
34 | | #define TERMINALFB_HPP |
35 | | |
36 | | #include <cassert> |
37 | | #include <climits> |
38 | | #include <cstdint> |
39 | | #include <deque> |
40 | | #include <list> |
41 | | #include <memory> |
42 | | #include <string> |
43 | | #include <vector> |
44 | | |
45 | | /* Terminal framebuffer */ |
46 | | |
47 | | namespace Terminal { |
48 | | using color_type = uint32_t; |
49 | | |
50 | | class Renditions |
51 | | { |
52 | | public: |
53 | | typedef enum |
54 | | { |
55 | | bold, |
56 | | faint, |
57 | | italic, |
58 | | underlined, |
59 | | blink, |
60 | | inverse, |
61 | | invisible, |
62 | | SIZE |
63 | | } attribute_type; |
64 | | |
65 | | private: |
66 | | static const uint64_t true_color_mask = 0x1000000; |
67 | | uint64_t foreground_color : 25; |
68 | | uint64_t background_color : 25; |
69 | | uint64_t attributes : 8; |
70 | | |
71 | | public: |
72 | | Renditions( color_type s_background ); |
73 | | void set_foreground_color( int num ); |
74 | | void set_background_color( int num ); |
75 | | void set_rendition( color_type num ); |
76 | | std::string sgr( void ) const; |
77 | | |
78 | | static unsigned int make_true_color( unsigned int r, unsigned int g, unsigned int b ) |
79 | 0 | { |
80 | 0 | return true_color_mask | ( r << 16 ) | ( g << 8 ) | b; |
81 | 0 | } |
82 | | |
83 | 0 | static bool is_true_color( unsigned int color ) { return ( color & true_color_mask ) != 0; } |
84 | | |
85 | | // unsigned int get_foreground_rendition() const { return foreground_color; } |
86 | 0 | unsigned int get_background_rendition() const { return background_color; } |
87 | | |
88 | | bool operator==( const Renditions& x ) const |
89 | 174k | { |
90 | 174k | return ( attributes == x.attributes ) && ( foreground_color == x.foreground_color ) |
91 | 174k | && ( background_color == x.background_color ); |
92 | 174k | } |
93 | | void set_attribute( attribute_type attr, bool val ) |
94 | 0 | { |
95 | 0 | attributes = val ? ( attributes | ( 1 << attr ) ) : ( attributes & ~( 1 << attr ) ); |
96 | 0 | } |
97 | 0 | bool get_attribute( attribute_type attr ) const { return attributes & ( 1 << attr ); } |
98 | 0 | void clear_attributes() { attributes = 0; } |
99 | | }; |
100 | | |
101 | | class Cell |
102 | | { |
103 | | private: |
104 | | typedef std::string content_type; /* can be std::string, std::vector<uint8_t>, or __gnu_cxx::__vstring */ |
105 | | content_type contents; |
106 | | Renditions renditions; |
107 | | unsigned int wide : 1; /* 0 = narrow, 1 = wide */ |
108 | | unsigned int fallback : 1; /* first character is combining character */ |
109 | | unsigned int wrap : 1; |
110 | | |
111 | | private: |
112 | | Cell(); |
113 | | |
114 | | public: |
115 | | Cell( color_type background_color ); |
116 | | |
117 | | void reset( color_type background_color ); |
118 | | |
119 | | bool operator==( const Cell& x ) const |
120 | 174k | { |
121 | 174k | return ( ( contents == x.contents ) && ( fallback == x.fallback ) && ( wide == x.wide ) |
122 | 174k | && ( renditions == x.renditions ) && ( wrap == x.wrap ) ); |
123 | 174k | } |
124 | | |
125 | 0 | bool operator!=( const Cell& x ) const { return !operator==( x ); } |
126 | | |
127 | | /* Accessors for contents field */ |
128 | | std::string debug_contents( void ) const; |
129 | | |
130 | 0 | bool empty( void ) const { return contents.empty(); } |
131 | | /* 32 seems like a reasonable limit on combining characters */ |
132 | 0 | bool full( void ) const { return contents.size() >= 32; } |
133 | 0 | void clear( void ) { contents.clear(); } |
134 | | |
135 | | bool is_blank( void ) const |
136 | 0 | { |
137 | | // XXX fix. |
138 | 0 | return ( contents.empty() || contents == " " || contents == "\xC2\xA0" ); |
139 | 0 | } |
140 | | |
141 | | bool contents_match( const Cell& other ) const |
142 | 0 | { |
143 | 0 | return ( is_blank() && other.is_blank() ) || ( contents == other.contents ); |
144 | 0 | } |
145 | | |
146 | | bool compare( const Cell& other ) const; |
147 | | |
148 | | // Is this a printing ISO 8859-1 character? |
149 | | static bool isprint_iso8859_1( const wchar_t c ) |
150 | 0 | { |
151 | 0 | return ( c <= 0xff && c >= 0xa0 ) || ( c <= 0x7e && c >= 0x20 ); |
152 | 0 | } |
153 | | |
154 | | static void append_to_str( std::string& dest, const wchar_t c ) |
155 | 0 | { |
156 | | /* ASCII? Cheat. */ |
157 | 0 | if ( static_cast<uint32_t>( c ) <= 0x7f ) { |
158 | 0 | dest.push_back( static_cast<char>( c ) ); |
159 | 0 | return; |
160 | 0 | } |
161 | 0 | static mbstate_t ps = mbstate_t(); |
162 | 0 | char tmp[MB_LEN_MAX]; |
163 | 0 | size_t ignore = wcrtomb( NULL, 0, &ps ); |
164 | 0 | (void)ignore; |
165 | 0 | size_t len = wcrtomb( tmp, c, &ps ); |
166 | 0 | dest.append( tmp, len ); |
167 | 0 | } |
168 | | |
169 | | void append( const wchar_t c ) |
170 | 0 | { |
171 | | /* ASCII? Cheat. */ |
172 | 0 | if ( static_cast<uint32_t>( c ) <= 0x7f ) { |
173 | 0 | contents.push_back( static_cast<char>( c ) ); |
174 | 0 | return; |
175 | 0 | } |
176 | 0 | static mbstate_t ps = mbstate_t(); |
177 | 0 | char tmp[MB_LEN_MAX]; |
178 | 0 | size_t ignore = wcrtomb( NULL, 0, &ps ); |
179 | 0 | (void)ignore; |
180 | 0 | size_t len = wcrtomb( tmp, c, &ps ); |
181 | 0 | contents.insert( contents.end(), tmp, tmp + len ); |
182 | 0 | } |
183 | | |
184 | | void print_grapheme( std::string& output ) const |
185 | 0 | { |
186 | 0 | if ( contents.empty() ) { |
187 | 0 | output.append( 1, ' ' ); |
188 | 0 | return; |
189 | 0 | } |
190 | | /* |
191 | | * cells that begin with combining character get combiner |
192 | | * attached to no-break space |
193 | | */ |
194 | 0 | if ( fallback ) { |
195 | 0 | output.append( "\xC2\xA0" ); |
196 | 0 | } |
197 | 0 | output.append( contents ); |
198 | 0 | } |
199 | | |
200 | | /* Other accessors */ |
201 | 0 | const Renditions& get_renditions( void ) const { return renditions; } |
202 | 0 | Renditions& get_renditions( void ) { return renditions; } |
203 | 0 | void set_renditions( const Renditions& r ) { renditions = r; } |
204 | 0 | bool get_wide( void ) const { return wide; } |
205 | 0 | void set_wide( bool w ) { wide = w; } |
206 | 174k | unsigned int get_width( void ) const { return wide + 1; } |
207 | 0 | bool get_fallback( void ) const { return fallback; } |
208 | 0 | void set_fallback( bool f ) { fallback = f; } |
209 | 2.18k | bool get_wrap( void ) const { return wrap; } |
210 | 0 | void set_wrap( bool f ) { wrap = f; } |
211 | | }; |
212 | | |
213 | | class Row |
214 | | { |
215 | | public: |
216 | | typedef std::vector<Cell> cells_type; |
217 | | cells_type cells; |
218 | | // gen is a generation counter. It can be used to quickly rule |
219 | | // out the possibility of two rows being identical; this is useful |
220 | | // in scrolling. |
221 | | uint64_t gen; |
222 | | |
223 | | private: |
224 | | Row(); |
225 | | |
226 | | public: |
227 | | Row( const size_t s_width, const color_type background_color ); |
228 | | |
229 | | void insert_cell( int col, color_type background_color ); |
230 | | void delete_cell( int col, color_type background_color ); |
231 | | |
232 | | void reset( color_type background_color ); |
233 | | |
234 | 2.18k | bool operator==( const Row& x ) const { return ( gen == x.gen && cells == x.cells ); } |
235 | | |
236 | 2.18k | bool get_wrap( void ) const { return cells.back().get_wrap(); } |
237 | 0 | void set_wrap( bool w ) { cells.back().set_wrap( w ); } |
238 | | |
239 | | uint64_t get_gen() const; |
240 | | }; |
241 | | |
242 | | class SavedCursor |
243 | | { |
244 | | public: |
245 | | int cursor_col, cursor_row; |
246 | | Renditions renditions; |
247 | | /* not implemented: character set shift state */ |
248 | | bool auto_wrap_mode; |
249 | | bool origin_mode; |
250 | | /* not implemented: state of selective erase */ |
251 | | |
252 | | SavedCursor(); |
253 | | }; |
254 | | |
255 | | class DrawState |
256 | | { |
257 | | private: |
258 | | int width, height; |
259 | | |
260 | | void new_grapheme( void ); |
261 | | void snap_cursor_to_border( void ); |
262 | | |
263 | | int cursor_col, cursor_row; |
264 | | int combining_char_col, combining_char_row; |
265 | | |
266 | | bool default_tabs; |
267 | | std::vector<bool> tabs; |
268 | | |
269 | | void reinitialize_tabs( unsigned int start ); |
270 | | |
271 | | int scrolling_region_top_row, scrolling_region_bottom_row; |
272 | | |
273 | | Renditions renditions; |
274 | | |
275 | | SavedCursor save; |
276 | | |
277 | | public: |
278 | | bool next_print_will_wrap; |
279 | | bool origin_mode; |
280 | | bool auto_wrap_mode; |
281 | | bool insert_mode; |
282 | | bool cursor_visible; |
283 | | bool reverse_video; |
284 | | bool bracketed_paste; |
285 | | |
286 | | enum MouseReportingMode |
287 | | { |
288 | | MOUSE_REPORTING_NONE = 0, |
289 | | MOUSE_REPORTING_X10 = 9, |
290 | | MOUSE_REPORTING_VT220 = 1000, |
291 | | MOUSE_REPORTING_VT220_HILIGHT = 1001, |
292 | | MOUSE_REPORTING_BTN_EVENT = 1002, |
293 | | MOUSE_REPORTING_ANY_EVENT = 1003 |
294 | | } mouse_reporting_mode; |
295 | | |
296 | | bool mouse_focus_event; // 1004 |
297 | | bool mouse_alternate_scroll; // 1007 |
298 | | |
299 | | enum MouseEncodingMode |
300 | | { |
301 | | MOUSE_ENCODING_DEFAULT = 0, |
302 | | MOUSE_ENCODING_UTF8 = 1005, |
303 | | MOUSE_ENCODING_SGR = 1006, |
304 | | MOUSE_ENCODING_URXVT = 1015 |
305 | | } mouse_encoding_mode; |
306 | | |
307 | | bool application_mode_cursor_keys; |
308 | | |
309 | | /* bold, etc. */ |
310 | | |
311 | | void move_row( int N, bool relative = false ); |
312 | | void move_col( int N, bool relative = false, bool implicit = false ); |
313 | | |
314 | 182 | int get_cursor_col( void ) const { return cursor_col; } |
315 | 182 | int get_cursor_row( void ) const { return cursor_row; } |
316 | 0 | int get_combining_char_col( void ) const { return combining_char_col; } |
317 | 0 | int get_combining_char_row( void ) const { return combining_char_row; } |
318 | 2.63k | int get_width( void ) const { return width; } |
319 | 4.91k | int get_height( void ) const { return height; } |
320 | | |
321 | | void set_tab( void ); |
322 | | void clear_tab( int col ); |
323 | 0 | void clear_default_tabs( void ) { default_tabs = false; } |
324 | | /* Default tabs can't be restored without resetting the draw state. */ |
325 | | int get_next_tab( int count ) const; |
326 | | |
327 | | void set_scrolling_region( int top, int bottom ); |
328 | | |
329 | 0 | int get_scrolling_region_top_row( void ) const { return scrolling_region_top_row; } |
330 | 0 | int get_scrolling_region_bottom_row( void ) const { return scrolling_region_bottom_row; } |
331 | | |
332 | | int limit_top( void ) const; |
333 | | int limit_bottom( void ) const; |
334 | | |
335 | 0 | void set_foreground_color( int x ) { renditions.set_foreground_color( x ); } |
336 | 0 | void set_background_color( int x ) { renditions.set_background_color( x ); } |
337 | 0 | void add_rendition( color_type x ) { renditions.set_rendition( x ); } |
338 | 182 | const Renditions& get_renditions( void ) const { return renditions; } |
339 | 0 | Renditions& get_renditions( void ) { return renditions; } |
340 | 0 | int get_background_rendition( void ) const { return renditions.get_background_rendition(); } |
341 | | |
342 | | void save_cursor( void ); |
343 | | void restore_cursor( void ); |
344 | 0 | void clear_saved_cursor( void ) { save = SavedCursor(); } |
345 | | |
346 | | void resize( int s_width, int s_height ); |
347 | | |
348 | | DrawState( int s_width, int s_height ); |
349 | | |
350 | | bool operator==( const DrawState& x ) const |
351 | 0 | { |
352 | | /* only compare fields that affect display */ |
353 | 0 | return ( width == x.width ) && ( height == x.height ) && ( cursor_col == x.cursor_col ) |
354 | 0 | && ( cursor_row == x.cursor_row ) && ( cursor_visible == x.cursor_visible ) |
355 | 0 | && ( reverse_video == x.reverse_video ) && ( renditions == x.renditions ) |
356 | 0 | && ( bracketed_paste == x.bracketed_paste ) && ( mouse_reporting_mode == x.mouse_reporting_mode ) |
357 | 0 | && ( mouse_focus_event == x.mouse_focus_event ) && ( mouse_alternate_scroll == x.mouse_alternate_scroll ) |
358 | 0 | && ( mouse_encoding_mode == x.mouse_encoding_mode ); |
359 | 0 | } |
360 | | }; |
361 | | |
362 | | class Framebuffer |
363 | | { |
364 | | // To minimize copying of rows and cells, we use shared_ptr to |
365 | | // share unchanged rows between multiple Framebuffers. If we |
366 | | // write to a row in a Framebuffer and it is shared with other |
367 | | // owners, we copy it first. The shared_ptr naturally manages the |
368 | | // usage of the actual rows themselves. |
369 | | // |
370 | | // We gain a couple of free extras by doing this: |
371 | | // |
372 | | // * A quick check for equality between rows in different |
373 | | // Framebuffers is to simply compare the pointer values. If they |
374 | | // are equal, then the rows are obviously identical. |
375 | | // * If no row is shared, the frame has not been modified. |
376 | | public: |
377 | | typedef std::vector<wchar_t> title_type; |
378 | | typedef std::shared_ptr<Row> row_pointer; |
379 | | typedef std::vector<row_pointer> rows_type; /* can be either std::vector or std::deque */ |
380 | | |
381 | | private: |
382 | | rows_type rows; |
383 | | title_type icon_name; |
384 | | title_type window_title; |
385 | | title_type clipboard; |
386 | | unsigned int bell_count; |
387 | | bool title_initialized; /* true if the window title has been set via an OSC */ |
388 | | |
389 | | row_pointer newrow( void ) |
390 | 0 | { |
391 | 0 | const size_t w = ds.get_width(); |
392 | 0 | const color_type c = ds.get_background_rendition(); |
393 | 0 | return std::make_shared<Row>( w, c ); |
394 | 0 | } |
395 | | |
396 | | public: |
397 | | Framebuffer( int s_width, int s_height ); |
398 | | Framebuffer( const Framebuffer& other ); |
399 | | Framebuffer& operator=( const Framebuffer& other ); |
400 | | DrawState ds; |
401 | | |
402 | 91 | const rows_type& get_rows() const { return rows; } |
403 | | |
404 | | void scroll( int N ); |
405 | | void move_rows_autoscroll( int rows ); |
406 | | |
407 | | inline const Row* get_row( int row ) const |
408 | 4.36k | { |
409 | 4.36k | if ( row == -1 ) |
410 | 0 | row = ds.get_cursor_row(); |
411 | | |
412 | 4.36k | return rows.at( row ).get(); |
413 | 4.36k | } |
414 | | |
415 | | inline const Cell* get_cell( int row = -1, int col = -1 ) const |
416 | 0 | { |
417 | 0 | if ( row == -1 ) |
418 | 0 | row = ds.get_cursor_row(); |
419 | 0 | if ( col == -1 ) |
420 | 0 | col = ds.get_cursor_col(); |
421 | |
|
422 | 0 | return &rows.at( row )->cells.at( col ); |
423 | 0 | } |
424 | | |
425 | | Row* get_mutable_row( int row ) |
426 | 0 | { |
427 | 0 | if ( row == -1 ) |
428 | 0 | row = ds.get_cursor_row(); |
429 | 0 | row_pointer& mutable_row = rows.at( row ); |
430 | | // If the row is shared, copy it. |
431 | 0 | if ( !mutable_row.unique() ) { |
432 | 0 | mutable_row = std::make_shared<Row>( *mutable_row ); |
433 | 0 | } |
434 | 0 | return mutable_row.get(); |
435 | 0 | } |
436 | | |
437 | | Cell* get_mutable_cell( int row = -1, int col = -1 ) |
438 | 0 | { |
439 | 0 | if ( row == -1 ) |
440 | 0 | row = ds.get_cursor_row(); |
441 | 0 | if ( col == -1 ) |
442 | 0 | col = ds.get_cursor_col(); |
443 | |
|
444 | 0 | return &get_mutable_row( row )->cells.at( col ); |
445 | 0 | } |
446 | | |
447 | | Cell* get_combining_cell( void ); |
448 | | |
449 | | void apply_renditions_to_cell( Cell* cell ); |
450 | | |
451 | | void insert_line( int before_row, int count ); |
452 | | void delete_line( int row, int count ); |
453 | | |
454 | | void insert_cell( int row, int col ); |
455 | | void delete_cell( int row, int col ); |
456 | | |
457 | | void reset( void ); |
458 | | void soft_reset( void ); |
459 | | |
460 | 0 | void set_title_initialized( void ) { title_initialized = true; } |
461 | 91 | bool is_title_initialized( void ) const { return title_initialized; } |
462 | 0 | void set_icon_name( const title_type& s ) { icon_name = s; } |
463 | 0 | void set_window_title( const title_type& s ) { window_title = s; } |
464 | 0 | void set_clipboard( const title_type& s ) { clipboard = s; } |
465 | 0 | const title_type& get_icon_name( void ) const { return icon_name; } |
466 | 0 | const title_type& get_window_title( void ) const { return window_title; } |
467 | 182 | const title_type& get_clipboard( void ) const { return clipboard; } |
468 | | |
469 | | void prefix_window_title( const title_type& s ); |
470 | | |
471 | | void resize( int s_width, int s_height ); |
472 | | |
473 | 0 | void reset_cell( Cell* c ) { c->reset( ds.get_background_rendition() ); } |
474 | 0 | void reset_row( Row* r ) { r->reset( ds.get_background_rendition() ); } |
475 | | |
476 | 0 | void ring_bell( void ) { bell_count++; } |
477 | 182 | unsigned int get_bell_count( void ) const { return bell_count; } |
478 | | |
479 | | bool operator==( const Framebuffer& x ) const |
480 | 0 | { |
481 | 0 | return ( rows == x.rows ) && ( window_title == x.window_title ) && ( clipboard == x.clipboard ) |
482 | 0 | && ( bell_count == x.bell_count ) && ( ds == x.ds ); |
483 | 0 | } |
484 | | }; |
485 | | } |
486 | | |
487 | | #endif |