Bug Summary

File:/rootdir/_build/../src/color-triple.hh
Warning:line 54, column 20
The left operand of '&' is a garbage value

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name bte.cc -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -mframe-pointer=none -relaxed-aliasing -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/rootdir/_build -resource-dir /usr/lib/llvm-14/lib/clang/14.0.6 -I src/libbte-2.91.so.0.6500.0.p -I src -I ../src -I . -I .. -I src/bte -I ../src/bte -I /usr/include/glib-2.0 -I /usr/lib/x86_64-linux-gnu/glib-2.0/include -I /usr/include/libmount -I /usr/include/blkid -I /usr/include/pango-1.0 -I /usr/include/harfbuzz -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/fribidi -I /usr/include/uuid -I /usr/include/cairo -I /usr/include/pixman-1 -I /usr/include/p11-kit-1 -I /usr/include/ctk-3.0 -I /usr/include/gdk-pixbuf-2.0 -I /usr/include/x86_64-linux-gnu -I /usr/include/gio-unix-2.0 -I /usr/include/atk-1.0 -I /usr/include/at-spi2-atk/2.0 -I /usr/include/at-spi-2.0 -I /usr/include/dbus-1.0 -I /usr/lib/x86_64-linux-gnu/dbus-1.0/include -D _FILE_OFFSET_BITS=64 -D G_LOG_DOMAIN="BTE" -D LOCALEDIR="/usr/local/share/locale" -D BTE_DISABLE_DEPRECATION_WARNINGS -D BTE_COMPILATION -U PARSER_INCLUDE_NOP -D CDK_VERSION_MIN_REQUIRED=(G_ENCODE_VERSION(3,18)) -D CDK_VERSION_MAX_ALLOWED=(G_ENCODE_VERSION(3,20)) -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/x86_64-linux-gnu/c++/12 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/backward -internal-isystem /usr/lib/llvm-14/lib/clang/14.0.6/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O3 -Wno-address-of-packed-member -Wno-missing-field-initializers -Wno-packed -Wno-switch-enum -Wno-unused-parameter -Wwrite-strings -std=gnu++17 -fdeprecated-macro -fdebug-compilation-dir=/rootdir/_build -ferror-limit 19 -fvisibility hidden -fvisibility-inlines-hidden -stack-protector 2 -fgnuc-version=4.2.1 -fcxx-exceptions -fexceptions -fcolor-diagnostics -vectorize-loops -vectorize-slp -analyzer-checker deadcode.DeadStores -analyzer-checker alpha.deadcode.UnreachableCode -analyzer-checker alpha.core.CastSize -analyzer-checker alpha.core.CastToStruct -analyzer-checker alpha.core.IdenticalExpr -analyzer-checker alpha.core.SizeofPtr -analyzer-checker alpha.security.ArrayBoundV2 -analyzer-checker alpha.security.MallocOverflow -analyzer-checker alpha.security.ReturnPtrRange -analyzer-checker alpha.unix.SimpleStream -analyzer-checker alpha.unix.cstring.BufferOverlap -analyzer-checker alpha.unix.cstring.NotNullTerminated -analyzer-checker alpha.unix.cstring.OutOfBounds -analyzer-checker alpha.core.FixedAddr -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /rootdir/html-report/2022-12-31-022803-10898-1 -x c++ ../src/bte.cc

../src/bte.cc

1/*
2 * Copyright (C) 2001-2004,2009,2010 Red Hat, Inc.
3 * Copyright © 2008, 2009, 2010 Christian Persch
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library 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 GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include "config.h"
21
22#include <math.h>
23#include <search.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/ioctl.h>
27#include <errno(*__errno_location ()).h>
28#include <fcntl.h>
29#ifdef HAVE_SYS_TERMIOS_H
30#include <sys/termios.h>
31#endif
32
33#include <glib.h>
34#include <glib-unix1.h>
35#include <glib/gi18n-lib.h>
36
37#include <bte/bte.h>
38#include "bteinternal.hh"
39#include "bidi.hh"
40#include "buffer.h"
41#include "debug.h"
42#include "reaper.hh"
43#include "ring.hh"
44#include "ringview.hh"
45#include "caps.hh"
46#include "widget.hh"
47
48#ifdef HAVE_WCHAR_H
49#include <wchar.h>
50#endif
51#ifdef HAVE_SYS_SYSLIMITS_H
52#include <sys/syslimits.h>
53#endif
54#ifdef HAVE_SYS_WAIT_H
55#include <sys/wait.h>
56#endif
57#include <glib.h>
58#include <glib-object.h>
59#include <cdk/cdk.h>
60#include <ctk/ctk.h>
61#include <pango/pango.h>
62#include "keymap.h"
63#include "marshal.h"
64#include "btepty.h"
65#include "btectk.hh"
66#include "cxx-utils.hh"
67#include "gobject-glue.hh"
68
69#ifdef WITH_A11Y
70#include "bteaccess.h"
71#endif
72
73#include <new> /* placement new */
74
75using namespace std::literals;
76
77#ifndef HAVE_ROUND
78static inline double round(double x) {
79 if(x - floor(x) < 0.5) {
80 return floor(x);
81 } else {
82 return ceil(x);
83 }
84}
85#endif
86
87#define WORD_CHAR_EXCEPTIONS_DEFAULT"-#%&+,./=?@\\_~\302\267"sv "-#%&+,./=?@\\_~\302\267"sv
88
89#define I_(string)(g_intern_static_string(string)) (g_intern_static_string(string))
90
91#define BTE_DRAW_OPAQUE(1.0) (1.0)
92
93namespace bte {
94namespace terminal {
95
96static int _bte_unichar_width(gunichar c, int utf8_ambiguous_width);
97static void stop_processing(bte::terminal::Terminal* that);
98static void add_process_timeout(bte::terminal::Terminal* that);
99static void add_update_timeout(bte::terminal::Terminal* that);
100static void remove_update_timeout(bte::terminal::Terminal* that);
101
102static gboolean process_timeout (gpointer data) noexcept;
103static gboolean update_timeout (gpointer data) noexcept;
104static cairo_region_t *bte_cairo_get_clip_region (cairo_t *cr);
105
106/* these static variables are guarded by the CDK mutex */
107static guint process_timeout_tag = 0;
108static gboolean in_process_timeout;
109static guint update_timeout_tag = 0;
110static gboolean in_update_timeout;
111static GList *g_active_terminals;
112
113static int
114_bte_unichar_width(gunichar c, int utf8_ambiguous_width)
115{
116 if (G_LIKELY (c < 0x80)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
c < 0x80) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 1))
)
117 return 1;
118 if (G_UNLIKELY (g_unichar_iszerowidth (c))(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
g_unichar_iszerowidth (c)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
119 return 0;
120 if (G_UNLIKELY (g_unichar_iswide (c))(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
g_unichar_iswide (c)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
121 return 2;
122 if (G_LIKELY (utf8_ambiguous_width == 1)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
utf8_ambiguous_width == 1) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1))
)
123 return 1;
124 if (G_UNLIKELY (g_unichar_iswide_cjk (c))(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
g_unichar_iswide_cjk (c)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
125 return 2;
126 return 1;
127}
128
129static void
130bte_g_array_fill(GArray *array, gconstpointer item, guint final_size)
131{
132 if (array->len >= final_size)
133 return;
134
135 final_size -= array->len;
136 do {
137 g_array_append_vals(array, item, 1);
138 } while (--final_size);
139}
140
141void
142Terminal::unset_widget() noexcept
143{
144 m_real_widget = nullptr;
145 m_terminal = nullptr;
146 m_widget = nullptr;
147}
148
149// FIXMEchpe replace this with a method on BteRing
150BteRowData*
151Terminal::ring_insert(bte::grid::row_t position,
152 bool fill)
153{
154 BteRowData *row;
155 BteRing *ring = m_screen->row_data;
156 bool const not_default_bg = (m_color_defaults.attr.back() != BTE_DEFAULT_BG257);
157
158 while (G_UNLIKELY (_bte_ring_next (ring) < position)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
_bte_ring_next (ring) < position) _g_boolean_var_ = 1; else
_g_boolean_var_ = 0; _g_boolean_var_; }), 0))
) {
159 row = _bte_ring_append (ring, get_bidi_flags());
160 if (not_default_bg)
161 _bte_row_data_fill (row, &m_color_defaults, m_column_count);
162 }
163 row = _bte_ring_insert (ring, position, get_bidi_flags());
164 if (fill && not_default_bg)
165 _bte_row_data_fill (row, &m_color_defaults, m_column_count);
166 return row;
167}
168
169// FIXMEchpe replace this with a method on BteRing
170BteRowData*
171Terminal::ring_append(bool fill)
172{
173 return ring_insert(_bte_ring_next(m_screen->row_data), fill);
174}
175
176// FIXMEchpe replace this with a method on BteRing
177void
178Terminal::ring_remove(bte::grid::row_t position)
179{
180 _bte_ring_remove(m_screen->row_data, position);
181}
182
183/* Reset defaults for character insertion. */
184void
185Terminal::reset_default_attributes(bool reset_hyperlink)
186{
187 auto const hyperlink_idx_save = m_defaults.attr.hyperlink_idx;
188 m_defaults = m_color_defaults = basic_cell;
189 if (!reset_hyperlink)
190 m_defaults.attr.hyperlink_idx = hyperlink_idx_save;
191}
192
193//FIXMEchpe this function is bad
194inline bte::view::coord_t
195Terminal::scroll_delta_pixel() const
196{
197 return round(m_screen->scroll_delta * m_cell_height);
198}
199
200/*
201 * Terminal::pixel_to_row:
202 * @y: Y coordinate is relative to viewport, top padding excluded
203 *
204 * Returns: absolute row
205 */
206inline bte::grid::row_t
207Terminal::pixel_to_row(bte::view::coord_t y) const
208{
209 return (scroll_delta_pixel() + y) / m_cell_height;
210}
211
212/*
213 * Terminal::pixel_to_row:
214 * @row: absolute row
215 *
216 * Returns: Y coordinate relative to viewport with top padding excluded. If the row is
217 * outside the viewport, may return any value < 0 or >= height
218 */
219inline bte::view::coord_t
220Terminal::row_to_pixel(bte::grid::row_t row) const
221{
222 // FIXMEchpe this is bad!
223 return row * m_cell_height - (glong)round(m_screen->scroll_delta * m_cell_height);
224}
225
226inline bte::grid::row_t
227Terminal::first_displayed_row() const
228{
229 return pixel_to_row(0);
230}
231
232inline bte::grid::row_t
233Terminal::last_displayed_row() const
234{
235 /* Get the logical row number displayed at the bottom pixel position */
236 auto r = pixel_to_row(m_view_usable_extents.height() - 1);
237
238 /* If we have an extra padding at the bottom which is currently unused,
239 * this number is one too big. Adjust here.
240 * E.g. have a terminal of size 80 x 24.5.
241 * Initially the bottom displayed row is (0-based) 23, but r is now 24.
242 * After producing more than a screenful of content and scrolling back
243 * all the way to the top, the bottom displayed row is (0-based) 24. */
244 r = MIN (r, m_screen->insert_delta + m_row_count - 1)(((r) < (m_screen->insert_delta + m_row_count - 1)) ? (
r) : (m_screen->insert_delta + m_row_count - 1))
;
245 return r;
246}
247
248/* Checks if the cursor is potentially at least partially onscreen.
249 * An outline cursor has an additional height of BTE_LINE_WIDTH pixels.
250 * It's also intentionally painted over the padding, up to BTE_LINE_WIDTH
251 * pixels under the real contents area. This method takes these into account.
252 * Only checks the cursor's row; not its visibility, shape, or offscreen column.
253 */
254inline bool
255Terminal::cursor_is_onscreen() const noexcept
256{
257 /* Note: the cursor can only be offscreen below the visible area, not above. */
258 auto cursor_top = row_to_pixel (m_screen->cursor.row) - BTE_LINE_WIDTH1;
259 auto display_bottom = m_view_usable_extents.height() + MIN(m_padding.bottom, BTE_LINE_WIDTH)(((m_padding.bottom) < (1)) ? (m_padding.bottom) : (1));
260 return cursor_top < display_bottom;
261}
262
263/* Invalidate the requested rows. This is to be used when only the desired
264 * rendering changes but not the underlying data, e.g. moving or blinking
265 * cursor, highligthing with the mouse etc.
266 *
267 * Note that row_end is inclusive. This is not as nice as end-exclusive,
268 * but saves us from a +1 almost everywhere where this method is called.
269 */
270void
271Terminal::invalidate_rows(bte::grid::row_t row_start,
272 bte::grid::row_t row_end /* inclusive */)
273{
274 if (G_UNLIKELY (!widget_realized())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!widget_realized()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
275 return;
276
277 if (m_invalidated_all)
278 return;
279
280 if (G_UNLIKELY (row_end < row_start)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
row_end < row_start) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
281 return;
282
283 _bte_debug_print (BTE_DEBUG_UPDATES,do { } while(0)
284 "Invalidating rows %ld..%ld.\n",do { } while(0)
285 row_start, row_end)do { } while(0);
286 _bte_debug_print (BTE_DEBUG_WORK, "?")do { } while(0);
287
288 /* Scrolled back, visible parts didn't change. */
289 if (row_start > last_displayed_row())
290 return;
291
292 /* Recognize if we're about to invalidate everything. */
293 if (row_start <= first_displayed_row() &&
294 row_end >= last_displayed_row()) {
295 invalidate_all();
296 return;
297 }
298
299 cairo_rectangle_int_t rect;
300 /* Convert the column and row start and end to pixel values
301 * by multiplying by the size of a character cell.
302 * Always include the extra pixel border and overlap pixel.
303 */
304 // FIXMEegmont invalidate the left and right padding too
305 rect.x = -1;
306 int xend = m_column_count * m_cell_width + 1;
307 rect.width = xend - rect.x;
308
309 /* Always add at least BTE_LINE_WIDTH pixels so the outline block cursor fits */
310 rect.y = row_to_pixel(row_start) - std::max(cell_overflow_top(), BTE_LINE_WIDTH1);
311 int yend = row_to_pixel(row_end + 1) + std::max(cell_overflow_bottom(), BTE_LINE_WIDTH1);
312 rect.height = yend - rect.y;
313
314 _bte_debug_print (BTE_DEBUG_UPDATES,do { } while(0)
315 "Invalidating pixels at (%d,%d)x(%d,%d).\n",do { } while(0)
316 rect.x, rect.y, rect.width, rect.height)do { } while(0);
317
318 if (m_active_terminals_link != nullptr) {
319 g_array_append_val(m_update_rects, rect)g_array_append_vals (m_update_rects, &(rect), 1);
320 /* Wait a bit before doing any invalidation, just in
321 * case updates are coming in really soon. */
322 add_update_timeout(this);
323 } else {
324 auto allocation = get_allocated_rect();
325 rect.x += allocation.x + m_padding.left;
326 rect.y += allocation.y + m_padding.top;
327 cairo_region_t *region = cairo_region_create_rectangle(&rect);
328 ctk_widget_queue_draw_region(m_widget, region);
329 cairo_region_destroy(region);
330 }
331
332 _bte_debug_print (BTE_DEBUG_WORK, "!")do { } while(0);
333}
334
335/* Invalidate the requested rows, extending the region in both directions up to
336 * an explicit newline (or a safety limit) to invalidate entire paragraphs of text.
337 * This is to be used whenever the underlying data changes, because any such
338 * change might alter the desired BiDi, syntax highlighting etc. of all other
339 * rows of the involved paragraph(s).
340 *
341 * Note that row_end is inclusive. This is not as nice as end-exclusive,
342 * but saves us from a +1 almost everywhere where this method is called.
343 */
344void
345Terminal::invalidate_rows_and_context(bte::grid::row_t row_start,
346 bte::grid::row_t row_end /* inclusive */)
347{
348 if (G_UNLIKELY (!widget_realized())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!widget_realized()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
349 return;
350
351 if (m_invalidated_all)
352 return;
353
354 if (G_UNLIKELY (row_end < row_start)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
row_end < row_start) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
355 return;
356
357 _bte_debug_print (BTE_DEBUG_UPDATES,do { } while(0)
358 "Invalidating rows %ld..%ld and context.\n",do { } while(0)
359 row_start, row_end)do { } while(0);
360
361 /* Safety limit: Scrolled back by so much that changes to the
362 * writable area may not affect the current viewport's rendering. */
363 if (m_screen->insert_delta - BTE_RINGVIEW_PARAGRAPH_LENGTH_MAX500 > last_displayed_row())
364 return;
365
366 /* Extending the start is a bit tricky.
367 * First extend it (towards lower numbered indices), but only up to
368 * insert_delta - 1. Remember that the row at insert_delta - 1 is
369 * still in the ring, hence checking its soft_wrapped flag is fast. */
370 while (row_start >= m_screen->insert_delta) {
371 if (!m_screen->row_data->is_soft_wrapped(row_start - 1))
372 break;
373 row_start--;
374 }
375
376 /* If we haven't seen a newline yet, stop walking backwards row by row.
377 * This is because we might need to access row_stream in order to check
378 * the wrapped state, a way too expensive operation while processing
379 * incoming data. Let displaying do extra work instead.
380 * So just invalidate everything to the top. */
381 if (row_start < m_screen->insert_delta) {
382 row_start = first_displayed_row();
383 }
384
385 /* Extending the end is simple. Just walk until we go offscreen or
386 * find an explicit newline. */
387 while (row_end < last_displayed_row()) {
388 if (!m_screen->row_data->is_soft_wrapped(row_end))
389 break;
390 row_end++;
391 }
392
393 invalidate_rows(row_start, row_end);
394}
395
396/* Convenience methods */
397void
398Terminal::invalidate_row(bte::grid::row_t row)
399{
400 invalidate_rows(row, row);
401}
402
403void
404Terminal::invalidate_row_and_context(bte::grid::row_t row)
405{
406 invalidate_rows_and_context(row, row);
407}
408
409/* This is only used by the selection code, so no need to extend the area. */
410void
411Terminal::invalidate(bte::grid::span const& s)
412{
413 if (!s.empty())
414 invalidate_rows(s.start_row(), s.last_row());
415}
416
417/* Invalidates the symmetrical difference ("XOR" area) of the two spans.
418 * This is only used by the selection code, so no need to extend the area. */
419void
420Terminal::invalidate_symmetrical_difference(bte::grid::span const& a, bte::grid::span const& b, bool block)
421{
422 if (a.empty() || b.empty() || a.start() >= b.end() || b.start() >= a.end()) {
423 /* One or both are empty (invalidate() will figure out which), or disjoint intervals. */
424 invalidate (a);
425 invalidate (b);
426 return;
427 }
428
429 if (block) {
430 /* We could optimize when the columns don't change, probably not worth it. */
431 invalidate_rows (std::min (a.start_row(), b.start_row()),
432 std::max (a.last_row(), b.last_row()));
433 } else {
434 if (a.start() != b.start()) {
435 invalidate_rows (std::min (a.start_row(), b.start_row()),
436 std::max (a.start_row(), b.start_row()));
437 }
438 if (a.end() != b.end()) {
439 invalidate_rows (std::min (a.last_row(), b.last_row()),
440 std::max (a.last_row(), b.last_row()));
441 }
442 }
443}
444
445void
446Terminal::invalidate_all()
447{
448 if (G_UNLIKELY (!widget_realized())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!widget_realized()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
449 return;
450
451 if (m_invalidated_all) {
452 return;
453 }
454
455 _bte_debug_print (BTE_DEBUG_WORK, "*")do { } while(0);
456 _bte_debug_print (BTE_DEBUG_UPDATES, "Invalidating all.\n")do { } while(0);
457
458 /* replace invalid regions with one covering the whole terminal */
459 reset_update_rects();
460 m_invalidated_all = TRUE(!(0));
461
462 if (m_active_terminals_link != nullptr) {
463 auto allocation = get_allocated_rect();
464 cairo_rectangle_int_t rect;
465 rect.x = -m_padding.left;
466 rect.y = -m_padding.top;
467 rect.width = allocation.width;
468 rect.height = allocation.height;
469
470 g_array_append_val(m_update_rects, rect)g_array_append_vals (m_update_rects, &(rect), 1);
471 /* Wait a bit before doing any invalidation, just in
472 * case updates are coming in really soon. */
473 add_update_timeout(this);
474 } else {
475 ctk_widget_queue_draw(m_widget);
476 }
477}
478
479/* Find the row in the given position in the backscroll buffer.
480 * Note that calling this method may invalidate the return value of
481 * a previous find_row_data() call. */
482// FIXMEchpe replace this with a method on BteRing
483BteRowData const*
484Terminal::find_row_data(bte::grid::row_t row) const
485{
486 BteRowData const* rowdata = nullptr;
487
488 if (G_LIKELY(_bte_ring_contains(m_screen->row_data, row))(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
_bte_ring_contains(m_screen->row_data, row)) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1))
) {
489 rowdata = _bte_ring_index(m_screen->row_data, row);
490 }
491 return rowdata;
492}
493
494/* Find the row in the given position in the backscroll buffer. */
495// FIXMEchpe replace this with a method on BteRing
496BteRowData*
497Terminal::find_row_data_writable(bte::grid::row_t row) const
498{
499 BteRowData *rowdata = nullptr;
500
501 if (G_LIKELY (_bte_ring_contains(m_screen->row_data, row))(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
_bte_ring_contains(m_screen->row_data, row)) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1))
) {
502 rowdata = _bte_ring_index_writable(m_screen->row_data, row);
503 }
504 return rowdata;
505}
506
507/* Find the character an the given position in the backscroll buffer.
508 * Note that calling this method may invalidate the return value of
509 * a previous find_row_data() call. */
510// FIXMEchpe replace this with a method on BteRing
511BteCell const*
512Terminal::find_charcell(bte::grid::column_t col,
513 bte::grid::row_t row) const
514{
515 BteRowData const* rowdata;
516 BteCell const* ret = nullptr;
517
518 if (_bte_ring_contains(m_screen->row_data, row)) {
519 rowdata = _bte_ring_index(m_screen->row_data, row);
520 ret = _bte_row_data_get (rowdata, col);
521 }
522 return ret;
523}
524
525// FIXMEchpe replace this with a method on BteRing
526bte::grid::column_t
527Terminal::find_start_column(bte::grid::column_t col,
528 bte::grid::row_t row) const
529{
530 BteRowData const* row_data = find_row_data(row);
531 if (G_UNLIKELY (col < 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
col < 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
)
532 return col;
533 if (row_data != nullptr) {
534 const BteCell *cell = _bte_row_data_get (row_data, col);
535 while (col > 0 && cell != NULL__null && cell->attr.fragment()) {
536 cell = _bte_row_data_get (row_data, --col);
537 }
538 }
539 return MAX(col, 0)(((col) > (0)) ? (col) : (0));
540}
541
542// FIXMEchpe replace this with a method on BteRing
543bte::grid::column_t
544Terminal::find_end_column(bte::grid::column_t col,
545 bte::grid::row_t row) const
546{
547 BteRowData const* row_data = find_row_data(row);
548 gint columns = 0;
549 if (G_UNLIKELY (col < 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
col < 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
)
550 return col;
551 if (row_data != NULL__null) {
552 const BteCell *cell = _bte_row_data_get (row_data, col);
553 while (col > 0 && cell != NULL__null && cell->attr.fragment()) {
554 cell = _bte_row_data_get (row_data, --col);
555 }
556 if (cell) {
557 columns = cell->attr.columns() - 1;
558 }
559 }
560 // FIXMEchp m__column_count - 1 ?
561 return MIN(col + columns, m_column_count)(((col + columns) < (m_column_count)) ? (col + columns) : (
m_column_count))
;
562}
563
564/* Sets the line ending to hard wrapped (explicit newline).
565 * Takes care of invalidating if this operation splits a paragraph into two. */
566void
567Terminal::set_hard_wrapped(bte::grid::row_t row)
568{
569 /* We can set the row just above insert_delta to hard wrapped. */
570 g_assert_cmpint(row, >=, m_screen->insert_delta - 1)do { gint64 __n1 = (row), __n2 = (m_screen->insert_delta -
1); if (__n1 >= __n2) ; else g_assertion_message_cmpnum (
"BTE", "../src/bte.cc", 570, ((const char*) (__PRETTY_FUNCTION__
)), "row" " " ">=" " " "m_screen->insert_delta - 1", (long
double) __n1, ">=", (long double) __n2, 'i'); } while (0)
;
571 g_assert_cmpint(row, <, m_screen->insert_delta + m_row_count)do { gint64 __n1 = (row), __n2 = (m_screen->insert_delta +
m_row_count); if (__n1 < __n2) ; else g_assertion_message_cmpnum
("BTE", "../src/bte.cc", 571, ((const char*) (__PRETTY_FUNCTION__
)), "row" " " "<" " " "m_screen->insert_delta + m_row_count"
, (long double) __n1, "<", (long double) __n2, 'i'); } while
(0)
;
572
573 BteRowData *row_data = find_row_data_writable(row);
574
575 /* It's okay for this row not to be covered by the ring. */
576 if (row_data == nullptr || !row_data->attr.soft_wrapped)
577 return;
578
579 row_data->attr.soft_wrapped = false;
580
581 m_ringview.invalidate();
582 invalidate_rows_and_context(row, row + 1);
583}
584
585/* Sets the line ending to soft wrapped (overflow to the next line).
586 * Takes care of invalidating if this operation joins two paragraphs into one.
587 * Also makes sure that the joined new paragraph receives the first one's bidi flags. */
588void
589Terminal::set_soft_wrapped(bte::grid::row_t row)
590{
591 g_assert_cmpint(row, >=, m_screen->insert_delta)do { gint64 __n1 = (row), __n2 = (m_screen->insert_delta);
if (__n1 >= __n2) ; else g_assertion_message_cmpnum ("BTE"
, "../src/bte.cc", 591, ((const char*) (__PRETTY_FUNCTION__))
, "row" " " ">=" " " "m_screen->insert_delta", (long double
) __n1, ">=", (long double) __n2, 'i'); } while (0)
;
592 g_assert_cmpint(row, <, m_screen->insert_delta + m_row_count)do { gint64 __n1 = (row), __n2 = (m_screen->insert_delta +
m_row_count); if (__n1 < __n2) ; else g_assertion_message_cmpnum
("BTE", "../src/bte.cc", 592, ((const char*) (__PRETTY_FUNCTION__
)), "row" " " "<" " " "m_screen->insert_delta + m_row_count"
, (long double) __n1, "<", (long double) __n2, 'i'); } while
(0)
;
593
594 BteRowData *row_data = find_row_data_writable(row);
595 g_assert(row_data != nullptr)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (row_data != nullptr) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 595, ((const char*) (__PRETTY_FUNCTION__
)), "row_data != nullptr"); } while (0)
;
596
597 if (row_data->attr.soft_wrapped)
598 return;
599
600 row_data->attr.soft_wrapped = true;
601
602 /* Each paragraph has to have consistent bidi flags across all of its rows.
603 * Spread the first paragraph's flags across the second one (if they differ). */
604 guint8 bidi_flags = row_data->attr.bidi_flags;
605 bte::grid::row_t i = row + 1;
606 row_data = find_row_data_writable(i);
607 if (row_data != nullptr && row_data->attr.bidi_flags != bidi_flags) {
608 do {
609 row_data->attr.bidi_flags = bidi_flags;
610 if (!row_data->attr.soft_wrapped)
611 break;
612 row_data = find_row_data_writable(++i);
613 } while (row_data != nullptr);
614 }
615
616 m_ringview.invalidate();
617 invalidate_rows_and_context(row, row + 1);
618}
619
620/* Determine the width of the portion of the preedit string which lies
621 * to the left of the cursor, or the entire string, in columns. */
622// FIXMEchpe this is for the view, so use int not gssize
623// FIXMEchpe this is only ever called with left_only=false, so remove the param
624gssize
625Terminal::get_preedit_width(bool left_only)
626{
627 gssize ret = 0;
628
629 char const *preedit = m_im_preedit.c_str();
630 for (int i = 0;
631 // FIXMEchpe preddit is != NULL at the start, and next_char never returns NULL either
632 (preedit != NULL__null) &&
633 (preedit[0] != '\0') &&
634 (!left_only || (i < m_im_preedit_cursor));
635 i++) {
636 gunichar c = g_utf8_get_char(preedit);
637 ret += _bte_unichar_width(c, m_utf8_ambiguous_width);
638 preedit = g_utf8_next_char(preedit)(char *)((preedit) + g_utf8_skip[*(const guchar *)(preedit)]);
639 }
640
641 return ret;
642}
643
644/* Determine the length of the portion of the preedit string which lies
645 * to the left of the cursor, or the entire string, in gunichars. */
646// FIXMEchpe this returns gssize but inside it uses int...
647gssize
648Terminal::get_preedit_length(bool left_only)
649{
650 ssize_t i = 0;
651
652 char const *preedit = m_im_preedit.c_str();
653 for (i = 0;
654 // FIXMEchpe useless check, see above
655 (preedit != NULL__null) &&
656 (preedit[0] != '\0') &&
657 (!left_only || (i < m_im_preedit_cursor));
658 i++) {
659 preedit = g_utf8_next_char(preedit)(char *)((preedit) + g_utf8_skip[*(const guchar *)(preedit)]);
660 }
661
662 return i;
663}
664
665void
666Terminal::invalidate_cursor_once(bool periodic)
667{
668 if (G_UNLIKELY(!widget_realized())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!widget_realized()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
669 return;
670
671 if (m_invalidated_all) {
672 return;
673 }
674
675 if (periodic) {
676 if (!m_cursor_blinks) {
677 return;
678 }
679 }
680
681 if (m_modes_private.DEC_TEXT_CURSOR()) {
682 auto row = m_screen->cursor.row;
683
684 _bte_debug_print(BTE_DEBUG_UPDATES,do { } while(0)
685 "Invalidating cursor in row %ld.\n",do { } while(0)
686 row)do { } while(0);
687 invalidate_row(row);
688 }
689}
690
691/* Invalidate the cursor repeatedly. */
692// FIXMEchpe this continually adds and removes the blink timeout. Find a better solution
693bool
694Terminal::cursor_blink_timer_callback()
695{
696 m_cursor_blink_state = !m_cursor_blink_state;
697 m_cursor_blink_time += m_cursor_blink_cycle;
698
699 invalidate_cursor_once(true);
700
701 /* only disable the blink if the cursor is currently shown.
702 * else, wait until next time.
703 */
704 if (m_cursor_blink_time / 1000 >= m_cursor_blink_timeout &&
705 m_cursor_blink_state) {
706 return false;
707 }
708
709 m_cursor_blink_timer.schedule(m_cursor_blink_cycle, bte::glib::Timer::Priority::eLOW);
710 return false;
711}
712
713/* Emit a "selection_changed" signal. */
714void
715Terminal::emit_selection_changed()
716{
717 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
718 "Emitting `selection-changed'.\n")do { } while(0);
719 g_signal_emit(m_terminal, signals[SIGNAL_SELECTION_CHANGED], 0);
720}
721
722/* Emit a "commit" signal.
723 * FIXMEchpe: remove this function
724 */
725void
726Terminal::emit_commit(std::string_view const& str)
727{
728 if (str.size() == 0)
729 return;
730
731 if (!widget() || !widget()->should_emit_signal(SIGNAL_COMMIT))
732 return;
733
734 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
735 "Emitting `commit' of %" G_GSSIZE_FORMAT" bytes.\n", str.size())do { } while(0);
736
737 // FIXMEchpe we do know for a fact that all uses of this function
738 // actually passed a 0-terminated string, so we can use @str directly
739 std::string result{str}; // 0-terminated
740
741 _BTE_DEBUG_IF(BTE_DEBUG_KEYBOARD)if (0) {
742 for (size_t i = 0; i < result.size(); i++) {
743 if ((((guint8) result[i]) < 32) ||
744 (((guint8) result[i]) > 127)) {
745 g_printerr(
746 "Sending <%02x> "
747 "to child.\n",
748 result[i]);
749 } else {
750 g_printerr(
751 "Sending '%c' "
752 "to child.\n",
753 result[i]);
754 }
755 }
756 }
757
758 g_signal_emit(m_terminal, signals[SIGNAL_COMMIT], 0, result.c_str(), (guint)result.size());
759}
760
761void
762Terminal::queue_contents_changed()
763{
764 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
765 "Queueing `contents-changed'.\n")do { } while(0);
766 m_contents_changed_pending = true;
767}
768
769//FIXMEchpe this has only one caller
770void
771Terminal::queue_cursor_moved()
772{
773 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
774 "Queueing `cursor-moved'.\n")do { } while(0);
775 m_cursor_moved_pending = true;
776}
777
778void
779Terminal::emit_eof()
780{
781 if (widget())
782 widget()->emit_eof();
783}
784
785static gboolean
786emit_eof_idle_cb(BteTerminal *terminal)
787try
788{
789 _bte_terminal_get_impl(terminal)->emit_eof();
790
791 return G_SOURCE_REMOVE(0);
792}
793catch (...)
794{
795 bte::log_exception();
796 return G_SOURCE_REMOVE(0);
797}
798
799void
800Terminal::queue_eof()
801{
802 _bte_debug_print(BTE_DEBUG_SIGNALS, "Queueing `eof'.\n")do { } while(0);
803
804 g_idle_add_full(G_PRIORITY_HIGH-100,
805 (GSourceFunc)emit_eof_idle_cb,
806 g_object_ref(m_terminal),
807 g_object_unref);
808}
809
810void
811Terminal::emit_child_exited()
812{
813 auto const status = m_child_exit_status;
814 m_child_exit_status = -1;
815
816 if (widget())
817 widget()->emit_child_exited(status);
818}
819
820static gboolean
821emit_child_exited_idle_cb(BteTerminal *terminal)
822try
823{
824 _bte_terminal_get_impl(terminal)->emit_child_exited();
825
826 return G_SOURCE_REMOVE(0);
827}
828catch (...)
829{
830 bte::log_exception();
831 return G_SOURCE_REMOVE(0);
832}
833
834/* Emit a "child-exited" signal on idle, so that if the handler destroys
835 * the terminal, we're not deep within terminal code callstack
836 */
837void
838Terminal::queue_child_exited()
839{
840 _bte_debug_print(BTE_DEBUG_SIGNALS, "Queueing `child-exited'.\n")do { } while(0);
841 m_child_exited_after_eos_pending = false;
842
843 g_idle_add_full(G_PRIORITY_HIGH-100,
844 (GSourceFunc)emit_child_exited_idle_cb,
845 g_object_ref(m_terminal),
846 g_object_unref);
847}
848
849bool
850Terminal::child_exited_eos_wait_callback()
851{
852 /* If we get this callback, there has been some time elapsed
853 * after child-exited, but no EOS yet. This happens for example
854 * when the primary child started other processes in the background,
855 * which inherited the PTY, and thus keep it open, see
856 * https://gitlab.gnome.org/GNOME/bte/issues/204
857 *
858 * Force an EOS.
859 */
860 if (pty())
861 pty_io_read(pty()->fd(), G_IO_HUP);
862
863 return false; // don't run again
864}
865
866/* Emit a "char-size-changed" signal. */
867void
868Terminal::emit_char_size_changed(int width,
869 int height)
870{
871 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
872 "Emitting `char-size-changed'.\n")do { } while(0);
873 /* FIXME on next API break, change the signature */
874 g_signal_emit(m_terminal, signals[SIGNAL_CHAR_SIZE_CHANGED], 0,
875 (guint)width, (guint)height);
876}
877
878/* Emit an "increase-font-size" signal. */
879void
880Terminal::emit_increase_font_size()
881{
882 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
883 "Emitting `increase-font-size'.\n")do { } while(0);
884 g_signal_emit(m_terminal, signals[SIGNAL_INCREASE_FONT_SIZE], 0);
885}
886
887/* Emit a "decrease-font-size" signal. */
888void
889Terminal::emit_decrease_font_size()
890{
891 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
892 "Emitting `decrease-font-size'.\n")do { } while(0);
893 g_signal_emit(m_terminal, signals[SIGNAL_DECREASE_FONT_SIZE], 0);
894}
895
896/* Emit a "text-inserted" signal. */
897void
898Terminal::emit_text_inserted()
899{
900#ifdef WITH_A11Y
901 if (!m_accessible_emit) {
902 return;
903 }
904 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
905 "Emitting `text-inserted'.\n")do { } while(0);
906 g_signal_emit(m_terminal, signals[SIGNAL_TEXT_INSERTED], 0);
907#endif
908}
909
910/* Emit a "text-deleted" signal. */
911void
912Terminal::emit_text_deleted()
913{
914#ifdef WITH_A11Y
915 if (!m_accessible_emit) {
916 return;
917 }
918 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
919 "Emitting `text-deleted'.\n")do { } while(0);
920 g_signal_emit(m_terminal, signals[SIGNAL_TEXT_DELETED], 0);
921#endif
922}
923
924/* Emit a "text-modified" signal. */
925void
926Terminal::emit_text_modified()
927{
928#ifdef WITH_A11Y
929 if (!m_accessible_emit) {
930 return;
931 }
932 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
933 "Emitting `text-modified'.\n")do { } while(0);
934 g_signal_emit(m_terminal, signals[SIGNAL_TEXT_MODIFIED], 0);
935#endif
936}
937
938/* Emit a "text-scrolled" signal. */
939void
940Terminal::emit_text_scrolled(long delta)
941{
942#ifdef WITH_A11Y
943 if (!m_accessible_emit) {
944 return;
945 }
946 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
947 "Emitting `text-scrolled'(%ld).\n", delta)do { } while(0);
948 // FIXMEchpe fix signal signature?
949 g_signal_emit(m_terminal, signals[SIGNAL_TEXT_SCROLLED], 0, (int)delta);
950#endif
951}
952
953void
954Terminal::emit_copy_clipboard()
955{
956 _bte_debug_print(BTE_DEBUG_SIGNALS, "Emitting 'copy-clipboard'.\n")do { } while(0);
957 g_signal_emit(m_terminal, signals[SIGNAL_COPY_CLIPBOARD], 0);
958}
959
960void
961Terminal::emit_paste_clipboard()
962{
963 _bte_debug_print(BTE_DEBUG_SIGNALS, "Emitting 'paste-clipboard'.\n")do { } while(0);
964 g_signal_emit(m_terminal, signals[SIGNAL_PASTE_CLIPBOARD], 0);
965}
966
967/* Emit a "hyperlink_hover_uri_changed" signal. */
968void
969Terminal::emit_hyperlink_hover_uri_changed(const CdkRectangle *bbox)
970{
971 GObject *object = G_OBJECT(m_terminal)((((GObject*) (void *) g_type_check_instance_cast ((GTypeInstance
*) ((m_terminal)), (((GType) ((20) << (2))))))))
;
972
973 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
974 "Emitting `hyperlink-hover-uri-changed'.\n")do { } while(0);
975 g_signal_emit(m_terminal, signals[SIGNAL_HYPERLINK_HOVER_URI_CHANGED], 0, m_hyperlink_hover_uri, bbox);
976 g_object_notify_by_pspec(object, pspecs[PROP_HYPERLINK_HOVER_URI]);
977}
978
979void
980Terminal::deselect_all()
981{
982 if (!m_selection_resolved.empty()) {
983 _bte_debug_print(BTE_DEBUG_SELECTION,do { } while(0)
984 "Deselecting all text.\n")do { } while(0);
985
986 m_selection_origin = m_selection_last = { -1, -1, 1 };
987 resolve_selection();
988
989 /* Don't free the current selection, as we need to keep
990 * hold of it for async copying from the clipboard. */
991
992 emit_selection_changed();
993 }
994}
995
996/* Clear the cache of the screen contents we keep. */
997void
998Terminal::match_contents_clear()
999{
1000 match_hilite_clear();
1001 if (m_match_contents != nullptr) {
1002 g_free(m_match_contents);
1003 m_match_contents = nullptr;
1004 }
1005 if (m_match_attributes != nullptr) {
1006 g_array_free(m_match_attributes, TRUE(!(0)));
1007 m_match_attributes = nullptr;
1008 }
1009}
1010
1011void
1012Terminal::match_contents_refresh()
1013
1014{
1015 match_contents_clear();
1016 GArray *array = g_array_new(FALSE(0), TRUE(!(0)), sizeof(struct _BteCharAttributes));
1017 auto match_contents = get_text_displayed(true /* wrap */,
1018 array);
1019 m_match_contents = g_string_free(match_contents, FALSE(0));
1020 m_match_attributes = array;
1021}
1022
1023void
1024Terminal::regex_match_remove_all() noexcept
1025{
1026 auto& match_regexes = match_regexes_writable();
1027 match_regexes.clear();
1028 match_regexes.shrink_to_fit();
1029
1030 match_hilite_clear();
1031}
1032
1033void
1034Terminal::regex_match_remove(int tag) noexcept
1035{
1036 auto i = regex_match_get_iter(tag);
1037 if (i == std::end(m_match_regexes))
1038 return;
1039
1040 match_regexes_writable().erase(i);
1041}
1042
1043/*
1044 * match_rowcol_to_offset:
1045 * @terminal:
1046 * @column:
1047 * @row:
1048 * @offset_ptr: (out):
1049 * @sattr_ptr: (out):
1050 * @ettr_ptr: (out):
1051 *
1052 * Maps (row, column) to an offset in m_match_attributes, and returns
1053 * that offset in @offset_ptr, and the start and end of the corresponding
1054 * line in @sattr_ptr and @eattr_ptr.
1055 */
1056bool
1057Terminal::match_rowcol_to_offset(bte::grid::column_t column,
1058 bte::grid::row_t row,
1059 gsize *offset_ptr,
1060 gsize *sattr_ptr,
1061 gsize *eattr_ptr)
1062{
1063 /* FIXME: use gsize, after making sure the code below doesn't underflow offset */
1064 gssize offset, sattr, eattr;
1065 struct _BteCharAttributes *attr = NULL__null;
1066
1067 /* Map the pointer position to a portion of the string. */
1068 // FIXME do a bsearch here?
1069 eattr = m_match_attributes->len;
1070 for (offset = eattr; offset--; ) {
1071 attr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(offset)])
1072 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(offset)])
1073 offset)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(offset)])
;
1074 if (row < attr->row) {
1075 eattr = offset;
1076 }
1077 if (row == attr->row &&
1078 column >= attr->column && column < attr->column + attr->columns) {
1079 break;
1080 }
1081 }
1082
1083 _BTE_DEBUG_IF(BTE_DEBUG_REGEX)if (0) {
1084 if (offset < 0)
1085 g_printerr("Cursor is not on a character.\n");
1086 else {
1087 gunichar c;
1088 char utf[7];
1089 c = g_utf8_get_char (m_match_contents + offset);
1090 utf[g_unichar_to_utf8(g_unichar_isprint(c) ? c : 0xFFFD, utf)] = 0;
1091
1092 g_printerr("Cursor is on character U+%04X '%s' at %" G_GSSIZE_FORMAT"li" ".\n",
1093 c, utf, offset);
1094 }
1095 }
1096
1097 /* If the pointer isn't on a matchable character, bug out. */
1098 if (offset < 0) {
1099 return false;
1100 }
1101
1102 /* If the pointer is on a newline, bug out. */
1103 if (m_match_contents[offset] == '\0') {
1104 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
1105 "Cursor is on newline.\n")do { } while(0);
1106 return false;
1107 }
1108
1109 /* Snip off any final newlines. */
1110 while (m_match_contents[eattr] == '\n' ||
1111 m_match_contents[eattr] == '\0') {
1112 eattr--;
1113 }
1114 /* and scan forwards to find the end of this line */
1115 while (!(m_match_contents[eattr] == '\n' ||
1116 m_match_contents[eattr] == '\0')) {
1117 eattr++;
1118 }
1119
1120 /* find the start of row */
1121 if (row == 0) {
1122 sattr = 0;
1123 } else {
1124 for (sattr = offset; sattr > 0; sattr--) {
1125 attr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(sattr)])
1126 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(sattr)])
1127 sattr)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(sattr)])
;
1128 if (row > attr->row) {
1129 break;
1130 }
1131 }
1132 }
1133 /* Scan backwards to find the start of this line */
1134 while (sattr > 0 &&
1135 ! (m_match_contents[sattr] == '\n' ||
1136 m_match_contents[sattr] == '\0')) {
1137 sattr--;
1138 }
1139 /* and skip any initial newlines. */
1140 while (m_match_contents[sattr] == '\n' ||
1141 m_match_contents[sattr] == '\0') {
1142 sattr++;
1143 }
1144 if (eattr <= sattr) { /* blank line */
1145 return false;
1146 }
1147 if (eattr <= offset || sattr > offset) {
1148 /* nothing to match on this line */
1149 return false;
1150 }
1151
1152 *offset_ptr = offset;
1153 *sattr_ptr = sattr;
1154 *eattr_ptr = eattr;
1155
1156 _BTE_DEBUG_IF(BTE_DEBUG_REGEX)if (0) {
1157 struct _BteCharAttributes *_sattr, *_eattr;
1158 _sattr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(sattr)])
1159 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(sattr)])
1160 sattr)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(sattr)])
;
1161 _eattr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(eattr - 1)])
1162 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(eattr - 1)])
1163 eattr - 1)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(eattr - 1)])
;
1164 g_printerr("Cursor is in line from %" G_GSIZE_FORMAT"lu" "(%ld,%ld) to %" G_GSIZE_FORMAT"lu" "(%ld,%ld)\n",
1165 sattr, _sattr->column, _sattr->row,
1166 eattr - 1, _eattr->column, _eattr->row);
1167 }
1168
1169 return true;
1170}
1171
1172/* creates a pcre match context with appropriate limits */
1173pcre2_match_context_8 *
1174Terminal::create_match_context()
1175{
1176 pcre2_match_context_8 *match_context;
1177
1178 match_context = pcre2_match_context_create_8(nullptr /* general context */);
1179 pcre2_set_match_limit_8(match_context, 65536); /* should be plenty */
1180 pcre2_set_recursion_limit_8(match_context, 64); /* should be plenty */
1181
1182 return match_context;
1183}
1184
1185bool
1186Terminal::match_check_pcre(pcre2_match_data_8 *match_data,
1187 pcre2_match_context_8 *match_context,
1188 bte::base::Regex const* regex,
1189 uint32_t match_flags,
1190 gsize sattr,
1191 gsize eattr,
1192 gsize offset,
1193 char **result_ptr,
1194 gsize *start,
1195 gsize *end,
1196 gsize *sblank_ptr,
1197 gsize *eblank_ptr)
1198{
1199 int (* match_fn) (const pcre2_code_8 *,
1200 PCRE2_SPTR8, PCRE2_SIZEsize_t, PCRE2_SIZEsize_t, uint32_t,
1201 pcre2_match_data_8 *, pcre2_match_context_8 *);
1202 gsize sblank = 0, eblank = G_MAXSIZE(9223372036854775807L *2UL+1UL);
1203 gsize position, line_length;
1204 const char *line;
1205 int r = 0;
1206
1207 if (regex->jited())
1208 match_fn = pcre2_jit_match_8;
1209 else
1210 match_fn = pcre2_match_8;
1211
1212 line = m_match_contents;
1213 /* FIXME: what we really want is to pass the whole data to pcre2_match, but
1214 * limit matching to between sattr and eattr, so that the extra data can
1215 * satisfy lookahead assertions. This needs new pcre2 API though.
1216 */
1217 line_length = eattr;
1218
1219 /* Iterate throught the matches until we either find one which contains the
1220 * offset, or we get no more matches.
1221 */
1222 pcre2_set_offset_limit_8(match_context, eattr);
1223 position = sattr;
1224 while (position < eattr &&
1225 ((r = match_fn(regex->code(),
1226 (PCRE2_SPTR8)line, line_length, /* subject, length */
1227 position, /* start offset */
1228 match_flags |
1229 PCRE2_NO_UTF_CHECK0x40000000u | PCRE2_NOTEMPTY0x00000004u | PCRE2_PARTIAL_SOFT0x00000010u /* FIXME: HARD? */,
1230 match_data,
1231 match_context)) >= 0 || r == PCRE2_ERROR_PARTIAL(-2))) {
1232 gsize ko = offset;
1233 gsize rm_so, rm_eo;
1234 gsize *ovector;
1235
1236 ovector = pcre2_get_ovector_pointer_8(match_data);
1237 rm_so = ovector[0];
1238 rm_eo = ovector[1];
1239 if (G_UNLIKELY(rm_so == PCRE2_UNSET || rm_eo == PCRE2_UNSET)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
rm_so == (~(size_t)0) || rm_eo == (~(size_t)0)) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 0))
)
1240 break;
1241
1242 /* The offsets should be "sane". We set NOTEMPTY, but check anyway */
1243 if (G_UNLIKELY(position == rm_eo)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
position == rm_eo) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 0))
) {
1244 /* rm_eo is before the end of subject string's length, so this is safe */
1245 position = g_utf8_next_char(line + rm_eo)(char *)((line + rm_eo) + g_utf8_skip[*(const guchar *)(line +
rm_eo)])
- line;
1246 continue;
1247 }
1248
1249 _BTE_DEBUG_IF(BTE_DEBUG_REGEX)if (0) {
1250 gchar *result;
1251 struct _BteCharAttributes *_sattr, *_eattr;
1252 result = g_strndup(line + rm_so, rm_eo - rm_so);
1253 _sattr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(rm_so)])
1254 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(rm_so)])
1255 rm_so)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(rm_so)])
;
1256 _eattr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(rm_eo - 1)])
1257 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(rm_eo - 1)])
1258 rm_eo - 1)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(rm_eo - 1)])
;
1259 g_printerr("%s match `%s' from %" G_GSIZE_FORMAT"lu" "(%ld,%ld) to %" G_GSIZE_FORMAT"lu" "(%ld,%ld) (%" G_GSSIZE_FORMAT"li" ").\n",
1260 r == PCRE2_ERROR_PARTIAL(-2) ? "Partial":"Full",
1261 result,
1262 rm_so,
1263 _sattr->column,
1264 _sattr->row,
1265 rm_eo - 1,
1266 _eattr->column,
1267 _eattr->row,
1268 offset);
1269 g_free(result);
1270 }
1271
1272 /* advance position */
1273 position = rm_eo;
1274
1275 /* FIXME: do handle newline / partial matches at end of line/start of next line */
1276 if (r == PCRE2_ERROR_PARTIAL(-2))
1277 continue;
1278
1279 /* If the pointer is in this substring, then we're done. */
1280 if (ko >= rm_so && ko < rm_eo) {
1281 *result_ptr = g_strndup(line + rm_so, rm_eo - rm_so);
1282 *start = rm_so;
1283 *end = rm_eo - 1;
1284 return true;
1285 }
1286
1287 if (ko >= rm_eo && rm_eo > sblank) {
1288 sblank = rm_eo;
1289 }
1290 if (ko < rm_so && rm_so < eblank) {
1291 eblank = rm_so;
1292 }
1293 }
1294
1295 if (G_UNLIKELY(r < PCRE2_ERROR_PARTIAL)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
r < (-2)) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
)
1296 _bte_debug_print(BTE_DEBUG_REGEX, "Unexpected pcre2_match error code: %d\n", r)do { } while(0);
1297
1298 *sblank_ptr = sblank;
1299 *eblank_ptr = eblank;
1300 return false;
1301}
1302
1303char *
1304Terminal::match_check_internal_pcre(bte::grid::column_t column,
1305 bte::grid::row_t row,
1306 MatchRegex const** match,
1307 size_t* start,
1308 size_t* end)
1309{
1310 gsize offset, sattr, eattr, start_blank, end_blank;
1311 pcre2_match_data_8 *match_data;
1312 pcre2_match_context_8 *match_context;
1313
1314 _bte_debug_print(BTE_DEBUG_REGEX,do { } while(0)
1315 "Checking for pcre match at (%ld,%ld).\n", row, column)do { } while(0);
1316
1317 if (!match_rowcol_to_offset(column, row,
1318 &offset, &sattr, &eattr))
1319 return nullptr;
1320
1321 start_blank = sattr;
1322 end_blank = eattr;
1323
1324 match_context = create_match_context();
1325 match_data = pcre2_match_data_create_8(256 /* should be plenty */, NULL__null /* general context */);
1326
1327 /* Now iterate over each regex we need to match against. */
1328 char* dingu_match{nullptr};
1329 for (auto const& rem : m_match_regexes) {
1330 gsize sblank, eblank;
1331
1332 if (match_check_pcre(match_data, match_context,
1333 rem.regex(),
1334 rem.match_flags(),
1335 sattr, eattr, offset,
1336 &dingu_match,
1337 start, end,
1338 &sblank, &eblank)) {
1339 _bte_debug_print(BTE_DEBUG_REGEX, "Matched dingu with tag %d\n", rem.tag())do { } while(0);
1340 *match = std::addressof(rem);
1341 break;
1342 }
1343
1344 if (sblank > start_blank) {
1345 start_blank = sblank;
1346 }
1347 if (eblank < end_blank) {
1348 end_blank = eblank;
1349 }
1350 }
1351
1352 if (dingu_match == nullptr) {
1353 /* If we get here, there was no dingu match.
1354 * Record smallest span where none of the dingus match.
1355 */
1356 *start = start_blank;
1357 *end = end_blank - 1;
1358 *match = nullptr;
1359
1360 _BTE_DEBUG_IF(BTE_DEBUG_REGEX)if (0) {
1361 struct _BteCharAttributes *_sattr, *_eattr;
1362 _sattr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(start_blank)])
1363 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(start_blank)])
1364 start_blank)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(start_blank)])
;
1365 _eattr = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(end_blank - 1)])
1366 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(end_blank - 1)])
1367 end_blank - 1)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(end_blank - 1)])
;
1368 g_printerr("No-match region from %" G_GSIZE_FORMAT"lu" "(%ld,%ld) to %" G_GSIZE_FORMAT"lu" "(%ld,%ld)\n",
1369 start_blank, _sattr->column, _sattr->row,
1370 end_blank - 1, _eattr->column, _eattr->row);
1371 }
1372 }
1373
1374 pcre2_match_data_free_8(match_data);
1375 pcre2_match_context_free_8(match_context);
1376
1377 return dingu_match;
1378}
1379
1380/*
1381 * bte_terminal_match_check_internal:
1382 * @terminal:
1383 * @column:
1384 * @row:
1385 * @match: (out):
1386 * @start: (out):
1387 * @end: (out):
1388 *
1389 * Checks m_match_contents for dingu matches, and returns the start, and
1390 * end of the match in @start, @end, and the matched regex in @match.
1391 * If no match occurs, @match will be set to %nullptr,
1392 * and if they are nonzero, @start and @end mark the smallest span in the @row
1393 * in which none of the dingus match.
1394 *
1395 * Returns: (transfer full): the matched string, or %nullptr
1396 */
1397char *
1398Terminal::match_check_internal(bte::grid::column_t column,
1399 bte::grid::row_t row,
1400 MatchRegex const** match,
1401 size_t* start,
1402 size_t* end)
1403{
1404 if (m_match_contents == nullptr) {
1405 match_contents_refresh();
1406 }
1407
1408 assert(match != nullptr)(static_cast <bool> (match != nullptr) ? void (0) : __assert_fail
("match != nullptr", "../src/bte.cc", 1408, __extension__ __PRETTY_FUNCTION__
))
;
1409 assert(start != nullptr)(static_cast <bool> (start != nullptr) ? void (0) : __assert_fail
("start != nullptr", "../src/bte.cc", 1409, __extension__ __PRETTY_FUNCTION__
))
;
1410 assert(end != nullptr)(static_cast <bool> (end != nullptr) ? void (0) : __assert_fail
("end != nullptr", "../src/bte.cc", 1410, __extension__ __PRETTY_FUNCTION__
))
;
1411
1412 *match = nullptr;
1413 *start = 0;
1414 *end = 0;
1415
1416 return match_check_internal_pcre(column, row, match, start, end);
1417}
1418
1419char*
1420Terminal::regex_match_check(bte::grid::column_t column,
1421 bte::grid::row_t row,
1422 int* tag)
1423{
1424 long delta = m_screen->scroll_delta;
1425 _bte_debug_print(BTE_DEBUG_EVENTS | BTE_DEBUG_REGEX,do { } while(0)
1426 "Checking for match at (%ld,%ld).\n",do { } while(0)
1427 row, column)do { } while(0);
1428
1429 char* ret{nullptr};
1430 Terminal::MatchRegex const* match{nullptr};
1431
1432 if (m_match_span.contains(row + delta, column)) {
1433 match = regex_match_current(); /* may be nullptr */
1434 ret = g_strdup(m_match);
1435 } else {
1436 gsize start, end;
1437
1438 ret = match_check_internal(column, row + delta,
1439 &match,
1440 &start, &end);
1441 }
1442 _BTE_DEBUG_IF(BTE_DEBUG_EVENTS | BTE_DEBUG_REGEX)if (0) {
1443 if (ret != NULL__null) g_printerr("Matched `%s'.\n", ret);
1444 }
1445 if (tag != nullptr)
1446 *tag = (match != nullptr) ? match->tag() : -1;
1447
1448 return ret;
1449}
1450
1451/*
1452 * Terminal::view_coords_from_event:
1453 * @event: a mouse event
1454 *
1455 * Translates the event coordinates to view coordinates, by
1456 * subtracting the padding and window offset.
1457 * Coordinates < 0 or >= m_usable_view_extents.width() or .height()
1458 * mean that the event coordinates are outside the usable area
1459 * at that side; use view_coords_visible() to check for that.
1460 */
1461bte::view::coords
1462Terminal::view_coords_from_event(MouseEvent const& event) const
1463{
1464 return bte::view::coords(event.x() - m_padding.left, event.y() - m_padding.top);
1465}
1466
1467bool
1468Terminal::widget_realized() const noexcept
1469{
1470 return m_real_widget ? m_real_widget->realized() : false;
1471}
1472
1473/*
1474 * Terminal::grid_coords_from_event:
1475 * @event: a mouse event
1476 *
1477 * Translates the event coordinates to view coordinates, by
1478 * subtracting the padding and window offset.
1479 * Coordinates < 0 or >= m_usable_view_extents.width() or .height()
1480 * mean that the event coordinates are outside the usable area
1481 * at that side; use grid_coords_visible() to check for that.
1482 */
1483bte::grid::coords
1484Terminal::grid_coords_from_event(MouseEvent const& event) const
1485{
1486 return grid_coords_from_view_coords(view_coords_from_event(event));
1487}
1488
1489/*
1490 * Terminal::confined_grid_coords_from_event:
1491 * @event: a mouse event
1492 *
1493 * Like grid_coords_from_event(), but also confines the coordinates
1494 * to an actual cell in the visible area.
1495 */
1496bte::grid::coords
1497Terminal::confined_grid_coords_from_event(MouseEvent const& event) const
1498{
1499 auto pos = view_coords_from_event(event);
1500 return confined_grid_coords_from_view_coords(pos);
1501}
1502
1503/*
1504 * Terminal::grid_coords_from_view_coords:
1505 * @pos: the view coordinates
1506 *
1507 * Translates view coordinates to grid coordinates. If the view coordinates point to
1508 * cells that are not visible, may return any value < 0 or >= m_column_count, and
1509 * < first_displayed_row() or > last_displayed_row(), resp.
1510 */
1511bte::grid::coords
1512Terminal::grid_coords_from_view_coords(bte::view::coords const& pos) const
1513{
1514 /* Our caller had to update the ringview (we can't do because we're const). */
1515 g_assert(m_ringview.is_updated())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_ringview.is_updated()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 1515, ((const char*) (__PRETTY_FUNCTION__
)), "m_ringview.is_updated()"); } while (0)
;
1516
1517 bte::grid::column_t col;
1518 if (pos.x >= 0 && pos.x < m_view_usable_extents.width())
1519 col = pos.x / m_cell_width;
1520 else if (pos.x < 0)
1521 col = -1;
1522 else
1523 col = m_column_count;
1524
1525 bte::grid::row_t row = pixel_to_row(pos.y);
1526
1527 /* BiDi: convert to logical column. */
1528 bte::base::BidiRow const* bidirow = m_ringview.get_bidirow(confine_grid_row(row));
1529 col = bidirow->vis2log(col);
1530
1531 return bte::grid::coords(row, col);
1532}
1533
1534bte::grid::row_t
1535Terminal::confine_grid_row(bte::grid::row_t const& row) const
1536{
1537 auto first_row = first_displayed_row();
1538 auto last_row = last_displayed_row();
1539
1540 return bte::clamp(row, first_row, last_row);
1541}
1542
1543/*
1544 * Terminal::confined_grid_coords_from_view_coords:
1545 * @pos: the view coordinates
1546 *
1547 * Like grid_coords_from_view_coords(), but also confines the coordinates
1548 * to an actual cell in the visible area.
1549 */
1550bte::grid::coords
1551Terminal::confined_grid_coords_from_view_coords(bte::view::coords const& pos) const
1552{
1553 auto rowcol = grid_coords_from_view_coords(pos);
1554 return confine_grid_coords(rowcol);
1555}
1556
1557/*
1558 * Terminal::view_coords_from_grid_coords:
1559 * @rowcol: the grid coordinates
1560 *
1561 * Translates grid coordinates to view coordinates. If the view coordinates are
1562 * outside the usable area, may return any value < 0 or >= m_usable_view_extents.
1563 *
1564 * Returns: %true if the coordinates are inside the usable area
1565 */
1566bte::view::coords
1567Terminal::view_coords_from_grid_coords(bte::grid::coords const& rowcol) const
1568{
1569 return bte::view::coords(rowcol.column() * m_cell_width,
1570 row_to_pixel(rowcol.row()));
1571}
1572
1573bool
1574Terminal::view_coords_visible(bte::view::coords const& pos) const
1575{
1576 return pos.x >= 0 && pos.x < m_view_usable_extents.width() &&
1577 pos.y >= 0 && pos.y < m_view_usable_extents.height();
1578}
1579
1580bool
1581Terminal::grid_coords_visible(bte::grid::coords const& rowcol) const
1582{
1583 return rowcol.column() >= 0 &&
1584 rowcol.column() < m_column_count &&
1585 rowcol.row() >= first_displayed_row() &&
1586 rowcol.row() <= last_displayed_row();
1587}
1588
1589bte::grid::coords
1590Terminal::confine_grid_coords(bte::grid::coords const& rowcol) const
1591{
1592 /* Confine clicks to the nearest actual cell. This is especially useful for
1593 * fullscreen bte so that you can click on the very edge of the screen.
1594 */
1595 auto first_row = first_displayed_row();
1596 auto last_row = last_displayed_row();
1597
1598 return bte::grid::coords(CLAMP(rowcol.row(), first_row, last_row)(((rowcol.row()) > (last_row)) ? (last_row) : (((rowcol.row
()) < (first_row)) ? (first_row) : (rowcol.row())))
,
1599 CLAMP(rowcol.column(), 0, m_column_count - 1)(((rowcol.column()) > (m_column_count - 1)) ? (m_column_count
- 1) : (((rowcol.column()) < (0)) ? (0) : (rowcol.column(
))))
);
1600}
1601
1602/*
1603 * Track mouse click and drag positions (the "origin" and "last" coordinates) with half cell accuracy,
1604 * that is, know whether the event occurred over the left/start or right/end half of the cell.
1605 * This is required because some selection modes care about the cell over which the event occurred,
1606 * while some care about the closest boundary between cells.
1607 *
1608 * Storing the actual view coordinates would become problematic when the font size changes (bug 756058),
1609 * and would cause too much work when the mouse moves within the half cell.
1610 *
1611 * Left/start margin or anything further to the left/start is denoted by column -1's right half,
1612 * right/end margin or anything further to the right/end is denoted by column m_column_count's left half.
1613 *
1614 * BiDi: returns logical position (start or end) for normal selection modes, visual position (left or
1615 * right) for block mode.
1616 */
1617bte::grid::halfcoords
1618Terminal::selection_grid_halfcoords_from_view_coords(bte::view::coords const& pos) const
1619{
1620 /* Our caller had to update the ringview (we can't do because we're const). */
1621 g_assert(m_ringview.is_updated())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_ringview.is_updated()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 1621, ((const char*) (__PRETTY_FUNCTION__
)), "m_ringview.is_updated()"); } while (0)
;
1622
1623 bte::grid::row_t row = pixel_to_row(pos.y);
1624 bte::grid::column_t col;
1625 bte::grid::half_t half;
1626
1627 if (pos.x < 0) {
1628 col = -1;
1629 half = 1;
1630 } else if (pos.x >= m_column_count * m_cell_width) {
1631 col = m_column_count;
1632 half = 0;
1633 } else {
1634 col = pos.x / m_cell_width;
1635 half = (pos.x * 2 / m_cell_width) % 2;
1636 }
1637
1638 if (!m_selection_block_mode) {
1639 /* BiDi: convert from visual to logical half column. */
1640 bte::base::BidiRow const* bidirow = m_ringview.get_bidirow(confine_grid_row(row));
1641
1642 if (bidirow->vis_is_rtl(col))
1643 half = 1 - half;
1644 col = bidirow->vis2log(col);
1645 }
1646
1647 return { row, bte::grid::halfcolumn_t(col, half) };
1648}
1649
1650/*
1651 * Called on Shift+Click to continue (extend or shrink) the previous selection.
1652 * Swaps the two endpoints of the selection if needed, so that m_selection_origin
1653 * contains the new fixed point and m_selection_last is the newly dragged end.
1654 * In block mode it might even switch to the other two corners.
1655 * As per CTK+'s generic selection behavior, retains the origin and last
1656 * endpoints if the Shift+click happened inside the selection.
1657 */
1658void
1659Terminal::selection_maybe_swap_endpoints(bte::view::coords const& pos)
1660{
1661 if (m_selection_resolved.empty())
1662 return;
1663
1664 /* Need to ensure the ringview is updated. */
1665 ringview_update();
1666
1667 auto current = selection_grid_halfcoords_from_view_coords (pos);
1668
1669 if (m_selection_block_mode) {
1670 if ((current.row() <= m_selection_origin.row() && m_selection_origin.row() < m_selection_last.row()) ||
1671 (current.row() >= m_selection_origin.row() && m_selection_origin.row() > m_selection_last.row())) {
1672 // FIXME see if we can use std::swap()
1673 auto tmp = m_selection_origin.row();
1674 m_selection_origin.set_row(m_selection_last.row());
1675 m_selection_last.set_row(tmp);
1676 }
1677 if ((current.halfcolumn() <= m_selection_origin.halfcolumn() && m_selection_origin.halfcolumn() < m_selection_last.halfcolumn()) ||
1678 (current.halfcolumn() >= m_selection_origin.halfcolumn() && m_selection_origin.halfcolumn() > m_selection_last.halfcolumn())) {
1679 // FIXME see if we can use std::swap()
1680 auto tmp = m_selection_origin.halfcolumn();
1681 m_selection_origin.set_halfcolumn(m_selection_last.halfcolumn());
1682 m_selection_last.set_halfcolumn(tmp);
1683 }
1684 } else {
1685 if ((current <= m_selection_origin && m_selection_origin < m_selection_last) ||
1686 (current >= m_selection_origin && m_selection_origin > m_selection_last)) {
1687 std::swap (m_selection_origin, m_selection_last);
1688 }
1689 }
1690
1691 _bte_debug_print(BTE_DEBUG_SELECTION,do { } while(0)
1692 "Selection maybe swap endpoints: origin=%s last=%s\n",do { } while(0)
1693 m_selection_origin.to_string(),do { } while(0)
1694 m_selection_last.to_string())do { } while(0);
1695}
1696
1697bool
1698Terminal::rowcol_from_event(MouseEvent const& event,
1699 long *column,
1700 long *row)
1701{
1702 auto rowcol = grid_coords_from_event(event);
1703 if (!grid_coords_visible(rowcol))
1704 return false;
1705
1706 *column = rowcol.column();
1707 *row = rowcol.row();
1708 return true;
1709}
1710
1711char *
1712Terminal::hyperlink_check(MouseEvent const& event)
1713{
1714 long col, row;
1715 const char *hyperlink;
1716 const char *separator;
1717
1718 if (!m_allow_hyperlink)
1719 return NULL__null;
1720
1721 /* Need to ensure the ringview is updated. */
1722 ringview_update();
1723
1724 if (!rowcol_from_event(event, &col, &row))
1725 return NULL__null;
1726
1727 _bte_ring_get_hyperlink_at_position(m_screen->row_data, row, col, false, &hyperlink);
1728
1729 if (hyperlink != NULL__null) {
1730 /* URI is after the first semicolon */
1731 separator = strchr(hyperlink, ';');
1732 g_assert(separator != NULL)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (separator != __null) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 1732, ((const char*) (__PRETTY_FUNCTION__
)), "separator != NULL"); } while (0)
;
1733 hyperlink = separator + 1;
1734 }
1735
1736 _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0)
1737 "hyperlink_check: \"%s\"\n",do { } while(0)
1738 hyperlink)do { } while(0);
1739
1740 return g_strdup(hyperlink);
1741}
1742
1743char *
1744Terminal::regex_match_check(MouseEvent const& event,
1745 int *tag)
1746{
1747 long col, row;
1748
1749 /* Need to ensure the ringview is updated. */
1750 ringview_update();
1751
1752 if (!rowcol_from_event(event, &col, &row))
1753 return FALSE(0);
1754
1755 /* FIXME Shouldn't rely on a deprecated, not sub-row aware method. */
1756 // FIXMEchpe fix this scroll_delta substraction!
1757 return regex_match_check(col, row - (long)m_screen->scroll_delta, tag);
1758}
1759
1760bool
1761Terminal::regex_match_check_extra(MouseEvent const& event,
1762 bte::base::Regex const** regexes,
1763 size_t n_regexes,
1764 uint32_t match_flags,
1765 char** matches)
1766{
1767 gsize offset, sattr, eattr;
1768 pcre2_match_data_8 *match_data;
1769 pcre2_match_context_8 *match_context;
1770 bool any_matches = false;
1771 long col, row;
1772 guint i;
1773
1774 assert(regexes != nullptr || n_regexes == 0)(static_cast <bool> (regexes != nullptr || n_regexes ==
0) ? void (0) : __assert_fail ("regexes != nullptr || n_regexes == 0"
, "../src/bte.cc", 1774, __extension__ __PRETTY_FUNCTION__))
;
1775 assert(matches != nullptr)(static_cast <bool> (matches != nullptr) ? void (0) : __assert_fail
("matches != nullptr", "../src/bte.cc", 1775, __extension__ __PRETTY_FUNCTION__
))
;
1776
1777 /* Need to ensure the ringview is updated. */
1778 ringview_update();
1779
1780 if (!rowcol_from_event(event, &col, &row))
1781 return false;
1782
1783 if (m_match_contents == nullptr) {
1784 match_contents_refresh();
1785 }
1786
1787 if (!match_rowcol_to_offset(col, row,
1788 &offset, &sattr, &eattr))
1789 return false;
1790
1791 match_context = create_match_context();
1792 match_data = pcre2_match_data_create_8(256 /* should be plenty */, nullptr /* general context */);
1793
1794 for (i = 0; i < n_regexes; i++) {
1795 gsize start, end, sblank, eblank;
1796 char *match_string;
1797
1798 g_return_val_if_fail(regexes[i] != nullptr, false)do { if ((__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (regexes[i] != nullptr) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1))) { } else { g_return_if_fail_warning
("BTE", ((const char*) (__PRETTY_FUNCTION__)), "regexes[i] != nullptr"
); return (false); } } while (0)
;
1799
1800 if (match_check_pcre(match_data, match_context,
1801 regexes[i], match_flags,
1802 sattr, eattr, offset,
1803 &match_string,
1804 &start, &end,
1805 &sblank, &eblank)) {
1806 _bte_debug_print(BTE_DEBUG_REGEX, "Matched regex with text: %s\n", match_string)do { } while(0);
1807 matches[i] = match_string;
1808 any_matches = true;
1809 } else
1810 matches[i] = nullptr;
1811 }
1812
1813 pcre2_match_data_free_8(match_data);
1814 pcre2_match_context_free_8(match_context);
1815
1816 return any_matches;
1817}
1818
1819/* Emit an adjustment changed signal on our adjustment object. */
1820void
1821Terminal::emit_adjustment_changed()
1822{
1823 if (m_adjustment_changed_pending) {
1824 bool changed = false;
1825 gdouble current, v;
1826
1827 auto vadjustment = m_vadjustment.get();
1828
1829 auto const freezer = bte::glib::FreezeObjectNotify{vadjustment};
1830
1831 v = _bte_ring_delta (m_screen->row_data);
1832 current = ctk_adjustment_get_lower(vadjustment);
1833 if (!_bte_double_equal(current, v)) {
1834 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
1835 "Changing lower bound from %.0f to %f\n",do { } while(0)
1836 current, v)do { } while(0);
1837 ctk_adjustment_set_lower(vadjustment, v);
1838 changed = true;
1839 }
1840
1841 v = m_screen->insert_delta + m_row_count;
1842 current = ctk_adjustment_get_upper(vadjustment);
1843 if (!_bte_double_equal(current, v)) {
1844 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
1845 "Changing upper bound from %.0f to %f\n",do { } while(0)
1846 current, v)do { } while(0);
1847 ctk_adjustment_set_upper(vadjustment, v);
1848 changed = true;
1849 }
1850
1851 /* The step increment should always be one. */
1852 v = ctk_adjustment_get_step_increment(vadjustment);
1853 if (!_bte_double_equal(v, 1)) {
1854 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
1855 "Changing step increment from %.0lf to 1\n", v)do { } while(0);
1856 ctk_adjustment_set_step_increment(vadjustment, 1);
1857 changed = true;
1858 }
1859
1860 /* Set the number of rows the user sees to the number of rows the
1861 * user sees. */
1862 v = ctk_adjustment_get_page_size(vadjustment);
1863 if (!_bte_double_equal(v, m_row_count)) {
1864 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
1865 "Changing page size from %.0f to %ld\n",do { } while(0)
1866 v, m_row_count)do { } while(0);
1867 ctk_adjustment_set_page_size(vadjustment,
1868 m_row_count);
1869 changed = true;
1870 }
1871
1872 /* Clicking in the empty area should scroll one screen, so set the
1873 * page size to the number of visible rows. */
1874 v = ctk_adjustment_get_page_increment(vadjustment);
1875 if (!_bte_double_equal(v, m_row_count)) {
1876 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
1877 "Changing page increment from "do { } while(0)
1878 "%.0f to %ld\n",do { } while(0)
1879 v, m_row_count)do { } while(0);
1880 ctk_adjustment_set_page_increment(vadjustment,
1881 m_row_count);
1882 changed = true;
1883 }
1884
1885 if (changed)
1886 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
1887 "Emitting adjustment_changed.\n")do { } while(0);
1888 m_adjustment_changed_pending = FALSE(0);
1889 }
1890 if (m_adjustment_value_changed_pending) {
1891 double v, delta;
1892 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
1893 "Emitting adjustment_value_changed.\n")do { } while(0);
1894 m_adjustment_value_changed_pending = FALSE(0);
1895
1896 auto vadjustment = m_vadjustment.get();
1897 v = ctk_adjustment_get_value(vadjustment);
1898 if (!_bte_double_equal(v, m_screen->scroll_delta)) {
1899 /* this little dance is so that the scroll_delta is
1900 * updated immediately, but we still handled scrolling
1901 * via the adjustment - e.g. user interaction with the
1902 * scrollbar
1903 */
1904 delta = m_screen->scroll_delta;
1905 m_screen->scroll_delta = v;
1906 ctk_adjustment_set_value(vadjustment, delta);
1907 }
1908 }
1909}
1910
1911/* Queue an adjustment-changed signal to be delivered when convenient. */
1912// FIXMEchpe this has just one caller, fold it into the call site
1913void
1914Terminal::queue_adjustment_changed()
1915{
1916 m_adjustment_changed_pending = true;
1917 add_update_timeout(this);
1918}
1919
1920void
1921Terminal::queue_adjustment_value_changed(double v)
1922{
1923 if (!_bte_double_equal(v, m_screen->scroll_delta)) {
1924 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
1925 "Adjustment value changed to %f\n",do { } while(0)
1926 v)do { } while(0);
1927 m_screen->scroll_delta = v;
1928 m_adjustment_value_changed_pending = true;
1929 add_update_timeout(this);
1930 }
1931}
1932
1933void
1934Terminal::queue_adjustment_value_changed_clamped(double v)
1935{
1936 auto vadjustment = m_vadjustment.get();
1937 auto const lower = ctk_adjustment_get_lower(vadjustment);
1938 auto const upper = ctk_adjustment_get_upper(vadjustment);
1939
1940 v = CLAMP(v, lower, MAX (lower, upper - m_row_count))(((v) > ((((lower) > (upper - m_row_count)) ? (lower) :
(upper - m_row_count)))) ? ((((lower) > (upper - m_row_count
)) ? (lower) : (upper - m_row_count))) : (((v) < (lower)) ?
(lower) : (v)))
;
1941
1942 queue_adjustment_value_changed(v);
1943}
1944
1945void
1946Terminal::adjust_adjustments()
1947{
1948 g_assert(m_screen != nullptr)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_screen != nullptr) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 1948, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen != nullptr"); } while (0)
;
1949 g_assert(m_screen->row_data != nullptr)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_screen->row_data != nullptr) _g_boolean_var_ = 1; else
_g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 1949, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen->row_data != nullptr"); } while (0)
;
1950
1951 queue_adjustment_changed();
1952
1953 /* The lower value should be the first row in the buffer. */
1954 long delta = _bte_ring_delta(m_screen->row_data);
1955 /* Snap the insert delta and the cursor position to be in the visible
1956 * area. Leave the scrolling delta alone because it will be updated
1957 * when the adjustment changes. */
1958 m_screen->insert_delta = MAX(m_screen->insert_delta, delta)(((m_screen->insert_delta) > (delta)) ? (m_screen->insert_delta
) : (delta))
;
1959 m_screen->cursor.row = MAX(m_screen->cursor.row,(((m_screen->cursor.row) > (m_screen->insert_delta))
? (m_screen->cursor.row) : (m_screen->insert_delta))
1960 m_screen->insert_delta)(((m_screen->cursor.row) > (m_screen->insert_delta))
? (m_screen->cursor.row) : (m_screen->insert_delta))
;
1961
1962 if (m_screen->scroll_delta > m_screen->insert_delta) {
1963 queue_adjustment_value_changed(m_screen->insert_delta);
1964 }
1965}
1966
1967/* Update the adjustment field of the widget. This function should be called
1968 * whenever we add rows to or remove rows from the history or switch screens. */
1969void
1970Terminal::adjust_adjustments_full()
1971{
1972 g_assert(m_screen != NULL)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_screen != __null) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 1972, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen != NULL"); } while (0)
;
1973 g_assert(m_screen->row_data != NULL)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_screen->row_data != __null) _g_boolean_var_ = 1; else
_g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 1973, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen->row_data != NULL"); } while (0)
;
1974
1975 adjust_adjustments();
1976 queue_adjustment_changed();
1977}
1978
1979/* Scroll a fixed number of lines up or down in the current screen. */
1980void
1981Terminal::scroll_lines(long lines)
1982{
1983 double destination;
1984 _bte_debug_print(BTE_DEBUG_ADJ, "Scrolling %ld lines.\n", lines)do { } while(0);
1985 /* Calculate the ideal position where we want to be before clamping. */
1986 destination = m_screen->scroll_delta;
1987 /* Snap to whole cell offset. */
1988 if (lines > 0)
1989 destination = floor(destination);
1990 else if (lines < 0)
1991 destination = ceil(destination);
1992 destination += lines;
1993 /* Tell the scrollbar to adjust itself. */
1994 queue_adjustment_value_changed_clamped(destination);
1995}
1996
1997/* Scroll so that the scroll delta is the minimum value. */
1998void
1999Terminal::maybe_scroll_to_top()
2000{
2001 queue_adjustment_value_changed(_bte_ring_delta(m_screen->row_data));
2002}
2003
2004void
2005Terminal::maybe_scroll_to_bottom()
2006{
2007 queue_adjustment_value_changed(m_screen->insert_delta);
2008 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
2009 "Snapping to bottom of screen\n")do { } while(0);
2010}
2011
2012/*
2013 * Terminal::set_encoding:
2014 * @charset: (allow-none): target charset, or %NULL to use UTF-8
2015 *
2016 * Changes the encoding the terminal will expect data from the child to
2017 * be encoded with. If @charset is %NULL, it uses "UTF-8".
2018 *
2019 * Returns: %true if the encoding could be changed to the specified one
2020 */
2021bool
2022Terminal::set_encoding(char const* charset,
2023 GError** error)
2024{
2025#ifdef WITH_ICU
2026 auto const to_utf8 = bool{charset == nullptr || g_ascii_strcasecmp(charset, "UTF-8") == 0};
2027
2028 if (to_utf8) {
2029 if (data_syntax() == DataSyntax::eECMA48_UTF8)
2030 return true;
2031
2032 m_converter.reset();
2033 m_data_syntax = DataSyntax::eECMA48_UTF8;
2034 } else {
2035 if (data_syntax() == DataSyntax::eECMA48_PCTERM &&
2036 m_converter->charset() == charset)
2037 return true;
2038
2039 auto converter = bte::base::ICUConverter::make(charset, error);
2040 if (!converter)
2041 return false;
2042
2043 m_converter = std::move(converter);
2044 m_data_syntax = DataSyntax::eECMA48_PCTERM;
2045 }
2046
2047 /* Note: we DON'T convert any pending output from the previous charset to
2048 * the new charset, since that is in general not possible without loss, and
2049 * also the output may include binary data (Terminal::feed_child_binary()).
2050 * So we just clear the outgoing queue. (FIXMEchpe: instead, we could flush
2051 * the outgooing and only change charsets once it's empty.)
2052 * Do not clear the incoming queue.
2053 */
2054 _bte_byte_array_clear(m_outgoing)g_byte_array_set_size (m_outgoing, 0);
2055
2056 reset_decoder();
2057
2058 if (pty())
2059 pty()->set_utf8(data_syntax() == DataSyntax::eECMA48_UTF8);
2060
2061 _bte_debug_print(BTE_DEBUG_IO,do { } while(0)
2062 "Set terminal encoding to `%s'.\n",do { } while(0)
2063 encoding())do { } while(0);
2064
2065 return true;
2066#else
2067 g_set_error_literal(error, G_CONVERT_ERRORg_convert_error_quark(), G_CONVERT_ERROR_NO_CONVERSION,
2068 "ICU support not available");
2069 return false;
2070#endif
2071}
2072
2073bool
2074Terminal::set_cjk_ambiguous_width(int width)
2075{
2076 g_assert(width == 1 || width == 2)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (width == 1 || width == 2) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 2076, ((const char*) (__PRETTY_FUNCTION__
)), "width == 1 || width == 2"); } while (0)
;
2077
2078 if (m_utf8_ambiguous_width == width)
2079 return false;
2080
2081 m_utf8_ambiguous_width = width;
2082 return true;
2083}
2084
2085// FIXMEchpe replace this with a method on BteRing
2086BteRowData *
2087Terminal::insert_rows (guint cnt)
2088{
2089 BteRowData *row;
2090 do {
2091 row = ring_append(false);
2092 } while(--cnt);
2093 return row;
2094}
2095
2096/* Make sure we have enough rows and columns to hold data at the current
2097 * cursor position. */
2098BteRowData *
2099Terminal::ensure_row()
2100{
2101 BteRowData *row;
2102
2103 /* Figure out how many rows we need to add. */
2104 auto const delta = m_screen->cursor.row - _bte_ring_next(m_screen->row_data) + 1;
2105 if (delta > 0) {
2106 row = insert_rows(delta);
2107 adjust_adjustments();
2108 } else {
2109 /* Find the row the cursor is in. */
2110 row = _bte_ring_index_writable(m_screen->row_data, m_screen->cursor.row);
2111 }
2112 g_assert(row != NULL)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (row != __null) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 2112, ((const char*) (__PRETTY_FUNCTION__
)), "row != NULL"); } while (0)
;
2113
2114 return row;
2115}
2116
2117BteRowData *
2118Terminal::ensure_cursor()
2119{
2120 BteRowData *row = ensure_row();
2121 _bte_row_data_fill(row, &basic_cell, m_screen->cursor.col);
2122
2123 return row;
2124}
2125
2126/* Update the insert delta so that the screen which includes it also
2127 * includes the end of the buffer. */
2128void
2129Terminal::update_insert_delta()
2130{
2131 /* The total number of lines. Add one to the cursor offset
2132 * because it's zero-based. */
2133 auto rows = _bte_ring_next(m_screen->row_data);
2134 auto delta = m_screen->cursor.row - rows + 1;
2135 if (G_UNLIKELY (delta > 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
delta > 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
) {
2136 insert_rows(delta);
2137 rows = _bte_ring_next(m_screen->row_data);
2138 }
2139
2140 /* Make sure that the bottom row is visible, and that it's in
2141 * the buffer (even if it's empty). This usually causes the
2142 * top row to become a history-only row. */
2143 delta = m_screen->insert_delta;
2144 delta = MIN(delta, rows - m_row_count)(((delta) < (rows - m_row_count)) ? (delta) : (rows - m_row_count
))
;
2145 delta = MAX(delta,(((delta) > (m_screen->cursor.row - (m_row_count - 1)))
? (delta) : (m_screen->cursor.row - (m_row_count - 1)))
2146 m_screen->cursor.row - (m_row_count - 1))(((delta) > (m_screen->cursor.row - (m_row_count - 1)))
? (delta) : (m_screen->cursor.row - (m_row_count - 1)))
;
2147 delta = MAX(delta, _bte_ring_delta(m_screen->row_data))(((delta) > (_bte_ring_delta(m_screen->row_data))) ? (delta
) : (_bte_ring_delta(m_screen->row_data)))
;
2148
2149 /* Adjust the insert delta and scroll if needed. */
2150 if (delta != m_screen->insert_delta) {
2151 m_screen->insert_delta = delta;
2152 adjust_adjustments();
2153 }
2154}
2155
2156/* Apply the desired mouse pointer, based on certain member variables. */
2157void
2158Terminal::apply_mouse_cursor()
2159{
2160 if (!widget_realized())
2161 return;
2162
2163 /* Show the cursor if over the widget and not autohidden, this is obvious.
2164 * Also show the cursor if outside the widget regardless of the autohidden state, so that if a popover is opened
2165 * and then the cursor returns (which doesn't trigger enter/motion events), it is visible.
2166 * That is, only hide the cursor if it's over the widget and is autohidden.
2167 * See bug 789390 and bug 789536 comment 6 for details. */
2168 if (!(m_mouse_autohide && m_mouse_cursor_autohidden && m_mouse_cursor_over_widget)) {
2169 if (m_hyperlink_hover_idx != 0) {
2170 _bte_debug_print(BTE_DEBUG_CURSOR,do { } while(0)
2171 "Setting hyperlink mouse cursor.\n")do { } while(0);
2172 m_real_widget->set_cursor(bte::platform::Widget::CursorType::eHyperlink);
2173 } else if (regex_match_has_current()) {
2174 m_real_widget->set_cursor(regex_match_current()->cursor());
2175 } else if (m_mouse_tracking_mode != MouseTrackingMode::eNONE) {
2176 _bte_debug_print(BTE_DEBUG_CURSOR,do { } while(0)
2177 "Setting mousing cursor.\n")do { } while(0);
2178 m_real_widget->set_cursor(bte::platform::Widget::CursorType::eMousing);
2179 } else {
2180 _bte_debug_print(BTE_DEBUG_CURSOR,do { } while(0)
2181 "Setting default mouse cursor.\n")do { } while(0);
2182 m_real_widget->set_cursor(bte::platform::Widget::CursorType::eDefault);
2183 }
2184 } else {
2185 _bte_debug_print(BTE_DEBUG_CURSOR,do { } while(0)
2186 "Setting to invisible cursor.\n")do { } while(0);
2187 m_real_widget->set_cursor(bte::platform::Widget::CursorType::eInvisible);
2188 }
2189}
2190
2191/* Show or hide the pointer if autohiding is enabled. */
2192void
2193Terminal::set_pointer_autohidden(bool autohidden)
2194{
2195 if (autohidden == m_mouse_cursor_autohidden)
2196 return;
2197
2198 m_mouse_cursor_autohidden = autohidden;
2199
2200 if (m_mouse_autohide) {
2201 hyperlink_hilite_update();
2202 match_hilite_update();
2203 apply_mouse_cursor();
2204 }
2205}
2206
2207/*
2208 * Get the actually used color from the palette.
2209 * The return value can be NULL only if entry is one of BTE_CURSOR_BG,
2210 * BTE_CURSOR_FG, BTE_HIGHLIGHT_BG or BTE_HIGHLIGHT_FG.
2211 */
2212bte::color::rgb const*
2213Terminal::get_color(int entry) const
2214{
2215 BtePaletteColor const* palette_color = &m_palette[entry];
2216 guint source;
2217 for (source = 0; source < G_N_ELEMENTS(palette_color->sources)(sizeof (palette_color->sources) / sizeof ((palette_color->
sources)[0]))
; source++)
2218 if (palette_color->sources[source].is_set)
2219 return &palette_color->sources[source].color;
2220 return nullptr;
2221}
2222
2223/* Set up a palette entry with a more-or-less match for the requested color. */
2224void
2225Terminal::set_color(int entry,
2226 int source,
2227 bte::color::rgb const& proposed)
2228{
2229 g_assert(entry >= 0 && entry < BTE_PALETTE_SIZE)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (entry >= 0 && entry < 263) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else
g_assertion_message_expr ("BTE", "../src/bte.cc", 2229, ((const
char*) (__PRETTY_FUNCTION__)), "entry >= 0 && entry < BTE_PALETTE_SIZE"
); } while (0)
;
2230
2231 BtePaletteColor *palette_color = &m_palette[entry];
2232
2233 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2234 "Set %s color[%d] to (%04x,%04x,%04x).\n",do { } while(0)
2235 source == BTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",do { } while(0)
2236 entry, proposed.red, proposed.green, proposed.blue)do { } while(0);
2237
2238 if (palette_color->sources[source].is_set &&
2239 palette_color->sources[source].color == proposed) {
2240 return;
2241 }
2242 palette_color->sources[source].is_set = TRUE(!(0));
2243 palette_color->sources[source].color = proposed;
2244
2245 /* If we're not realized yet, there's nothing else to do. */
2246 if (!widget_realized())
2247 return;
2248
2249 /* and redraw */
2250 if (entry == BTE_CURSOR_BG261 || entry == BTE_CURSOR_FG262)
2251 invalidate_cursor_once();
2252 else
2253 invalidate_all();
2254}
2255
2256void
2257Terminal::reset_color(int entry,
2258 int source)
2259{
2260 g_assert(entry >= 0 && entry < BTE_PALETTE_SIZE)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (entry >= 0 && entry < 263) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else
g_assertion_message_expr ("BTE", "../src/bte.cc", 2260, ((const
char*) (__PRETTY_FUNCTION__)), "entry >= 0 && entry < BTE_PALETTE_SIZE"
); } while (0)
;
2261
2262 BtePaletteColor *palette_color = &m_palette[entry];
2263
2264 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2265 "Reset %s color[%d].\n",do { } while(0)
2266 source == BTE_COLOR_SOURCE_ESCAPE ? "escape" : "API",do { } while(0)
2267 entry)do { } while(0);
2268
2269 if (!palette_color->sources[source].is_set) {
2270 return;
2271 }
2272 palette_color->sources[source].is_set = FALSE(0);
2273
2274 /* If we're not realized yet, there's nothing else to do. */
2275 if (!widget_realized())
2276 return;
2277
2278 /* and redraw */
2279 if (entry == BTE_CURSOR_BG261 || entry == BTE_CURSOR_FG262)
2280 invalidate_cursor_once();
2281 else
2282 invalidate_all();
2283}
2284
2285bool
2286Terminal::set_background_alpha(double alpha)
2287{
2288 g_assert(alpha >= 0. && alpha <= 1.)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (alpha >= 0. && alpha <= 1.) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else
g_assertion_message_expr ("BTE", "../src/bte.cc", 2288, ((const
char*) (__PRETTY_FUNCTION__)), "alpha >= 0. && alpha <= 1."
); } while (0)
;
2289
2290 if (_bte_double_equal(alpha, m_background_alpha))
2291 return false;
2292
2293 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2294 "Setting background alpha to %.3f\n", alpha)do { } while(0);
2295 m_background_alpha = alpha;
2296
2297 invalidate_all();
2298
2299 return true;
2300}
2301
2302void
2303Terminal::set_colors_default()
2304{
2305 set_colors(nullptr, nullptr, nullptr, 0);
2306}
2307
2308/*
2309 * Terminal::set_colors:
2310 * @terminal: a #BteTerminal
2311 * @foreground: (allow-none): the new foreground color, or %NULL
2312 * @background: (allow-none): the new background color, or %NULL
2313 * @palette: (array length=palette_size zero-terminated=0): the color palette
2314 * @palette_size: the number of entries in @palette
2315 *
2316 * @palette specifies the new values for the 256 palette colors: 8 standard colors,
2317 * their 8 bright counterparts, 6x6x6 color cube, and 24 grayscale colors.
2318 * Omitted entries will default to a hardcoded value.
2319 *
2320 * @palette_size must be 0, 8, 16, 232 or 256.
2321 *
2322 * If @foreground is %NULL and @palette_size is greater than 0, the new foreground
2323 * color is taken from @palette[7]. If @background is %NULL and @palette_size is
2324 * greater than 0, the new background color is taken from @palette[0].
2325 */
2326void
2327Terminal::set_colors(bte::color::rgb const* foreground,
2328 bte::color::rgb const* background,
2329 bte::color::rgb const* new_palette,
2330 gsize palette_size)
2331{
2332 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2333 "Set color palette [%" G_GSIZE_FORMAT " elements].\n",do { } while(0)
2334 palette_size)do { } while(0);
2335
2336 /* Accept NULL as the default foreground and background colors if we
2337 * got a palette. */
2338 if ((foreground == NULL__null) && (palette_size >= 8)) {
2339 foreground = &new_palette[7];
2340 }
2341 if ((background == NULL__null) && (palette_size >= 8)) {
2342 background = &new_palette[0];
2343 }
2344
2345 /* Initialize each item in the palette if we got any entries to work
2346 * with. */
2347 for (gsize i = 0; i < G_N_ELEMENTS(m_palette)(sizeof (m_palette) / sizeof ((m_palette)[0])); i++) {
2348 bte::color::rgb color;
2349 bool unset = false;
2350
2351 if (i < 16) {
2352 color.blue = (i & 4) ? 0xc000 : 0;
2353 color.green = (i & 2) ? 0xc000 : 0;
2354 color.red = (i & 1) ? 0xc000 : 0;
2355 if (i > 7) {
2356 color.blue += 0x3fff;
2357 color.green += 0x3fff;
2358 color.red += 0x3fff;
2359 }
2360 }
2361 else if (i < 232) {
2362 int j = i - 16;
2363 int r = j / 36, g = (j / 6) % 6, b = j % 6;
2364 int red = (r == 0) ? 0 : r * 40 + 55;
2365 int green = (g == 0) ? 0 : g * 40 + 55;
2366 int blue = (b == 0) ? 0 : b * 40 + 55;
2367 color.red = red | red << 8 ;
2368 color.green = green | green << 8;
2369 color.blue = blue | blue << 8;
2370 } else if (i < 256) {
2371 int shade = 8 + (i - 232) * 10;
2372 color.red = color.green = color.blue = shade | shade << 8;
2373 }
2374 else switch (i) {
2375 case BTE_DEFAULT_BG257:
2376 if (background) {
2377 color = *background;
2378 } else {
2379 color.red = 0;
2380 color.blue = 0;
2381 color.green = 0;
2382 }
2383 break;
2384 case BTE_DEFAULT_FG256:
2385 if (foreground) {
2386 color = *foreground;
2387 } else {
2388 color.red = 0xc000;
2389 color.blue = 0xc000;
2390 color.green = 0xc000;
2391 }
2392 break;
2393 case BTE_BOLD_FG258:
2394 unset = true;
2395 break;
2396 case BTE_HIGHLIGHT_BG260:
2397 unset = true;
2398 break;
2399 case BTE_HIGHLIGHT_FG259:
2400 unset = true;
2401 break;
2402 case BTE_CURSOR_BG261:
2403 unset = true;
2404 break;
2405 case BTE_CURSOR_FG262:
2406 unset = true;
2407 break;
2408 }
2409
2410 /* Override from the supplied palette if there is one. */
2411 if (i < palette_size) {
2412 color = new_palette[i];
2413 }
2414
2415 /* Set up the color entry. */
2416 if (unset)
2417 reset_color(i, BTE_COLOR_SOURCE_API1);
2418 else
2419 set_color(i, BTE_COLOR_SOURCE_API1, color);
2420 }
2421}
2422
2423/*
2424 * Terminal::set_color_bold:
2425 * @bold: (allow-none): the new bold color or %NULL
2426 *
2427 * Sets the color used to draw bold text in the default foreground color.
2428 * If @bold is %NULL then the default color is used.
2429 */
2430void
2431Terminal::set_color_bold(bte::color::rgb const& color)
2432{
2433 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2434 "Set %s color to (%04x,%04x,%04x).\n", "bold",do { } while(0)
2435 color.red, color.green, color.blue)do { } while(0);
2436 set_color(BTE_BOLD_FG258, BTE_COLOR_SOURCE_API1, color);
2437}
2438
2439void
2440Terminal::reset_color_bold()
2441{
2442 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2443 "Reset %s color.\n", "bold")do { } while(0);
2444 reset_color(BTE_BOLD_FG258, BTE_COLOR_SOURCE_API1);
2445}
2446
2447/*
2448 * Terminal::set_color_foreground:
2449 * @foreground: the new foreground color
2450 *
2451 * Sets the foreground color used to draw normal text.
2452 */
2453void
2454Terminal::set_color_foreground(bte::color::rgb const& color)
2455{
2456 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2457 "Set %s color to (%04x,%04x,%04x).\n", "foreground",do { } while(0)
2458 color.red, color.green, color.blue)do { } while(0);
2459 set_color(BTE_DEFAULT_FG256, BTE_COLOR_SOURCE_API1, color);
2460}
2461
2462/*
2463 * Terminal::set_color_background:
2464 * @background: the new background color
2465 *
2466 * Sets the background color for text which does not have a specific background
2467 * color assigned. Only has effect when no background image is set and when
2468 * the terminal is not transparent.
2469 */
2470void
2471Terminal::set_color_background(bte::color::rgb const& color)
2472{
2473 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2474 "Set %s color to (%04x,%04x,%04x).\n", "background",do { } while(0)
2475 color.red, color.green, color.blue)do { } while(0);
2476 set_color(BTE_DEFAULT_BG257, BTE_COLOR_SOURCE_API1, color);
2477}
2478
2479/*
2480 * Terminal::set_color_cursor_background:
2481 * @cursor_background: (allow-none): the new color to use for the text cursor, or %NULL
2482 *
2483 * Sets the background color for text which is under the cursor. If %NULL, text
2484 * under the cursor will be drawn with foreground and background colors
2485 * reversed.
2486 */
2487void
2488Terminal::set_color_cursor_background(bte::color::rgb const& color)
2489{
2490 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2491 "Set %s color to (%04x,%04x,%04x).\n", "cursor background",do { } while(0)
2492 color.red, color.green, color.blue)do { } while(0);
2493 set_color(BTE_CURSOR_BG261, BTE_COLOR_SOURCE_API1, color);
2494}
2495
2496void
2497Terminal::reset_color_cursor_background()
2498{
2499 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2500 "Reset %s color.\n", "cursor background")do { } while(0);
2501 reset_color(BTE_CURSOR_BG261, BTE_COLOR_SOURCE_API1);
2502}
2503
2504/*
2505 * Terminal::set_color_cursor_foreground:
2506 * @cursor_foreground: (allow-none): the new color to use for the text cursor, or %NULL
2507 *
2508 * Sets the foreground color for text which is under the cursor. If %NULL, text
2509 * under the cursor will be drawn with foreground and background colors
2510 * reversed.
2511 */
2512void
2513Terminal::set_color_cursor_foreground(bte::color::rgb const& color)
2514{
2515 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2516 "Set %s color to (%04x,%04x,%04x).\n", "cursor foreground",do { } while(0)
2517 color.red, color.green, color.blue)do { } while(0);
2518 set_color(BTE_CURSOR_FG262, BTE_COLOR_SOURCE_API1, color);
2519}
2520
2521void
2522Terminal::reset_color_cursor_foreground()
2523{
2524 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2525 "Reset %s color.\n", "cursor foreground")do { } while(0);
2526 reset_color(BTE_CURSOR_FG262, BTE_COLOR_SOURCE_API1);
2527}
2528
2529/*
2530 * Terminal::set_color_highlight_background:
2531 * @highlight_background: (allow-none): the new color to use for highlighted text, or %NULL
2532 *
2533 * Sets the background color for text which is highlighted. If %NULL,
2534 * it is unset. If neither highlight background nor highlight foreground are set,
2535 * highlighted text (which is usually highlighted because it is selected) will
2536 * be drawn with foreground and background colors reversed.
2537 */
2538void
2539Terminal::set_color_highlight_background(bte::color::rgb const& color)
2540{
2541 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2542 "Set %s color to (%04x,%04x,%04x).\n", "highlight background",do { } while(0)
2543 color.red, color.green, color.blue)do { } while(0);
2544 set_color(BTE_HIGHLIGHT_BG260, BTE_COLOR_SOURCE_API1, color);
2545}
2546
2547void
2548Terminal::reset_color_highlight_background()
2549{
2550 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2551 "Reset %s color.\n", "highlight background")do { } while(0);
2552 reset_color(BTE_HIGHLIGHT_BG260, BTE_COLOR_SOURCE_API1);
2553}
2554
2555/*
2556 * Terminal::set_color_highlight_foreground:
2557 * @highlight_foreground: (allow-none): the new color to use for highlighted text, or %NULL
2558 *
2559 * Sets the foreground color for text which is highlighted. If %NULL,
2560 * it is unset. If neither highlight background nor highlight foreground are set,
2561 * highlighted text (which is usually highlighted because it is selected) will
2562 * be drawn with foreground and background colors reversed.
2563 */
2564void
2565Terminal::set_color_highlight_foreground(bte::color::rgb const& color)
2566{
2567 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2568 "Set %s color to (%04x,%04x,%04x).\n", "highlight foreground",do { } while(0)
2569 color.red, color.green, color.blue)do { } while(0);
2570 set_color(BTE_HIGHLIGHT_FG259, BTE_COLOR_SOURCE_API1, color);
2571}
2572
2573void
2574Terminal::reset_color_highlight_foreground()
2575{
2576 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2577 "Reset %s color.\n", "highlight foreground")do { } while(0);
2578 reset_color(BTE_HIGHLIGHT_FG259, BTE_COLOR_SOURCE_API1);
2579}
2580
2581/*
2582 * Terminal::cleanup_fragments:
2583 * @start: the starting column, inclusive
2584 * @end: the end column, exclusive
2585 *
2586 * Needs to be called before modifying the contents in the cursor's row,
2587 * between the two given columns. Cleans up TAB and CJK fragments to the
2588 * left of @start and to the right of @end. If a CJK is split in half,
2589 * the remaining half is replaced by a space. If a TAB at @start is split,
2590 * it is replaced by spaces. If a TAB at @end is split, it is replaced by
2591 * a shorter TAB. @start and @end can be equal if characters will be
2592 * inserted at the location rather than overwritten.
2593 *
2594 * The area between @start and @end is not cleaned up, hence the whole row
2595 * can be left in an inconsistent state. It is expected that the caller
2596 * will fill up that range afterwards, resulting in a consistent row again.
2597 *
2598 * Invalidates the cells that visually change outside of the range,
2599 * because the caller can't reasonably be expected to take care of this.
2600 */
2601void
2602Terminal::cleanup_fragments(long start,
2603 long end)
2604{
2605 BteRowData *row = ensure_row();
2606 const BteCell *cell_start;
2607 BteCell *cell_end, *cell_col;
2608 gboolean cell_start_is_fragment;
2609 long col;
2610
2611 g_assert(end >= start)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (end >= start) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 2611, ((const char*) (__PRETTY_FUNCTION__
)), "end >= start"); } while (0)
;
2612
2613 /* Remember whether the cell at start is a fragment. We'll need to know it when
2614 * handling the left hand side, but handling the right hand side first might
2615 * overwrite it if start == end (inserting to the middle of a character). */
2616 cell_start = _bte_row_data_get (row, start);
2617 cell_start_is_fragment = cell_start != NULL__null && cell_start->attr.fragment();
2618
2619 /* On the right hand side, try to replace a TAB by a shorter TAB if we can.
2620 * This requires that the TAB on the left (which might be the same TAB) is
2621 * not yet converted to spaces, so start on the right hand side. */
2622 cell_end = _bte_row_data_get_writable (row, end);
2623 if (G_UNLIKELY (cell_end != NULL && cell_end->attr.fragment())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
cell_end != __null && cell_end->attr.fragment()) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 0))
) {
2624 col = end;
2625 do {
2626 col--;
2627 g_assert(col >= 0)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (col >= 0) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr (
"BTE", "../src/bte.cc", 2627, ((const char*) (__PRETTY_FUNCTION__
)), "col >= 0"); } while (0)
; /* The first cell can't be a fragment. */
2628 cell_col = _bte_row_data_get_writable (row, col);
2629 } while (cell_col->attr.fragment());
2630 if (cell_col->c == '\t') {
2631 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2632 "Replacing right part of TAB with a shorter one at %ld (%ld cells) => %ld (%ld cells)\n",do { } while(0)
2633 col, (long) cell_col->attr.columns(), end, (long) cell_col->attr.columns() - (end - col))do { } while(0);
2634 cell_end->c = '\t';
2635 cell_end->attr.set_fragment(false);
2636 g_assert(cell_col->attr.columns() > end - col)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (cell_col->attr.columns() > end - col) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else
g_assertion_message_expr ("BTE", "../src/bte.cc", 2636, ((const
char*) (__PRETTY_FUNCTION__)), "cell_col->attr.columns() > end - col"
); } while (0)
;
2637 cell_end->attr.set_columns(cell_col->attr.columns() - (end - col));
2638 } else {
2639 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2640 "Cleaning CJK right half at %ld\n",do { } while(0)
2641 end)do { } while(0);
2642 g_assert(end - col == 1 && cell_col->attr.columns() == 2)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (end - col == 1 && cell_col->attr.columns() ==
2) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 1)) ; else g_assertion_message_expr ("BTE", "../src/bte.cc"
, 2642, ((const char*) (__PRETTY_FUNCTION__)), "end - col == 1 && cell_col->attr.columns() == 2"
); } while (0)
;
2643 cell_end->c = ' ';
2644 cell_end->attr.set_fragment(false);
2645 cell_end->attr.set_columns(1);
2646 invalidate_row_and_context(m_screen->cursor.row); /* FIXME can we do cheaper? */
2647 }
2648 }
2649
2650 /* Handle the left hand side. Converting longer TABs to shorter ones probably
2651 * wouldn't make that much sense here, so instead convert to spaces. */
2652 if (G_UNLIKELY (cell_start_is_fragment)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
cell_start_is_fragment) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
) {
2653 gboolean keep_going = TRUE(!(0));
2654 col = start;
2655 do {
2656 col--;
2657 g_assert(col >= 0)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (col >= 0) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr (
"BTE", "../src/bte.cc", 2657, ((const char*) (__PRETTY_FUNCTION__
)), "col >= 0"); } while (0)
; /* The first cell can't be a fragment. */
2658 cell_col = _bte_row_data_get_writable (row, col);
2659 if (!cell_col->attr.fragment()) {
2660 if (cell_col->c == '\t') {
2661 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2662 "Replacing left part of TAB with spaces at %ld (%ld => %ld cells)\n",do { } while(0)
2663 col, (long)cell_col->attr.columns(), start - col)do { } while(0);
2664 /* nothing to do here */
2665 } else {
2666 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
2667 "Cleaning CJK left half at %ld\n",do { } while(0)
2668 col)do { } while(0);
2669 g_assert(start - col == 1)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (start - col == 1) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 2669, ((const char*) (__PRETTY_FUNCTION__
)), "start - col == 1"); } while (0)
;
2670 invalidate_row_and_context(m_screen->cursor.row); /* FIXME can we do cheaper? */
2671 }
2672 keep_going = FALSE(0);
2673 }
2674 cell_col->c = ' ';
2675 cell_col->attr.set_fragment(false);
2676 cell_col->attr.set_columns(1);
2677 } while (keep_going);
2678 }
2679}
2680
2681/* Cursor down, with scrolling. */
2682void
2683Terminal::cursor_down(bool explicit_sequence)
2684{
2685 long start, end;
2686
2687 if (m_scrolling_restricted) {
2688 start = m_screen->insert_delta + m_scrolling_region.start;
2689 end = m_screen->insert_delta + m_scrolling_region.end;
2690 } else {
2691 start = m_screen->insert_delta;
2692 end = start + m_row_count - 1;
2693 }
2694 if (m_screen->cursor.row == end) {
2695 if (m_scrolling_restricted) {
2696 if (start == m_screen->insert_delta) {
2697 /* Set the boundary to hard wrapped where
2698 * we're about to tear apart the contents. */
2699 set_hard_wrapped(m_screen->cursor.row);
2700 /* Scroll this line into the scrollback
2701 * buffer by inserting a line at the next
2702 * line and scrolling the area up. */
2703 m_screen->insert_delta++;
2704 m_screen->cursor.row++;
2705 /* Update start and end, too. */
2706 start++;
2707 end++;
2708 ring_insert(m_screen->cursor.row, false);
2709 /* Repaint the affected lines, which is _below_
2710 * the region (bug 131). No need to extend,
2711 * set_hard_wrapped() took care of invalidating
2712 * the context lines if necessary. */
2713 invalidate_rows(m_screen->cursor.row,
2714 m_screen->insert_delta + m_row_count - 1);
2715 /* Force scroll. */
2716 adjust_adjustments();
2717 } else {
2718 /* Set the boundaries to hard wrapped where
2719 * we're about to tear apart the contents. */
2720 set_hard_wrapped(start - 1);
2721 set_hard_wrapped(end);
2722 /* Scroll by removing a line and inserting a new one. */
2723 ring_remove(start);
2724 ring_insert(end, true);
2725 /* Repaint the affected lines. No need to extend,
2726 * set_hard_wrapped() took care of invalidating
2727 * the context lines if necessary. */
2728 invalidate_rows(start, end);
2729 }
2730 } else {
2731 /* Scroll up with history. */
2732 m_screen->cursor.row++;
2733 update_insert_delta();
2734 }
2735
2736 /* Handle bce (background color erase), however, diverge from xterm:
2737 * only fill the new row with the background color if scrolling
2738 * happens due to an explicit escape sequence, not due to autowrapping.
2739 * See bug 754596 for details. */
2740 bool const not_default_bg = (m_color_defaults.attr.back() != BTE_DEFAULT_BG257);
2741
2742 if (explicit_sequence && not_default_bg) {
2743 BteRowData *rowdata = ensure_row();
2744 _bte_row_data_fill (rowdata, &m_color_defaults, m_column_count);
2745 }
2746 } else if (m_screen->cursor.row < m_screen->insert_delta + m_row_count - 1) {
2747 /* Otherwise, just move the cursor down; unless it's already in the last
2748 * physical row (which is possible with scrolling region, see #176). */
2749 m_screen->cursor.row++;
2750 }
2751}
2752
2753/* Drop the scrollback. */
2754void
2755Terminal::drop_scrollback()
2756{
2757 /* Only for normal screen; alternate screen doesn't have a scrollback. */
2758 _bte_ring_drop_scrollback (m_normal_screen.row_data,
2759 m_normal_screen.insert_delta);
2760
2761 if (m_screen == &m_normal_screen) {
2762 queue_adjustment_value_changed(m_normal_screen.insert_delta);
2763 adjust_adjustments_full();
2764 }
2765}
2766
2767/* Restore cursor on a screen. */
2768void
2769Terminal::restore_cursor(BteScreen *screen__)
2770{
2771 screen__->cursor.col = screen__->saved.cursor.col;
2772 screen__->cursor.row = screen__->insert_delta + CLAMP(screen__->saved.cursor.row,(((screen__->saved.cursor.row) > (m_row_count - 1)) ? (
m_row_count - 1) : (((screen__->saved.cursor.row) < (0)
) ? (0) : (screen__->saved.cursor.row)))
2773 0, m_row_count - 1)(((screen__->saved.cursor.row) > (m_row_count - 1)) ? (
m_row_count - 1) : (((screen__->saved.cursor.row) < (0)
) ? (0) : (screen__->saved.cursor.row)))
;
2774
2775 m_modes_ecma.set_modes(screen__->saved.modes_ecma);
2776
2777 m_modes_private.set_DEC_REVERSE_IMAGE(screen__->saved.reverse_mode);
2778 m_modes_private.set_DEC_ORIGIN(screen__->saved.origin_mode);
2779
2780 m_defaults = screen__->saved.defaults;
2781 m_color_defaults = screen__->saved.color_defaults;
2782 m_character_replacements[0] = screen__->saved.character_replacements[0];
2783 m_character_replacements[1] = screen__->saved.character_replacements[1];
2784 m_character_replacement = screen__->saved.character_replacement;
2785}
2786
2787/* Save cursor on a screen__. */
2788void
2789Terminal::save_cursor(BteScreen *screen__)
2790{
2791 screen__->saved.cursor.col = screen__->cursor.col;
2792 screen__->saved.cursor.row = screen__->cursor.row - screen__->insert_delta;
2793
2794 screen__->saved.modes_ecma = m_modes_ecma.get_modes();
2795
2796 screen__->saved.reverse_mode = m_modes_private.DEC_REVERSE_IMAGE();
2797 screen__->saved.origin_mode = m_modes_private.DEC_ORIGIN();
2798
2799 screen__->saved.defaults = m_defaults;
2800 screen__->saved.color_defaults = m_color_defaults;
2801 screen__->saved.character_replacements[0] = m_character_replacements[0];
2802 screen__->saved.character_replacements[1] = m_character_replacements[1];
2803 screen__->saved.character_replacement = m_character_replacement;
2804}
2805
2806/* Insert a single character into the stored data array. */
2807void
2808Terminal::insert_char(gunichar c,
2809 bool insert,
2810 bool invalidate_now)
2811{
2812 BteCellAttr attr;
2813 BteRowData *row;
2814 long col;
2815 int columns, i;
2816 bool line_wrapped = false; /* cursor moved before char inserted */
2817 gunichar c_unmapped = c;
2818
2819 /* DEC Special Character and Line Drawing Set. VT100 and higher (per XTerm docs). */
2820 static const gunichar line_drawing_map[32] = {
2821 0x0020, /* _ => blank (space) */
2822 0x25c6, /* ` => diamond */
2823 0x2592, /* a => checkerboard */
2824 0x2409, /* b => HT symbol */
2825 0x240c, /* c => FF symbol */
2826 0x240d, /* d => CR symbol */
2827 0x240a, /* e => LF symbol */
2828 0x00b0, /* f => degree */
2829 0x00b1, /* g => plus/minus */
2830 0x2424, /* h => NL symbol */
2831 0x240b, /* i => VT symbol */
2832 0x2518, /* j => downright corner */
2833 0x2510, /* k => upright corner */
2834 0x250c, /* l => upleft corner */
2835 0x2514, /* m => downleft corner */
2836 0x253c, /* n => cross */
2837 0x23ba, /* o => scan line 1/9 */
2838 0x23bb, /* p => scan line 3/9 */
2839 0x2500, /* q => horizontal line (also scan line 5/9) */
2840 0x23bc, /* r => scan line 7/9 */
2841 0x23bd, /* s => scan line 9/9 */
2842 0x251c, /* t => left t */
2843 0x2524, /* u => right t */
2844 0x2534, /* v => bottom t */
2845 0x252c, /* w => top t */
2846 0x2502, /* x => vertical line */
2847 0x2264, /* y => <= */
2848 0x2265, /* z => >= */
2849 0x03c0, /* { => pi */
2850 0x2260, /* | => not equal */
2851 0x00a3, /* } => pound currency sign */
2852 0x00b7, /* ~ => bullet */
2853 };
2854
2855 insert |= m_modes_ecma.IRM();
2856
2857 /* If we've enabled the special drawing set, map the characters to
2858 * Unicode. */
2859 if (G_UNLIKELY (*m_character_replacement == BTE_CHARACTER_REPLACEMENT_LINE_DRAWING)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
*m_character_replacement == BTE_CHARACTER_REPLACEMENT_LINE_DRAWING
) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
) {
2860 if (c >= 95 && c <= 126)
2861 c = line_drawing_map[c - 95];
2862 }
2863
2864 /* Figure out how many columns this character should occupy. */
2865 columns = _bte_unichar_width(c, m_utf8_ambiguous_width);
2866
2867 /* If we're autowrapping here, do it. */
2868 col = m_screen->cursor.col;
2869 if (G_UNLIKELY (columns && col + columns > m_column_count)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
columns && col + columns > m_column_count) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 0))
) {
2870 if (m_modes_private.DEC_AUTOWRAP()) {
2871 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
2872 "Autowrapping before character\n")do { } while(0);
2873 /* Wrap. */
2874 /* XXX clear to the end of line */
2875 col = m_screen->cursor.col = 0;
2876 /* Mark this line as soft-wrapped. */
2877 row = ensure_row();
2878 set_soft_wrapped(m_screen->cursor.row);
2879 cursor_down(false);
2880 ensure_row();
2881 apply_bidi_attributes(m_screen->cursor.row, row->attr.bidi_flags, BTE_BIDI_FLAG_ALL);
2882 } else {
2883 /* Don't wrap, stay at the rightmost column. */
2884 col = m_screen->cursor.col =
2885 m_column_count - columns;
2886 }
2887 line_wrapped = true;
2888 }
2889
2890 _bte_debug_print(BTE_DEBUG_PARSER,do { } while(0)
2891 "Inserting U+%04X '%lc' (colors %" G_GUINT64_FORMAT ") (%ld+%d, %ld), delta = %ld; ",do { } while(0)
2892 (unsigned int)c, g_unichar_isprint(c) ? c : 0xfffd,do { } while(0)
2893 m_color_defaults.attr.colors(),do { } while(0)
2894 col, columns, (long)m_screen->cursor.row,do { } while(0)
2895 (long)m_screen->insert_delta)do { } while(0);
2896
2897 //FIXMEchpe
2898 if (G_UNLIKELY(c == 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
c == 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
)
2899 goto not_inserted;
2900
2901 if (G_UNLIKELY (columns == 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
columns == 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
) {
2902
2903 /* It's a combining mark */
2904
2905 long row_num;
2906 BteCell *cell;
2907
2908 _bte_debug_print(BTE_DEBUG_PARSER, "combining U+%04X", c)do { } while(0);
2909
2910 row_num = m_screen->cursor.row;
2911 row = NULL__null;
2912 if (G_UNLIKELY (col == 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
col == 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
) {
2913 /* We are at first column. See if the previous line softwrapped.
2914 * If it did, move there. Otherwise skip inserting. */
2915
2916 if (G_LIKELY (row_num > 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
row_num > 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0
; _g_boolean_var_; }), 1))
) {
2917 row_num--;
2918 row = find_row_data_writable(row_num);
2919
2920 if (row) {
2921 if (!row->attr.soft_wrapped)
2922 row = NULL__null;
2923 else
2924 col = _bte_row_data_length (row)((row)->len + 0);
2925 }
2926 }
2927 } else {
2928 row = find_row_data_writable(row_num);
2929 }
2930
2931 if (G_UNLIKELY (!row || !col)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!row || !col) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
)
2932 goto not_inserted;
2933
2934 /* Combine it on the previous cell */
2935
2936 col--;
2937 cell = _bte_row_data_get_writable (row, col);
2938
2939 if (G_UNLIKELY (!cell)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!cell) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
)
2940 goto not_inserted;
2941
2942 /* Find the previous cell */
2943 while (cell && cell->attr.fragment() && col > 0)
2944 cell = _bte_row_data_get_writable (row, --col);
2945 if (G_UNLIKELY (!cell || cell->c == '\t')(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!cell || cell->c == '\t') _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
2946 goto not_inserted;
2947
2948 /* Combine the new character on top of the cell string */
2949 c = _bte_unistr_append_unichar (cell->c, c);
2950
2951 /* And set it */
2952 columns = cell->attr.columns();
2953 for (i = 0; i < columns; i++) {
2954 cell = _bte_row_data_get_writable (row, col++);
2955 cell->c = c;
2956 }
2957
2958 goto done;
2959 } else {
2960 m_last_graphic_character = c_unmapped;
2961 }
2962
2963 /* Make sure we have enough rows to hold this data. */
2964 row = ensure_cursor();
2965 g_assert(row != NULL)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (row != __null) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 2965, ((const char*) (__PRETTY_FUNCTION__
)), "row != NULL"); } while (0)
;
2966
2967 if (insert) {
2968 cleanup_fragments(col, col);
2969 for (i = 0; i < columns; i++)
2970 _bte_row_data_insert (row, col + i, &basic_cell);
2971 } else {
2972 cleanup_fragments(col, col + columns);
2973 _bte_row_data_fill (row, &basic_cell, col + columns);
2974 }
2975
2976 attr = m_defaults.attr;
2977 attr.set_columns(columns);
2978
2979 {
2980 BteCell *pcell = _bte_row_data_get_writable (row, col);
2981 pcell->c = c;
2982 pcell->attr = attr;
2983 col++;
2984 }
2985
2986 /* insert wide-char fragments */
2987 attr.set_fragment(true);
2988 for (i = 1; i < columns; i++) {
2989 BteCell *pcell = _bte_row_data_get_writable (row, col);
2990 pcell->c = c;
2991 pcell->attr = attr;
2992 col++;
2993 }
2994 if (_bte_row_data_length (row)((row)->len + 0) > m_column_count)
2995 cleanup_fragments(m_column_count, _bte_row_data_length (row)((row)->len + 0));
2996 _bte_row_data_shrink (row, m_column_count);
2997
2998 m_screen->cursor.col = col;
2999
3000done:
3001 /* Signal that this part of the window needs drawing. */
3002 if (G_UNLIKELY (invalidate_now)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
invalidate_now) _g_boolean_var_ = 1; else _g_boolean_var_ = 0
; _g_boolean_var_; }), 0))
) {
3003 invalidate_row_and_context(m_screen->cursor.row);
3004 }
3005
3006 /* We added text, so make a note of it. */
3007 m_text_inserted_flag = TRUE(!(0));
3008
3009not_inserted:
3010 _bte_debug_print(BTE_DEBUG_ADJ|BTE_DEBUG_PARSER,do { } while(0)
3011 "insertion delta => %ld.\n",do { } while(0)
3012 (long)m_screen->insert_delta)do { } while(0);
3013
3014 m_line_wrapped = line_wrapped;
3015}
3016
3017guint8
3018Terminal::get_bidi_flags() const noexcept
3019{
3020 return (m_modes_ecma.BDSM() ? BTE_BIDI_FLAG_IMPLICIT : 0) |
3021 (m_bidi_rtl ? BTE_BIDI_FLAG_RTL : 0) |
3022 (m_modes_private.BTE_BIDI_AUTO() ? BTE_BIDI_FLAG_AUTO : 0) |
3023 (m_modes_private.BTE_BIDI_BOX_MIRROR() ? BTE_BIDI_FLAG_BOX_MIRROR : 0);
3024}
3025
3026/* Apply the specified BiDi parameters on the paragraph beginning at the specified line. */
3027void
3028Terminal::apply_bidi_attributes(bte::grid::row_t start, guint8 bidi_flags, guint8 bidi_flags_mask)
3029{
3030 bte::grid::row_t row = start;
3031 BteRowData *rowdata;
3032
3033 bidi_flags &= bidi_flags_mask;
3034
3035 _bte_debug_print(BTE_DEBUG_BIDI,do { } while(0)
3036 "Applying BiDi parameters from row %ld.\n", row)do { } while(0);
3037
3038 rowdata = _bte_ring_index_writable (m_screen->row_data, row);
3039 if (rowdata == nullptr || (rowdata->attr.bidi_flags & bidi_flags_mask) == bidi_flags) {
3040 _bte_debug_print(BTE_DEBUG_BIDI,do { } while(0)
3041 "BiDi parameters didn't change for this paragraph.\n")do { } while(0);
3042 return;
3043 }
3044
3045 while (true) {
3046 rowdata->attr.bidi_flags &= ~bidi_flags_mask;
3047 rowdata->attr.bidi_flags |= bidi_flags;
3048
3049 if (!rowdata->attr.soft_wrapped)
3050 break;
3051
3052 rowdata = _bte_ring_index_writable (m_screen->row_data, row + 1);
3053 if (rowdata == nullptr)
3054 break;
3055 row++;
3056 }
3057
3058 _bte_debug_print(BTE_DEBUG_BIDI,do { } while(0)
3059 "Applied BiDi parameters to rows %ld..%ld.\n", start, row)do { } while(0);
3060
3061 m_ringview.invalidate();
3062 invalidate_rows(start, row);
3063}
3064
3065/* Apply the current BiDi parameters covered by bidi_flags_mask on the current paragraph
3066 * if the cursor is at the first position of this paragraph. */
3067void
3068Terminal::maybe_apply_bidi_attributes(guint8 bidi_flags_mask)
3069{
3070 _bte_debug_print(BTE_DEBUG_BIDI,do { } while(0)
3071 "Maybe applying BiDi parameters on current paragraph.\n")do { } while(0);
3072
3073 if (m_screen->cursor.col != 0) {
3074 _bte_debug_print(BTE_DEBUG_BIDI,do { } while(0)
3075 "No, cursor not in first column.\n")do { } while(0);
3076 return;
3077 }
3078
3079 auto row = m_screen->cursor.row;
3080
3081 if (row > _bte_ring_delta (m_screen->row_data)) {
3082 const BteRowData *rowdata = _bte_ring_index (m_screen->row_data, row - 1);
3083 if (rowdata != nullptr && rowdata->attr.soft_wrapped) {
3084 _bte_debug_print(BTE_DEBUG_BIDI,do { } while(0)
3085 "No, we're not after a hard wrap.\n")do { } while(0);
3086 return;
3087 }
3088 }
3089
3090 _bte_debug_print(BTE_DEBUG_BIDI,do { } while(0)
3091 "Yes, applying.\n")do { } while(0);
3092
3093 apply_bidi_attributes (row, get_bidi_flags(), bidi_flags_mask);
3094}
3095
3096static void
3097reaper_child_exited_cb(BteReaper *reaper,
3098 int ipid,
3099 int status,
3100 bte::terminal::Terminal* that) noexcept
3101try
3102{
3103 that->child_watch_done(pid_t{ipid}, status);
3104 // @that might be destroyed at this point
3105}
3106catch (...)
3107{
3108 bte::log_exception();
3109}
3110
3111void
3112Terminal::child_watch_done(pid_t pid,
3113 int status)
3114{
3115 if (pid != m_pty_pid)
3116 return;
3117
3118 _BTE_DEBUG_IF (BTE_DEBUG_LIFECYCLE)if (0) {
3119 g_printerr ("Child[%d] exited with status %d\n",
3120 pid, status);
3121#ifdef HAVE_SYS_WAIT_H
3122 if (WIFEXITED (status)(((status) & 0x7f) == 0)) {
3123 g_printerr ("Child[%d] exit code %d.\n",
3124 pid, WEXITSTATUS (status)(((status) & 0xff00) >> 8));
3125 } else if (WIFSIGNALED (status)(((signed char) (((status) & 0x7f) + 1) >> 1) > 0
)
) {
3126 g_printerr ("Child[%d] dies with signal %d.\n",
3127 pid, WTERMSIG (status)((status) & 0x7f));
3128 }
3129#endif
3130 }
3131
3132 /* Disconnect from the reaper */
3133 if (m_reaper) {
3134 g_signal_handlers_disconnect_by_func(m_reaper,g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
3135 (gpointer)reaper_child_exited_cb,g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
3136 this)g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
;
3137 g_object_unref(m_reaper);
3138 m_reaper = nullptr;
3139 }
3140
3141 m_pty_pid = -1;
3142
3143 /* If we still have a PTY, or data to process, defer emitting the signals
3144 * until we have EOF on the PTY, so that we can process all pending data.
3145 */
3146 if (pty() || !m_incoming_queue.empty()) {
3147 m_child_exit_status = status;
3148 m_child_exited_after_eos_pending = true;
3149
3150 m_child_exited_eos_wait_timer.schedule_seconds(2); // FIXME: better value?
3151 } else {
3152 m_child_exited_after_eos_pending = false;
3153
3154 if (widget())
3155 widget()->emit_child_exited(status);
3156 }
3157}
3158
3159static void
3160mark_input_source_invalid_cb(bte::terminal::Terminal* that)
3161{
3162 _bte_debug_print (BTE_DEBUG_IO, "Removed PTY input source\n")do { } while(0);
3163 that->m_pty_input_source = 0;
3164}
3165
3166/* Read and handle data from the child. */
3167static gboolean
3168io_read_cb(int fd,
3169 GIOCondition condition,
3170 bte::terminal::Terminal* that)
3171{
3172 return that->pty_io_read(fd, condition);
3173}
3174
3175void
3176Terminal::connect_pty_read()
3177{
3178 if (m_pty_input_source != 0 || !pty())
3179 return;
3180
3181 _bte_debug_print (BTE_DEBUG_IO, "Adding PTY input source\n")do { } while(0);
3182
3183 m_pty_input_source = g_unix_fd_add_full(BTE_CHILD_INPUT_PRIORITY200,
3184 pty()->fd(),
3185 (GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_ERR),
3186 (GUnixFDSourceFunc)io_read_cb,
3187 this,
3188 (GDestroyNotify)mark_input_source_invalid_cb);
3189}
3190
3191static void
3192mark_output_source_invalid_cb(bte::terminal::Terminal* that)
3193{
3194 _bte_debug_print (BTE_DEBUG_IO, "Removed PTY output source\n")do { } while(0);
3195 that->m_pty_output_source = 0;
3196}
3197
3198/* Send locally-encoded characters to the child. */
3199static gboolean
3200io_write_cb(int fd,
3201 GIOCondition condition,
3202 bte::terminal::Terminal* that)
3203{
3204 return that->pty_io_write(fd, condition);
3205}
3206
3207void
3208Terminal::connect_pty_write()
3209{
3210 if (m_pty_output_source != 0 || !pty())
3211 return;
3212
3213 g_warn_if_fail(m_input_enabled)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_input_enabled) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_warn_message ("BTE", "../src/bte.cc"
, 3213, ((const char*) (__PRETTY_FUNCTION__)), "m_input_enabled"
); } while (0)
;
3214
3215 /* Anything to write? */
3216 if (_bte_byte_array_length(m_outgoing)((m_outgoing)->len) == 0)
3217 return;
3218
3219 /* Do one write. FIXMEchpe why? */
3220 if (!pty_io_write (pty()->fd(), G_IO_OUT))
3221 return;
3222
3223 _bte_debug_print (BTE_DEBUG_IO, "Adding PTY output source\n")do { } while(0);
3224
3225 m_pty_output_source = g_unix_fd_add_full(BTE_CHILD_OUTPUT_PRIORITY-100,
3226 pty()->fd(),
3227 G_IO_OUT,
3228 (GUnixFDSourceFunc)io_write_cb,
3229 this,
3230 (GDestroyNotify)mark_output_source_invalid_cb);
3231}
3232
3233void
3234Terminal::disconnect_pty_read()
3235{
3236 if (m_pty_input_source != 0) {
3237 _bte_debug_print (BTE_DEBUG_IO, "Removing PTY input source\n")do { } while(0);
3238 g_source_remove(m_pty_input_source);
3239 // FIXMEchpe the destroy notify should already have done this!
3240 m_pty_input_source = 0;
3241 }
3242}
3243
3244void
3245Terminal::disconnect_pty_write()
3246{
3247 if (m_pty_output_source != 0) {
3248 _bte_debug_print (BTE_DEBUG_IO, "Removing PTY output source\n")do { } while(0);
3249 g_source_remove(m_pty_output_source);
3250 // FIXMEchpe the destroy notify should already have done this!
3251 m_pty_output_source = 0;
3252 }
3253}
3254
3255void
3256Terminal::pty_termios_changed()
3257{
3258 _bte_debug_print(BTE_DEBUG_IO, "Termios changed\n")do { } while(0);
3259}
3260
3261void
3262Terminal::pty_scroll_lock_changed(bool locked)
3263{
3264 _bte_debug_print(BTE_DEBUG_IO, "Output %s (^%c)\n",do { } while(0)
3265 locked ? "stopped" : "started",do { } while(0)
3266 locked ? 'Q' : 'S')do { } while(0);
3267}
3268
3269/*
3270 * Terminal::watch_child:
3271 * @child_pid: a #pid_t
3272 *
3273 * Watches @child_pid. When the process exists, the #BteTerminal::child-exited
3274 * signal will be called with the child's exit status.
3275 *
3276 * Prior to calling this function, a #BtePty must have been set in @terminal
3277 * using bte_terminal_set_pty().
3278 * When the child exits, the terminal's #BtePty will be set to %NULL.
3279 *
3280 * Note: g_child_watch_add() or g_child_watch_add_full() must not have
3281 * been called for @child_pid, nor a #GSource for it been created with
3282 * g_child_watch_source_new().
3283 *
3284 * Note: when using the g_spawn_async() family of functions,
3285 * the %G_SPAWN_DO_NOT_REAP_CHILD flag MUST have been passed.
3286 */
3287void
3288Terminal::watch_child (pid_t child_pid)
3289{
3290 // FIXMEchpe: support passing child_pid = -1 to remove the wathch
3291 g_assert(child_pid != -1)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (child_pid != -1) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 3291, ((const char*) (__PRETTY_FUNCTION__
)), "child_pid != -1"); } while (0)
;
3292 if (!pty())
3293 return;
3294
3295 auto const freezer = bte::glib::FreezeObjectNotify{m_terminal};
3296
3297 /* Set this as the child's pid. */
3298 m_pty_pid = child_pid;
3299
3300 /* Catch a child-exited signal from the child pid. */
3301 auto reaper = bte_reaper_ref();
3302 bte_reaper_add_child(child_pid);
3303 if (reaper != m_reaper) {
3304 if (m_reaper) {
3305 g_signal_handlers_disconnect_by_func(m_reaper,g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
3306 (gpointer)reaper_child_exited_cb,g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
3307 this)g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
;
3308 g_object_unref(m_reaper);
3309 }
3310 m_reaper = reaper; /* adopts */
3311 g_signal_connect(m_reaper, "child-exited",g_signal_connect_data ((m_reaper), ("child-exited"), (((GCallback
) (reaper_child_exited_cb))), (this), __null, (GConnectFlags)
0)
3312 G_CALLBACK(reaper_child_exited_cb),g_signal_connect_data ((m_reaper), ("child-exited"), (((GCallback
) (reaper_child_exited_cb))), (this), __null, (GConnectFlags)
0)
3313 this)g_signal_connect_data ((m_reaper), ("child-exited"), (((GCallback
) (reaper_child_exited_cb))), (this), __null, (GConnectFlags)
0)
;
3314 } else {
3315 g_object_unref(reaper);
3316 }
3317
3318 /* FIXMEchpe: call set_size() here? */
3319}
3320
3321/* Reset the input method context. */
3322void
3323Terminal::im_reset()
3324{
3325 if (widget())
3326 widget()->im_reset();
3327
3328 im_preedit_reset();
3329}
3330
3331void
3332Terminal::process_incoming()
3333{
3334 switch (data_syntax()) {
3335 case DataSyntax::eECMA48_UTF8: process_incoming_utf8(); break;
3336#ifdef WITH_ICU
3337 case DataSyntax::eECMA48_PCTERM: process_incoming_pcterm(); break;
3338#endif
3339 default: g_assert_not_reached()do { g_assertion_message_expr ("BTE", "../src/bte.cc", 3339, (
(const char*) (__PRETTY_FUNCTION__)), __null); } while (0)
; break;
3340 }
3341}
3342
3343
3344/* Note that this code is mostly copied to process_incoming_pcterm() below; any non-charset-decoding
3345 * related changes made here need to be made there, too.
3346 * FIXMEchpe: refactor this to share more code with process_incoming_pcterm().
3347 */
3348void
3349Terminal::process_incoming_utf8()
3350{
3351 BteVisualPosition saved_cursor;
3352 gboolean saved_cursor_visible;
3353 CursorStyle saved_cursor_style;
3354 bte::grid::row_t bbox_top, bbox_bottom;
3355 gboolean modified, bottom;
3356 gboolean invalidated_text;
3357 gboolean in_scroll_region;
3358
3359 _bte_debug_print(BTE_DEBUG_IO,do { } while(0)
3360 "Handler processing %" G_GSIZE_FORMAT " bytes over %" G_GSIZE_FORMAT " chunks.\n",do { } while(0)
3361 m_input_bytes,do { } while(0)
3362 m_incoming_queue.size())do { } while(0);
3363 _bte_debug_print (BTE_DEBUG_WORK, "(")do { } while(0);
3364
3365 auto previous_screen = m_screen;
3366
3367 bottom = m_screen->insert_delta == (long)m_screen->scroll_delta;
3368
3369 /* Save the current cursor position. */
3370 saved_cursor = m_screen->cursor;
3371 saved_cursor_visible = m_modes_private.DEC_TEXT_CURSOR();
3372 saved_cursor_style = m_cursor_style;
3373
3374 in_scroll_region = m_scrolling_restricted
3375 && (m_screen->cursor.row >= (m_screen->insert_delta + m_scrolling_region.start))
3376 && (m_screen->cursor.row <= (m_screen->insert_delta + m_scrolling_region.end));
3377
3378 /* We should only be called when there's data to process. */
3379 g_assert(!m_incoming_queue.empty())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (!m_incoming_queue.empty()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 3379, ((const char*) (__PRETTY_FUNCTION__
)), "!m_incoming_queue.empty()"); } while (0)
;
3380
3381 modified = FALSE(0);
3382 invalidated_text = FALSE(0);
3383
3384 bbox_bottom = -G_MAXINT2147483647;
3385 bbox_top = G_MAXINT2147483647;
3386
3387 bte::parser::Sequence seq{m_parser};
3388
3389 m_line_wrapped = false;
3390
3391 size_t bytes_processed = 0;
3392
3393 while (!m_incoming_queue.empty()) {
3394 auto chunk = std::move(m_incoming_queue.front());
3395 m_incoming_queue.pop();
3396
3397 g_assert_nonnull(chunk.get())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if ((chunk.get()) != nullptr) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message ("BTE"
, "../src/bte.cc", 3397, ((const char*) (__PRETTY_FUNCTION__)
), "'" "chunk.get()" "' should not be nullptr"); } while (0)
;
3398
3399 _BTE_DEBUG_IF(BTE_DEBUG_IO)if (0) {
3400 _bte_debug_hexdump("Incoming buffer", chunk->data, chunk->len);
3401 }
3402
3403 bytes_processed += chunk->len;
3404
3405 auto const* ip = chunk->data;
3406 auto const* iend = chunk->data + chunk->len;
3407
3408 for ( ; ip < iend; ++ip) {
3409
3410 switch (m_utf8_decoder.decode(*ip)) {
3411 case bte::base::UTF8Decoder::REJECT_REWIND:
3412 /* Rewind the stream.
3413 * Note that this will never lead to a loop, since in the
3414 * next round this byte *will* be consumed.
3415 */
3416 --ip;
3417 [[fallthrough]];
3418 case bte::base::UTF8Decoder::REJECT:
3419 m_utf8_decoder.reset();
3420 /* Fall through to insert the U+FFFD replacement character. */
3421 [[fallthrough]];
3422 case bte::base::UTF8Decoder::ACCEPT: {
3423 auto rv = m_parser.feed(m_utf8_decoder.codepoint());
3424 if (G_UNLIKELY(rv < 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
rv < 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
) {
3425#ifdef DEBUG
3426 uint32_t c = m_utf8_decoder.codepoint();
3427 char c_buf[7];
3428 g_snprintf(c_buf, sizeof(c_buf), "%lc", c);
3429 char const* wp_str = g_unichar_isprint(c) ? c_buf : _bte_debug_sequence_to_string(c_buf, -1);
3430 _bte_debug_print(BTE_DEBUG_PARSER, "Parser error on U+%04X [%s]!\n",do { } while(0)
3431 c, wp_str)do { } while(0);
3432#endif
3433 break;
3434 }
3435
3436#ifdef BTE_DEBUG
3437 if (rv != BTE_SEQ_NONE)
3438 g_assert((bool)seq)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if ((bool)seq) _g_boolean_var_ = 1; else _g_boolean_var_ = 0
; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr ("BTE"
, "../src/bte.cc", 3438, ((const char*) (__PRETTY_FUNCTION__)
), "(bool)seq"); } while (0)
;
3439#endif
3440
3441 _BTE_DEBUG_IF(BTE_DEBUG_PARSER)if (0) {
3442 if (rv != BTE_SEQ_NONE) {
3443 seq.print();
3444 }
3445 }
3446
3447 // FIXMEchpe this assumes that the only handler inserting
3448 // a character is GRAPHIC, which isn't true (at least ICH, REP, SUB
3449 // also do, and invalidate directly for now)...
3450
3451 switch (rv) {
3452 case BTE_SEQ_GRAPHIC: {
3453
3454 bbox_top = std::min(bbox_top,
3455 m_screen->cursor.row);
3456
3457 // does insert_char(c, false, false)
3458 GRAPHIC(seq);
3459 _bte_debug_print(BTE_DEBUG_PARSER,do { } while(0)
3460 "Last graphic is now U+%04X %lc\n",do { } while(0)
3461 m_last_graphic_character,do { } while(0)
3462 g_unichar_isprint(m_last_graphic_character) ? m_last_graphic_character : 0xfffd)do { } while(0);
3463
3464 if (m_line_wrapped) {
3465 m_line_wrapped = false;
3466 /* line wrapped, correct bbox */
3467 if (invalidated_text &&
3468 (m_screen->cursor.row > bbox_bottom + BTE_CELL_BBOX_SLACK1 ||
3469 m_screen->cursor.row < bbox_top - BTE_CELL_BBOX_SLACK1)) {
3470 invalidate_rows_and_context(bbox_top, bbox_bottom);
3471 bbox_bottom = -G_MAXINT2147483647;
3472 bbox_top = G_MAXINT2147483647;
3473 }
3474 bbox_top = std::min(bbox_top,
3475 m_screen->cursor.row);
3476 }
3477 /* Add the cells over which we have moved to the region
3478 * which we need to refresh for the user. */
3479 bbox_bottom = std::max(bbox_bottom,
3480 m_screen->cursor.row);
3481 invalidated_text = TRUE(!(0));
3482
3483 /* We *don't* emit flush pending signals here. */
3484 modified = TRUE(!(0));
3485
3486 break;
3487 }
3488
3489 case BTE_SEQ_NONE:
3490 case BTE_SEQ_IGNORE:
3491 break;
3492
3493 default: {
3494 switch (seq.command()) {
3495#define _BTE_CMD(cmd) case BTE_CMD_##cmd: cmd(seq); break;
3496#define _BTE_NOP(cmd)
3497#include "parser-cmd.hh"
3498#undef _BTE_CMD
3499#undef _BTE_NOP
3500 default:
3501 _bte_debug_print(BTE_DEBUG_PARSER,do { } while(0)
3502 "Unknown parser command %d\n", seq.command())do { } while(0);
3503 break;
3504 }
3505
3506 m_last_graphic_character = 0;
3507
3508 modified = TRUE(!(0));
3509
3510 // FIXME m_screen may be != previous_screen, check for that!
3511
3512 gboolean new_in_scroll_region = m_scrolling_restricted
3513 && (m_screen->cursor.row >= (m_screen->insert_delta + m_scrolling_region.start))
3514 && (m_screen->cursor.row <= (m_screen->insert_delta + m_scrolling_region.end));
3515
3516 /* if we have moved greatly during the sequence handler, or moved
3517 * into a scroll_region from outside it, restart the bbox.
3518 */
3519 if (invalidated_text &&
3520 ((new_in_scroll_region && !in_scroll_region) ||
3521 (m_screen->cursor.row > bbox_bottom + BTE_CELL_BBOX_SLACK1 ||
3522 m_screen->cursor.row < bbox_top - BTE_CELL_BBOX_SLACK1))) {
3523 invalidate_rows_and_context(bbox_top, bbox_bottom);
3524 invalidated_text = FALSE(0);
3525 bbox_bottom = -G_MAXINT2147483647;
3526 bbox_top = G_MAXINT2147483647;
3527 }
3528
3529 in_scroll_region = new_in_scroll_region;
3530
3531 break;
3532 }
3533 }
3534 break;
3535 }
3536 }
3537 }
3538
3539 if (chunk->eos()) {
3540 m_eos_pending = true;
3541 /* If there's an unfinished character in the queue, insert a replacement character */
3542 if (m_utf8_decoder.flush()) {
3543 insert_char(m_utf8_decoder.codepoint(), false, true);
3544 }
3545
3546 break;
3547 }
3548 }
3549
3550#ifdef BTE_DEBUG
3551 /* Some safety checks: ensure the visible parts of the buffer
3552 * are all in the buffer. */
3553 g_assert_cmpint(m_screen->insert_delta, >=, _bte_ring_delta(m_screen->row_data))do { gint64 __n1 = (m_screen->insert_delta), __n2 = (_bte_ring_delta
(m_screen->row_data)); if (__n1 >= __n2) ; else g_assertion_message_cmpnum
("BTE", "../src/bte.cc", 3553, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen->insert_delta" " " ">=" " " "_bte_ring_delta(m_screen->row_data)"
, (long double) __n1, ">=", (long double) __n2, 'i'); } while
(0)
;
3554
3555 /* The cursor shouldn't be above or below the addressable
3556 * part of the display buffer. */
3557 g_assert_cmpint(m_screen->cursor.row, >=, m_screen->insert_delta)do { gint64 __n1 = (m_screen->cursor.row), __n2 = (m_screen
->insert_delta); if (__n1 >= __n2) ; else g_assertion_message_cmpnum
("BTE", "../src/bte.cc", 3557, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen->cursor.row" " " ">=" " " "m_screen->insert_delta"
, (long double) __n1, ">=", (long double) __n2, 'i'); } while
(0)
;
3558#endif
3559
3560 if (modified) {
3561 /* Keep the cursor on-screen if we scroll on output, or if
3562 * we're currently at the bottom of the buffer. */
3563 update_insert_delta();
3564 if (m_scroll_on_output || bottom) {
3565 maybe_scroll_to_bottom();
3566 }
3567 /* Deselect the current selection if its contents are changed
3568 * by this insertion. */
3569 if (!m_selection_resolved.empty()) {
3570 //FIXMEchpe: this is atrocious
3571 auto selection = get_selected_text();
3572 if ((selection == nullptr) ||
3573 (m_selection[BTE_SELECTION_PRIMARY] == nullptr) ||
3574 (strcmp(selection->str, m_selection[BTE_SELECTION_PRIMARY]->str) != 0)) {
3575 deselect_all();
3576 }
3577 if (selection)
3578 g_string_free(selection, TRUE(!(0)));
3579 }
3580 }
3581
3582 if (modified || (m_screen != previous_screen)) {
3583 m_ringview.invalidate();
3584 /* Signal that the visible contents changed. */
3585 queue_contents_changed();
3586 }
3587
3588 emit_pending_signals();
3589
3590 if (invalidated_text) {
3591 invalidate_rows_and_context(bbox_top, bbox_bottom);
3592 }
3593
3594 if ((saved_cursor.col != m_screen->cursor.col) ||
3595 (saved_cursor.row != m_screen->cursor.row)) {
3596 /* invalidate the old and new cursor positions */
3597 if (saved_cursor_visible)
3598 invalidate_row(saved_cursor.row);
3599 invalidate_cursor_once();
3600 check_cursor_blink();
3601 /* Signal that the cursor moved. */
3602 queue_cursor_moved();
3603 } else if ((saved_cursor_visible != m_modes_private.DEC_TEXT_CURSOR()) ||
3604 (saved_cursor_style != m_cursor_style)) {
3605 invalidate_row(saved_cursor.row);
3606 check_cursor_blink();
3607 }
3608
3609 /* Tell the input method where the cursor is. */
3610 im_update_cursor();
3611
3612 /* After processing some data, do a hyperlink GC. The multiplier is totally arbitrary, feel free to fine tune. */
3613 _bte_ring_hyperlink_maybe_gc(m_screen->row_data, bytes_processed * 8);
3614
3615 _bte_debug_print (BTE_DEBUG_WORK, ")")do { } while(0);
3616 _bte_debug_print (BTE_DEBUG_IO,do { } while(0)
3617 "%" G_GSIZE_FORMAT " bytes in %" G_GSIZE_FORMAT " chunks left to process.\n",do { } while(0)
3618 m_input_bytes,do { } while(0)
3619 m_incoming_queue.size())do { } while(0);
3620}
3621
3622#ifdef WITH_ICU
3623
3624/* Note that this is mostly a copy of process_incoming_utf8() above; any non-charset-decoding
3625 * related changes made here need to be made there, too.
3626 * FIXMEchpe: refactor this to share more code with process_incoming_utf8().
3627 */
3628void
3629Terminal::process_incoming_pcterm()
3630{
3631 BteVisualPosition saved_cursor;
3632 gboolean saved_cursor_visible;
3633 CursorStyle saved_cursor_style;
3634 bte::grid::row_t bbox_top, bbox_bottom;
3635 gboolean modified, bottom;
3636 gboolean invalidated_text;
3637 gboolean in_scroll_region;
3638
3639 _bte_debug_print(BTE_DEBUG_IO,do { } while(0)
3640 "Handler processing %" G_GSIZE_FORMAT " bytes over %" G_GSIZE_FORMAT " chunks.\n",do { } while(0)
3641 m_input_bytes,do { } while(0)
3642 m_incoming_queue.size())do { } while(0);
3643 _bte_debug_print (BTE_DEBUG_WORK, "(")do { } while(0);
3644
3645 auto previous_screen = m_screen;
3646
3647 bottom = m_screen->insert_delta == (long)m_screen->scroll_delta;
3648
3649 /* Save the current cursor position. */
3650 saved_cursor = m_screen->cursor;
3651 saved_cursor_visible = m_modes_private.DEC_TEXT_CURSOR();
3652 saved_cursor_style = m_cursor_style;
3653
3654 in_scroll_region = m_scrolling_restricted
3655 && (m_screen->cursor.row >= (m_screen->insert_delta + m_scrolling_region.start))
3656 && (m_screen->cursor.row <= (m_screen->insert_delta + m_scrolling_region.end));
3657
3658 /* We should only be called when there's data to process. */
3659 g_assert(!m_incoming_queue.empty())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (!m_incoming_queue.empty()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 3659, ((const char*) (__PRETTY_FUNCTION__
)), "!m_incoming_queue.empty()"); } while (0)
;
3660
3661 modified = FALSE(0);
3662 invalidated_text = FALSE(0);
3663
3664 bbox_bottom = -G_MAXINT2147483647;
3665 bbox_top = G_MAXINT2147483647;
3666
3667 bte::parser::Sequence seq{m_parser};
3668
3669 m_line_wrapped = false;
3670
3671 size_t bytes_processed = 0;
3672
3673 auto& decoder = m_converter->decoder();
3674
3675 while (!m_incoming_queue.empty()) {
3676 auto chunk = std::move(m_incoming_queue.front());
3677 m_incoming_queue.pop();
3678
3679 g_assert_nonnull(chunk.get())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if ((chunk.get()) != nullptr) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message ("BTE"
, "../src/bte.cc", 3679, ((const char*) (__PRETTY_FUNCTION__)
), "'" "chunk.get()" "' should not be nullptr"); } while (0)
;
3680
3681 _BTE_DEBUG_IF(BTE_DEBUG_IO)if (0) {
3682 _bte_debug_hexdump("Incoming buffer", chunk->data, chunk->len);
3683 }
3684
3685 bytes_processed += chunk->len;
3686
3687 auto const* ip = chunk->data;
3688 auto const* iend = chunk->data + chunk->len;
3689
3690 auto eos = bool{false};
3691 auto flush = bool{false};
3692
3693 start:
3694 while (ip < iend || flush) {
3695 switch (decoder.decode(&ip, flush)) {
3696 case bte::base::ICUDecoder::Result::eSomething: {
3697 auto rv = m_parser.feed(decoder.codepoint());
3698 if (G_UNLIKELY(rv < 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
rv < 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
) {
3699#ifdef BTE_DEBUG
3700 uint32_t c = decoder.codepoint();
3701 char c_buf[7];
3702 g_snprintf(c_buf, sizeof(c_buf), "%lc", c);
3703 char const* wp_str = g_unichar_isprint(c) ? c_buf : _bte_debug_sequence_to_string(c_buf, -1);
3704 _bte_debug_print(BTE_DEBUG_PARSER, "Parser error on U+%04X [%s]!\n",do { } while(0)
3705 c, wp_str)do { } while(0);
3706#endif
3707 break;
3708 }
3709
3710#ifdef BTE_DEBUG
3711 if (rv != BTE_SEQ_NONE)
3712 g_assert((bool)seq)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if ((bool)seq) _g_boolean_var_ = 1; else _g_boolean_var_ = 0
; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr ("BTE"
, "../src/bte.cc", 3712, ((const char*) (__PRETTY_FUNCTION__)
), "(bool)seq"); } while (0)
;
3713#endif
3714
3715 _BTE_DEBUG_IF(BTE_DEBUG_PARSER)if (0) {
3716 if (rv != BTE_SEQ_NONE) {
3717 seq.print();
3718 }
3719 }
3720
3721 // FIXMEchpe this assumes that the only handler inserting
3722 // a character is GRAPHIC, which isn't true (at least ICH, REP, SUB
3723 // also do, and invalidate directly for now)...
3724
3725 switch (rv) {
3726 case BTE_SEQ_GRAPHIC: {
3727
3728 bbox_top = std::min(bbox_top,
3729 m_screen->cursor.row);
3730
3731 // does insert_char(c, false, false)
3732 GRAPHIC(seq);
3733 _bte_debug_print(BTE_DEBUG_PARSER,do { } while(0)
3734 "Last graphic is now U+%04X %lc\n",do { } while(0)
3735 m_last_graphic_character,do { } while(0)
3736 g_unichar_isprint(m_last_graphic_character) ? m_last_graphic_character : 0xfffd)do { } while(0);
3737
3738 if (m_line_wrapped) {
3739 m_line_wrapped = false;
3740 /* line wrapped, correct bbox */
3741 if (invalidated_text &&
3742 (m_screen->cursor.row > bbox_bottom + BTE_CELL_BBOX_SLACK1 ||
3743 m_screen->cursor.row < bbox_top - BTE_CELL_BBOX_SLACK1)) {
3744 invalidate_rows_and_context(bbox_top, bbox_bottom);
3745 bbox_bottom = -G_MAXINT2147483647;
3746 bbox_top = G_MAXINT2147483647;
3747 }
3748 bbox_top = std::min(bbox_top,
3749 m_screen->cursor.row);
3750 }
3751 /* Add the cells over which we have moved to the region
3752 * which we need to refresh for the user. */
3753 bbox_bottom = std::max(bbox_bottom,
3754 m_screen->cursor.row);
3755 invalidated_text = TRUE(!(0));
3756
3757 /* We *don't* emit flush pending signals here. */
3758 modified = TRUE(!(0));
3759
3760 break;
3761 }
3762
3763 case BTE_SEQ_NONE:
3764 case BTE_SEQ_IGNORE:
3765 break;
3766
3767 default: {
3768 switch (seq.command()) {
3769#define _BTE_CMD(cmd) case BTE_CMD_##cmd: cmd(seq); break;
3770#define _BTE_NOP(cmd)
3771#include "parser-cmd.hh"
3772#undef _BTE_CMD
3773#undef _BTE_NOP
3774 default:
3775 _bte_debug_print(BTE_DEBUG_PARSER,do { } while(0)
3776 "Unknown parser command %d\n", seq.command())do { } while(0);
3777 break;
3778 }
3779
3780 m_last_graphic_character = 0;
3781
3782 modified = TRUE(!(0));
3783
3784 // FIXME m_screen may be != previous_screen, check for that!
3785
3786 gboolean new_in_scroll_region = m_scrolling_restricted
3787 && (m_screen->cursor.row >= (m_screen->insert_delta + m_scrolling_region.start))
3788 && (m_screen->cursor.row <= (m_screen->insert_delta + m_scrolling_region.end));
3789
3790 /* if we have moved greatly during the sequence handler, or moved
3791 * into a scroll_region from outside it, restart the bbox.
3792 */
3793 if (invalidated_text &&
3794 ((new_in_scroll_region && !in_scroll_region) ||
3795 (m_screen->cursor.row > bbox_bottom + BTE_CELL_BBOX_SLACK1 ||
3796 m_screen->cursor.row < bbox_top - BTE_CELL_BBOX_SLACK1))) {
3797 invalidate_rows_and_context(bbox_top, bbox_bottom);
3798 invalidated_text = FALSE(0);
3799 bbox_bottom = -G_MAXINT2147483647;
3800 bbox_top = G_MAXINT2147483647;
3801 }
3802
3803 in_scroll_region = new_in_scroll_region;
3804
3805 break;
3806 }
3807 }
3808 break;
3809 }
3810 case bte::base::ICUDecoder::Result::eNothing:
3811 flush = false;
3812 break;
3813
3814 case bte::base::ICUDecoder::Result::eError:
3815 // FIXMEchpe do we need ++ip here?
3816 decoder.reset();
3817 break;
3818
3819 }
3820 }
3821
3822 if (eos) {
3823 /* Done processing the last chunk */
3824 m_eos_pending = true;
3825 break;
3826 }
3827
3828 if (chunk->eos()) {
3829 /* On EOS, we still need to flush the decoder before we can finish */
3830 eos = flush = true;
3831 goto start;
3832 }
3833 }
3834
3835#ifdef BTE_DEBUG
3836 /* Some safety checks: ensure the visible parts of the buffer
3837 * are all in the buffer. */
3838 g_assert_cmpint(m_screen->insert_delta, >=, _bte_ring_delta(m_screen->row_data))do { gint64 __n1 = (m_screen->insert_delta), __n2 = (_bte_ring_delta
(m_screen->row_data)); if (__n1 >= __n2) ; else g_assertion_message_cmpnum
("BTE", "../src/bte.cc", 3838, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen->insert_delta" " " ">=" " " "_bte_ring_delta(m_screen->row_data)"
, (long double) __n1, ">=", (long double) __n2, 'i'); } while
(0)
;
3839
3840 /* The cursor shouldn't be above or below the addressable
3841 * part of the display buffer. */
3842 g_assert_cmpint(m_screen->cursor.row, >=, m_screen->insert_delta)do { gint64 __n1 = (m_screen->cursor.row), __n2 = (m_screen
->insert_delta); if (__n1 >= __n2) ; else g_assertion_message_cmpnum
("BTE", "../src/bte.cc", 3842, ((const char*) (__PRETTY_FUNCTION__
)), "m_screen->cursor.row" " " ">=" " " "m_screen->insert_delta"
, (long double) __n1, ">=", (long double) __n2, 'i'); } while
(0)
;
3843#endif
3844
3845 if (modified) {
3846 /* Keep the cursor on-screen if we scroll on output, or if
3847 * we're currently at the bottom of the buffer. */
3848 update_insert_delta();
3849 if (m_scroll_on_output || bottom) {
3850 maybe_scroll_to_bottom();
3851 }
3852 /* Deselect the current selection if its contents are changed
3853 * by this insertion. */
3854 if (!m_selection_resolved.empty()) {
3855 //FIXMEchpe: this is atrocious
3856 auto selection = get_selected_text();
3857 if ((selection == nullptr) ||
3858 (m_selection[BTE_SELECTION_PRIMARY] == nullptr) ||
3859 (strcmp(selection->str, m_selection[BTE_SELECTION_PRIMARY]->str) != 0)) {
3860 deselect_all();
3861 }
3862 if (selection)
3863 g_string_free(selection, TRUE(!(0)));
3864 }
3865 }
3866
3867 if (modified || (m_screen != previous_screen)) {
3868 m_ringview.invalidate();
3869 /* Signal that the visible contents changed. */
3870 queue_contents_changed();
3871 }
3872
3873 emit_pending_signals();
3874
3875 if (invalidated_text) {
3876 invalidate_rows_and_context(bbox_top, bbox_bottom);
3877 }
3878
3879 if ((saved_cursor.col != m_screen->cursor.col) ||
3880 (saved_cursor.row != m_screen->cursor.row)) {
3881 /* invalidate the old and new cursor positions */
3882 if (saved_cursor_visible)
3883 invalidate_row(saved_cursor.row);
3884 invalidate_cursor_once();
3885 check_cursor_blink();
3886 /* Signal that the cursor moved. */
3887 queue_cursor_moved();
3888 } else if ((saved_cursor_visible != m_modes_private.DEC_TEXT_CURSOR()) ||
3889 (saved_cursor_style != m_cursor_style)) {
3890 invalidate_row(saved_cursor.row);
3891 check_cursor_blink();
3892 }
3893
3894 /* Tell the input method where the cursor is. */
3895 im_update_cursor();
3896
3897 /* After processing some data, do a hyperlink GC. The multiplier is totally arbitrary, feel free to fine tune. */
3898 _bte_ring_hyperlink_maybe_gc(m_screen->row_data, bytes_processed * 8);
3899
3900 _bte_debug_print (BTE_DEBUG_WORK, ")")do { } while(0);
3901 _bte_debug_print (BTE_DEBUG_IO,do { } while(0)
3902 "%" G_GSIZE_FORMAT " bytes in %" G_GSIZE_FORMAT " chunks left to process.\n",do { } while(0)
3903 m_input_bytes,do { } while(0)
3904 m_incoming_queue.size())do { } while(0);
3905}
3906
3907#endif /* WITH_ICU */
3908
3909bool
3910Terminal::pty_io_read(int const fd,
3911 GIOCondition const condition)
3912{
3913 _bte_debug_print (BTE_DEBUG_WORK, ".")do { } while(0);
3914 _bte_debug_print(BTE_DEBUG_IO, "::pty_io_read condition %02x\n", condition)do { } while(0);
3915
3916 /* We need to check for EOS so that we can shut down the PTY.
3917 * When we get G_IO_HUP without G_IO_IN, we can process the EOF now.
3918 * However when we get G_IO_IN | G_IO_HUP, there is still data to be
3919 * read in this round, and in potentially more rounds; read the data
3920 * now, do *not* process the EOS (unless the read returns EIO, which
3921 * does happen and appears to mean that despite G_IO_IN no data was
3922 * actually available to be read, not even the cpkt header), and
3923 * otherwise wait for further calls which will have G_IO_HUP (and
3924 * possibly G_IO_IN again).
3925 */
3926 auto eos = bool{condition == G_IO_HUP};
3927
3928 /* There is data to read */
3929 auto err = int{0};
3930 auto again = bool{true};
3931 bte::base::Chunk* chunk{nullptr};
3932 if (condition & (G_IO_IN | G_IO_PRI)) {
3933 guchar *bp;
3934 int rem, len;
3935 guint bytes, max_bytes;
3936
3937 /* Limit the amount read between updates, so as to
3938 * 1. maintain fairness between multiple terminals;
3939 * 2. prevent reading the entire output of a command in one
3940 * pass, i.e. we always try to refresh the terminal ~40Hz.
3941 * See time_process_incoming() where we estimate the
3942 * maximum number of bytes we can read/process in between
3943 * updates.
3944 */
3945 max_bytes = m_active_terminals_link != nullptr ?
3946 g_list_length(g_active_terminals) - 1 : 0;
3947 if (max_bytes) {
3948 max_bytes = m_max_input_bytes / max_bytes;
3949 } else {
3950 max_bytes = m_max_input_bytes;
3951 }
3952 bytes = m_input_bytes;
3953
3954 /* If possible, try adding more data to the chunk at the back of the queue */
3955 if (!m_incoming_queue.empty())
3956 chunk = m_incoming_queue.back().get();
3957
3958 do {
3959 /* No chunk, chunk sealed or at least ¾ full? Get a new chunk */
3960 if (!chunk ||
3961 chunk->sealed() ||
3962 chunk->len >= 3 * chunk->capacity() / 4) {
3963 m_incoming_queue.push(bte::base::Chunk::get());
3964
3965 chunk = m_incoming_queue.back().get();
3966 }
3967
3968 rem = chunk->remaining_capacity();
3969 bp = chunk->data + chunk->len;
3970 len = 0;
3971 do {
3972 /* We'd like to read (fd, bp, rem); but due to TIOCPKT mode
3973 * there's an extra input byte returned at the beginning.
3974 * We need to see what that byte is, but otherwise drop it
3975 * and write continuously to chunk->data.
3976 */
3977 char pkt_header;
3978 char save = bp[-1];
3979 errno(*__errno_location ()) = 0;
3980 int ret = read (fd, bp - 1, rem + 1);
3981 pkt_header = bp[-1];
3982 bp[-1] = save;
3983 switch (ret){
3984 case -1:
3985 err = errno(*__errno_location ());
3986 goto out;
3987 case 0:
3988 eos = true;
3989 goto out;
3990 default:
3991 ret--;
3992
3993 if (pkt_header == TIOCPKT_DATA0) {
3994 bp += ret;
3995 rem -= ret;
3996 len += ret;
3997 } else {
3998 if (pkt_header & TIOCPKT_IOCTL64) {
3999 /* We'd like to always be informed when the termios change,
4000 * so we can e.g. detect when no-echo is en/disabled and
4001 * change the cursor/input method/etc., but unfortunately
4002 * the kernel only sends this flag when (old or new) 'local flags'
4003 * include EXTPROC, which is not used often, and due to its side
4004 * effects, cannot be enabled by bte by default.
4005 *
4006 * FIXME: improve the kernel! see discussion in bug 755371
4007 * starting at comment 12
4008 */
4009 pty_termios_changed();
4010 }
4011 if (pkt_header & TIOCPKT_STOP4) {
4012 pty_scroll_lock_changed(true);
4013 }
4014 if (pkt_header & TIOCPKT_START8) {
4015 pty_scroll_lock_changed(false);
4016 }
4017 }
4018 break;
4019 }
4020 } while (rem);
4021out:
4022 chunk->len += len;
4023 bytes += len;
4024 } while (bytes < max_bytes &&
4025 chunk->len == chunk->capacity());
4026
4027 /* We may have an empty chunk at the back of the queue, but
4028 * that doesn't matter, we'll fill it next time.
4029 */
4030
4031 if (!is_processing()) {
4032 add_process_timeout(this);
4033 }
4034 m_pty_input_active = len != 0;
4035 m_input_bytes = bytes;
4036 again = bytes < max_bytes;
4037
4038 _bte_debug_print (BTE_DEBUG_IO, "read %d/%d bytes, again? %s, active? %s\n",do { } while(0)
4039 bytes, max_bytes,do { } while(0)
4040 again ? "yes" : "no",do { } while(0)
4041 m_pty_input_active ? "yes" : "no")do { } while(0);
4042 }
4043
4044 if (condition & G_IO_ERR)
4045 err = EIO5;
4046
4047 /* Error? */
4048 switch (err) {
4049 case 0: /* no error */
4050 break;
4051 case EIO5: /* EOS */
4052 eos = true;
4053 break;
4054 case EAGAIN11:
4055 case EBUSY16: /* do nothing */
4056 break;
4057 default:
4058 auto errsv = bte::libc::ErrnoSaver{};
4059 _bte_debug_print (BTE_DEBUG_IO, "Error reading from child: %s",do { } while(0)
4060 g_strerror(errsv))do { } while(0);
4061 break;
4062 }
4063
4064 if (eos) {
4065 _bte_debug_print(BTE_DEBUG_IO, "got PTY EOF\n")do { } while(0);
4066
4067 /* Make a note of the EOS; but do not process it since there may be data
4068 * to be processed first in the incomding queue.
4069 */
4070 if (!chunk || chunk->sealed()) {
4071 m_incoming_queue.push(bte::base::Chunk::get());
4072 chunk = m_incoming_queue.back().get();
4073 }
4074
4075 chunk->set_sealed();
4076 chunk->set_eos();
4077
4078 /* Cancel wait timer */
4079 m_child_exited_eos_wait_timer.abort();
4080
4081 /* Need to process the EOS */
4082 if (!is_processing()) {
4083 add_process_timeout(this);
4084 }
4085
4086 again = false;
4087 }
4088
4089 return again;
4090}
4091
4092/*
4093 * Terminal::feed:
4094 * @data: data
4095 *
4096 * Interprets @data as if it were data received from a child process.
4097 */
4098void
4099Terminal::feed(std::string_view const& data,
4100 bool start_processing_)
4101{
4102 auto length = data.size();
4103 auto ptr = data.data();
4104
4105 bte::base::Chunk* chunk = nullptr;
4106 if (!m_incoming_queue.empty()) {
4107 auto& achunk = m_incoming_queue.back();
4108 if (length < achunk->remaining_capacity() && !achunk->sealed())
4109 chunk = achunk.get();
4110 }
4111 if (chunk == nullptr) {
4112 m_incoming_queue.push(bte::base::Chunk::get());
4113 chunk = m_incoming_queue.back().get();
4114 }
4115
4116 /* Break the incoming data into chunks. */
4117 do {
4118 auto rem = chunk->remaining_capacity();
4119 auto len = std::min(length, rem);
4120 memcpy (chunk->data + chunk->len, ptr, len);
4121 chunk->len += len;
4122 length -= len;
4123 if (length == 0)
4124 break;
4125
4126 ptr += len;
4127
4128 /* Get another chunk for the remaining data */
4129 m_incoming_queue.push(bte::base::Chunk::get());
4130 chunk = m_incoming_queue.back().get();
4131 } while (true);
4132
4133 if (start_processing_)
4134 start_processing();
4135}
4136
4137bool
4138Terminal::pty_io_write(int const fd,
4139 GIOCondition const condition)
4140{
4141 auto const count = write(fd,
4142 m_outgoing->data,
4143 _bte_byte_array_length(m_outgoing)((m_outgoing)->len));
4144 if (count != -1) {
4145 _BTE_DEBUG_IF (BTE_DEBUG_IO)if (0) {
4146 _bte_debug_hexdump("Outgoing buffer written",
4147 (uint8_t const*)m_outgoing->data,
4148 count);
4149 }
4150 _bte_byte_array_consume(m_outgoing, count)g_byte_array_remove_range (m_outgoing, 0, count);
4151 }
4152
4153 /* Run again if there are more bytes to write */
4154 return _bte_byte_array_length(m_outgoing)((m_outgoing)->len) != 0;
4155}
4156
4157/* Send some UTF-8 data to the child. */
4158void
4159Terminal::send_child(std::string_view const& data)
4160{
4161 // FIXMEchpe remove
4162 if (!m_input_enabled)
4163 return;
4164
4165 /* Note that for backward compatibility, we need to emit the
4166 * ::commit signal even if there is no PTY. See issue bte#222.
4167 */
4168
4169 switch (data_syntax()) {
4170 case DataSyntax::eECMA48_UTF8:
4171 emit_commit(data);
4172 if (pty())
4173 _bte_byte_array_append(m_outgoing, data.data(), data.size())g_byte_array_append (m_outgoing, (const guint8 *) (data.data(
)), data.size())
;
4174 break;
4175
4176#ifdef WITH_ICU
4177 case DataSyntax::eECMA48_PCTERM: {
4178 auto converted = m_converter->convert(data);
4179
4180 emit_commit(converted);
4181 if (pty())
4182 _bte_byte_array_append(m_outgoing, converted.data(), converted.size())g_byte_array_append (m_outgoing, (const guint8 *) (converted.
data()), converted.size())
;
4183 break;
4184 }
4185#endif
4186
4187 default:
4188 g_assert_not_reached()do { g_assertion_message_expr ("BTE", "../src/bte.cc", 4188, (
(const char*) (__PRETTY_FUNCTION__)), __null); } while (0)
;
4189 return;
4190 }
4191
4192 /* If we need to start waiting for the child pty to
4193 * become available for writing, set that up here. */
4194 connect_pty_write();
4195}
4196
4197/*
4198 * BteTerminal::feed_child:
4199 * @str: data to send to the child
4200 *
4201 * Sends UTF-8 text to the child as if it were entered by the user
4202 * at the keyboard.
4203 *
4204 * Does nothing if input is disabled.
4205 */
4206void
4207Terminal::feed_child(std::string_view const& str)
4208{
4209 if (!m_input_enabled)
4210 return;
4211
4212 send_child(str);
4213}
4214
4215/*
4216 * Terminal::feed_child_binary:
4217 * @data: data to send to the child
4218 *
4219 * Sends a block of binary data to the child.
4220 *
4221 * Does nothing if input is disabled.
4222 */
4223void
4224Terminal::feed_child_binary(std::string_view const& data)
4225{
4226 if (!m_input_enabled)
4227 return;
4228
4229 /* If there's a place for it to go, add the data to the
4230 * outgoing buffer. */
4231 if (!pty())
4232 return;
4233
4234 emit_commit(data);
4235 _bte_byte_array_append(m_outgoing, data.data(), data.size())g_byte_array_append (m_outgoing, (const guint8 *) (data.data(
)), data.size())
;
4236
4237 /* If we need to start waiting for the child pty to
4238 * become available for writing, set that up here. */
4239 connect_pty_write();
4240}
4241
4242void
4243Terminal::send(bte::parser::u8SequenceBuilder const& builder,
4244 bool c1,
4245 bte::parser::u8SequenceBuilder::Introducer introducer,
4246 bte::parser::u8SequenceBuilder::ST st) noexcept
4247{
4248 std::string str;
4249 builder.to_string(str, c1, -1, introducer, st);
4250 feed_child(str);
4251}
4252
4253void
4254Terminal::send(bte::parser::Sequence const& seq,
4255 bte::parser::u8SequenceBuilder const& builder) noexcept
4256{
4257 // FIXMEchpe always take c1 & ST from @seq?
4258 if (seq.type() == BTE_SEQ_OSC &&
4259 builder.type() == BTE_SEQ_OSC) {
4260 /* If we reply to a BEL-terminated OSC, reply with BEL-terminated OSC
4261 * as well, see https://bugzilla.gnome.org/show_bug.cgi?id=722446 and
4262 * https://gitlab.gnome.org/GNOME/bte/issues/65 .
4263 */
4264 send(builder, false,
4265 bte::parser::u8SequenceBuilder::Introducer::DEFAULT,
4266 seq.terminator() == 0x7 ? bte::parser::u8SequenceBuilder::ST::BEL
4267 : bte::parser::u8SequenceBuilder::ST::DEFAULT);
4268 } else {
4269 send(builder, false);
4270 }
4271}
4272
4273void
4274Terminal::send(unsigned int type,
4275 std::initializer_list<int> params) noexcept
4276{
4277 // FIXMEchpe take c1 & ST from @seq
4278 send(bte::parser::ReplyBuilder{type, params}, false);
4279}
4280
4281void
4282Terminal::reply(bte::parser::Sequence const& seq,
4283 unsigned int type,
4284 std::initializer_list<int> params) noexcept
4285{
4286 send(seq, bte::parser::ReplyBuilder{type, params});
4287}
4288
4289#if 0
4290void
4291Terminal::reply(bte::parser::Sequence const& seq,
4292 unsigned int type,
4293 std::initializer_list<int> params,
4294 std::string const& str) noexcept
4295{
4296 bte::parser::ReplyBuilder reply_builder{type, params};
4297 reply_builder.set_string(str);
4298 send(seq, reply_builder);
4299}
4300#endif
4301
4302void
4303Terminal::reply(bte::parser::Sequence const& seq,
4304 unsigned int type,
4305 std::initializer_list<int> params,
4306 bte::parser::ReplyBuilder const& builder) noexcept
4307{
4308 std::string str;
4309 builder.to_string(str, true, -1,
4310 bte::parser::ReplyBuilder::Introducer::NONE,
4311 bte::parser::ReplyBuilder::ST::NONE);
4312
4313 bte::parser::ReplyBuilder reply_builder{type, params};
4314 reply_builder.set_string(std::move(str));
4315 send(seq, reply_builder);
4316}
4317
4318void
4319Terminal::reply(bte::parser::Sequence const& seq,
4320 unsigned int type,
4321 std::initializer_list<int> params,
4322 char const* format,
4323 ...) noexcept
4324{
4325 char buf[128];
4326 va_list vargs;
4327 va_start(vargs, format)__builtin_va_start(vargs, format);
4328 auto len = g_vsnprintf(buf, sizeof(buf), format, vargs);
4329 va_end(vargs)__builtin_va_end(vargs);
4330 g_assert_cmpint(len, <, sizeof(buf))do { gint64 __n1 = (len), __n2 = (sizeof(buf)); if (__n1 <
__n2) ; else g_assertion_message_cmpnum ("BTE", "../src/bte.cc"
, 4330, ((const char*) (__PRETTY_FUNCTION__)), "len" " " "<"
" " "sizeof(buf)", (long double) __n1, "<", (long double)
__n2, 'i'); } while (0)
;
4331
4332 bte::parser::ReplyBuilder builder{type, params};
4333 builder.set_string(std::string{buf});
4334
4335 send(seq, builder);
4336}
4337
4338void
4339Terminal::im_commit(std::string_view const& str)
4340{
4341 if (!m_input_enabled)
4342 return;
4343
4344 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
4345 "Input method committed `%s'.\n", std::string{str}.c_str())do { } while(0);
4346 send_child(str);
4347
4348 /* Committed text was committed because the user pressed a key, so
4349 * we need to obey the scroll-on-keystroke setting. */
4350 if (m_scroll_on_keystroke && m_input_enabled) {
4351 maybe_scroll_to_bottom();
4352 }
4353}
4354
4355void
4356Terminal::im_preedit_set_active(bool active) noexcept
4357{
4358 m_im_preedit_active = active;
4359}
4360
4361void
4362Terminal::im_preedit_reset() noexcept
4363{
4364 m_im_preedit.clear();
4365 m_im_preedit.shrink_to_fit();
4366 m_im_preedit_cursor = 0;
4367 m_im_preedit_attrs.reset();
4368}
4369
4370void
4371Terminal::im_preedit_changed(std::string_view const& str,
4372 int cursorpos,
4373 pango_attr_list_unique_type&& attrs) noexcept
4374{
4375 /* Queue the area where the current preedit string is being displayed
4376 * for repainting. */
4377 invalidate_cursor_once();
4378
4379 im_preedit_reset();
4380 m_im_preedit = str;
4381 m_im_preedit_attrs = std::move(attrs);
4382 m_im_preedit_cursor = cursorpos;
4383
4384 /* Invalidate again with the new cursor position */
4385 invalidate_cursor_once();
4386
4387 /* And tell the input method where the cursor is on the screen */
4388 im_update_cursor();
4389}
4390
4391bool
4392Terminal::im_retrieve_surrounding()
4393{
4394 /* FIXME: implement this! Bug #726191 */
4395 return false;
4396}
4397
4398bool
4399Terminal::im_delete_surrounding(int offset,
4400 int n_chars)
4401{
4402 /* FIXME: implement this! Bug #726191 */
4403 return false;
4404}
4405
4406void
4407Terminal::im_update_cursor()
4408{
4409 if (!widget_realized())
4410 return;
4411
4412 cairo_rectangle_int_t rect;
4413 rect.x = m_screen->cursor.col * m_cell_width + m_padding.left +
4414 get_preedit_width(false) * m_cell_width;
4415 rect.width = m_cell_width; // FIXMEchpe: if columns > 1 ?
4416 rect.y = row_to_pixel(m_screen->cursor.row) + m_padding.top;
4417 rect.height = m_cell_height;
4418 m_real_widget->im_set_cursor_location(&rect);
4419}
4420
4421void
4422Terminal::set_border_padding(CtkBorder const* padding)
4423{
4424 if (memcmp(padding, &m_padding, sizeof(*padding)) != 0) {
4425 _bte_debug_print(BTE_DEBUG_MISC | BTE_DEBUG_WIDGET_SIZE,do { } while(0)
4426 "Setting padding to (%d,%d,%d,%d)\n",do { } while(0)
4427 padding->left, padding->right,do { } while(0)
4428 padding->top, padding->bottom)do { } while(0);
4429
4430 m_padding = *padding;
4431 update_view_extents();
4432 ctk_widget_queue_resize(m_widget);
4433 } else {
4434 _bte_debug_print(BTE_DEBUG_MISC | BTE_DEBUG_WIDGET_SIZE,do { } while(0)
4435 "Keeping padding the same at (%d,%d,%d,%d)\n",do { } while(0)
4436 padding->left, padding->right,do { } while(0)
4437 padding->top, padding->bottom)do { } while(0);
4438
4439 }
4440}
4441
4442void
4443Terminal::set_cursor_aspect(float aspect)
4444{
4445 if (_bte_double_equal(aspect, m_cursor_aspect_ratio))
4446 return;
4447
4448 m_cursor_aspect_ratio = aspect;
4449 invalidate_cursor_once();
4450}
4451
4452void
4453Terminal::widget_style_updated()
4454{
4455 // FIXMEchpe: remove taking font info from the widget style
4456 set_font_desc(m_unscaled_font_desc.get());
4457}
4458
4459void
4460Terminal::add_cursor_timeout()
4461{
4462 if (m_cursor_blink_timer)
4463 return; /* already added */
4464
4465 m_cursor_blink_time = 0;
4466 m_cursor_blink_timer.schedule(m_cursor_blink_cycle, bte::glib::Timer::Priority::eLOW);
4467}
4468
4469void
4470Terminal::remove_cursor_timeout()
4471{
4472 if (!m_cursor_blink_timer)
4473 return; /* already removed */
4474
4475 m_cursor_blink_timer.abort();
4476 if (!m_cursor_blink_state) {
4477 invalidate_cursor_once();
4478 m_cursor_blink_state = true;
4479 }
4480}
4481
4482/* Activates / disactivates the cursor blink timer to reduce wakeups */
4483void
4484Terminal::check_cursor_blink()
4485{
4486 if (m_has_focus &&
4487 m_cursor_blinks &&
4488 m_modes_private.DEC_TEXT_CURSOR())
4489 add_cursor_timeout();
4490 else
4491 remove_cursor_timeout();
4492}
4493
4494void
4495Terminal::beep()
4496{
4497 if (m_audible_bell)
4498 m_real_widget->beep();
4499}
4500
4501bool
4502Terminal::widget_key_press(KeyEvent const& event)
4503{
4504 char *normal = NULL__null;
4505 gsize normal_length = 0;
4506 struct termios tio;
4507 gboolean scrolled = FALSE(0), steal = FALSE(0), modifier = FALSE(0), handled,
4508 suppress_alt_esc = FALSE(0), add_modifiers = FALSE(0);
4509 guint keyval = 0;
4510 gunichar keychar = 0;
4511 char keybuf[BTE_UTF8_BPC(4)];
4512
4513 /* If it's a keypress, record that we got the event, in case the
4514 * input method takes the event from us. */
4515 // FIXMEchpe this is ::widget_key_press; what other event type could it even be!?
4516 if (event.is_key_press()) {
4517 /* Store a copy of the key. */
4518 keyval = event.keyval();
4519 m_modifiers = event.modifiers();
4520
4521 // FIXMEchpe?
4522 if (m_cursor_blink_timer) {
4523 remove_cursor_timeout();
4524 add_cursor_timeout();
4525 }
4526
4527 /* Determine if this is just a modifier key. */
4528 modifier = _bte_keymap_key_is_modifier(keyval);
4529
4530 /* Unless it's a modifier key, hide the pointer. */
4531 if (!modifier) {
4532 set_pointer_autohidden(true);
4533 }
4534
4535 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
4536 "Keypress, modifiers=0x%x, "do { } while(0)
4537 "keyval=0x%x, raw string=`%s'.\n",do { } while(0)
4538 m_modifiers,do { } while(0)
4539 keyval, event.string())do { } while(0);
4540
4541 /* We steal many keypad keys here. */
4542 if (!m_im_preedit_active) {
4543 switch (keyval) {
4544 case CDK_KEY_KP_Add0xffab:
4545 case CDK_KEY_KP_Subtract0xffad:
4546 case CDK_KEY_KP_Multiply0xffaa:
4547 case CDK_KEY_KP_Divide0xffaf:
4548 case CDK_KEY_KP_Enter0xff8d:
4549 steal = TRUE(!(0));
4550 break;
4551 default:
4552 break;
4553 }
4554 if (m_modifiers & BTE_ALT_MASKCDK_MOD1_MASK) {
4555 steal = TRUE(!(0));
4556 }
4557 switch (keyval) {
4558 case CDK_KEY_ISO_Lock0xfe01:
4559 case CDK_KEY_ISO_Level2_Latch0xfe02:
4560 case CDK_KEY_ISO_Level3_Shift0xfe03:
4561 case CDK_KEY_ISO_Level3_Latch0xfe04:
4562 case CDK_KEY_ISO_Level3_Lock0xfe05:
4563 case CDK_KEY_ISO_Level5_Shift0xfe11:
4564 case CDK_KEY_ISO_Level5_Latch0xfe12:
4565 case CDK_KEY_ISO_Level5_Lock0xfe13:
4566 case CDK_KEY_ISO_Group_Shift0xff7e:
4567 case CDK_KEY_ISO_Group_Latch0xfe06:
4568 case CDK_KEY_ISO_Group_Lock0xfe07:
4569 case CDK_KEY_ISO_Next_Group0xfe08:
4570 case CDK_KEY_ISO_Next_Group_Lock0xfe09:
4571 case CDK_KEY_ISO_Prev_Group0xfe0a:
4572 case CDK_KEY_ISO_Prev_Group_Lock0xfe0b:
4573 case CDK_KEY_ISO_First_Group0xfe0c:
4574 case CDK_KEY_ISO_First_Group_Lock0xfe0d:
4575 case CDK_KEY_ISO_Last_Group0xfe0e:
4576 case CDK_KEY_ISO_Last_Group_Lock0xfe0f:
4577 case CDK_KEY_Multi_key0xff20:
4578 case CDK_KEY_Codeinput0xff37:
4579 case CDK_KEY_SingleCandidate0xff3c:
4580 case CDK_KEY_MultipleCandidate0xff3d:
4581 case CDK_KEY_PreviousCandidate0xff3e:
4582 case CDK_KEY_Kanji0xff21:
4583 case CDK_KEY_Muhenkan0xff22:
4584 case CDK_KEY_Henkan_Mode0xff23:
4585 /* case CDK_KEY_Henkan: is CDK_KEY_Henkan_Mode */
4586 case CDK_KEY_Romaji0xff24:
4587 case CDK_KEY_Hiragana0xff25:
4588 case CDK_KEY_Katakana0xff26:
4589 case CDK_KEY_Hiragana_Katakana0xff27:
4590 case CDK_KEY_Zenkaku0xff28:
4591 case CDK_KEY_Hankaku0xff29:
4592 case CDK_KEY_Zenkaku_Hankaku0xff2a:
4593 case CDK_KEY_Touroku0xff2b:
4594 case CDK_KEY_Massyo0xff2c:
4595 case CDK_KEY_Kana_Lock0xff2d:
4596 case CDK_KEY_Kana_Shift0xff2e:
4597 case CDK_KEY_Eisu_Shift0xff2f:
4598 case CDK_KEY_Eisu_toggle0xff30:
4599 /* case CDK_KEY_Kanji_Bangou: is CDK_KEY_Codeinput */
4600 /* case CDK_KEY_Zen_Koho: is CDK_KEY_MultipleCandidate */
4601 /* case CDK_KEY_Mae_Koho: is CDK_KEY_PreviousCandidate */
4602 /* case CDK_KEY_kana_switch: is CDK_KEY_ISO_Group_Shift */
4603 case CDK_KEY_Hangul0xff31:
4604 case CDK_KEY_Hangul_Start0xff32:
4605 case CDK_KEY_Hangul_End0xff33:
4606 case CDK_KEY_Hangul_Hanja0xff34:
4607 case CDK_KEY_Hangul_Jamo0xff35:
4608 case CDK_KEY_Hangul_Romaja0xff36:
4609 /* case CDK_KEY_Hangul_Codeinput: is CDK_KEY_Codeinput */
4610 case CDK_KEY_Hangul_Jeonja0xff38:
4611 case CDK_KEY_Hangul_Banja0xff39:
4612 case CDK_KEY_Hangul_PreHanja0xff3a:
4613 case CDK_KEY_Hangul_PostHanja0xff3b:
4614 /* case CDK_KEY_Hangul_SingleCandidate: is CDK_KEY_SingleCandidate */
4615 /* case CDK_KEY_Hangul_MultipleCandidate: is CDK_KEY_MultipleCandidate */
4616 /* case CDK_KEY_Hangul_PreviousCandidate: is CDK_KEY_PreviousCandidate */
4617 case CDK_KEY_Hangul_Special0xff3f:
4618 /* case CDK_KEY_Hangul_switch: is CDK_KEY_ISO_Group_Shift */
4619
4620 steal = FALSE(0);
4621 break;
4622 default:
4623 break;
4624 }
4625 }
4626 }
4627
4628 /* Let the input method at this one first. */
4629 if (!steal && m_input_enabled) {
4630 if (m_real_widget->im_filter_keypress(event)) {
4631 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
4632 "Keypress taken by IM.\n")do { } while(0);
4633 return true;
4634 }
4635 }
4636
4637 /* Now figure out what to send to the child. */
4638 if (event.is_key_press() && !modifier) {
4639 handled = FALSE(0);
4640 /* Map the key to a sequence name if we can. */
4641 switch (event.keyval()) {
4642 case CDK_KEY_BackSpace0xff08:
4643 switch (m_backspace_binding) {
4644 case EraseMode::eASCII_BACKSPACE:
4645 normal = g_strdup("");
4646 normal_length = 1;
4647 suppress_alt_esc = FALSE(0);
4648 break;
4649 case EraseMode::eASCII_DELETE:
4650 normal = g_strdup("");
4651 normal_length = 1;
4652 suppress_alt_esc = FALSE(0);
4653 break;
4654 case EraseMode::eDELETE_SEQUENCE:
4655 normal = g_strdup("\e[3~");
4656 normal_length = 4;
4657 add_modifiers = TRUE(!(0));
4658 suppress_alt_esc = TRUE(!(0));
4659 break;
4660 case EraseMode::eTTY:
4661 if (pty() &&
4662 tcgetattr(pty()->fd(), &tio) != -1)
4663 {
4664 normal = g_strdup_printf("%c", tio.c_cc[VERASE2]);
4665 normal_length = 1;
4666 }
4667 suppress_alt_esc = FALSE(0);
4668 break;
4669 case EraseMode::eAUTO:
4670 default:
4671#ifndef _POSIX_VDISABLE'\0'
4672#define _POSIX_VDISABLE'\0' '\0'
4673#endif
4674 if (pty() &&
4675 tcgetattr(pty()->fd(), &tio) != -1 &&
4676 tio.c_cc[VERASE2] != _POSIX_VDISABLE'\0')
4677 {
4678 normal = g_strdup_printf("%c", tio.c_cc[VERASE2]);
4679 normal_length = 1;
4680 }
4681 else
4682 {
4683 normal = g_strdup("");
4684 normal_length = 1;
4685 suppress_alt_esc = FALSE(0);
4686 }
4687 suppress_alt_esc = FALSE(0);
4688 break;
4689 }
4690 /* Toggle ^H vs ^? if Ctrl is pressed */
4691 if (normal_length == 1 && m_modifiers & CDK_CONTROL_MASK) {
4692 if (normal[0] == '\010')
4693 normal[0] = '\177';
4694 else if (normal[0] == '\177')
4695 normal[0] = '\010';
4696 }
4697 handled = TRUE(!(0));
4698 break;
4699 case CDK_KEY_KP_Delete0xff9f:
4700 case CDK_KEY_Delete0xffff:
4701 switch (m_delete_binding) {
4702 case EraseMode::eASCII_BACKSPACE:
4703 normal = g_strdup("\010");
4704 normal_length = 1;
4705 break;
4706 case EraseMode::eASCII_DELETE:
4707 normal = g_strdup("\177");
4708 normal_length = 1;
4709 break;
4710 case EraseMode::eTTY:
4711 if (pty() &&
4712 tcgetattr(pty()->fd(), &tio) != -1)
4713 {
4714 normal = g_strdup_printf("%c", tio.c_cc[VERASE2]);
4715 normal_length = 1;
4716 }
4717 suppress_alt_esc = FALSE(0);
4718 break;
4719 case EraseMode::eDELETE_SEQUENCE:
4720 case EraseMode::eAUTO:
4721 default:
4722 normal = g_strdup("\e[3~");
4723 normal_length = 4;
4724 add_modifiers = TRUE(!(0));
4725 break;
4726 }
4727 handled = TRUE(!(0));
4728 /* FIXMEchpe: why? this overrides the FALSE set above? */
4729 suppress_alt_esc = TRUE(!(0));
4730 break;
4731 case CDK_KEY_KP_Insert0xff9e:
4732 case CDK_KEY_Insert0xff63:
4733 if (m_modifiers & CDK_SHIFT_MASK) {
4734 if (m_modifiers & CDK_CONTROL_MASK) {
4735 emit_paste_clipboard();
4736 handled = TRUE(!(0));
4737 suppress_alt_esc = TRUE(!(0));
4738 } else {
4739 widget_paste(CDK_SELECTION_PRIMARY((CdkAtom)((gpointer) (gulong) (1))));
4740 handled = TRUE(!(0));
4741 suppress_alt_esc = TRUE(!(0));
4742 }
4743 } else if (m_modifiers & CDK_CONTROL_MASK) {
4744 emit_copy_clipboard();
4745 handled = TRUE(!(0));
4746 suppress_alt_esc = TRUE(!(0));
4747 }
4748 break;
4749 /* Keypad/motion keys. */
4750 case CDK_KEY_KP_Up0xff97:
4751 case CDK_KEY_Up0xff52:
4752 if (m_screen == &m_normal_screen &&
4753 m_modifiers & CDK_CONTROL_MASK &&
4754 m_modifiers & CDK_SHIFT_MASK) {
4755 scroll_lines(-1);
4756 scrolled = TRUE(!(0));
4757 handled = TRUE(!(0));
4758 suppress_alt_esc = TRUE(!(0));
4759 }
4760 break;
4761 case CDK_KEY_KP_Down0xff99:
4762 case CDK_KEY_Down0xff54:
4763 if (m_screen == &m_normal_screen &&
4764 m_modifiers & CDK_CONTROL_MASK &&
4765 m_modifiers & CDK_SHIFT_MASK) {
4766 scroll_lines(1);
4767 scrolled = TRUE(!(0));
4768 handled = TRUE(!(0));
4769 suppress_alt_esc = TRUE(!(0));
4770 }
4771 break;
4772 case CDK_KEY_KP_Page_Up0xff9a:
4773 case CDK_KEY_Page_Up0xff55:
4774 if (m_screen == &m_normal_screen &&
4775 m_modifiers & CDK_SHIFT_MASK) {
4776 scroll_pages(-1);
4777 scrolled = TRUE(!(0));
4778 handled = TRUE(!(0));
4779 suppress_alt_esc = TRUE(!(0));
4780 }
4781 break;
4782 case CDK_KEY_KP_Page_Down0xff9b:
4783 case CDK_KEY_Page_Down0xff56:
4784 if (m_screen == &m_normal_screen &&
4785 m_modifiers & CDK_SHIFT_MASK) {
4786 scroll_pages(1);
4787 scrolled = TRUE(!(0));
4788 handled = TRUE(!(0));
4789 suppress_alt_esc = TRUE(!(0));
4790 }
4791 break;
4792 case CDK_KEY_KP_Home0xff95:
4793 case CDK_KEY_Home0xff50:
4794 if (m_screen == &m_normal_screen &&
4795 m_modifiers & CDK_SHIFT_MASK) {
4796 maybe_scroll_to_top();
4797 scrolled = TRUE(!(0));
4798 handled = TRUE(!(0));
4799 }
4800 break;
4801 case CDK_KEY_KP_End0xff9c:
4802 case CDK_KEY_End0xff57:
4803 if (m_screen == &m_normal_screen &&
4804 m_modifiers & CDK_SHIFT_MASK) {
4805 maybe_scroll_to_bottom();
4806 scrolled = TRUE(!(0));
4807 handled = TRUE(!(0));
4808 }
4809 break;
4810 /* Let Shift +/- tweak the font, like XTerm does. */
4811 case CDK_KEY_KP_Add0xffab:
4812 case CDK_KEY_KP_Subtract0xffad:
4813 if (m_modifiers & (CDK_SHIFT_MASK | CDK_CONTROL_MASK)) {
4814 switch (keyval) {
4815 case CDK_KEY_KP_Add0xffab:
4816 emit_increase_font_size();
4817 handled = TRUE(!(0));
4818 suppress_alt_esc = TRUE(!(0));
4819 break;
4820 case CDK_KEY_KP_Subtract0xffad:
4821 emit_decrease_font_size();
4822 handled = TRUE(!(0));
4823 suppress_alt_esc = TRUE(!(0));
4824 break;
4825 }
4826 }
4827 break;
4828 default:
4829 break;
4830 }
4831 /* If the above switch statement didn't do the job, try mapping
4832 * it to a literal or capability name. */
4833 if (handled == FALSE(0)) {
4834 if (G_UNLIKELY (m_enable_bidi &&(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_enable_bidi && m_modes_private.BTE_BIDI_SWAP_ARROW_KEYS
() && (keyval == 0xff51 || keyval == 0xff53 || keyval
== 0xff96 || keyval == 0xff98)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
4835 m_modes_private.BTE_BIDI_SWAP_ARROW_KEYS() &&(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_enable_bidi && m_modes_private.BTE_BIDI_SWAP_ARROW_KEYS
() && (keyval == 0xff51 || keyval == 0xff53 || keyval
== 0xff96 || keyval == 0xff98)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
4836 (keyval == CDK_KEY_Left ||(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_enable_bidi && m_modes_private.BTE_BIDI_SWAP_ARROW_KEYS
() && (keyval == 0xff51 || keyval == 0xff53 || keyval
== 0xff96 || keyval == 0xff98)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
4837 keyval == CDK_KEY_Right ||(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_enable_bidi && m_modes_private.BTE_BIDI_SWAP_ARROW_KEYS
() && (keyval == 0xff51 || keyval == 0xff53 || keyval
== 0xff96 || keyval == 0xff98)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
4838 keyval == CDK_KEY_KP_Left ||(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_enable_bidi && m_modes_private.BTE_BIDI_SWAP_ARROW_KEYS
() && (keyval == 0xff51 || keyval == 0xff53 || keyval
== 0xff96 || keyval == 0xff98)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
4839 keyval == CDK_KEY_KP_Right))(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_enable_bidi && m_modes_private.BTE_BIDI_SWAP_ARROW_KEYS
() && (keyval == 0xff51 || keyval == 0xff53 || keyval
== 0xff96 || keyval == 0xff98)) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
) {
4840 /* In keyboard arrow swapping mode, the left and right arrows need swapping
4841 * if the cursor stands inside a (possibly autodetected) RTL paragraph. */
4842 ensure_row();
4843 BteRowData const *row_data = find_row_data(m_screen->cursor.row);
4844 bool rtl;
4845 if ((row_data->attr.bidi_flags & (BTE_BIDI_FLAG_IMPLICIT | BTE_BIDI_FLAG_AUTO))
4846 == (BTE_BIDI_FLAG_IMPLICIT | BTE_BIDI_FLAG_AUTO)) {
4847 /* Implicit paragraph with autodetection. Need to run the BiDi algorithm
4848 * to get the autodetected direction.
4849 * m_ringview is for the onscreen contents and the cursor may be offscreen.
4850 * Better leave that alone and use a temporary ringview for the cursor's row. */
4851 bte::base::RingView ringview;
4852 ringview.set_ring(m_screen->row_data);
4853 ringview.set_rows(m_screen->cursor.row, 1);
4854 ringview.set_width(m_column_count);
4855 ringview.update();
4856 rtl = ringview.get_bidirow(m_screen->cursor.row)->base_is_rtl();
4857 } else {
4858 /* Not an implicit paragraph with autodetection, no autodetection
4859 * is required. Take the direction straight from the stored data. */
4860 rtl = !!(row_data->attr.bidi_flags & BTE_BIDI_FLAG_RTL);
4861 }
4862 if (rtl) {
4863 switch (keyval) {
4864 case CDK_KEY_Left0xff51:
4865 keyval = CDK_KEY_Right0xff53;
4866 break;
4867 case CDK_KEY_Right0xff53:
4868 keyval = CDK_KEY_Left0xff51;
4869 break;
4870 case CDK_KEY_KP_Left0xff96:
4871 keyval = CDK_KEY_KP_Right0xff98;
4872 break;
4873 case CDK_KEY_KP_Right0xff98:
4874 keyval = CDK_KEY_KP_Left0xff96;
4875 break;
4876 }
4877 }
4878 }
4879
4880 _bte_keymap_map(keyval, m_modifiers,
4881 m_modes_private.DEC_APPLICATION_CURSOR_KEYS(),
4882 m_modes_private.DEC_APPLICATION_KEYPAD(),
4883 &normal,
4884 &normal_length);
4885 /* If we found something this way, suppress
4886 * escape-on-alt. */
4887 if (normal != NULL__null && normal_length > 0) {
4888 suppress_alt_esc = TRUE(!(0));
4889 }
4890 }
4891
4892 /* Shall we do this here or earlier? See bug 375112 and bug 589557 */
4893 if (m_modifiers & CDK_CONTROL_MASK && widget())
4894 keyval = widget()->key_event_translate_ctrlkey(event);
4895
4896 /* If we didn't manage to do anything, try to salvage a
4897 * printable string. */
4898 if (handled == FALSE(0) && normal == NULL__null) {
4899
4900 /* Convert the keyval to a gunichar. */
4901 keychar = cdk_keyval_to_unicode(keyval);
4902 normal_length = 0;
4903 if (keychar != 0) {
4904 /* Convert the gunichar to a string. */
4905 normal_length = g_unichar_to_utf8(keychar,
4906 keybuf);
4907 if (normal_length != 0) {
4908 normal = (char *)g_malloc(normal_length + 1);
4909 memcpy(normal, keybuf, normal_length);
4910 normal[normal_length] = '\0';
4911 } else {
4912 normal = NULL__null;
4913 }
4914 }
4915 if ((normal != NULL__null) &&
4916 (m_modifiers & CDK_CONTROL_MASK)) {
4917 /* Replace characters which have "control"
4918 * counterparts with those counterparts. */
4919 for (size_t i = 0; i < normal_length; i++) {
4920 if ((((guint8)normal[i]) >= 0x40) &&
4921 (((guint8)normal[i]) < 0x80)) {
4922 normal[i] &= (~(0x60));
4923 }
4924 }
4925 }
4926 _BTE_DEBUG_IF (BTE_DEBUG_EVENTS)if (0) {
4927 if (normal) g_printerr(
4928 "Keypress, modifiers=0x%x, "
4929 "keyval=0x%x, cooked string=`%s'.\n",
4930 m_modifiers,
4931 keyval, normal);
4932 }
4933 }
4934 /* If we got normal characters, send them to the child. */
4935 if (normal != NULL__null) {
4936 if (add_modifiers) {
4937 _bte_keymap_key_add_key_modifiers(keyval,
4938 m_modifiers,
4939 m_modes_private.DEC_APPLICATION_CURSOR_KEYS(),
4940 &normal,
4941 &normal_length);
4942 }
4943 if (m_modes_private.XTERM_META_SENDS_ESCAPE() &&
4944 !suppress_alt_esc &&
4945 (normal_length > 0) &&
4946 (m_modifiers & BTE_ALT_MASKCDK_MOD1_MASK)) {
4947 feed_child(_BTE_CAP_ESC"\033", 1);
4948 }
4949 if (normal_length > 0) {
4950 send_child({normal, normal_length});
4951 }
4952 g_free(normal);
4953 }
4954 /* Keep the cursor on-screen. */
4955 if (!scrolled && !modifier &&
4956 m_scroll_on_keystroke && m_input_enabled) {
4957 maybe_scroll_to_bottom();
4958 }
4959 return true;
4960 }
4961 return false;
4962}
4963
4964bool
4965Terminal::widget_key_release(KeyEvent const& event)
4966{
4967 m_modifiers = event.modifiers();
4968
4969 if (m_input_enabled &&
4970 m_real_widget->im_filter_keypress(event))
4971 return true;
4972
4973 return false;
4974}
4975
4976static const guint8 word_char_by_category[] = {
4977 [G_UNICODE_CONTROL] = 2,
4978 [G_UNICODE_FORMAT] = 2,
4979 [G_UNICODE_UNASSIGNED] = 2,
4980 [G_UNICODE_PRIVATE_USE] = 0,
4981 [G_UNICODE_SURROGATE] = 2,
4982 [G_UNICODE_LOWERCASE_LETTER] = 1,
4983 [G_UNICODE_MODIFIER_LETTER] = 1,
4984 [G_UNICODE_OTHER_LETTER] = 1,
4985 [G_UNICODE_TITLECASE_LETTER] = 1,
4986 [G_UNICODE_UPPERCASE_LETTER] = 1,
4987 [G_UNICODE_SPACING_MARK] = 0,
4988 [G_UNICODE_ENCLOSING_MARK] = 0,
4989 [G_UNICODE_NON_SPACING_MARK] = 0,
4990 [G_UNICODE_DECIMAL_NUMBER] = 1,
4991 [G_UNICODE_LETTER_NUMBER] = 1,
4992 [G_UNICODE_OTHER_NUMBER] = 1,
4993 [G_UNICODE_CONNECT_PUNCTUATION] = 0,
4994 [G_UNICODE_DASH_PUNCTUATION] = 0,
4995 [G_UNICODE_CLOSE_PUNCTUATION] = 0,
4996 [G_UNICODE_FINAL_PUNCTUATION] = 0,
4997 [G_UNICODE_INITIAL_PUNCTUATION] = 0,
4998 [G_UNICODE_OTHER_PUNCTUATION] = 0,
4999 [G_UNICODE_OPEN_PUNCTUATION] = 0,
5000 [G_UNICODE_CURRENCY_SYMBOL] = 0,
5001 [G_UNICODE_MODIFIER_SYMBOL] = 0,
5002 [G_UNICODE_MATH_SYMBOL] = 0,
5003 [G_UNICODE_OTHER_SYMBOL] = 0,
5004 [G_UNICODE_LINE_SEPARATOR] = 2,
5005 [G_UNICODE_PARAGRAPH_SEPARATOR] = 2,
5006 [G_UNICODE_SPACE_SEPARATOR] = 2,
5007};
5008
5009/*
5010 * Terminal::is_word_char:
5011 * @c: a candidate Unicode code point
5012 *
5013 * Checks if a particular character is considered to be part of a word or not.
5014 *
5015 * Returns: %TRUE if the character is considered to be part of a word
5016 */
5017bool
5018Terminal::is_word_char(gunichar c) const
5019{
5020 const guint8 v = word_char_by_category[g_unichar_type(c)];
5021
5022 if (v)
5023 return v == 1;
5024
5025 /* Do we have an exception? */
5026 return std::find(std::begin(m_word_char_exceptions), std::end(m_word_char_exceptions), char32_t(c)) != std::end(m_word_char_exceptions);
5027}
5028
5029/* Check if the characters in the two given locations are in the same class
5030 * (word vs. non-word characters).
5031 * Note that calling this method may invalidate the return value of
5032 * a previous find_row_data() call. */
5033bool
5034Terminal::is_same_class(bte::grid::column_t acol,
5035 bte::grid::row_t arow,
5036 bte::grid::column_t bcol,
5037 bte::grid::row_t brow) const
5038{
5039 BteCell const* pcell = nullptr;
5040 bool word_char;
5041 if ((pcell = find_charcell(acol, arow)) != nullptr && pcell->c != 0) {
5042 /* Group together if they're fragments of the very same character (not just character value) */
5043 if (arow == brow) {
5044 auto a2 = acol, b2 = bcol;
5045 while (a2 > 0 && find_charcell(a2, arow)->attr.fragment()) a2--;
5046 while (b2 > 0 && find_charcell(b2, brow)->attr.fragment()) b2--;
5047 if (a2 == b2)
5048 return true;
5049 }
5050
5051 word_char = is_word_char(_bte_unistr_get_base(pcell->c));
5052
5053 /* Lets not group non-wordchars together (bug #25290) */
5054 if (!word_char)
5055 return false;
5056
5057 pcell = find_charcell(bcol, brow);
5058 if (pcell == NULL__null || pcell->c == 0) {
5059 return false;
5060 }
5061 if (word_char != is_word_char(_bte_unistr_get_base(pcell->c))) {
5062 return false;
5063 }
5064 return true;
5065 }
5066 return false;
5067}
5068
5069/*
5070 * Convert the mouse click or drag location (left or right half of a cell) into a selection endpoint
5071 * (a boundary between characters), extending the selection according to the current mode, in the
5072 * direction given in the @after parameter.
5073 *
5074 * All four selection modes require different strategies.
5075 *
5076 * In char mode, what matters is which vertical character boundary is closer, taking multi-cell characters
5077 * (CJKs, TABs) into account. Given the string "abcdef", if the user clicks on the boundary between "a"
5078 * and "b" (perhaps on the right half of "a", perhaps on the left half of "b"), and moves the mouse to the
5079 * boundary between "e" and "f" (perhaps a bit over "e", perhaps a bit over "f"), the selection should be
5080 * "bcde". By dragging the mouse back to approximately the click location, it is possible to select the
5081 * empty string. This is the common sense behavior impemented by basically every graphical toolkit
5082 * (unfortunately not by many terminal emulators), and also the one we go for.
5083 *
5084 * Word mode is the trickiest one. Many implementations have weird corner case bugs (e.g. don't highlight
5085 * a word if you double click on the second half of its last letter, or even highlight it if you click on
5086 * the first half of the following space). I think it is expected that double-clicking anywhere over a
5087 * word (including the first half of its first letter, or the last half of its last letter), but over no
5088 * other character, selects this entire word. By dragging the mouse it's not possible to select nothing,
5089 * the word (or non-word character) initially clicked on is always part of the selection. (An exception
5090 * is when clicking occurred over the margin, or an unused cell in a soft-wrapped row (due to CJK
5091 * wrapping).) Also, for symmetry reasons, the word (or non-word character) under the current mouse
5092 * location is also always selected.
5093 *
5094 * Line (paragraph) mode is conceptually quite similar to word mode (the cell, thus the entire row under
5095 * the click's location is always included), but is much easier to implement.
5096 *
5097 * In block mode, similarly to char mode, we care about vertical character boundary. (This is somewhat
5098 * debatable, as results in asymmetrical behavior along the two axes: a rectangle can disappear by
5099 * becoming zero wide, but not zero high.) We cannot take care of CJKs at the endpoints now because CJKs
5100 * can cross the boundary in any included row. Taking care of them needs to go to cell_is_selected_*().
5101 * We don't care about used vs. unused cells either. The event coordinate is simply rounded to the
5102 * nearest vertical cell boundary.
5103 */
5104bte::grid::coords
5105Terminal::resolve_selection_endpoint(bte::grid::halfcoords const& rowcolhalf, bool after) const
5106{
5107 auto row = rowcolhalf.row();
5108 auto col = rowcolhalf.halfcolumn().column(); /* Points to an actual cell now. At the end of this
5109 method it'll point to a boundary. */
5110 auto half = rowcolhalf.halfcolumn().half(); /* 0 for left half, 1 for right half of the cell. */
5111 BteRowData const* rowdata;
5112 BteCell const* cell;
5113 int len;
5114
5115 if (m_selection_block_mode) {
5116 /* Just find the nearest cell boundary within the line, not caring about CJKs, unused
5117 * cells, or wrapping at EOL. The @after parameter is unused in this mode. */
5118 col += half;
5119 col = std::clamp (col, 0L, m_column_count);
5120 } else {
5121 switch (m_selection_type) {
5122 case SelectionType::eCHAR:
5123 /* Find the nearest actual character boundary, taking CJKs and TABs into account.
5124 * If at least halfway through the first unused cell, or over the right margin
5125 * then wrap to the beginning of the next line.
5126 * The @after parameter is unused in this mode. */
5127 if (col < 0) {
5128 col = 0;
5129 } else if (col >= m_column_count) {
5130 /* If on the right padding, select the entire line including a possible
5131 * newline character. This way if a line is fully filled and ends in a
5132 * newline, there's only a half cell width for which the line is selected
5133 * without the newline, but at least there's a way to include the newline
5134 * by moving the mouse to the right (bug 724253). */
5135 col = 0;
5136 row++;
5137 } else {
5138 bte::grid::column_t char_begin, char_end; /* cell boundaries */
5139 rowdata = find_row_data(row);
5140 if (rowdata && col < _bte_row_data_nonempty_length(rowdata)) {
5141 /* Clicked over a used cell. Check for multi-cell characters. */
5142 char_begin = col;
5143 while (char_begin > 0) {
5144 cell = _bte_row_data_get (rowdata, char_begin);
5145 if (!cell->attr.fragment())
5146 break;
5147 char_begin--;
5148 }
5149 cell = _bte_row_data_get (rowdata, char_begin);
5150 char_end = char_begin + cell->attr.columns();
5151 } else {
5152 /* Clicked over unused area. Just go with cell boundaries. */
5153 char_begin = col;
5154 char_end = char_begin + 1;
5155 }
5156 /* Which boundary is closer? */
5157 if (col * 2 + half < char_begin + char_end)
5158 col = char_begin;
5159 else
5160 col = char_end;
5161
5162 /* Maybe wrap to the beginning of the next line. */
5163 if (col > (rowdata ? _bte_row_data_nonempty_length(rowdata) : 0)) {
5164 col = 0;
5165 row++;
5166 }
5167 }
5168 break;
5169
5170 case SelectionType::eWORD:
5171 /* Initialization for the cumbersome cases where the click didn't occur over an actual used cell. */
5172 rowdata = find_row_data(row);
5173 if (col < 0) {
5174 /* Clicked over the left margin.
5175 * - If within a word (that is, the first letter in this row, and the last
5176 * letter of the previous row belong to the same word) then select the
5177 * letter according to the direction and continue expanding.
5178 * - Otherwise stop, the boundary is here before the first letter. */
5179 if (row > 0 &&
5180 (rowdata = find_row_data(row - 1)) != nullptr &&
5181 rowdata->attr.soft_wrapped &&
5182 (len = _bte_row_data_nonempty_length(rowdata)) > 0 &&
5183 is_same_class(len - 1, row - 1, 0, row) /* invalidates rowdata! */) {
5184 if (!after) {
5185 col = len - 1;
5186 row--;
5187 } else {
5188 col = 0;
5189 }
5190 /* go on with expanding */
5191 } else {
5192 col = 0; /* end-exclusive */
5193 break; /* done, don't expand any more */
5194 }
5195 } else if (col >= (rowdata ? _bte_row_data_nonempty_length(rowdata) : 0)) {
5196 /* Clicked over the right margin, or right unused area.
5197 * - If within a word (that is, the last letter in this row, and the first
5198 * letter of the next row belong to the same word) then select the letter
5199 * according to the direction and continue expanding.
5200 * - Otherwise, if the row is soft-wrapped and we're over the unused area
5201 * (which can happen if a CJK wrapped) or over the right margin, then
5202 * stop, the boundary is wrapped to the beginning of the next row.
5203 * - Otherwise select the newline only and stop. */
5204 if (rowdata != nullptr &&
5205 rowdata->attr.soft_wrapped) {
5206 if ((len = _bte_row_data_nonempty_length(rowdata)) > 0 &&
5207 is_same_class(len - 1, row, 0, row + 1) /* invalidates rowdata! */) {
5208 if (!after) {
5209 col = len - 1;
5210 } else {
5211 col = 0;
5212 row++;
5213 }
5214 /* go on with expanding */
5215 } else {
5216 col = 0; /* end-exclusive */
5217 row++;
5218 break; /* done, don't expand any more */
5219 }
5220 } else {
5221 if (!after) {
5222 col = rowdata ? _bte_row_data_nonempty_length(rowdata) : 0; /* end-exclusive */
5223 } else {
5224 col = 0; /* end-exclusive */
5225 row++;
5226 }
5227 break; /* done, don't expand any more */
5228 }
5229 }
5230
5231 /* Expand in the given direction. */
5232 if (!after) {
5233 /* Keep selecting to the left (and then up) as long as the next character
5234 * we look at is of the same class as the current start point. */
5235 while (true) {
5236 /* Back up within the row. */
5237 for (; col > 0; col--) {
5238 if (!is_same_class(col - 1, row, col, row)) {
5239 break;
5240 }
5241 }
5242 if (col > 0) {
5243 /* We hit a stopping point, so stop. */
5244 break;
5245 }
5246 if (row == 0) {
5247 /* At the very beginning. */
5248 break;
5249 }
5250 rowdata = find_row_data(row - 1);
5251 if (!rowdata || !rowdata->attr.soft_wrapped) {
5252 /* Reached a hard newline. */
5253 break;
5254 }
5255 len = _bte_row_data_nonempty_length(rowdata);
5256 /* len might be smaller than m_column_count if a CJK wrapped */
5257 if (!is_same_class(len - 1, row - 1, col, row) /* invalidates rowdata! */) {
5258 break;
5259 }
5260 /* Move on to the previous line. */
5261 col = len - 1;
5262 row--;
5263 }
5264 } else {
5265 /* Keep selecting to the right (and then down) as long as the next character
5266 * we look at is of the same class as the current end point. */
5267 while (true) {
5268 rowdata = find_row_data(row);
5269 if (!rowdata) {
5270 break;
5271 }
5272 len = _bte_row_data_nonempty_length(rowdata);
5273 bool soft_wrapped = rowdata->attr.soft_wrapped;
5274 /* Move forward within the row. */
5275 for (; col < len - 1; col++) {
5276 if (!is_same_class(col, row, col + 1, row) /* invalidates rowdata! */) {
5277 break;
5278 }
5279 }
5280 if (col < len - 1) {
5281 /* We hit a stopping point, so stop. */
5282 break;
5283 }
5284 if (!soft_wrapped) {
5285 /* Reached a hard newline. */
5286 break;
5287 }
5288 if (!is_same_class(col, row, 0, row + 1)) {
5289 break;
5290 }
5291 /* Move on to the next line. */
5292 col = 0;
5293 row++;
5294 }
5295 col++; /* col points to an actual cell, we need end-exclusive instead. */
5296 }
5297 break;
5298
5299 case SelectionType::eLINE:
5300 if (!after) {
5301 /* Back up as far as we can go. */
5302 while (row > 0 &&
5303 _bte_ring_contains (m_screen->row_data, row - 1) &&
5304 m_screen->row_data->is_soft_wrapped(row - 1)) {
5305 row--;
5306 }
5307 } else {
5308 /* Move forward as far as we can go. */
5309 while (_bte_ring_contains (m_screen->row_data, row) &&
5310 m_screen->row_data->is_soft_wrapped(row)) {
5311 row++;
5312 }
5313 row++; /* One more row, since the column is 0. */
5314 }
5315 col = 0;
5316 break;
5317 }
5318 }
5319
5320 return { row, col };
5321}
5322
5323/*
5324 * Creates the selection's span from the origin and last coordinates.
5325 *
5326 * The origin and last points might be in reverse order; in block mode they might even point to the
5327 * two other corners of the rectangle than the ones we're interested in.
5328 * The resolved span will contain the endpoints in the proper order.
5329 *
5330 * In word & line (paragraph) modes it extends the selection accordingly.
5331 *
5332 * Also makes sure to invalidate the regions that changed, and update m_selecting_had_delta.
5333 *
5334 * FIXMEegmont it always resolves both endpoints. With a bit of extra bookkeeping it could usually
5335 * just resolve the moving one.
5336 */
5337void
5338Terminal::resolve_selection()
5339{
5340 if (m_selection_origin.row() < 0 || m_selection_last.row() < 0) {
5341 invalidate (m_selection_resolved);
5342 m_selection_resolved.clear();
5343 _bte_debug_print(BTE_DEBUG_SELECTION, "Selection resolved to %s.\n", m_selection_resolved.to_string())do { } while(0);
5344 return;
5345 }
5346
5347 auto m_selection_resolved_old = m_selection_resolved;
5348
5349 if (m_selection_block_mode) {
5350 auto top = std::min (m_selection_origin.row(), m_selection_last.row());
5351 auto bottom = std::max (m_selection_origin.row(), m_selection_last.row());
5352 auto left = std::min (m_selection_origin.halfcolumn(), m_selection_last.halfcolumn());
5353 auto right = std::max (m_selection_origin.halfcolumn(), m_selection_last.halfcolumn());
5354
5355 auto topleft = resolve_selection_endpoint ({ top, left }, false);
5356 auto bottomright = resolve_selection_endpoint ({ bottom, right }, true);
5357
5358 if (topleft.column() == bottomright.column()) {
5359 m_selection_resolved.clear();
5360 } else {
5361 m_selection_resolved.set (topleft, bottomright);
5362 }
5363 } else {
5364 auto start = std::min (m_selection_origin, m_selection_last);
5365 auto end = std::max (m_selection_origin, m_selection_last);
5366
5367 m_selection_resolved.set (resolve_selection_endpoint (start, false),
5368 resolve_selection_endpoint (end, true));
5369 }
5370
5371 if (!m_selection_resolved.empty())
5372 m_selecting_had_delta = true;
5373
5374 _bte_debug_print(BTE_DEBUG_SELECTION, "Selection resolved to %s.\n", m_selection_resolved.to_string())do { } while(0);
5375
5376 invalidate_symmetrical_difference (m_selection_resolved_old, m_selection_resolved, m_selection_block_mode);
5377}
5378
5379void
5380Terminal::modify_selection (bte::view::coords const& pos)
5381{
5382 g_assert (m_selecting)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_selecting) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr (
"BTE", "../src/bte.cc", 5382, ((const char*) (__PRETTY_FUNCTION__
)), "m_selecting"); } while (0)
;
5383
5384 /* Need to ensure the ringview is updated. */
5385 ringview_update();
5386
5387 auto current = selection_grid_halfcoords_from_view_coords (pos);
5388
5389 if (current == m_selection_last)
5390 return;
5391
5392 _bte_debug_print(BTE_DEBUG_SELECTION,do { } while(0)
5393 "Selection dragged to %s.\n",do { } while(0)
5394 current.to_string())do { } while(0);
5395
5396 m_selection_last = current;
5397 resolve_selection();
5398}
5399
5400/* Check if a cell is selected or not. BiDi: the coordinate is logical. */
5401bool
5402Terminal::cell_is_selected_log(bte::grid::column_t lcol,
5403 bte::grid::row_t row) const
5404{
5405 /* Our caller had to update the ringview (we can't do because we're const). */
5406 g_assert(m_ringview.is_updated())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_ringview.is_updated()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 5406, ((const char*) (__PRETTY_FUNCTION__
)), "m_ringview.is_updated()"); } while (0)
;
5407
5408 if (m_selection_block_mode) {
5409 /* In block mode, make sure CJKs and TABs aren't cut in half. */
5410 while (lcol > 0) {
5411 BteCell const* cell = find_charcell(lcol, row);
5412 if (!cell || !cell->attr.fragment())
5413 break;
5414 lcol--;
5415 }
5416 /* Convert to visual. */
5417 bte::base::BidiRow const* bidirow = m_ringview.get_bidirow(row);
5418 bte::grid::column_t vcol = bidirow->log2vis(lcol);
5419 return m_selection_resolved.box_contains ({ row, vcol });
5420 } else {
5421 /* In normal modes, resolve_selection() made sure to generate such boundaries for m_selection_resolved. */
5422 return m_selection_resolved.contains ({ row, lcol });
5423 }
5424}
5425
5426/* Check if a cell is selected or not. BiDi: the coordinate is visual. */
5427bool
5428Terminal::cell_is_selected_vis(bte::grid::column_t vcol,
5429 bte::grid::row_t row) const
5430{
5431 /* Our caller had to update the ringview (we can't do because we're const). */
5432 g_assert(m_ringview.is_updated())do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_ringview.is_updated()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 5432, ((const char*) (__PRETTY_FUNCTION__
)), "m_ringview.is_updated()"); } while (0)
;
5433
5434 /* Convert to logical column. */
5435 bte::base::BidiRow const* bidirow = m_ringview.get_bidirow(row);
5436 bte::grid::column_t lcol = bidirow->vis2log(vcol);
5437
5438 return cell_is_selected_log(lcol, row);
5439}
5440
5441void
5442Terminal::widget_paste_received(char const* text)
5443{
5444 gchar *paste, *p;
5445 gsize run;
5446 unsigned char c;
5447
5448 if (text == nullptr)
5449 return;
5450
5451 gsize len = strlen(text);
5452 _bte_debug_print(BTE_DEBUG_SELECTION,do { } while(0)
5453 "Pasting %" G_GSIZE_FORMAT " UTF-8 bytes.\n", len)do { } while(0);
5454 // FIXMEchpe this cannot happen ever
5455 if (!g_utf8_validate(text, len, NULL__null)) {
5456 g_warning("Paste not valid UTF-8, dropping.");
5457 return;
5458 }
5459
5460 /* Convert newlines to carriage returns, which more software
5461 * is able to cope with (cough, pico, cough).
5462 * Filter out control chars except HT, CR (even stricter than xterm).
5463 * Also filter out C1 controls: U+0080 (0xC2 0x80) - U+009F (0xC2 0x9F). */
5464 p = paste = (gchar *) g_malloc(len + 1);
5465 while (p != nullptr && text[0] != '\0') {
5466 run = strcspn(text, "\x01\x02\x03\x04\x05\x06\x07"
5467 "\x08\x0A\x0B\x0C\x0E\x0F"
5468 "\x10\x11\x12\x13\x14\x15\x16\x17"
5469 "\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
5470 "\x7F\xC2");
5471 memcpy(p, text, run);
5472 p += run;
5473 text += run;
5474 switch (text[0]) {
5475 case '\x00':
5476 break;
5477 case '\x0A':
5478 *p = '\x0D';
5479 p++;
5480 text++;
5481 break;
5482 case '\xC2':
5483 c = text[1];
5484 if (c >= 0x80 && c <= 0x9F) {
5485 /* Skip both bytes of a C1 */
5486 text += 2;
5487 } else {
5488 /* Move along, nothing to see here */
5489 *p = '\xC2';
5490 p++;
5491 text++;
5492 }
5493 break;
5494 default:
5495 /* Swallow this byte */
5496 text++;
5497 break;
5498 }
5499 }
5500
5501 bool const bracketed_paste = m_modes_private.XTERM_READLINE_BRACKETED_PASTE();
5502 // FIXMEchpe can we not hardcode C0 controls here?
5503 if (bracketed_paste)
5504 feed_child("\e[200~"sv);
5505 // FIXMEchpe add a way to avoid the extra string copy done here
5506 feed_child(paste, p - paste);
5507 if (bracketed_paste)
5508 feed_child("\e[201~"sv);
5509 g_free(paste);
5510}
5511
5512bool
5513Terminal::feed_mouse_event(bte::grid::coords const& rowcol /* confined */,
5514 int button,
5515 bool is_drag,
5516 bool is_release)
5517{
5518 unsigned char cb = 0;
5519
5520 /* Don't send events on scrollback contents: bug 755187. */
5521 if (grid_coords_in_scrollback(rowcol))
5522 return false;
5523
5524 /* Make coordinates 1-based. */
5525 auto cx = rowcol.column() + 1;
5526 auto cy = rowcol.row() - m_screen->insert_delta + 1;
5527
5528 /* Encode the button information in cb. */
5529 switch (button) {
5530 case 0: /* No button, just dragging. */
5531 cb = 3;
5532 break;
5533 case 1: /* Left. */
5534 cb = 0;
5535 break;
5536 case 2: /* Middle. */
5537 cb = 1;
5538 break;
5539 case 3: /* Right. */
5540 cb = 2;
5541 break;
5542 case 4:
5543 cb = 64; /* Scroll up. */
5544 break;
5545 case 5:
5546 cb = 65; /* Scroll down. */
5547 break;
5548 }
5549
5550 /* With the exception of the 1006 mode, button release is also encoded here. */
5551 /* Note that if multiple extensions are enabled, the 1006 is used, so it's okay to check for only that. */
5552 if (is_release && !m_modes_private.XTERM_MOUSE_EXT_SGR()) {
5553 cb = 3;
5554 }
5555
5556 /* Encode the modifiers. */
5557 if (m_mouse_tracking_mode >= MouseTrackingMode::eSEND_XY_ON_BUTTON) {
5558 if (m_modifiers & CDK_SHIFT_MASK) {
5559 cb |= 4;
5560 }
5561 if (m_modifiers & BTE_ALT_MASKCDK_MOD1_MASK) {
5562 cb |= 8;
5563 }
5564 if (m_modifiers & CDK_CONTROL_MASK) {
5565 cb |= 16;
5566 }
5567 }
5568
5569 /* Encode a drag event. */
5570 if (is_drag) {
5571 cb |= 32;
5572 }
5573
5574 /* Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. */
5575 if (m_modes_private.XTERM_MOUSE_EXT_SGR()) {
5576 /* xterm's extended mode (1006) */
5577 send(is_release ? BTE_REPLY_XTERM_MOUSE_EXT_SGR_REPORT_BUTTON_RELEASE
5578 : BTE_REPLY_XTERM_MOUSE_EXT_SGR_REPORT_BUTTON_PRESS,
5579 {cb, (int)cx, (int)cy});
5580 } else if (cx <= 223 && cy <= 223) {
5581 /* legacy mode */
5582 char buf[8];
5583 size_t len = g_snprintf(buf, sizeof(buf), _BTE_CAP_CSI"\033" "[" "M%c%c%c", 32 + cb, 32 + (guchar)cx, 32 + (guchar)cy);
5584
5585 /* Send event direct to the child, this is binary not text data */
5586 feed_child_binary({buf, len});
5587 }
5588
5589 return true;
5590}
5591
5592void
5593Terminal::feed_focus_event(bool in)
5594{
5595 send(in ? BTE_REPLY_XTERM_FOCUS_IN : BTE_REPLY_XTERM_FOCUS_OUT, {});
5596}
5597
5598void
5599Terminal::feed_focus_event_initial()
5600{
5601 /* We immediately send the terminal a focus event, since otherwise
5602 * it has no way to know the current status.
5603 */
5604 feed_focus_event(m_has_focus);
5605}
5606
5607void
5608Terminal::maybe_feed_focus_event(bool in)
5609{
5610 if (m_modes_private.XTERM_FOCUS())
5611 feed_focus_event(in);
5612}
5613
5614/*
5615 * Terminal::maybe_send_mouse_button:
5616 * @terminal:
5617 * @event:
5618 *
5619 * Sends a mouse button click or release notification to the application,
5620 * if the terminal is in mouse tracking mode.
5621 *
5622 * Returns: %TRUE iff the event was consumed
5623 */
5624bool
5625Terminal::maybe_send_mouse_button(bte::grid::coords const& unconfined_rowcol,
5626 MouseEvent const& event)
5627{
5628 switch (event.type()) {
5629 case EventBase::Type::eMOUSE_PRESS:
5630 if (m_mouse_tracking_mode < MouseTrackingMode::eSEND_XY_ON_CLICK) {
5631 return false;
5632 }
5633 break;
5634 case EventBase::Type::eMOUSE_RELEASE:
5635 if (m_mouse_tracking_mode < MouseTrackingMode::eSEND_XY_ON_BUTTON) {
5636 return false;
5637 }
5638 break;
5639 case EventBase::Type::eMOUSE_DOUBLE_PRESS:
5640 case EventBase::Type::eMOUSE_TRIPLE_PRESS:
5641 default:
5642 return false;
5643 }
5644
5645 auto rowcol = confine_grid_coords(unconfined_rowcol);
5646 return feed_mouse_event(rowcol,
5647 event.button_value(),
5648 false /* not drag */,
5649 event.is_mouse_release());
5650}
5651
5652/*
5653 * Terminal::maybe_send_mouse_drag:
5654 * @terminal:
5655 * @event:
5656 *
5657 * Sends a mouse motion notification to the application,
5658 * if the terminal is in mouse tracking mode.
5659 *
5660 * Returns: %TRUE iff the event was consumed
5661 */
5662bool
5663Terminal::maybe_send_mouse_drag(bte::grid::coords const& unconfined_rowcol,
5664 MouseEvent const& event)
5665{
5666 /* Need to ensure the ringview is updated. */
5667 ringview_update();
5668
5669 auto rowcol = confine_grid_coords(unconfined_rowcol);
5670
5671 /* First determine if we even want to send notification. */
5672 switch (event.type()) {
5673 case EventBase::Type::eMOUSE_MOTION:
5674 if (m_mouse_tracking_mode < MouseTrackingMode::eCELL_MOTION_TRACKING)
5675 return false;
5676
5677 if (m_mouse_tracking_mode < MouseTrackingMode::eALL_MOTION_TRACKING) {
5678
5679 if (m_mouse_pressed_buttons == 0) {
5680 return false;
5681 }
5682 /* The xterm doc is not clear as to whether
5683 * all-tracking also sends degenerate same-cell events;
5684 * we don't.
5685 */
5686 if (rowcol == confined_grid_coords_from_view_coords(m_mouse_last_position))
5687 return false;
5688 }
5689 break;
5690 default:
5691 return false;
5692 }
5693
5694 /* As per xterm, report the leftmost pressed button - if any. */
5695 int button;
5696 if (m_mouse_pressed_buttons & 1)
5697 button = 1;
5698 else if (m_mouse_pressed_buttons & 2)
5699 button = 2;
5700 else if (m_mouse_pressed_buttons & 4)
5701 button = 3;
5702 else
5703 button = 0;
5704
5705 return feed_mouse_event(rowcol,
5706 button,
5707 true /* drag */,
5708 false /* not release */);
5709}
5710
5711/*
5712 * Terminal::hyperlink_invalidate_and_get_bbox
5713 *
5714 * Invalidates cells belonging to the non-zero hyperlink idx, in order to
5715 * stop highlighting the previously hovered hyperlink or start highlighting
5716 * the new one. Optionally stores the coordinates of the bounding box.
5717 */
5718void
5719Terminal::hyperlink_invalidate_and_get_bbox(bte::base::Ring::hyperlink_idx_t idx,
5720 CdkRectangle *bbox)
5721{
5722 auto first_row = first_displayed_row();
5723 auto end_row = last_displayed_row() + 1;
5724 bte::grid::row_t row, top = LONG_MAX9223372036854775807L, bottom = -1;
5725 bte::grid::column_t col, left = LONG_MAX9223372036854775807L, right = -1;
5726 const BteRowData *rowdata;
5727
5728 g_assert (idx != 0)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (idx != 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0
; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr ("BTE"
, "../src/bte.cc", 5728, ((const char*) (__PRETTY_FUNCTION__)
), "idx != 0"); } while (0)
;
5729
5730 for (row = first_row; row < end_row; row++) {
5731 rowdata = _bte_ring_index(m_screen->row_data, row);
5732 if (rowdata != NULL__null) {
5733 bool do_invalidate_row = false;
5734 for (col = 0; col < rowdata->len; col++) {
5735 if (G_UNLIKELY (rowdata->cells[col].attr.hyperlink_idx == idx)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
rowdata->cells[col].attr.hyperlink_idx == idx) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 0))
) {
5736 do_invalidate_row = true;
5737 top = MIN(top, row)(((top) < (row)) ? (top) : (row));
5738 bottom = MAX(bottom, row)(((bottom) > (row)) ? (bottom) : (row));
5739 left = MIN(left, col)(((left) < (col)) ? (left) : (col));
5740 right = MAX(right, col)(((right) > (col)) ? (right) : (col));
5741 }
5742 }
5743 if (G_UNLIKELY (do_invalidate_row)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
do_invalidate_row) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 0))
) {
5744 invalidate_row(row);
5745 }
5746 }
5747 }
5748
5749 if (bbox == NULL__null)
5750 return;
5751
5752 /* If bbox != NULL, we're looking for the new hovered hyperlink which always has onscreen bits. */
5753 g_assert (top != LONG_MAX && bottom != -1 && left != LONG_MAX && right != -1)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (top != 9223372036854775807L && bottom != -1 &&
left != 9223372036854775807L && right != -1) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else
g_assertion_message_expr ("BTE", "../src/bte.cc", 5753, ((const
char*) (__PRETTY_FUNCTION__)), "top != LONG_MAX && bottom != -1 && left != LONG_MAX && right != -1"
); } while (0)
;
5754
5755 auto allocation = get_allocated_rect();
5756 bbox->x = allocation.x + m_padding.left + left * m_cell_width;
5757 bbox->y = allocation.y + m_padding.top + row_to_pixel(top);
5758 bbox->width = (right - left + 1) * m_cell_width;
5759 bbox->height = (bottom - top + 1) * m_cell_height;
5760 _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0)
5761 "Hyperlink bounding box: x=%d y=%d w=%d h=%d\n",do { } while(0)
5762 bbox->x, bbox->y, bbox->width, bbox->height)do { } while(0);
5763}
5764
5765/*
5766 * Terminal::hyperlink_hilite_update:
5767 *
5768 * Checks the coordinates for hyperlink. Updates m_hyperlink_hover_idx
5769 * and m_hyperlink_hover_uri, and schedules to update the highlighting.
5770 */
5771void
5772Terminal::hyperlink_hilite_update()
5773{
5774 const BteRowData *rowdata;
5775 bool do_check_hilite;
5776 bte::grid::coords rowcol;
5777 bte::base::Ring::hyperlink_idx_t new_hyperlink_hover_idx = 0;
5778 CdkRectangle bbox;
5779 const char *separator;
5780
5781 if (!m_allow_hyperlink)
5782 return;
5783
5784 _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0)
5785 "hyperlink_hilite_update\n")do { } while(0);
5786
5787 /* Need to ensure the ringview is updated. */
5788 ringview_update();
5789
5790 /* m_mouse_last_position contains the current position, see bug 789536 comment 24. */
5791 auto pos = m_mouse_last_position;
5792
5793 /* Whether there's any chance we'd highlight something */
5794 do_check_hilite = view_coords_visible(pos) &&
5795 m_mouse_cursor_over_widget &&
5796 !(m_mouse_autohide && m_mouse_cursor_autohidden) &&
5797 !m_selecting;
5798 if (do_check_hilite) {
5799 rowcol = grid_coords_from_view_coords(pos);
5800 rowdata = find_row_data(rowcol.row());
5801 if (rowdata && rowcol.column() < rowdata->len) {
5802 new_hyperlink_hover_idx = rowdata->cells[rowcol.column()].attr.hyperlink_idx;
5803 }
5804 }
5805
5806 if (new_hyperlink_hover_idx == m_hyperlink_hover_idx) {
5807 _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0)
5808 "hyperlink did not change\n")do { } while(0);
5809 return;
5810 }
5811
5812 /* Invalidate cells of the old hyperlink. */
5813 if (m_hyperlink_hover_idx != 0) {
5814 hyperlink_invalidate_and_get_bbox(m_hyperlink_hover_idx, NULL__null);
5815 }
5816
5817 /* This might be different from new_hyperlink_hover_idx. If in the stream, that one contains
5818 * the pseudo idx BTE_HYPERLINK_IDX_TARGET_IN_STREAM and now a real idx is allocated.
5819 * Plus, the ring's internal belief of the hovered hyperlink is also updated. */
5820 if (do_check_hilite) {
5821 m_hyperlink_hover_idx = _bte_ring_get_hyperlink_at_position(m_screen->row_data, rowcol.row(), rowcol.column(), true, &m_hyperlink_hover_uri);
5822 } else {
5823 m_hyperlink_hover_idx = 0;
5824 m_hyperlink_hover_uri = nullptr;
5825 }
5826
5827 /* Invalidate cells of the new hyperlink. Get the bounding box. */
5828 if (m_hyperlink_hover_idx != 0) {
5829 /* URI is after the first semicolon */
5830 separator = strchr(m_hyperlink_hover_uri, ';');
5831 g_assert(separator != NULL)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (separator != __null) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 5831, ((const char*) (__PRETTY_FUNCTION__
)), "separator != NULL"); } while (0)
;
5832 m_hyperlink_hover_uri = separator + 1;
5833
5834 hyperlink_invalidate_and_get_bbox(m_hyperlink_hover_idx, &bbox);
5835 g_assert(bbox.width > 0 && bbox.height > 0)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (bbox.width > 0 && bbox.height > 0) _g_boolean_var_
= 1; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else
g_assertion_message_expr ("BTE", "../src/bte.cc", 5835, ((const
char*) (__PRETTY_FUNCTION__)), "bbox.width > 0 && bbox.height > 0"
); } while (0)
;
5836 }
5837 _bte_debug_print(BTE_DEBUG_HYPERLINK,do { } while(0)
5838 "Hover idx: %d \"%s\"\n",do { } while(0)
5839 m_hyperlink_hover_idx,do { } while(0)
5840 m_hyperlink_hover_uri)do { } while(0);
5841
5842 /* Underlining hyperlinks has precedence over regex matches. So when the hovered hyperlink changes,
5843 * the regex match might need to become or stop being underlined. */
5844 if (regex_match_has_current())
5845 invalidate_match_span();
5846
5847 apply_mouse_cursor();
5848
5849 emit_hyperlink_hover_uri_changed(m_hyperlink_hover_idx != 0 ? &bbox : NULL__null);
5850}
5851
5852/*
5853 * Terminal::match_hilite_clear:
5854 *
5855 * Reset match variables and invalidate the old match region if highlighted.
5856 */
5857void
5858Terminal::match_hilite_clear()
5859{
5860 if (regex_match_has_current())
5861 invalidate_match_span();
5862
5863 m_match_span.clear();
5864 m_match_current = nullptr;
5865
5866 g_free(m_match);
5867 m_match = nullptr;
5868}
5869
5870/* This is only used by the dingu matching code, so no need to extend the area. */
5871void
5872Terminal::invalidate_match_span()
5873{
5874 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
5875 "Invalidating match span %s\n", m_match_span.to_string())do { } while(0);
5876 invalidate(m_match_span);
5877}
5878
5879/*
5880 * Terminal::match_hilite_update:
5881 *
5882 * Checks the coordinates for dingu matches, setting m_match_span to
5883 * the match region or the no-matches region, and if there is a match,
5884 * sets it to display highlighted.
5885 */
5886void
5887Terminal::match_hilite_update()
5888{
5889 /* Need to ensure the ringview is updated. */
5890 ringview_update();
5891
5892 /* m_mouse_last_position contains the current position, see bug 789536 comment 24. */
5893 auto pos = m_mouse_last_position;
5894
5895 glong col = pos.x / m_cell_width;
5896 glong row = pixel_to_row(pos.y);
5897
5898 /* BiDi: convert to logical column. */
5899 bte::base::BidiRow const* bidirow = m_ringview.get_bidirow(confine_grid_row(row));
5900 col = bidirow->vis2log(col);
5901
5902 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
5903 "Match hilite update (%ld, %ld) -> %ld, %ld\n",do { } while(0)
5904 pos.x, pos.y, col, row)do { } while(0);
5905
5906 /* Whether there's any chance we'd highlight something */
5907 bool do_check_hilite = view_coords_visible(pos) &&
5908 m_mouse_cursor_over_widget &&
5909 !(m_mouse_autohide && m_mouse_cursor_autohidden) &&
5910 !m_selecting;
5911 if (!do_check_hilite) {
5912 if (regex_match_has_current())
5913 match_hilite_clear();
5914 return;
5915 }
5916
5917 if (m_match_span.contains(row, col)) {
5918 /* Already highlighted. */
5919 return;
5920 }
5921
5922 /* Reset match variables and invalidate the old match region if highlighted */
5923 match_hilite_clear();
5924
5925 /* Check for matches. */
5926 gsize start, end;
5927 auto new_match = match_check_internal(col,
5928 row,
5929 &m_match_current,
5930 &start,
5931 &end);
5932
5933 /* Read the new locations. */
5934 if (start < m_match_attributes->len &&
5935 end < m_match_attributes->len) {
5936 struct _BteCharAttributes const *sa, *ea;
5937 sa = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(start)])
5938 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(start)])
5939 start)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(start)])
;
5940 ea = &g_array_index(m_match_attributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(end)])
5941 struct _BteCharAttributes,(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(end)])
5942 end)(((struct _BteCharAttributes*) (void *) (m_match_attributes)->
data) [(end)])
;
5943
5944 /* convert from inclusive to exclusive (a.k.a. boundary) ending, taking a possible last CJK character into account */
5945 m_match_span = bte::grid::span(sa->row, sa->column, ea->row, ea->column + ea->columns);
5946 }
5947
5948 g_assert(!m_match)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (!m_match) _g_boolean_var_ = 1; else _g_boolean_var_ = 0
; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr ("BTE"
, "../src/bte.cc", 5948, ((const char*) (__PRETTY_FUNCTION__)
), "!m_match"); } while (0)
; /* from match_hilite_clear() above */
5949 m_match = new_match;
5950
5951 if (m_match) {
5952 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
5953 "Matched %s.\n", m_match_span.to_string())do { } while(0);
5954 invalidate_match_span();
5955 } else {
5956 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
5957 "No matches %s.\n", m_match_span.to_string())do { } while(0);
5958 }
5959
5960 apply_mouse_cursor();
5961}
5962
5963/* Note that the clipboard has cleared. */
5964static void
5965clipboard_clear_cb(CtkClipboard *clipboard,
5966 gpointer user_data)
5967{
5968 auto that = reinterpret_cast<bte::terminal::Terminal*>(user_data);
5969 that->widget_clipboard_cleared(clipboard);
5970}
5971
5972void
5973Terminal::widget_clipboard_cleared(CtkClipboard *clipboard_)
5974{
5975 if (m_changing_selection)
5976 return;
5977
5978 if (clipboard_ == m_clipboard[BTE_SELECTION_PRIMARY]) {
5979 if (m_selection_owned[BTE_SELECTION_PRIMARY] &&
5980 !m_selection_resolved.empty()) {
5981 _bte_debug_print(BTE_DEBUG_SELECTION, "Lost selection.\n")do { } while(0);
5982 deselect_all();
5983 }
5984 m_selection_owned[BTE_SELECTION_PRIMARY] = false;
5985 } else if (clipboard_ == m_clipboard[BTE_SELECTION_CLIPBOARD]) {
5986 m_selection_owned[BTE_SELECTION_CLIPBOARD] = false;
5987 }
5988}
5989
5990/* Supply the selected text to the clipboard. */
5991static void
5992clipboard_copy_cb(CtkClipboard *clipboard,
5993 CtkSelectionData *data,
5994 guint info,
5995 gpointer user_data)
5996{
5997 auto that = reinterpret_cast<bte::terminal::Terminal*>(user_data);
5998 that->widget_clipboard_requested(clipboard, data, info);
5999}
6000
6001static char*
6002text_to_utf16_mozilla(GString* text,
6003 gsize* len_ptr)
6004{
6005 /* Use g_convert() instead of g_utf8_to_utf16() since the former
6006 * adds a BOM which Mozilla requires for text/html format.
6007 */
6008 return g_convert(text->str, text->len,
6009 "UTF-16", /* conver to UTF-16 */
6010 "UTF-8", /* convert from UTF-8 */
6011 nullptr /* out bytes_read */,
6012 len_ptr,
6013 nullptr);
6014}
6015
6016void
6017Terminal::widget_clipboard_requested(CtkClipboard *target_clipboard,
6018 CtkSelectionData *data,
6019 guint info)
6020{
6021 for (auto sel = 0; sel < LAST_BTE_SELECTION; sel++) {
6022 if (target_clipboard == m_clipboard[sel] &&
6023 m_selection[sel] != nullptr) {
6024 _BTE_DEBUG_IF(BTE_DEBUG_SELECTION)if (0) {
6025 int i;
6026 g_printerr("Setting selection %d (%" G_GSIZE_FORMAT"lu" " UTF-8 bytes.) for target %s\n",
6027 sel,
6028 m_selection[sel]->len,
6029 cdk_atom_name(ctk_selection_data_get_target(data)));
6030 char const* selection_text = m_selection[sel]->str;
6031 for (i = 0; selection_text[i] != '\0'; i++) {
6032 g_printerr("0x%04x ", selection_text[i]);
6033 if ((i & 0x7) == 0x7)
6034 g_printerr("\n");
6035 }
6036 g_printerr("\n");
6037 }
6038 if (info == BTE_TARGET_TEXT) {
6039 ctk_selection_data_set_text(data,
6040 m_selection[sel]->str,
6041 m_selection[sel]->len);
6042 } else if (info == BTE_TARGET_HTML) {
6043 gsize len;
6044 auto selection = text_to_utf16_mozilla(m_selection[sel], &len);
6045 // FIXMEchpe this makes yet another copy of the data... :(
6046 if (selection)
6047 ctk_selection_data_set(data,
6048 cdk_atom_intern_static_string("text/html"),
6049 16,
6050 (const guchar *)selection,
6051 len);
6052 g_free(selection);
6053 } else {
6054 /* Not reached */
6055 }
6056 }
6057 }
6058}
6059
6060/* Convert the internal color code (either index or RGB) into RGB. */
6061template <unsigned int redbits,
6062 unsigned int greenbits,
6063 unsigned int bluebits>
6064void
6065Terminal::rgb_from_index(guint index,
6066 bte::color::rgb& color) const
6067{
6068 bool dim = false;
6069 if (!(index & BTE_RGB_COLOR_MASK(redbits, greenbits, bluebits)(1U << ((redbits) + (greenbits) + (bluebits)))) && (index & BTE_DIM_COLOR(1U << 10))) {
6070 index &= ~BTE_DIM_COLOR(1U << 10);
6071 dim = true;
6072 }
6073
6074 if (index >= BTE_LEGACY_COLORS_OFFSET(1U << 9) && index < BTE_LEGACY_COLORS_OFFSET(1U << 9) + BTE_LEGACY_FULL_COLOR_SET_SIZE16)
6075 index -= BTE_LEGACY_COLORS_OFFSET(1U << 9);
6076 if (index < BTE_PALETTE_SIZE263) {
6077 color = *get_color(index);
6078 if (dim) {
6079 /* magic formula taken from xterm */
6080 color.red = color.red * 2 / 3;
6081 color.green = color.green * 2 / 3;
6082 color.blue = color.blue * 2 / 3;
6083 }
6084 } else if (index & BTE_RGB_COLOR_MASK(redbits, greenbits, bluebits)(1U << ((redbits) + (greenbits) + (bluebits)))) {
6085 color.red = BTE_RGB_COLOR_GET_COMPONENT(index, greenbits + bluebits, redbits)((((index) >> (greenbits + bluebits)) & ((1U <<
(redbits)) - 1U)) << (8 - redbits) | ((1U << (8 -
redbits)) >> 1))
* 0x101U;
6086 color.green = BTE_RGB_COLOR_GET_COMPONENT(index, bluebits, greenbits)((((index) >> (bluebits)) & ((1U << (greenbits
)) - 1U)) << (8 - greenbits) | ((1U << (8 - greenbits
)) >> 1))
* 0x101U;
6087 color.blue = BTE_RGB_COLOR_GET_COMPONENT(index, 0, bluebits)((((index) >> (0)) & ((1U << (bluebits)) - 1U
)) << (8 - bluebits) | ((1U << (8 - bluebits)) >>
1))
* 0x101U;
6088 } else {
6089 g_assert_not_reached()do { g_assertion_message_expr ("BTE", "../src/bte.cc", 6089, (
(const char*) (__PRETTY_FUNCTION__)), __null); } while (0)
;
6090 }
6091}
6092
6093GString*
6094Terminal::get_text(bte::grid::row_t start_row,
6095 bte::grid::column_t start_col,
6096 bte::grid::row_t end_row,
6097 bte::grid::column_t end_col,
6098 bool block,
6099 bool wrap,
6100 GArray *attributes)
6101{
6102 const BteCell *pcell = NULL__null;
6103 GString *string;
6104 struct _BteCharAttributes attr;
6105 bte::color::rgb fore, back;
6106 std::unique_ptr<bte::base::RingView> ringview;
6107 bte::base::BidiRow const *bidirow = nullptr;
6108 bte::grid::column_t vcol;
6109
6110 if (attributes)
6111 g_array_set_size (attributes, 0);
6112
6113 string = g_string_new(NULL__null);
6114 memset(&attr, 0, sizeof(attr));
6115
6116 if (start_col < 0)
6117 start_col = 0;
6118
6119 if (m_enable_bidi && block) {
6120 /* Rectangular selection operates on the visual contents, not the logical.
6121 * m_ringview corresponds to the currently onscreen bits, therefore does not
6122 * necessarily include the entire selection. Also we want m_ringview's size
6123 * to be limited, even if the user selects a giant rectangle.
6124 * So use a new ringview for the selection. */
6125 ringview = std::make_unique<bte::base::RingView>();
6126 ringview->set_ring(m_screen->row_data);
6127 ringview->set_rows(start_row, end_row - start_row + 1);
6128 ringview->set_width(m_column_count);
6129 ringview->update();
6130 }
6131
6132 bte::grid::column_t lcol = block ? 0 : start_col;
6133 bte::grid::row_t row;
6134 for (row = start_row; row < end_row + 1; row++, lcol = 0) {
6135 BteRowData const* row_data = find_row_data(row);
6136 gsize last_empty, last_nonempty;
6137 bte::grid::column_t last_emptycol, last_nonemptycol;
6138 bte::grid::column_t line_last_column = (!block && row == end_row) ? end_col : m_column_count;
6139
6140 last_empty = last_nonempty = string->len;
6141 last_emptycol = last_nonemptycol = -1;
6142
6143 attr.row = row;
6144 attr.column = lcol;
6145 pcell = NULL__null;
6146 if (row_data != NULL__null) {
6147 bidirow = ringview ? ringview->get_bidirow(row) : nullptr;
6148 while (lcol < line_last_column &&
6149 (pcell = _bte_row_data_get (row_data, lcol))) {
6150
6151 /* In block mode, we scan each row from its very beginning to its very end in logical order,
6152 * and here filter out the characters that are visually outside of the block. */
6153 if (bidirow) {
6154 vcol = bidirow->log2vis(lcol);
6155 if (vcol < start_col || vcol >= end_col) {
6156 lcol++;
6157 continue;
6158 }
6159 }
6160
6161 attr.column = lcol;
6162
6163 /* If it's not part of a multi-column character,
6164 * and passes the selection criterion, add it to
6165 * the selection. */
6166 if (!pcell->attr.fragment()) {
6167 /* Store the attributes of this character. */
6168 // FIXMEchpe shouldn't this use determine_colors?
6169 uint32_t fg, bg, dc;
6170 bte_color_triple_get(pcell->attr.colors(), &fg, &bg, &dc);
6171 rgb_from_index<8, 8, 8>(fg, fore);
6172 rgb_from_index<8, 8, 8>(bg, back);
6173 attr.fore.red = fore.red;
6174 attr.fore.green = fore.green;
6175 attr.fore.blue = fore.blue;
6176 attr.back.red = back.red;
6177 attr.back.green = back.green;
6178 attr.back.blue = back.blue;
6179 attr.underline = (pcell->attr.underline() == 1);
6180 attr.strikethrough = pcell->attr.strikethrough();
6181 attr.columns = pcell->attr.columns();
6182
6183 /* Store the cell string */
6184 if (pcell->c == 0) {
6185 /* Empty cells of nondefault background color are
6186 * stored as NUL characters. Treat them as spaces,
6187 * but make a note of the last occurrence. */
6188 g_string_append_c (string, ' ')g_string_append_c_inline (string, ' ');
6189 last_empty = string->len;
6190 last_emptycol = lcol;
6191 } else {
6192 _bte_unistr_append_to_string (pcell->c, string);
6193 last_nonempty = string->len;
6194 last_nonemptycol = lcol;
6195 }
6196
6197 /* If we added text to the string, record its
6198 * attributes, one per byte. */
6199 if (attributes) {
6200 bte_g_array_fill(attributes,
6201 &attr, string->len);
6202 }
6203 }
6204
6205 lcol++;
6206 }
6207 }
6208
6209 /* Empty cells of nondefault background color can appear anywhere in a line,
6210 * not just at the end, e.g. between "foo" and "bar" here:
6211 * echo -e '\e[46mfoo\e[K\e[7Gbar\e[m'
6212 * Strip off the trailing ones, preserve the middle ones. */
6213 if (last_empty > last_nonempty) {
6214
6215 lcol = last_emptycol + 1;
6216
6217 if (row_data != NULL__null) {
6218 while ((pcell = _bte_row_data_get (row_data, lcol))) {
6219 lcol++;
6220
6221 if (pcell->attr.fragment())
6222 continue;
6223
6224 if (pcell->c != 0)
6225 break;
6226 }
6227 }
6228 if (pcell == NULL__null) {
6229 g_string_truncate(string, last_nonempty);
6230 if (attributes)
6231 g_array_set_size(attributes, string->len);
6232 attr.column = last_nonemptycol;
6233 }
6234 }
6235
6236 /* Adjust column, in case we want to append a newline */
6237 //FIXMEchpe MIN ?
6238 attr.column = MAX(m_column_count, attr.column + 1)(((m_column_count) > (attr.column + 1)) ? (m_column_count)
: (attr.column + 1))
;
6239
6240 /* Add a newline in block mode. */
6241 if (block) {
6242 string = g_string_append_c(string, '\n')g_string_append_c_inline (string, '\n');
6243 }
6244 /* Else, if the last visible column on this line was in range and
6245 * not soft-wrapped, append a newline. */
6246 else if (row < end_row) {
6247 /* If we didn't softwrap, add a newline. */
6248 /* XXX need to clear row->soft_wrap on deletion! */
6249 if (!m_screen->row_data->is_soft_wrapped(row)) {
6250 string = g_string_append_c(string, '\n')g_string_append_c_inline (string, '\n');
6251 }
6252 }
6253
6254 /* Make sure that the attributes array is as long as the string. */
6255 if (attributes) {
6256 bte_g_array_fill (attributes, &attr, string->len);
6257 }
6258 }
6259
6260 /* Sanity check. */
6261 if (attributes != nullptr)
6262 g_assert_cmpuint(string->len, ==, attributes->len)do { guint64 __n1 = (string->len), __n2 = (attributes->
len); if (__n1 == __n2) ; else g_assertion_message_cmpnum ("BTE"
, "../src/bte.cc", 6262, ((const char*) (__PRETTY_FUNCTION__)
), "string->len" " " "==" " " "attributes->len", (long double
) __n1, "==", (long double) __n2, 'i'); } while (0)
;
6263
6264 return string;
6265}
6266
6267GString*
6268Terminal::get_text_displayed(bool wrap,
6269 GArray *attributes)
6270{
6271 return get_text(first_displayed_row(), 0,
6272 last_displayed_row() + 1, 0,
6273 false /* block */, wrap,
6274 attributes);
6275}
6276
6277/* This is distinct from just using first/last_displayed_row since a11y
6278 * doesn't know about sub-row displays.
6279 */
6280GString*
6281Terminal::get_text_displayed_a11y(bool wrap,
6282 GArray *attributes)
6283{
6284 return get_text(m_screen->scroll_delta, 0,
6285 m_screen->scroll_delta + m_row_count - 1 + 1, 0,
6286 false /* block */, wrap,
6287 attributes);
6288}
6289
6290GString*
6291Terminal::get_selected_text(GArray *attributes)
6292{
6293 return get_text(m_selection_resolved.start_row(),
6294 m_selection_resolved.start_column(),
6295 m_selection_resolved.end_row(),
6296 m_selection_resolved.end_column(),
6297 m_selection_block_mode,
6298 true /* wrap */,
6299 attributes);
6300}
6301
6302#ifdef BTE_DEBUG
6303unsigned int
6304Terminal::checksum_area(bte::grid::row_t start_row,
6305 bte::grid::column_t start_col,
6306 bte::grid::row_t end_row,
6307 bte::grid::column_t end_col)
6308{
6309 unsigned int checksum = 0;
6310
6311 auto text = get_text(start_row, start_col, end_row, end_col,
6312 true /* block */, false /* wrap */,
6313 nullptr /* not interested in attributes */);
6314 if (text == nullptr)
6315 return checksum;
6316
6317 char const* end = (char const*)text->str + text->len;
6318 for (char const *p = text->str; p < end; p = g_utf8_next_char(p)(char *)((p) + g_utf8_skip[*(const guchar *)(p)])) {
6319 auto const c = g_utf8_get_char(p);
6320 if (c == '\n')
6321 continue;
6322 checksum += c;
6323 }
6324 g_string_free(text, true);
6325
6326 return checksum & 0xffff;
6327}
6328#endif /* BTE_DEBUG */
6329
6330/*
6331 * Compares the visual attributes of a BteCellAttr for equality, but ignores
6332 * attributes that tend to change from character to character or are otherwise
6333 * strange (in particular: fragment, columns).
6334 */
6335// FIXMEchpe: make BteCellAttr a class with operator==
6336static bool
6337bte_terminal_cellattr_equal(BteCellAttr const* attr1,
6338 BteCellAttr const* attr2)
6339{
6340 //FIXMEchpe why exclude DIM here?
6341 return (((attr1->attr ^ attr2->attr) & BTE_ATTR_ALL_MASK(((((1U << ((1))) - 1U) << ((((0) + (4)) + (1))))
) | ((((1U << ((1))) - 1U) << (((((0) + (4)) + (1
)) + (1))))) | ((((1U << ((2))) - 1U) << ((((((0)
+ (4)) + (1)) + (1)) + (1))))) | ((((1U << ((1))) - 1U
) << (((((((0) + (4)) + (1)) + (1)) + (1)) + (2))))) | (
(((1U << ((1))) - 1U) << ((((((((0) + (4)) + (1))
+ (1)) + (1)) + (2)) + (1))))) | ((((1U << ((1))) - 1U
) << (((((((((0) + (4)) + (1)) + (1)) + (1)) + (2)) + (
1)) + (1))))) | ((((1U << ((1))) - 1U) << (((((((
(((0) + (4)) + (1)) + (1)) + (1)) + (2)) + (1)) + (1)) + (1))
))) | ((((1U << ((1))) - 1U) << ((((((((((((0) + (
4)) + (1)) + (1)) + (1)) + (2)) + (1)) + (1)) + (1)) + (1)) +
(1))))))
) == 0 &&
6342 attr1->colors() == attr2->colors() &&
6343 attr1->hyperlink_idx == attr2->hyperlink_idx);
6344}
6345
6346/*
6347 * Wraps a given string according to the BteCellAttr in HTML tags. Used
6348 * old-style HTML (and not CSS) for better compatibility with, for example,
6349 * evolution's mail editor component.
6350 */
6351char *
6352Terminal::cellattr_to_html(BteCellAttr const* attr,
6353 char const* text) const
6354{
6355 GString *string;
6356 guint fore, back, deco;
6357
6358 string = g_string_new(text);
6359
6360 determine_colors(attr, false, false, &fore, &back, &deco);
6361
6362 if (attr->bold()) {
6363 g_string_prepend(string, "<b>");
6364 g_string_append(string, "</b>");
6365 }
6366 if (attr->italic()) {
6367 g_string_prepend(string, "<i>");
6368 g_string_append(string, "</i>");
6369 }
6370 /* <u> should be inside <font> so that it inherits its color by default */
6371 if (attr->underline() != 0) {
6372 static const char styles[][7] = {"", "single", "double", "wavy"};
6373 char *tag, *colorattr;
6374
6375 if (deco != BTE_DEFAULT_FG256) {
6376 bte::color::rgb color;
6377
6378 rgb_from_index<4, 5, 4>(deco, color);
6379 colorattr = g_strdup_printf(";text-decoration-color:#%02X%02X%02X",
6380 color.red >> 8,
6381 color.green >> 8,
6382 color.blue >> 8);
6383 } else {
6384 colorattr = g_strdup("");
6385 }
6386
6387 tag = g_strdup_printf("<u style=\"text-decoration-style:%s%s\">",
6388 styles[attr->underline()],
6389 colorattr);
6390 g_string_prepend(string, tag);
6391 g_free(tag);
6392 g_free(colorattr);
6393 g_string_append(string, "</u>");
6394 }
6395 if (fore != BTE_DEFAULT_FG256 || attr->reverse()) {
6396 bte::color::rgb color;
6397 char *tag;
6398
6399 rgb_from_index<8, 8, 8>(fore, color);
6400 tag = g_strdup_printf("<font color=\"#%02X%02X%02X\">",
6401 color.red >> 8,
6402 color.green >> 8,
6403 color.blue >> 8);
6404 g_string_prepend(string, tag);
6405 g_free(tag);
6406 g_string_append(string, "</font>");
6407 }
6408 if (back != BTE_DEFAULT_BG257 || attr->reverse()) {
6409 bte::color::rgb color;
6410 char *tag;
6411
6412 rgb_from_index<8, 8, 8>(back, color);
6413 tag = g_strdup_printf("<span style=\"background-color:#%02X%02X%02X\">",
6414 color.red >> 8,
6415 color.green >> 8,
6416 color.blue >> 8);
6417 g_string_prepend(string, tag);
6418 g_free(tag);
6419 g_string_append(string, "</span>");
6420 }
6421 if (attr->strikethrough()) {
6422 g_string_prepend(string, "<strike>");
6423 g_string_append(string, "</strike>");
6424 }
6425 if (attr->overline()) {
6426 g_string_prepend(string, "<span style=\"text-decoration-line:overline\">");
6427 g_string_append(string, "</span>");
6428 }
6429 if (attr->blink()) {
6430 g_string_prepend(string, "<blink>");
6431 g_string_append(string, "</blink>");
6432 }
6433 /* reverse and invisible are not supported */
6434
6435 return g_string_free(string, FALSE(0));
6436}
6437
6438/*
6439 * Similar to find_charcell(), but takes a BteCharAttribute for
6440 * indexing and returns the BteCellAttr.
6441 */
6442BteCellAttr const*
6443Terminal::char_to_cell_attr(BteCharAttributes const* attr) const
6444{
6445 BteCell const* cell = find_charcell(attr->column, attr->row);
6446 if (cell)
6447 return &cell->attr;
6448 return nullptr;
6449}
6450
6451/*
6452 * Terminal::attributes_to_html:
6453 * @text: A string as returned by the bte_terminal_get_* family of functions.
6454 * @attrs: (array) (element-type Bte.CharAttributes): text attributes, as created by bte_terminal_get_*
6455 *
6456 * Marks the given text up according to the given attributes, using HTML <span>
6457 * commands, and wraps the string in a <pre> element. The attributes have to be
6458 * "fresh" in the sense that the terminal must not have changed since they were
6459 * obtained using the bte_terminal_get* function.
6460 *
6461 * Returns: (transfer full): a newly allocated text string, or %NULL.
6462 */
6463GString*
6464Terminal::attributes_to_html(GString* text_string,
6465 GArray* attrs)
6466{
6467 GString *string;
6468 guint from,to;
6469 const BteCellAttr *attr;
6470 char *escaped, *marked;
6471
6472 char const* text = text_string->str;
6473 auto len = text_string->len;
6474 g_assert_cmpuint(len, ==, attrs->len)do { guint64 __n1 = (len), __n2 = (attrs->len); if (__n1 ==
__n2) ; else g_assertion_message_cmpnum ("BTE", "../src/bte.cc"
, 6474, ((const char*) (__PRETTY_FUNCTION__)), "len" " " "=="
" " "attrs->len", (long double) __n1, "==", (long double)
__n2, 'i'); } while (0)
;
6475
6476 /* Initial size fits perfectly if the text has no attributes and no
6477 * characters that need to be escaped
6478 */
6479 string = g_string_sized_new (len + 11);
6480
6481 g_string_append(string, "<pre>");
6482 /* Find streches with equal attributes. Newlines are treated specially,
6483 * so that the <span> do not cover multiple lines.
6484 */
6485 from = to = 0;
6486 while (text[from] != '\0') {
6487 g_assert(from == to)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (from == to) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr (
"BTE", "../src/bte.cc", 6487, ((const char*) (__PRETTY_FUNCTION__
)), "from == to"); } while (0)
;
6488 if (text[from] == '\n') {
6489 g_string_append_c(string, '\n')g_string_append_c_inline (string, '\n');
6490 from = ++to;
6491 } else {
6492 attr = char_to_cell_attr(
6493 &g_array_index(attrs, BteCharAttributes, from)(((BteCharAttributes*) (void *) (attrs)->data) [(from)]));
6494 while (text[to] != '\0' && text[to] != '\n' &&
6495 bte_terminal_cellattr_equal(attr,
6496 char_to_cell_attr(
6497 &g_array_index(attrs, BteCharAttributes, to)(((BteCharAttributes*) (void *) (attrs)->data) [(to)]))))
6498 {
6499 to++;
6500 }
6501 escaped = g_markup_escape_text(text + from, to - from);
6502 marked = cellattr_to_html(attr, escaped);
6503 g_string_append(string, marked);
6504 g_free(escaped);
6505 g_free(marked);
6506 from = to;
6507 }
6508 }
6509 g_string_append(string, "</pre>");
6510
6511 return string;
6512}
6513
6514static CtkTargetEntry*
6515targets_for_format(BteFormat format,
6516 int *n_targets)
6517{
6518 switch (format) {
6519 case BTE_FORMAT_TEXT: {
6520 static CtkTargetEntry *text_targets = nullptr;
6521 static int n_text_targets;
6522
6523 if (text_targets == nullptr) {
6524 auto list = ctk_target_list_new (nullptr, 0);
6525 ctk_target_list_add_text_targets (list, BTE_TARGET_TEXT);
6526
6527 text_targets = ctk_target_table_new_from_list (list, &n_text_targets);
6528 ctk_target_list_unref (list);
6529 }
6530
6531 *n_targets = n_text_targets;
6532 return text_targets;
6533 }
6534
6535 case BTE_FORMAT_HTML: {
6536 static CtkTargetEntry *html_targets = nullptr;
6537 static int n_html_targets;
6538
6539 if (html_targets == nullptr) {
6540 auto list = ctk_target_list_new (nullptr, 0);
6541 ctk_target_list_add_text_targets (list, BTE_TARGET_TEXT);
6542 ctk_target_list_add (list,
6543 cdk_atom_intern_static_string("text/html"),
6544 0,
6545 BTE_TARGET_HTML);
6546
6547 html_targets = ctk_target_table_new_from_list (list, &n_html_targets);
6548 ctk_target_list_unref (list);
6549 }
6550
6551 *n_targets = n_html_targets;
6552 return html_targets;
6553 }
6554 default:
6555 g_assert_not_reached()do { g_assertion_message_expr ("BTE", "../src/bte.cc", 6555, (
(const char*) (__PRETTY_FUNCTION__)), __null); } while (0)
;
6556 }
6557}
6558
6559/* Place the selected text onto the clipboard. Do this asynchronously so that
6560 * we get notified when the selection we placed on the clipboard is replaced. */
6561void
6562Terminal::widget_copy(BteSelection sel,
6563 BteFormat format)
6564{
6565 /* Only put HTML on the CLIPBOARD, not PRIMARY */
6566 g_assert(sel == BTE_SELECTION_CLIPBOARD || format == BTE_FORMAT_TEXT)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (sel == BTE_SELECTION_CLIPBOARD || format == BTE_FORMAT_TEXT
) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 1)) ; else g_assertion_message_expr ("BTE", "../src/bte.cc"
, 6566, ((const char*) (__PRETTY_FUNCTION__)), "sel == BTE_SELECTION_CLIPBOARD || format == BTE_FORMAT_TEXT"
); } while (0)
;
6567
6568 /* Chuck old selected text and retrieve the newly-selected text. */
6569 GArray *attributes = g_array_new(FALSE(0), TRUE(!(0)), sizeof(struct _BteCharAttributes));
6570 auto selection = get_selected_text(attributes);
6571
6572 if (m_selection[sel]) {
6573 g_string_free(m_selection[sel], TRUE(!(0)));
6574 m_selection[sel] = nullptr;
6575 }
6576
6577 if (selection == nullptr) {
6578 g_array_free(attributes, TRUE(!(0)));
6579 m_selection_owned[sel] = false;
6580 return;
6581 }
6582
6583 if (format == BTE_FORMAT_HTML) {
6584 m_selection[sel] = attributes_to_html(selection, attributes);
6585 g_string_free(selection, TRUE(!(0)));
6586 } else {
6587 m_selection[sel] = selection;
6588 }
6589
6590 g_array_free (attributes, TRUE(!(0)));
6591
6592 /* Place the text on the clipboard. */
6593 _bte_debug_print(BTE_DEBUG_SELECTION,do { } while(0)
6594 "Assuming ownership of selection.\n")do { } while(0);
6595
6596 int n_targets;
6597 auto targets = targets_for_format(format, &n_targets);
6598
6599 m_changing_selection = true;
6600 ctk_clipboard_set_with_data(m_clipboard[sel],
6601 targets,
6602 n_targets,
6603 clipboard_copy_cb,
6604 clipboard_clear_cb,
6605 this);
6606 m_changing_selection = false;
6607
6608 ctk_clipboard_set_can_store(m_clipboard[sel], nullptr, 0);
6609 m_selection_owned[sel] = true;
6610 m_selection_format[sel] = format;
6611}
6612
6613/* Paste from the given clipboard. */
6614void
6615Terminal::widget_paste(CdkAtom board)
6616{
6617 if (!m_input_enabled)
6618 return;
6619
6620 auto clip = ctk_clipboard_get_for_display(ctk_widget_get_display(m_widget), board);
6621 if (!clip)
6622 return;
6623
6624 _bte_debug_print(BTE_DEBUG_SELECTION, "Requesting clipboard contents.\n")do { } while(0);
6625
6626 m_paste_request.request_text(clip, &Terminal::widget_paste_received, this);
6627}
6628
6629/* Confine coordinates into the visible area. Padding is already subtracted. */
6630void
6631Terminal::confine_coordinates(long *xp,
6632 long *yp)
6633{
6634 long x = *xp;
6635 long y = *yp;
6636 long y_stop;
6637
6638 /* Allow to use the bottom extra padding only if there's content there. */
6639 y_stop = MIN(m_view_usable_extents.height(),(((m_view_usable_extents.height()) < (row_to_pixel(m_screen
->insert_delta + m_row_count))) ? (m_view_usable_extents.height
()) : (row_to_pixel(m_screen->insert_delta + m_row_count))
)
6640 row_to_pixel(m_screen->insert_delta + m_row_count))(((m_view_usable_extents.height()) < (row_to_pixel(m_screen
->insert_delta + m_row_count))) ? (m_view_usable_extents.height
()) : (row_to_pixel(m_screen->insert_delta + m_row_count))
)
;
6641
6642 if (y < 0) {
6643 y = 0;
6644 if (!m_selection_block_mode)
6645 x = 0;
6646 } else if (y >= y_stop) {
6647 y = y_stop - 1;
6648 if (!m_selection_block_mode)
6649 x = m_column_count * m_cell_width - 1;
6650 }
6651 if (x < 0) {
6652 x = 0;
6653 } else if (x >= m_column_count * m_cell_width) {
6654 x = m_column_count * m_cell_width - 1;
6655 }
6656
6657 *xp = x;
6658 *yp = y;
6659}
6660
6661/* Start selection at the location of the event.
6662 * In case of regular selection, this is called with the original click's location
6663 * once the mouse has moved by the ctk drag threshold.
6664 */
6665void
6666Terminal::start_selection (bte::view::coords const& pos,
6667 SelectionType type)
6668{
6669 if (m_selection_block_mode)
6670 type = SelectionType::eCHAR;
6671
6672 /* Need to ensure the ringview is updated. */
6673 ringview_update();
6674
6675 m_selection_origin = m_selection_last = selection_grid_halfcoords_from_view_coords(pos);
6676
6677 /* Record the selection type. */
6678 m_selection_type = type;
6679 m_selecting = TRUE(!(0));
6680 m_selecting_had_delta = false; /* resolve_selection() below will most likely flip it to true. */
6681 m_will_select_after_threshold = false;
6682
6683 _bte_debug_print(BTE_DEBUG_SELECTION,do { } while(0)
6684 "Selection started at %s.\n",do { } while(0)
6685 m_selection_origin.to_string())do { } while(0);
6686
6687 /* Take care of updating the display. */
6688 resolve_selection();
6689
6690 /* Temporarily stop caring about input from the child. */
6691 disconnect_pty_read();
6692}
6693
6694bool
6695Terminal::maybe_end_selection()
6696{
6697 if (m_selecting) {
6698 /* Copy only if something was selected. */
6699 if (!m_selection_resolved.empty() &&
6700 m_selecting_had_delta) {
6701 widget_copy(BTE_SELECTION_PRIMARY, BTE_FORMAT_TEXT);
6702 emit_selection_changed();
6703 }
6704 stop_autoscroll(); /* Required before setting m_selecting to false, see #105. */
6705 m_selecting = false;
6706
6707 /* Reconnect to input from the child if we paused it. */
6708 connect_pty_read();
6709
6710 return true;
6711 }
6712
6713 if (m_will_select_after_threshold)
6714 return true;
6715
6716 return false;
6717}
6718
6719/*
6720 * Terminal::select_all:
6721 *
6722 * Selects all text within the terminal (including the scrollback buffer).
6723 */
6724void
6725Terminal::select_all()
6726{
6727 deselect_all();
6728
6729 m_selecting_had_delta = TRUE(!(0));
6730
6731 m_selection_resolved.set ({ _bte_ring_delta (m_screen->row_data), 0 },
6732 { _bte_ring_next (m_screen->row_data), 0 });
6733
6734 _bte_debug_print(BTE_DEBUG_SELECTION, "Selecting *all* text.\n")do { } while(0);
6735
6736 widget_copy(BTE_SELECTION_PRIMARY, BTE_FORMAT_TEXT);
6737 emit_selection_changed();
6738
6739 invalidate_all();
6740}
6741
6742bool
6743Terminal::mouse_autoscroll_timer_callback()
6744{
6745 bool extend = false;
6746 long x, y, xmax, ymax;
6747 glong adj;
6748
6749 auto again = bool{true};
6750
6751 /* Provide an immediate effect for mouse wigglers. */
6752 if (m_mouse_last_position.y < 0) {
6753 if (m_vadjustment) {
6754 /* Try to scroll up by one line. */
6755 adj = m_screen->scroll_delta - 1;
6756 queue_adjustment_value_changed_clamped(adj);
6757 extend = true;
6758 }
6759 _bte_debug_print(BTE_DEBUG_EVENTS, "Autoscrolling down.\n")do { } while(0);
6760 }
6761 if (m_mouse_last_position.y >= m_view_usable_extents.height()) {
6762 if (m_vadjustment) {
6763 /* Try to scroll up by one line. */
6764 adj = m_screen->scroll_delta + 1;
6765 queue_adjustment_value_changed_clamped(adj);
6766 extend = true;
6767 }
6768 _bte_debug_print(BTE_DEBUG_EVENTS, "Autoscrolling up.\n")do { } while(0);
6769 }
6770 if (extend) {
6771 // FIXMEchpe use confine_view_coords here
6772 /* Don't select off-screen areas. That just confuses people. */
6773 xmax = m_column_count * m_cell_width;
6774 ymax = m_row_count * m_cell_height;
6775
6776 x = CLAMP(m_mouse_last_position.x, 0, xmax)(((m_mouse_last_position.x) > (xmax)) ? (xmax) : (((m_mouse_last_position
.x) < (0)) ? (0) : (m_mouse_last_position.x)))
;
6777 y = CLAMP(m_mouse_last_position.y, 0, ymax)(((m_mouse_last_position.y) > (ymax)) ? (ymax) : (((m_mouse_last_position
.y) < (0)) ? (0) : (m_mouse_last_position.y)))
;
6778 /* If we clamped the Y, mess with the X to get the entire
6779 * lines. */
6780 if (m_mouse_last_position.y < 0 && !m_selection_block_mode) {
6781 x = 0;
6782 }
6783 if (m_mouse_last_position.y >= ymax && !m_selection_block_mode) {
6784 x = m_column_count * m_cell_width;
6785 }
6786 /* Extend selection to cover the newly-scrolled area. */
6787 modify_selection(bte::view::coords(x, y));
6788 } else {
6789 /* Stop autoscrolling. */
6790 again = false;
6791 }
6792 return again;
6793}
6794
6795/* Start autoscroll. */
6796void
6797Terminal::start_autoscroll()
6798{
6799 if (m_mouse_autoscroll_timer)
6800 return;
6801
6802 m_mouse_autoscroll_timer.schedule(666 / m_row_count, // FIXME WTF?
6803 bte::glib::Timer::Priority::eLOW);
6804}
6805
6806bool
6807Terminal::widget_mouse_motion(MouseEvent const& event)
6808{
6809 /* Need to ensure the ringview is updated. */
6810 ringview_update();
6811
6812 auto pos = view_coords_from_event(event);
6813 auto rowcol = grid_coords_from_view_coords(pos);
6814
6815 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
6816 "Motion notify %s %s\n",do { } while(0)
6817 pos.to_string(), rowcol.to_string())do { } while(0);
6818
6819 m_modifiers = event.modifiers();
6820
6821 if (m_will_select_after_threshold) {
6822 if (!ctk_drag_check_threshold(m_widget,
6823 m_mouse_last_position.x,
6824 m_mouse_last_position.y,
6825 pos.x, pos.y))
6826 return true;
6827
6828 start_selection(bte::view::coords(m_mouse_last_position.x, m_mouse_last_position.y),
6829 SelectionType::eCHAR);
6830 }
6831
6832 auto handled = bool{false};
6833 if (m_selecting &&
6834 (m_mouse_handled_buttons & 1) != 0) {
6835 _bte_debug_print(BTE_DEBUG_EVENTS, "Mousing drag 1.\n")do { } while(0);
6836 modify_selection(pos);
6837
6838 /* Start scrolling if we need to. */
6839 if (pos.y < 0 || pos.y >= m_view_usable_extents.height()) {
6840 /* Give mouse wigglers something. */
6841 stop_autoscroll();
6842 mouse_autoscroll_timer_callback();
6843 start_autoscroll();
6844 }
6845
6846 handled = true;
6847 }
6848
6849 if (!handled && m_input_enabled)
6850 maybe_send_mouse_drag(rowcol, event);
6851
6852 if (pos != m_mouse_last_position) {
6853 m_mouse_last_position = pos;
6854
6855 set_pointer_autohidden(false);
6856 hyperlink_hilite_update();
6857 match_hilite_update();
6858 }
6859
6860 return handled;
6861}
6862
6863bool
6864Terminal::widget_mouse_press(MouseEvent const& event)
6865{
6866 bool handled = false;
6867 gboolean start_selecting = FALSE(0), extend_selecting = FALSE(0);
6868
6869 /* Need to ensure the ringview is updated. */
6870 ringview_update();
6871
6872 auto pos = view_coords_from_event(event);
6873 auto rowcol = grid_coords_from_view_coords(pos);
6874
6875 m_modifiers = event.modifiers();
6876
6877 switch (event.type()) {
6878 case EventBase::Type::eMOUSE_PRESS:
6879 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
6880 "Button %d single-click at %s\n",do { } while(0)
6881 event.button_value(),do { } while(0)
6882 rowcol.to_string())do { } while(0);
6883 /* Handle this event ourselves. */
6884 switch (event.button()) {
6885 case MouseEvent::Button::eLEFT:
6886 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
6887 "Handling click ourselves.\n")do { } while(0);
6888 /* Grab focus. */
6889 if (!m_has_focus)
6890 widget()->grab_focus();
6891
6892 /* If we're in event mode, and the user held down the
6893 * shift key, we start selecting. */
6894 if (m_mouse_tracking_mode != MouseTrackingMode::eNONE) {
6895 if (m_modifiers & CDK_SHIFT_MASK) {
6896 start_selecting = TRUE(!(0));
6897 }
6898 } else {
6899 /* If the user hit shift, then extend the
6900 * selection instead. */
6901 if ((m_modifiers & CDK_SHIFT_MASK) &&
6902 !m_selection_resolved.empty()) {
6903 extend_selecting = TRUE(!(0));
6904 } else {
6905 start_selecting = TRUE(!(0));
6906 }
6907 }
6908 if (start_selecting) {
6909 deselect_all();
6910 m_will_select_after_threshold = true;
6911 m_selection_block_mode = !!(m_modifiers & CDK_CONTROL_MASK);
6912 handled = true;
6913 }
6914 if (extend_selecting) {
6915 /* The whole selection code needs to be
6916 * rewritten. For now, put this here to
6917 * fix bug 614658 */
6918 m_selecting = TRUE(!(0));
6919 selection_maybe_swap_endpoints (pos);
6920 modify_selection(pos);
6921 handled = true;
6922 }
6923 break;
6924 /* Paste if the user pressed shift or we're not sending events
6925 * to the app. */
6926 case MouseEvent::Button::eMIDDLE:
6927 if ((m_modifiers & CDK_SHIFT_MASK) ||
6928 m_mouse_tracking_mode == MouseTrackingMode::eNONE) {
6929 if (widget()->primary_paste_enabled()) {
6930 widget_paste(CDK_SELECTION_PRIMARY((CdkAtom)((gpointer) (gulong) (1))));
6931 handled = true;
6932 }
6933 }
6934 break;
6935 case MouseEvent::Button::eRIGHT:
6936 default:
6937 break;
6938 }
6939 if (event.button_value() >= 1 && event.button_value() <= 3) {
6940 if (handled)
6941 m_mouse_handled_buttons |= (1 << (event.button_value() - 1));
6942 else
6943 m_mouse_handled_buttons &= ~(1 << (event.button_value() - 1));
6944 }
6945 /* If we haven't done anything yet, try sending the mouse
6946 * event to the app. */
6947 if (handled == FALSE(0)) {
6948 handled = maybe_send_mouse_button(rowcol, event);
6949 }
6950 break;
6951 case EventBase::Type::eMOUSE_DOUBLE_PRESS:
6952 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
6953 "Button %d double-click at %s\n",do { } while(0)
6954 event.button_value(),do { } while(0)
6955 rowcol.to_string())do { } while(0);
6956 switch (event.button()) {
6957 case MouseEvent::Button::eLEFT:
6958 if (m_will_select_after_threshold) {
6959 start_selection(pos,
6960 SelectionType::eCHAR);
6961 handled = true;
6962 }
6963 if ((m_mouse_handled_buttons & 1) != 0) {
6964 start_selection(pos,
6965 SelectionType::eWORD);
6966 handled = true;
6967 }
6968 break;
6969 case MouseEvent::Button::eMIDDLE:
6970 case MouseEvent::Button::eRIGHT:
6971 default:
6972 break;
6973 }
6974 break;
6975 case EventBase::Type::eMOUSE_TRIPLE_PRESS:
6976 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
6977 "Button %d triple-click at %s\n",do { } while(0)
6978 event.button_value(),do { } while(0)
6979 rowcol.to_string())do { } while(0);
6980 switch (event.button()) {
6981 case MouseEvent::Button::eLEFT:
6982 if ((m_mouse_handled_buttons & 1) != 0) {
6983 start_selection(pos,
6984 SelectionType::eLINE);
6985 handled = true;
6986 }
6987 break;
6988 case MouseEvent::Button::eMIDDLE:
6989 case MouseEvent::Button::eRIGHT:
6990 default:
6991 break;
6992 }
6993 default:
6994 break;
6995 }
6996
6997 /* Save the pointer state for later use. */
6998 if (event.button_value() >= 1 && event.button_value() <= 3)
6999 m_mouse_pressed_buttons |= (1 << (event.button_value() - 1));
7000
7001 m_mouse_last_position = pos;
7002
7003 set_pointer_autohidden(false);
7004 hyperlink_hilite_update();
7005 match_hilite_update();
7006
7007 return handled;
7008}
7009
7010bool
7011Terminal::widget_mouse_release(MouseEvent const& event)
7012{
7013 bool handled = false;
7014
7015 /* Need to ensure the ringview is updated. */
7016 ringview_update();
7017
7018 auto pos = view_coords_from_event(event);
7019 auto rowcol = grid_coords_from_view_coords(pos);
7020
7021 stop_autoscroll();
7022
7023 m_modifiers = event.modifiers();
7024
7025 switch (event.type()) {
7026 case EventBase::Type::eMOUSE_RELEASE:
7027 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
7028 "Button %d released at %s\n",do { } while(0)
7029 event.button_value(), rowcol.to_string())do { } while(0);
7030 switch (event.button()) {
7031 case MouseEvent::Button::eLEFT:
7032 if ((m_mouse_handled_buttons & 1) != 0)
7033 handled = maybe_end_selection();
7034 break;
7035 case MouseEvent::Button::eMIDDLE:
7036 handled = (m_mouse_handled_buttons & 2) != 0;
7037 m_mouse_handled_buttons &= ~2;
7038 break;
7039 case MouseEvent::Button::eRIGHT:
7040 default:
7041 break;
7042 }
7043 if (!handled && m_input_enabled) {
7044 handled = maybe_send_mouse_button(rowcol, event);
7045 }
7046 break;
7047 default:
7048 break;
7049 }
7050
7051 /* Save the pointer state for later use. */
7052 if (event.button_value() >= 1 && event.button_value() <= 3)
7053 m_mouse_pressed_buttons &= ~(1 << (event.button_value() - 1));
7054
7055 m_mouse_last_position = pos;
7056 m_will_select_after_threshold = false;
7057
7058 set_pointer_autohidden(false);
7059 hyperlink_hilite_update();
7060 match_hilite_update();
7061
7062 return handled;
7063}
7064
7065void
7066Terminal::widget_focus_in()
7067{
7068 _bte_debug_print(BTE_DEBUG_EVENTS, "Focus in.\n")do { } while(0);
7069
7070 m_has_focus = true;
7071 widget()->grab_focus();
7072
7073 /* We only have an IM context when we're realized, and there's not much
7074 * point to painting the cursor if we don't have a window. */
7075 if (widget_realized()) {
7076 m_cursor_blink_state = TRUE(!(0));
7077
7078 /* If blinking gets enabled now, do a full repaint.
7079 * If blinking gets disabled, only repaint if there's blinking stuff present
7080 * (we could further optimize by checking its current phase). */
7081 if (m_text_blink_mode == TextBlinkMode::eFOCUSED ||
7082 (m_text_blink_mode == TextBlinkMode::eUNFOCUSED && m_text_blink_timer)) {
7083 invalidate_all();
7084 }
7085
7086 check_cursor_blink();
7087
7088 m_real_widget->im_focus_in();
7089 invalidate_cursor_once();
7090 maybe_feed_focus_event(true);
7091 }
7092}
7093
7094void
7095Terminal::widget_focus_out()
7096{
7097 _bte_debug_print(BTE_DEBUG_EVENTS, "Focus out.\n")do { } while(0);
7098
7099 /* We only have an IM context when we're realized, and there's not much
7100 * point to painting ourselves if we don't have a window. */
7101 if (widget_realized()) {
7102 maybe_feed_focus_event(false);
7103
7104 maybe_end_selection();
7105
7106 /* If blinking gets enabled now, do a full repaint.
7107 * If blinking gets disabled, only repaint if there's blinking stuff present
7108 * (we could further optimize by checking its current phase). */
7109 if (m_text_blink_mode == TextBlinkMode::eUNFOCUSED ||
7110 (m_text_blink_mode == TextBlinkMode::eFOCUSED && m_text_blink_timer)) {
7111 invalidate_all();
7112 }
7113
7114 m_real_widget->im_focus_out();
7115 invalidate_cursor_once();
7116
7117 m_mouse_pressed_buttons = 0;
7118 m_mouse_handled_buttons = 0;
7119 }
7120
7121 m_has_focus = false;
7122 check_cursor_blink();
7123}
7124
7125void
7126Terminal::widget_mouse_enter(MouseEvent const& event)
7127{
7128 auto pos = view_coords_from_event(event);
7129
7130 // FIXMEchpe read event modifiers here
7131
7132 _bte_debug_print(BTE_DEBUG_EVENTS, "Enter at %s\n", pos.to_string())do { } while(0);
7133
7134 m_mouse_cursor_over_widget = TRUE(!(0));
7135 m_mouse_last_position = pos;
7136
7137 set_pointer_autohidden(false);
7138 hyperlink_hilite_update();
7139 match_hilite_update();
7140 apply_mouse_cursor();
7141}
7142
7143void
7144Terminal::widget_mouse_leave(MouseEvent const& event)
7145{
7146 auto pos = view_coords_from_event(event);
7147
7148 // FIXMEchpe read event modifiers here
7149
7150 _bte_debug_print(BTE_DEBUG_EVENTS, "Leave at %s\n", pos.to_string())do { } while(0);
7151
7152 m_mouse_cursor_over_widget = FALSE(0);
7153 m_mouse_last_position = pos;
7154
7155 hyperlink_hilite_update();
7156 match_hilite_update();
7157 apply_mouse_cursor();
7158}
7159
7160/* Apply the changed metrics, and queue a resize if need be.
7161 *
7162 * The cell's height consists of 4 parts, from top to bottom:
7163 * - char_spacing.top: half of the extra line spacing,
7164 * - char_ascent: the font's ascent,
7165 * - char_descent: the font's descent,
7166 * - char_spacing.bottom: the other half of the extra line spacing.
7167 * Extra line spacing is typically 0, beef up cell_height_scale to get actual pixels
7168 * here. Similarly, increase cell_width_scale to get nonzero char_spacing.{left,right}.
7169 */
7170void
7171Terminal::apply_font_metrics(int cell_width,
7172 int cell_height,
7173 int char_ascent,
7174 int char_descent,
7175 CtkBorder char_spacing)
7176{
7177 int char_height;
7178 bool resize = false, cresize = false;
7179
7180 /* Sanity check for broken font changes. */
7181 cell_width = MAX(cell_width, 1)(((cell_width) > (1)) ? (cell_width) : (1));
7182 cell_height = MAX(cell_height, 2)(((cell_height) > (2)) ? (cell_height) : (2));
7183 char_ascent = MAX(char_ascent, 1)(((char_ascent) > (1)) ? (char_ascent) : (1));
7184 char_descent = MAX(char_descent, 1)(((char_descent) > (1)) ? (char_descent) : (1));
7185
7186 /* For convenience only. */
7187 char_height = char_ascent + char_descent;
7188
7189 /* Change settings, and keep track of when we've changed anything. */
7190 if (cell_width != m_cell_width) {
7191 resize = cresize = true;
7192 m_cell_width = cell_width;
7193 }
7194 if (cell_height != m_cell_height) {
7195 resize = cresize = true;
7196 m_cell_height = cell_height;
7197 }
7198 if (char_ascent != m_char_ascent) {
7199 resize = true;
7200 m_char_ascent = char_ascent;
7201 }
7202 if (char_descent != m_char_descent) {
7203 resize = true;
7204 m_char_descent = char_descent;
7205 }
7206 if (memcmp(&char_spacing, &m_char_padding, sizeof(CtkBorder)) != 0) {
7207 resize = true;
7208 m_char_padding = char_spacing;
7209 }
7210 m_line_thickness = MAX (MIN (char_descent / 2, char_height / 14), 1)((((((char_descent / 2) < (char_height / 14)) ? (char_descent
/ 2) : (char_height / 14))) > (1)) ? ((((char_descent / 2
) < (char_height / 14)) ? (char_descent / 2) : (char_height
/ 14))) : (1))
;
7211 /* FIXME take these from pango_font_metrics_get_{underline,strikethrough}_{position,thickness} */
7212 m_underline_thickness = m_line_thickness;
7213 m_underline_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - m_underline_thickness)(((char_spacing.top + char_ascent + m_line_thickness) < (cell_height
- m_underline_thickness)) ? (char_spacing.top + char_ascent +
m_line_thickness) : (cell_height - m_underline_thickness))
;
7214 m_double_underline_thickness = m_line_thickness;
7215 /* FIXME make sure this doesn't reach the baseline (switch to thinner lines, or one thicker line in that case) */
7216 m_double_underline_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - 3 * m_double_underline_thickness)(((char_spacing.top + char_ascent + m_line_thickness) < (cell_height
- 3 * m_double_underline_thickness)) ? (char_spacing.top + char_ascent
+ m_line_thickness) : (cell_height - 3 * m_double_underline_thickness
))
;
7217 m_undercurl_thickness = m_line_thickness;
7218 m_undercurl_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - _bte_draw_get_undercurl_height(cell_width, m_undercurl_thickness))(((char_spacing.top + char_ascent + m_line_thickness) < (cell_height
- _bte_draw_get_undercurl_height(cell_width, m_undercurl_thickness
))) ? (char_spacing.top + char_ascent + m_line_thickness) : (
cell_height - _bte_draw_get_undercurl_height(cell_width, m_undercurl_thickness
)))
;
7219 m_strikethrough_thickness = m_line_thickness;
7220 m_strikethrough_position = char_spacing.top + char_ascent - char_height / 4;
7221 m_overline_thickness = m_line_thickness;
7222 m_overline_position = char_spacing.top; /* FIXME */
7223 m_regex_underline_thickness = 1; /* FIXME */
7224 m_regex_underline_position = char_spacing.top + char_height - m_regex_underline_thickness; /* FIXME */
7225
7226 /* Queue a resize if anything's changed. */
7227 if (resize) {
7228 if (widget_realized()) {
7229 ctk_widget_queue_resize_no_redraw(m_widget);
7230 }
7231 }
7232 /* Emit a signal that the font changed. */
7233 if (cresize) {
7234 if (pty()) {
7235 /* Update pixel size of PTY. */
7236 pty()->set_size(m_row_count,
7237 m_column_count,
7238 m_cell_height,
7239 m_cell_width);
7240 }
7241 emit_char_size_changed(m_cell_width, m_cell_height);
7242 }
7243 /* Repaint. */
7244 invalidate_all();
7245}
7246
7247void
7248Terminal::ensure_font()
7249{
7250 {
7251 /* Load default fonts, if no fonts have been loaded. */
7252 if (!m_has_fonts) {
7253 set_font_desc(m_unscaled_font_desc.get());
7254 }
7255 if (m_fontdirty) {
7256 int cell_width, cell_height;
7257 int char_ascent, char_descent;
7258 CtkBorder char_spacing;
7259 m_fontdirty = false;
7260 m_draw.set_text_font(
7261 m_widget,
7262 m_fontdesc.get(),
7263 m_cell_width_scale,
7264 m_cell_height_scale);
7265 m_draw.get_text_metrics(
7266 &cell_width, &cell_height,
7267 &char_ascent, &char_descent,
7268 &char_spacing);
7269 apply_font_metrics(cell_width, cell_height,
7270 char_ascent, char_descent,
7271 char_spacing);
7272 }
7273 }
7274}
7275
7276void
7277Terminal::update_font()
7278{
7279 /* We'll get called again later */
7280 if (!m_unscaled_font_desc)
7281 return;
7282
7283 auto desc = pango_font_description_copy(m_unscaled_font_desc.get());
7284
7285 double size = pango_font_description_get_size(desc);
7286 if (pango_font_description_get_size_is_absolute(desc)) {
7287 pango_font_description_set_absolute_size(desc, m_font_scale * size);
7288 } else {
7289 pango_font_description_set_size(desc, m_font_scale * size);
7290 }
7291
7292 m_fontdesc.reset(desc); /* adopts */
7293 m_fontdirty = true;
7294 m_has_fonts = true;
7295
7296 /* Set the drawing font. */
7297 if (widget_realized()) {
7298 ensure_font();
7299 }
7300}
7301
7302/*
7303 * Terminal::set_font_desc:
7304 * @font_desc: (allow-none): a #PangoFontDescription for the desired font, or %nullptr
7305 *
7306 * Sets the font used for rendering all text displayed by the terminal,
7307 * overriding any fonts set using ctk_widget_modify_font(). The terminal
7308 * will immediately attempt to load the desired font, retrieve its
7309 * metrics, and attempt to resize itself to keep the same number of rows
7310 * and columns. The font scale is applied to the specified font.
7311 */
7312bool
7313Terminal::set_font_desc(PangoFontDescription const* font_desc)
7314{
7315 /* Create an owned font description. */
7316 PangoFontDescription *desc;
7317
7318 auto context = ctk_widget_get_style_context(m_widget);
7319 ctk_style_context_save(context);
7320 ctk_style_context_set_state (context, CTK_STATE_FLAG_NORMAL);
7321 ctk_style_context_get(context, CTK_STATE_FLAG_NORMAL, "font", &desc, nullptr);
7322 ctk_style_context_restore(context);
7323
7324 pango_font_description_set_family_static (desc, "monospace");
7325 if (font_desc != nullptr) {
7326 pango_font_description_merge (desc, font_desc, TRUE(!(0)));
7327 _BTE_DEBUG_IF(BTE_DEBUG_MISC)if (0) {
7328 if (desc) {
7329 char *tmp;
7330 tmp = pango_font_description_to_string(desc);
7331 g_printerr("Using pango font \"%s\".\n", tmp);
7332 g_free (tmp);
7333 }
7334 }
7335 } else {
7336 _bte_debug_print(BTE_DEBUG_MISC,do { } while(0)
7337 "Using default monospace font.\n")do { } while(0);
7338 }
7339
7340 bool const same_desc = m_unscaled_font_desc &&
7341 pango_font_description_equal(m_unscaled_font_desc.get(), desc);
7342
7343 /* Note that we proceed to recreating the font even if the description
7344 * are the same. This is because maybe screen
7345 * font options were changed, or new fonts installed. Those will be
7346 * detected at font creation time and respected.
7347 */
7348
7349 m_unscaled_font_desc.reset(desc); /* adopts */
7350 update_font();
7351
7352 return !same_desc;
7353}
7354
7355bool
7356Terminal::set_font_scale(gdouble scale)
7357{
7358 /* FIXME: compare old and new scale in pixel space */
7359 if (_bte_double_equal(scale, m_font_scale))
7360 return false;
7361
7362 m_font_scale = scale;
7363 update_font();
7364
7365 return true;
7366}
7367
7368bool
7369Terminal::set_cell_width_scale(double scale)
7370{
7371 /* FIXME: compare old and new scale in pixel space */
7372 if (_bte_double_equal(scale, m_cell_width_scale))
7373 return false;
7374
7375 m_cell_width_scale = scale;
7376 /* Set the drawing font. */
7377 m_fontdirty = true;
7378 if (widget_realized()) {
7379 ensure_font();
7380 }
7381
7382 return true;
7383}
7384
7385bool
7386Terminal::set_cell_height_scale(double scale)
7387{
7388 /* FIXME: compare old and new scale in pixel space */
7389 if (_bte_double_equal(scale, m_cell_height_scale))
7390 return false;
7391
7392 m_cell_height_scale = scale;
7393 /* Set the drawing font. */
7394 m_fontdirty = true;
7395 if (widget_realized()) {
7396 ensure_font();
7397 }
7398
7399 return true;
7400}
7401
7402/* Read and refresh our perception of the size of the PTY. */
7403void
7404Terminal::refresh_size()
7405{
7406 if (!pty())
7407 return;
7408
7409 int rows, columns;
7410 if (!pty()->get_size(&rows, &columns)) {
7411 /* Error reading PTY size, use defaults */
7412 rows = BTE_ROWS24;
7413 columns = BTE_COLUMNS80;
7414 }
7415
7416 if (m_row_count == rows &&
7417 m_column_count == columns)
7418 return;
7419
7420 m_row_count = rows;
7421 m_column_count = columns;
7422 m_tabstops.resize(columns);
7423}
7424
7425/* Resize the given screen (normal or alternate) of the terminal. */
7426void
7427Terminal::screen_set_size(BteScreen *screen_,
7428 long old_columns,
7429 long old_rows,
7430 bool do_rewrap)
7431{
7432 BteRing *ring = screen_->row_data;
7433 BteVisualPosition cursor_saved_absolute;
7434 BteVisualPosition below_viewport;
7435 BteVisualPosition below_current_paragraph;
7436 BteVisualPosition selection_start, selection_end;
7437 BteVisualPosition *markers[7];
7438 gboolean was_scrolled_to_top = ((long) ceil(screen_->scroll_delta) == _bte_ring_delta(ring));
7439 gboolean was_scrolled_to_bottom = ((long) screen_->scroll_delta == screen_->insert_delta);
7440 glong old_top_lines;
7441 double new_scroll_delta;
7442
7443 if (m_selection_block_mode && do_rewrap && old_columns != m_column_count)
7444 deselect_all();
7445
7446 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7447 "Resizing %s screen_\n"do { } while(0)
7448 "Old insert_delta=%ld scroll_delta=%f\n"do { } while(0)
7449 " cursor (absolute) row=%ld col=%ld\n"do { } while(0)
7450 " cursor_saved (relative to insert_delta) row=%ld col=%ld\n",do { } while(0)
7451 screen_ == &m_normal_screen ? "normal" : "alternate",do { } while(0)
7452 screen_->insert_delta, screen_->scroll_delta,do { } while(0)
7453 screen_->cursor.row, screen_->cursor.col,do { } while(0)
7454 screen_->saved.cursor.row, screen_->saved.cursor.col)do { } while(0);
7455
7456 cursor_saved_absolute.row = screen_->saved.cursor.row + screen_->insert_delta;
7457 cursor_saved_absolute.col = screen_->saved.cursor.col;
7458 below_viewport.row = screen_->scroll_delta + old_rows;
7459 below_viewport.col = 0;
7460 below_current_paragraph.row = screen_->cursor.row + 1;
7461 while (below_current_paragraph.row < _bte_ring_next(ring)
7462 && _bte_ring_index(ring, below_current_paragraph.row - 1)->attr.soft_wrapped) {
7463 below_current_paragraph.row++;
7464 }
7465 below_current_paragraph.col = 0;
7466 memset(&markers, 0, sizeof(markers));
7467 markers[0] = &cursor_saved_absolute;
7468 markers[1] = &below_viewport;
7469 markers[2] = &below_current_paragraph;
7470 markers[3] = &screen_->cursor;
7471 if (!m_selection_resolved.empty()) {
7472 selection_start.row = m_selection_resolved.start_row();
7473 selection_start.col = m_selection_resolved.start_column();
7474 selection_end.row = m_selection_resolved.end_row();
7475 selection_end.col = m_selection_resolved.end_column();
7476 markers[4] = &selection_start;
7477 markers[5] = &selection_end;
7478 }
7479
7480 old_top_lines = below_current_paragraph.row - screen_->insert_delta;
7481
7482 if (do_rewrap && old_columns != m_column_count)
7483 _bte_ring_rewrap(ring, m_column_count, markers);
7484
7485 if (_bte_ring_length(ring) > m_row_count) {
7486 /* The content won't fit without scrollbars. Before figuring out the position, we might need to
7487 drop some lines from the ring if the cursor is not at the bottom, as XTerm does. See bug 708213.
7488 This code is really tricky, see ../doc/rewrap.txt for details! */
7489 glong new_top_lines, drop1, drop2, drop3, drop;
7490 screen_->insert_delta = _bte_ring_next(ring) - m_row_count;
7491 new_top_lines = below_current_paragraph.row - screen_->insert_delta;
7492 drop1 = _bte_ring_length(ring) - m_row_count;
7493 drop2 = _bte_ring_next(ring) - below_current_paragraph.row;
7494 drop3 = old_top_lines - new_top_lines;
7495 drop = MIN(MIN(drop1, drop2), drop3)((((((drop1) < (drop2)) ? (drop1) : (drop2))) < (drop3)
) ? ((((drop1) < (drop2)) ? (drop1) : (drop2))) : (drop3))
;
7496 if (drop > 0) {
7497 int new_ring_next = screen_->insert_delta + m_row_count - drop;
7498 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7499 "Dropping %ld [== MIN(%ld, %ld, %ld)] rows at the bottom\n",do { } while(0)
7500 drop, drop1, drop2, drop3)do { } while(0);
7501 _bte_ring_shrink(ring, new_ring_next - _bte_ring_delta(ring));
7502 }
7503 }
7504
7505 if (!m_selection_resolved.empty()) {
7506 m_selection_resolved.set ({ selection_start.row, selection_start.col },
7507 { selection_end.row, selection_end.col });
7508 }
7509
7510 /* Figure out new insert and scroll deltas */
7511 if (_bte_ring_length(ring) <= m_row_count) {
7512 /* Everything fits without scrollbars. Align at top. */
7513 screen_->insert_delta = _bte_ring_delta(ring);
7514 new_scroll_delta = screen_->insert_delta;
7515 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7516 "Everything fits without scrollbars\n")do { } while(0);
7517 } else {
7518 /* Scrollbar required. Can't afford unused lines at bottom. */
7519 screen_->insert_delta = _bte_ring_next(ring) - m_row_count;
7520 if (was_scrolled_to_bottom) {
7521 /* Was scrolled to bottom, keep this way. */
7522 new_scroll_delta = screen_->insert_delta;
7523 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7524 "Scroll to bottom\n")do { } while(0);
7525 } else if (was_scrolled_to_top) {
7526 /* Was scrolled to top, keep this way. Not sure if this special case is worth it. */
7527 new_scroll_delta = _bte_ring_delta(ring);
7528 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7529 "Scroll to top\n")do { } while(0);
7530 } else {
7531 /* Try to scroll so that the bottom visible row stays.
7532 More precisely, the character below the bottom left corner stays in that
7533 (invisible) row.
7534 So if the bottom of the screen_ was at a hard line break then that hard
7535 line break will stay there.
7536 TODO: What would be the best behavior if the bottom of the screen_ is a
7537 soft line break, i.e. only a partial line is visible at the bottom? */
7538 new_scroll_delta = below_viewport.row - m_row_count;
7539 /* Keep the old fractional part. */
7540 new_scroll_delta += screen_->scroll_delta - floor(screen_->scroll_delta);
7541 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7542 "Scroll so bottom row stays\n")do { } while(0);
7543 }
7544 }
7545
7546 /* Don't clamp, they'll be clamped when restored. Until then remember off-screen_ values
7547 since they might become on-screen_ again on subsequent resizes. */
7548 screen_->saved.cursor.row = cursor_saved_absolute.row - screen_->insert_delta;
7549 screen_->saved.cursor.col = cursor_saved_absolute.col;
7550
7551 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7552 "New insert_delta=%ld scroll_delta=%f\n"do { } while(0)
7553 " cursor (absolute) row=%ld col=%ld\n"do { } while(0)
7554 " cursor_saved (relative to insert_delta) row=%ld col=%ld\n\n",do { } while(0)
7555 screen_->insert_delta, new_scroll_delta,do { } while(0)
7556 screen_->cursor.row, screen_->cursor.col,do { } while(0)
7557 screen_->saved.cursor.row, screen_->saved.cursor.col)do { } while(0);
7558
7559 if (screen_ == m_screen)
7560 queue_adjustment_value_changed(new_scroll_delta);
7561 else
7562 screen_->scroll_delta = new_scroll_delta;
7563}
7564
7565void
7566Terminal::set_size(long columns,
7567 long rows)
7568{
7569 glong old_columns, old_rows;
7570
7571 _bte_debug_print(BTE_DEBUG_RESIZE,do { } while(0)
7572 "Setting PTY size to %ldx%ld.\n",do { } while(0)
7573 columns, rows)do { } while(0);
7574
7575 old_rows = m_row_count;
7576 old_columns = m_column_count;
7577
7578 if (pty()) {
7579 /* Try to set the terminal size, and read it back,
7580 * in case something went awry.
7581 */
7582 if (!pty()->set_size(rows,
7583 columns,
7584 m_cell_height,
7585 m_cell_width)) {
7586 // nothing we can do here
7587 }
7588 refresh_size();
7589 } else {
7590 m_row_count = rows;
7591 m_column_count = columns;
7592 m_tabstops.resize(columns);
7593 }
7594 if (old_rows != m_row_count || old_columns != m_column_count) {
7595 m_scrolling_restricted = FALSE(0);
7596
7597 _bte_ring_set_visible_rows(m_normal_screen.row_data, m_row_count);
7598 _bte_ring_set_visible_rows(m_alternate_screen.row_data, m_row_count);
7599
7600 /* Resize the normal screen and (if rewrapping is enabled) rewrap it even if the alternate screen is visible: bug 415277 */
7601 screen_set_size(&m_normal_screen, old_columns, old_rows, m_rewrap_on_resize);
7602 /* Resize the alternate screen if it's the current one, but never rewrap it: bug 336238 comment 60 */
7603 if (m_screen == &m_alternate_screen)
7604 screen_set_size(&m_alternate_screen, old_columns, old_rows, false);
7605
7606 /* Ensure scrollback buffers cover the screen. */
7607 set_scrollback_lines(m_scrollback_lines);
7608
7609 /* Ensure the cursor is valid */
7610 m_screen->cursor.row = CLAMP (m_screen->cursor.row,(((m_screen->cursor.row) > ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1)))) ? ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1))) : (((m_screen->cursor.row)
< (_bte_ring_delta (m_screen->row_data))) ? (_bte_ring_delta
(m_screen->row_data)) : (m_screen->cursor.row)))
7611 _bte_ring_delta (m_screen->row_data),(((m_screen->cursor.row) > ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1)))) ? ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1))) : (((m_screen->cursor.row)
< (_bte_ring_delta (m_screen->row_data))) ? (_bte_ring_delta
(m_screen->row_data)) : (m_screen->cursor.row)))
7612 MAX (_bte_ring_delta (m_screen->row_data),(((m_screen->cursor.row) > ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1)))) ? ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1))) : (((m_screen->cursor.row)
< (_bte_ring_delta (m_screen->row_data))) ? (_bte_ring_delta
(m_screen->row_data)) : (m_screen->cursor.row)))
7613 _bte_ring_next (m_screen->row_data) - 1))(((m_screen->cursor.row) > ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1)))) ? ((((_bte_ring_delta (m_screen
->row_data)) > (_bte_ring_next (m_screen->row_data) -
1)) ? (_bte_ring_delta (m_screen->row_data)) : (_bte_ring_next
(m_screen->row_data) - 1))) : (((m_screen->cursor.row)
< (_bte_ring_delta (m_screen->row_data))) ? (_bte_ring_delta
(m_screen->row_data)) : (m_screen->cursor.row)))
;
7614
7615 adjust_adjustments_full();
7616 ctk_widget_queue_resize_no_redraw(m_widget);
7617 /* Our visible text changed. */
7618 emit_text_modified();
7619 }
7620}
7621
7622/* Redraw the widget. */
7623static void
7624bte_terminal_vadjustment_value_changed_cb(bte::terminal::Terminal* that) noexcept
7625try
7626{
7627 that->vadjustment_value_changed();
7628}
7629catch (...)
7630{
7631 bte::log_exception();
7632}
7633
7634void
7635Terminal::vadjustment_value_changed()
7636{
7637 /* Read the new adjustment value and save the difference. */
7638 auto const adj = ctk_adjustment_get_value(m_vadjustment.get());
7639 double dy = adj - m_screen->scroll_delta;
7640 m_screen->scroll_delta = adj;
7641
7642 /* Sanity checks. */
7643 if (G_UNLIKELY(!widget_realized())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!widget_realized()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
7644 return;
7645
7646 /* FIXME: do this check in pixel space */
7647 if (!_bte_double_equal(dy, 0)) {
7648 _bte_debug_print(BTE_DEBUG_ADJ,do { } while(0)
7649 "Scrolling by %f\n", dy)do { } while(0);
7650 invalidate_all();
7651 match_contents_clear();
7652 emit_text_scrolled(dy);
7653 queue_contents_changed();
7654 } else {
7655 _bte_debug_print(BTE_DEBUG_ADJ, "Not scrolling\n")do { } while(0);
7656 }
7657}
7658
7659void
7660Terminal::widget_set_vadjustment(bte::glib::RefPtr<CtkAdjustment>&& adjustment)
7661{
7662 if (adjustment && adjustment == m_vadjustment)
7663 return;
7664 if (!adjustment && m_vadjustment)
7665 return;
7666
7667 if (m_vadjustment) {
7668 /* Disconnect our signal handlers from this object. */
7669 g_signal_handlers_disconnect_by_func(m_vadjustment.get(),g_signal_handlers_disconnect_matched ((m_vadjustment.get()), (
GSignalMatchType) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA)
, 0, 0, __null, ((void*)bte_terminal_vadjustment_value_changed_cb
), (this))
7670 (void*)bte_terminal_vadjustment_value_changed_cb,g_signal_handlers_disconnect_matched ((m_vadjustment.get()), (
GSignalMatchType) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA)
, 0, 0, __null, ((void*)bte_terminal_vadjustment_value_changed_cb
), (this))
7671 this)g_signal_handlers_disconnect_matched ((m_vadjustment.get()), (
GSignalMatchType) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA)
, 0, 0, __null, ((void*)bte_terminal_vadjustment_value_changed_cb
), (this))
;
7672 }
7673
7674 if (adjustment)
7675 m_vadjustment = std::move(adjustment);
7676 else
7677 m_vadjustment = bte::glib::make_ref_sink(CTK_ADJUSTMENT(ctk_adjustment_new(0, 0, 0, 0, 0, 0))((((CtkAdjustment*) (void *) g_type_check_instance_cast ((GTypeInstance
*) ((ctk_adjustment_new(0, 0, 0, 0, 0, 0))), ((ctk_adjustment_get_type
()))))))
);
7678
7679 /* We care about the offset, not the top or bottom. */
7680 g_signal_connect_swapped(m_vadjustment.get(),g_signal_connect_data ((m_vadjustment.get()), ("value-changed"
), (((GCallback) (bte_terminal_vadjustment_value_changed_cb))
), (this), __null, G_CONNECT_SWAPPED)
7681 "value-changed",g_signal_connect_data ((m_vadjustment.get()), ("value-changed"
), (((GCallback) (bte_terminal_vadjustment_value_changed_cb))
), (this), __null, G_CONNECT_SWAPPED)
7682 G_CALLBACK(bte_terminal_vadjustment_value_changed_cb),g_signal_connect_data ((m_vadjustment.get()), ("value-changed"
), (((GCallback) (bte_terminal_vadjustment_value_changed_cb))
), (this), __null, G_CONNECT_SWAPPED)
7683 this)g_signal_connect_data ((m_vadjustment.get()), ("value-changed"
), (((GCallback) (bte_terminal_vadjustment_value_changed_cb))
), (this), __null, G_CONNECT_SWAPPED)
;
7684}
7685
7686Terminal::Terminal(bte::platform::Widget* w,
7687 BteTerminal *t) :
7688 m_real_widget(w),
7689 m_terminal(t),
7690 m_widget(&t->widget),
7691 m_normal_screen(BTE_SCROLLBACK_INIT512, true),
7692 m_alternate_screen(BTE_ROWS24, false),
7693 m_screen(&m_normal_screen)
7694{
7695 widget_set_vadjustment({});
7696
7697 /* Inits allocation to 1x1 @ -1,-1 */
7698 cairo_rectangle_int_t allocation;
7699 ctk_widget_get_allocation(m_widget, &allocation);
7700 set_allocated_rect(allocation);
7701
7702 int i;
7703 CdkDisplay *display;
7704
7705 /* NOTE! We allocated zeroed memory, just fill in non-zero stuff. */
7706
7707 // FIXMEegmont make this store row indices only, maybe convert to a bitmap
7708 m_update_rects = g_array_sized_new(FALSE(0) /* zero terminated */,
7709 FALSE(0) /* clear */,
7710 sizeof(cairo_rectangle_int_t),
7711 32 /* preallocated size */);
7712
7713 /* Set up dummy metrics, value != 0 to avoid division by 0 */
7714 // FIXMEchpe this is wrong. These values must not be used before
7715 // the view has been set up, so if they are, that's a bug
7716 m_cell_width = 1;
7717 m_cell_height = 1;
7718 m_char_ascent = 1;
7719 m_char_descent = 1;
7720 m_char_padding = {0, 0, 0, 0};
7721 m_line_thickness = 1;
7722 m_underline_position = 1;
7723 m_double_underline_position = 1;
7724 m_undercurl_position = 1.;
7725 m_strikethrough_position = 1;
7726 m_overline_position = 1;
7727 m_regex_underline_position = 1;
7728
7729 reset_default_attributes(true);
7730
7731 /* Set up the desired palette. */
7732 set_colors_default();
7733 for (i = 0; i < BTE_PALETTE_SIZE263; i++)
7734 m_palette[i].sources[BTE_COLOR_SOURCE_ESCAPE0].is_set = FALSE(0);
7735
7736 /* Set up I/O encodings. */
7737 m_outgoing = _bte_byte_array_newg_byte_array_new();
7738
7739 /* Setting the terminal type and size requires the PTY master to
7740 * be set up properly first. */
7741 set_size(BTE_COLUMNS80, BTE_ROWS24);
7742
7743 /* Default is 0, forces update in bte_terminal_set_scrollback_lines */
7744 set_scrollback_lines(BTE_SCROLLBACK_INIT512);
7745
7746 /* Selection info. */
7747 display = ctk_widget_get_display(m_widget);
7748 m_clipboard[BTE_SELECTION_PRIMARY] = ctk_clipboard_get_for_display(display, CDK_SELECTION_PRIMARY((CdkAtom)((gpointer) (gulong) (1))));
7749 m_clipboard[BTE_SELECTION_CLIPBOARD] = ctk_clipboard_get_for_display(display, CDK_SELECTION_CLIPBOARD((CdkAtom)((gpointer) (gulong) (69))));
7750 m_selection_owned[BTE_SELECTION_PRIMARY] = false;
7751 m_selection_owned[BTE_SELECTION_CLIPBOARD] = false;
7752
7753 /* Initialize the saved cursor. */
7754 save_cursor(&m_normal_screen);
7755 save_cursor(&m_alternate_screen);
7756
7757 /* Matching data. */
7758 m_match_span.clear(); // FIXMEchpe unnecessary
7759 match_hilite_clear(); // FIXMEchpe unnecessary
7760
7761 /* Word chars */
7762 set_word_char_exceptions(WORD_CHAR_EXCEPTIONS_DEFAULT"-#%&+,./=?@\\_~\302\267"sv);
7763
7764 update_view_extents();
7765
7766#ifdef BTE_DEBUG
7767 if (g_test_flags != 0) {
7768 feed("\e[1m\e[31mWARNING:\e[39m Test mode enabled. This is insecure!\e[0m\n\e[G"sv, false);
7769 }
7770#endif
7771
7772#ifndef WITH_GNUTLS
7773 std::string str{"\e[1m\e[31m"};
7774 str.append(_("WARNING")((char *) g_dgettext ("bte-2.91", "WARNING")));
7775 str.append(":\e[39m ");
7776 str.append(_("GnuTLS not enabled; data will be written to disk unencrypted!")((char *) g_dgettext ("bte-2.91", "GnuTLS not enabled; data will be written to disk unencrypted!"
))
);
7777 str.append("\e[0m\n\e[G");
7778
7779 feed(str, false);
7780#endif
7781}
7782
7783void
7784Terminal::widget_get_preferred_width(int *minimum_width,
7785 int *natural_width)
7786{
7787 _bte_debug_print(BTE_DEBUG_LIFECYCLE, "bte_terminal_get_preferred_width()\n")do { } while(0);
7788
7789 ensure_font();
7790
7791 refresh_size();
7792
7793 *minimum_width = m_cell_width * 2; /* have room for a CJK or emoji */
7794 *natural_width = m_cell_width * m_column_count;
7795
7796 *minimum_width += m_padding.left +
7797 m_padding.right;
7798 *natural_width += m_padding.left +
7799 m_padding.right;
7800
7801 _bte_debug_print(BTE_DEBUG_WIDGET_SIZE,do { } while(0)
7802 "[Terminal %p] minimum_width=%d, natural_width=%d for %ldx%ld cells (padding %d,%d;%d,%d).\n",do { } while(0)
7803 m_terminal,do { } while(0)
7804 *minimum_width, *natural_width,do { } while(0)
7805 m_column_count,do { } while(0)
7806 m_row_count,do { } while(0)
7807 m_padding.left, m_padding.right, m_padding.top, m_padding.bottom)do { } while(0);
7808}
7809
7810void
7811Terminal::widget_get_preferred_height(int *minimum_height,
7812 int *natural_height)
7813{
7814 _bte_debug_print(BTE_DEBUG_LIFECYCLE, "bte_terminal_get_preferred_height()\n")do { } while(0);
7815
7816 ensure_font();
7817
7818 refresh_size();
7819
7820 *minimum_height = m_cell_height * 1;
7821 *natural_height = m_cell_height * m_row_count;
7822
7823 *minimum_height += m_padding.top +
7824 m_padding.bottom;
7825 *natural_height += m_padding.top +
7826 m_padding.bottom;
7827
7828 _bte_debug_print(BTE_DEBUG_WIDGET_SIZE,do { } while(0)
7829 "[Terminal %p] minimum_height=%d, natural_height=%d for %ldx%ld cells (padding %d,%d;%d,%d).\n",do { } while(0)
7830 m_terminal,do { } while(0)
7831 *minimum_height, *natural_height,do { } while(0)
7832 m_column_count,do { } while(0)
7833 m_row_count,do { } while(0)
7834 m_padding.left, m_padding.right, m_padding.top, m_padding.bottom)do { } while(0);
7835}
7836
7837void
7838Terminal::widget_size_allocate(CtkAllocation *allocation)
7839{
7840 glong width, height;
7841 gboolean repaint, update_scrollback;
7842
7843 _bte_debug_print(BTE_DEBUG_LIFECYCLE,do { } while(0)
7844 "bte_terminal_size_allocate()\n")do { } while(0);
7845
7846 width = (allocation->width - (m_padding.left + m_padding.right)) /
7847 m_cell_width;
7848 height = (allocation->height - (m_padding.top + m_padding.bottom)) /
7849 m_cell_height;
7850 width = MAX(width, 1)(((width) > (1)) ? (width) : (1));
7851 height = MAX(height, 1)(((height) > (1)) ? (height) : (1));
7852
7853 _bte_debug_print(BTE_DEBUG_WIDGET_SIZE,do { } while(0)
7854 "[Terminal %p] Sizing window to %dx%d (%ldx%ld, padding %d,%d;%d,%d).\n",do { } while(0)
7855 m_terminal,do { } while(0)
7856 allocation->width, allocation->height,do { } while(0)
7857 width, height,do { } while(0)
7858 m_padding.left, m_padding.right, m_padding.top, m_padding.bottom)do { } while(0);
7859
7860 auto current_allocation = get_allocated_rect();
7861
7862 repaint = current_allocation.width != allocation->width
7863 || current_allocation.height != allocation->height;
7864 update_scrollback = current_allocation.height != allocation->height;
7865
7866 /* Set our allocation to match the structure. */
7867 ctk_widget_set_allocation(m_widget, allocation);
7868 set_allocated_rect(*allocation);
7869
7870 if (width != m_column_count
7871 || height != m_row_count
7872 || update_scrollback)
7873 {
7874 /* Set the size of the pseudo-terminal. */
7875 set_size(width, height);
7876
7877 /* Notify viewers that the contents have changed. */
7878 queue_contents_changed();
7879 }
7880
7881 if (widget_realized()) {
7882 /* Force a repaint if we were resized. */
7883 if (repaint) {
7884 reset_update_rects();
7885 invalidate_all();
7886 }
7887 }
7888}
7889
7890void
7891Terminal::widget_unmap()
7892{
7893 m_ringview.pause();
7894}
7895
7896void
7897Terminal::widget_unrealize()
7898{
7899 /* Deallocate the cursors. */
7900 m_mouse_cursor_over_widget = FALSE(0);
7901
7902 match_hilite_clear();
7903
7904 m_im_preedit_active = FALSE(0);
7905
7906 /* Drop font cache */
7907 m_draw.clear_font_cache();
7908 m_fontdirty = true;
7909
7910 /* Remove the cursor blink timeout function. */
7911 remove_cursor_timeout();
7912
7913 /* Remove the contents blink timeout function. */
7914 m_text_blink_timer.abort();
7915
7916 /* Cancel any pending redraws. */
7917 remove_update_timeout(this);
7918
7919 /* Cancel any pending signals */
7920 m_contents_changed_pending = FALSE(0);
7921 m_cursor_moved_pending = FALSE(0);
7922 m_text_modified_flag = FALSE(0);
7923 m_text_inserted_flag = FALSE(0);
7924 m_text_deleted_flag = FALSE(0);
7925
7926 /* Clear modifiers. */
7927 m_modifiers = 0;
7928}
7929
7930void
7931Terminal::set_blink_settings(bool blink,
7932 int blink_time,
7933 int blink_timeout) noexcept
7934{
7935 m_cursor_blink_cycle = blink_time / 2;
7936 m_cursor_blink_timeout = blink_timeout;
7937
7938 update_cursor_blinks();
7939
7940 /* Misuse ctk-cursor-blink-time for text blinking as well. This might change in the future. */
7941 m_text_blink_cycle = m_cursor_blink_cycle;
7942 if (m_text_blink_timer) {
7943 /* The current phase might have changed, and an already installed
7944 * timer to blink might fire too late. So remove the timer and
7945 * repaint the contents (which will install a correct new timer). */
7946 m_text_blink_timer.abort();
7947 invalidate_all();
7948 }
7949}
7950
7951Terminal::~Terminal()
7952{
7953 /* Make sure not to change selection while in destruction. See issue bte#89. */
7954 m_changing_selection = true;
7955
7956 int sel;
7957
7958 terminate_child();
7959 unset_pty(false /* don't notify widget */);
7960 remove_update_timeout(this);
7961
7962 /* Stop processing input. */
7963 stop_processing(this);
7964
7965 /* Free matching data. */
7966 if (m_match_attributes != NULL__null) {
7967 g_array_free(m_match_attributes, TRUE(!(0)));
7968 }
7969 g_free(m_match_contents);
7970
7971 if (m_search_attrs)
7972 g_array_free (m_search_attrs, TRUE(!(0)));
7973
7974 /* Disconnect from autoscroll requests. */
7975 stop_autoscroll();
7976
7977 /* Cancel pending adjustment change notifications. */
7978 m_adjustment_changed_pending = FALSE(0);
7979
7980 /* Free any selected text, but if we currently own the selection,
7981 * throw the text onto the clipboard without an owner so that it
7982 * doesn't just disappear. */
7983 for (sel = BTE_SELECTION_PRIMARY; sel < LAST_BTE_SELECTION; sel++) {
7984 if (m_selection[sel] != nullptr) {
7985 if (m_selection_owned[sel]) {
7986 // FIXMEchpe we should check m_selection_format[sel]
7987 // and also put text/html on if it's BTE_FORMAT_HTML
7988 ctk_clipboard_set_text(m_clipboard[sel],
7989 m_selection[sel]->str,
7990 m_selection[sel]->len);
7991 }
7992 g_string_free(m_selection[sel], TRUE(!(0)));
7993 m_selection[sel] = nullptr;
7994 }
7995 }
7996
7997 /* Stop listening for child-exited signals. */
7998 if (m_reaper) {
7999 g_signal_handlers_disconnect_by_func(m_reaper,g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
8000 (gpointer)reaper_child_exited_cb,g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
8001 this)g_signal_handlers_disconnect_matched ((m_reaper), (GSignalMatchType
) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA), 0, 0, __null, (
(gpointer)reaper_child_exited_cb), (this))
;
8002 g_object_unref(m_reaper);
8003 }
8004
8005 /* Discard any pending data. */
8006 _bte_byte_array_free(m_outgoing)g_byte_array_free (m_outgoing, (!(0)));
8007 m_outgoing = nullptr;
8008
8009 /* Free public-facing data. */
8010 if (m_vadjustment) {
8011 /* Disconnect our signal handlers from this object. */
8012 g_signal_handlers_disconnect_by_func(m_vadjustment.get(),g_signal_handlers_disconnect_matched ((m_vadjustment.get()), (
GSignalMatchType) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA)
, 0, 0, __null, ((void*)bte_terminal_vadjustment_value_changed_cb
), (this))
8013 (void*)bte_terminal_vadjustment_value_changed_cb,g_signal_handlers_disconnect_matched ((m_vadjustment.get()), (
GSignalMatchType) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA)
, 0, 0, __null, ((void*)bte_terminal_vadjustment_value_changed_cb
), (this))
8014 this)g_signal_handlers_disconnect_matched ((m_vadjustment.get()), (
GSignalMatchType) (G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA)
, 0, 0, __null, ((void*)bte_terminal_vadjustment_value_changed_cb
), (this))
;
8015 }
8016
8017 /* Update rects */
8018 g_array_free(m_update_rects, TRUE(!(0)) /* free segment */);
8019}
8020
8021void
8022Terminal::widget_realize()
8023{
8024 m_mouse_cursor_over_widget = FALSE(0); /* We'll receive an enter_notify_event if the window appears under the cursor. */
8025
8026 m_im_preedit_active = FALSE(0);
8027
8028 /* Clear modifiers. */
8029 m_modifiers = 0;
8030
8031 // Create the font cache
8032 ensure_font();
8033}
8034
8035static inline void
8036swap (guint *a, guint *b)
8037{
8038 guint tmp;
8039 tmp = *a, *a = *b, *b = tmp;
8040}
8041
8042// FIXMEchpe probably @attr should be passed by ref
8043void
8044Terminal::determine_colors(BteCellAttr const* attr,
8045 bool is_selected,
8046 bool is_cursor,
8047 guint *pfore,
8048 guint *pback,
8049 guint *pdeco) const
8050{
8051 guint fore, back, deco;
8052
8053 g_assert(attr)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (attr) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 1)) ; else g_assertion_message_expr ("BTE", "../src/bte.cc"
, 8053, ((const char*) (__PRETTY_FUNCTION__)), "attr"); } while
(0)
;
8054
8055 /* Start with cell colors */
8056 bte_color_triple_get(attr->colors(), &fore, &back, &deco);
8057
8058 /* Reverse-mode switches default fore and back colors */
8059 if (G_UNLIKELY (m_modes_private.DEC_REVERSE_IMAGE())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_modes_private.DEC_REVERSE_IMAGE()) _g_boolean_var_ = 1; else
_g_boolean_var_ = 0; _g_boolean_var_; }), 0))
) {
8060 if (fore == BTE_DEFAULT_FG256)
8061 fore = BTE_DEFAULT_BG257;
8062 if (back == BTE_DEFAULT_BG257)
8063 back = BTE_DEFAULT_FG256;
8064 }
8065
8066 /* Handle bold by using set bold color or brightening */
8067 if (attr->bold()) {
8068 if (fore == BTE_DEFAULT_FG256 && get_color(BTE_BOLD_FG258) != NULL__null) {
8069 fore = BTE_BOLD_FG258;
8070 } else if (m_bold_is_bright &&
8071 fore >= BTE_LEGACY_COLORS_OFFSET(1U << 9) &&
8072 fore < BTE_LEGACY_COLORS_OFFSET(1U << 9) + BTE_LEGACY_COLOR_SET_SIZE8) {
8073 fore += BTE_COLOR_BRIGHT_OFFSET8;
8074 }
8075 }
8076
8077 /* Handle dim colors. Only apply to palette colors, dimming direct RGB wouldn't make sense.
8078 * Apply to the foreground color only, but do this before handling reverse/highlight so that
8079 * those can be used to dim the background instead. */
8080 if (attr->dim() && !(fore & BTE_RGB_COLOR_MASK(8, 8, 8)(1U << ((8) + (8) + (8))))) {
8081 fore |= BTE_DIM_COLOR(1U << 10);
8082 }
8083
8084 /* Reverse cell? */
8085 if (attr->reverse()) {
8086 swap (&fore, &back);
8087 }
8088
8089 /* Selection: use hightlight back/fore, or inverse */
8090 if (is_selected) {
8091 /* XXX what if hightlight back is same color as current back? */
8092 bool do_swap = true;
8093 if (get_color(BTE_HIGHLIGHT_BG260) != NULL__null) {
8094 back = BTE_HIGHLIGHT_BG260;
8095 do_swap = false;
8096 }
8097 if (get_color(BTE_HIGHLIGHT_FG259) != NULL__null) {
8098 fore = BTE_HIGHLIGHT_FG259;
8099 do_swap = false;
8100 }
8101 if (do_swap)
8102 swap (&fore, &back);
8103 }
8104
8105 /* Cursor: use cursor back, or inverse */
8106 if (is_cursor) {
8107 /* XXX what if cursor back is same color as current back? */
8108 bool do_swap = true;
8109 if (get_color(BTE_CURSOR_BG261) != NULL__null) {
8110 back = BTE_CURSOR_BG261;
8111 do_swap = false;
8112 }
8113 if (get_color(BTE_CURSOR_FG262) != NULL__null) {
8114 fore = BTE_CURSOR_FG262;
8115 do_swap = false;
8116 }
8117 if (do_swap)
8118 swap (&fore, &back);
8119 }
8120
8121 /* Invisible? */
8122 /* FIXME: This is dead code, this is not where we actually handle invisibile.
8123 * Instead, draw_cells() is not called from draw_rows().
8124 * That is required for the foreground to be transparent if so is the background. */
8125 if (attr->invisible()) {
8126 fore = back;
8127 deco = BTE_DEFAULT_FG256;
8128 }
8129
8130 *pfore = fore;
8131 *pback = back;
8132 *pdeco = deco;
8133}
8134
8135void
8136Terminal::determine_colors(BteCell const* cell,
8137 bool highlight,
8138 guint *fore,
8139 guint *back,
8140 guint *deco) const
8141{
8142 determine_colors(cell ? &cell->attr : &basic_cell.attr,
8143 highlight, false /* not cursor */,
8144 fore, back, deco);
8145}
8146
8147void
8148Terminal::determine_cursor_colors(BteCell const* cell,
8149 bool highlight,
8150 guint *fore,
8151 guint *back,
8152 guint *deco) const
8153{
8154 determine_colors(cell ? &cell->attr : &basic_cell.attr,
8155 highlight, true /* cursor */,
8156 fore, back, deco);
8157}
8158
8159// FIXMEchpe this constantly removes and reschedules the timer. improve this!
8160bool
8161Terminal::text_blink_timer_callback()
8162{
8163 invalidate_all();
8164 return false; /* don't run again */
8165}
8166
8167/* Draw a string of characters with similar attributes. */
8168void
8169Terminal::draw_cells(bte::view::DrawingContext::TextRequest* items,
8170 gssize n,
8171 uint32_t fore,
8172 uint32_t back,
8173 uint32_t deco,
8174 bool clear,
8175 bool draw_default_bg,
8176 uint32_t attr,
8177 bool hyperlink,
8178 bool hilite,
8179 int column_width,
8180 int row_height)
8181{
8182 int i, xl, xr, y;
8183 gint columns = 0;
8184 bte::color::rgb fg, bg, dc;
8185
8186 g_assert(n > 0)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (n > 0) _g_boolean_var_ = 1; else _g_boolean_var_ = 0
; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr ("BTE"
, "../src/bte.cc", 8186, ((const char*) (__PRETTY_FUNCTION__)
), "n > 0"); } while (0)
;
8187#if 0
8188 _BTE_DEBUG_IF(BTE_DEBUG_CELLS)if (0) {
8189 GString *str = g_string_new (NULL__null);
8190 gchar *tmp;
8191 for (i = 0; i < n; i++) {
8192 g_string_append_unichar (str, items[i].c);
8193 }
8194 tmp = g_string_free (str, FALSE(0));
8195 g_printerr ("draw_cells('%s', fore=%d, back=%d, deco=%d, bold=%d,"
8196 " ul=%d, strike=%d, ol=%d, blink=%d,"
8197 " hyperlink=%d, hilite=%d, boxed=%d)\n",
8198 tmp, fore, back, deco, bold,
8199 underline, strikethrough, overline, blink,
8200 hyperlink, hilite, boxed);
8201 g_free (tmp);
8202 }
8203#endif
8204
8205 rgb_from_index<8, 8, 8>(fore, fg);
8206 rgb_from_index<8, 8, 8>(back, bg);
8207 // FIXMEchpe defer resolving deco color until we actually need to draw an underline?
8208 if (deco == BTE_DEFAULT_FG256)
8209 dc = fg;
8210 else
8211 rgb_from_index<4, 5, 4>(deco, dc);
8212
8213 if (clear && (draw_default_bg || back != BTE_DEFAULT_BG257)) {
8214 /* Paint the background. */
8215 i = 0;
8216 while (i < n) {
8217 xl = items[i].x;
8218 xr = items[i].x + items[i].columns * column_width;
8219 y = items[i].y;
8220 /* Items are not necessarily contiguous in LTR order.
8221 * Combine as long as they form a single visual run. */
8222 for (i++; i < n && items[i].y == y; i++) {
8223 if (G_LIKELY (items[i].x == xr)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
items[i].x == xr) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 1))
) {
8224 xr += items[i].columns * column_width; /* extend to the right */
8225 } else if (items[i].x + items[i].columns * column_width == xl) {
8226 xl = items[i].x; /* extend to the left */
8227 } else {
8228 break; /* break the run */
8229 }
8230 }
8231 m_draw.fill_rectangle(
8232 xl,
8233 y,
8234 xr - xl, row_height,
8235 &bg, BTE_DRAW_OPAQUE(1.0));
8236 }
8237 }
8238
8239 if (attr & BTE_ATTR_BLINK(1U << (((((((((0) + (4)) + (1)) + (1)) + (1)) + (2)) +
(1)) + (1)) + (1)))
) {
8240 /* Notify the caller that cells with the "blink" attribute were encountered (regardless of
8241 * whether they're actually painted or skipped now), so that the caller can set up a timer
8242 * to make them blink if it wishes to. */
8243 m_text_to_blink = true;
8244
8245 /* This is for the "off" state of blinking text. Invisible text could also be handled here,
8246 * but it's not, it's handled outside by not even calling this method.
8247 * Setting fg = bg and painting the text would not work for two reasons: it'd be opaque
8248 * even if the background is translucent, and this method can be called with a continuous
8249 * run of identical fg, yet different bg colored cells. So we simply bail out. */
8250 if (!m_text_blink_state)
8251 return;
8252 }
8253
8254 /* Draw whatever SFX are required. Do this before drawing the letters,
8255 * so that if the descent of a letter crosses an underline of a different color,
8256 * it's the letter's color that wins. Other kinds of decorations always have the
8257 * same color as the text, so the order is irrelevant there. */
8258 if ((attr & (BTE_ATTR_UNDERLINE_MASK((((1U << ((2))) - 1U) << ((((((0) + (4)) + (1)) +
(1)) + (1)))))
|
8259 BTE_ATTR_STRIKETHROUGH_MASK((((1U << ((1))) - 1U) << (((((((0) + (4)) + (1))
+ (1)) + (1)) + (2)))))
|
8260 BTE_ATTR_OVERLINE_MASK((((1U << ((1))) - 1U) << ((((((((0) + (4)) + (1)
) + (1)) + (1)) + (2)) + (1)))))
|
8261 BTE_ATTR_BOXED_MASK((((1U << ((1))) - 1U) << ((31)))))) |
8262 hyperlink | hilite) {
8263 i = 0;
8264 while (i < n) {
8265 xl = items[i].x;
8266 xr = items[i].x + items[i].columns * column_width;
8267 columns = items[i].columns;
8268 y = items[i].y;
8269 /* Items are not necessarily contiguous in LTR order.
8270 * Combine as long as they form a single visual run. */
8271 for (i++; i < n && items[i].y == y; i++) {
8272 if (G_LIKELY (items[i].x == xr)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
items[i].x == xr) _g_boolean_var_ = 1; else _g_boolean_var_ =
0; _g_boolean_var_; }), 1))
) {
8273 xr += items[i].columns * column_width; /* extend to the right */
8274 columns += items[i].columns;
8275 } else if (items[i].x + items[i].columns * column_width == xl) {
8276 xl = items[i].x; /* extend to the left */
8277 columns += items[i].columns;
8278 } else {
8279 break; /* break the run */
8280 }
8281 }
8282 switch (bte_attr_get_value(attr, BTE_ATTR_UNDERLINE_VALUE_MASK(((1U << ((2))) - 1U)), BTE_ATTR_UNDERLINE_SHIFT(((((0) + (4)) + (1)) + (1)) + (1)))) {
8283 case 1:
8284 m_draw.draw_line(
8285 xl,
8286 y + m_underline_position,
8287 xr - 1,
8288 y + m_underline_position + m_underline_thickness - 1,
8289 BTE_LINE_WIDTH1,
8290 &dc, BTE_DRAW_OPAQUE(1.0));
8291 break;
8292 case 2:
8293 m_draw.draw_line(
8294 xl,
8295 y + m_double_underline_position,
8296 xr - 1,
8297 y + m_double_underline_position + m_double_underline_thickness - 1,
8298 BTE_LINE_WIDTH1,
8299 &dc, BTE_DRAW_OPAQUE(1.0));
8300 m_draw.draw_line(
8301 xl,
8302 y + m_double_underline_position + 2 * m_double_underline_thickness,
8303 xr - 1,
8304 y + m_double_underline_position + 3 * m_double_underline_thickness - 1,
8305 BTE_LINE_WIDTH1,
8306 &dc, BTE_DRAW_OPAQUE(1.0));
8307 break;
8308 case 3:
8309 m_draw.draw_undercurl(
8310 xl,
8311 y + m_undercurl_position,
8312 m_undercurl_thickness,
8313 columns,
8314 &dc, BTE_DRAW_OPAQUE(1.0));
8315 break;
8316 }
8317 if (attr & BTE_ATTR_STRIKETHROUGH(1U << ((((((0) + (4)) + (1)) + (1)) + (1)) + (2)))) {
8318 m_draw.draw_line(
8319 xl,
8320 y + m_strikethrough_position,
8321 xr - 1,
8322 y + m_strikethrough_position + m_strikethrough_thickness - 1,
8323 BTE_LINE_WIDTH1,
8324 &fg, BTE_DRAW_OPAQUE(1.0));
8325 }
8326 if (attr & BTE_ATTR_OVERLINE(1U << (((((((0) + (4)) + (1)) + (1)) + (1)) + (2)) + (
1)))
) {
8327 m_draw.draw_line(
8328 xl,
8329 y + m_overline_position,
8330 xr - 1,
8331 y + m_overline_position + m_overline_thickness - 1,
8332 BTE_LINE_WIDTH1,
8333 &fg, BTE_DRAW_OPAQUE(1.0));
8334 }
8335 if (hilite) {
8336 m_draw.draw_line(
8337 xl,
8338 y + m_regex_underline_position,
8339 xr - 1,
8340 y + m_regex_underline_position + m_regex_underline_thickness - 1,
8341 BTE_LINE_WIDTH1,
8342 &fg, BTE_DRAW_OPAQUE(1.0));
8343 } else if (hyperlink) {
8344 for (double j = 1.0 / 6.0; j < columns; j += 0.5) {
8345 m_draw.fill_rectangle(
8346 xl + j * column_width,
8347 y + m_regex_underline_position,
8348 MAX(column_width / 6.0, 1.0)(((column_width / 6.0) > (1.0)) ? (column_width / 6.0) : (
1.0))
,
8349 m_regex_underline_thickness,
8350 &fg, BTE_DRAW_OPAQUE(1.0));
8351 }
8352 }
8353 if (attr & BTE_ATTR_BOXED(1U << (31))) {
8354 m_draw.draw_rectangle(
8355 xl,
8356 y,
8357 xr - xl,
8358 row_height,
8359 &fg, BTE_DRAW_OPAQUE(1.0));
8360 }
8361 }
8362 }
8363
8364 m_draw.draw_text(
8365 items, n,
8366 attr,
8367 &fg, BTE_DRAW_OPAQUE(1.0));
8368}
8369
8370/* FIXME: we don't have a way to tell CTK+ what the default text attributes
8371 * should be, so for now at least it's assuming white-on-black is the norm and
8372 * is using "black-on-white" to signify "inverse". Pick up on that state and
8373 * fix things. Do this here, so that if we suddenly get red-on-black, we'll do
8374 * the right thing. */
8375void
8376Terminal::fudge_pango_colors(GSList *attributes,
8377 BteCell *cells,
8378 gsize n)
8379{
8380 gsize i, sumlen = 0;
8381 struct _fudge_cell_props{
8382 gboolean saw_fg, saw_bg;
8383 bte::color::rgb fg, bg;
8384 guint index;
8385 }*props = g_newa (struct _fudge_cell_props, n)((struct _fudge_cell_props*) __builtin_alloca (sizeof (struct
_fudge_cell_props) * (gsize) (n)))
;
8386
8387 for (i = 0; i < n; i++) {
8388 gchar ubuf[7];
8389 gint len = g_unichar_to_utf8 (cells[i].c, ubuf);
8390 props[i].index = sumlen;
8391 props[i].saw_fg = props[i].saw_bg = FALSE(0);
8392 sumlen += len;
8393 }
8394
8395 while (attributes != NULL__null) {
8396 PangoAttribute *attr = (PangoAttribute *)attributes->data;
8397 PangoAttrColor *color;
8398 switch (attr->klass->type) {
8399 case PANGO_ATTR_FOREGROUND:
8400 for (i = 0; i < n; i++) {
8401 if (props[i].index < attr->start_index) {
8402 continue;
8403 }
8404 if (props[i].index >= attr->end_index) {
8405 break;
8406 }
8407 props[i].saw_fg = TRUE(!(0));
8408 color = (PangoAttrColor*) attr;
8409 props[i].fg = color->color;
8410 }
8411 break;
8412 case PANGO_ATTR_BACKGROUND:
8413 for (i = 0; i < n; i++) {
8414 if (props[i].index < attr->start_index) {
8415 continue;
8416 }
8417 if (props[i].index >= attr->end_index) {
8418 break;
8419 }
8420 props[i].saw_bg = TRUE(!(0));
8421 color = (PangoAttrColor*) attr;
8422 props[i].bg = color->color;
8423 }
8424 break;
8425 default:
8426 break;
8427 }
8428 attributes = g_slist_next(attributes)((attributes) ? (((GSList *)(attributes))->next) : __null);
8429 }
8430
8431 for (i = 0; i < n; i++) {
8432 if (props[i].saw_fg && props[i].saw_bg &&
8433 (props[i].fg.red == 0xffff) &&
8434 (props[i].fg.green == 0xffff) &&
8435 (props[i].fg.blue == 0xffff) &&
8436 (props[i].bg.red == 0) &&
8437 (props[i].bg.green == 0) &&
8438 (props[i].bg.blue == 0)) {
8439 cells[i].attr.copy_colors(m_color_defaults.attr);
8440 cells[i].attr.set_reverse(true);
8441 }
8442 }
8443}
8444
8445/* Apply the attribute given in the PangoAttribute to the list of cells. */
8446void
8447Terminal::apply_pango_attr(PangoAttribute *attr,
8448 BteCell *cells,
8449 gsize n_cells)
8450{
8451 guint i, ival;
8452 PangoAttrInt *attrint;
8453 PangoAttrColor *attrcolor;
8454
8455 switch (attr->klass->type) {
46
Control jumps to 'case PANGO_ATTR_FOREGROUND:' at line 8456
8456 case PANGO_ATTR_FOREGROUND:
8457 case PANGO_ATTR_BACKGROUND:
8458 attrcolor = (PangoAttrColor*) attr;
8459 ival = BTE_RGB_COLOR(8, 8, 8,((1U << ((8) + (8) + (8))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (8))) & ((1U
<< (8)) - 1U)) << ((8) + (8))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (8)
)) & ((1U << (8)) - 1U)) << (8)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (8))
) & ((1U << (8)) - 1U)))
8460 ((attrcolor->color.red & 0xFF00) >> 8),((1U << ((8) + (8) + (8))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (8))) & ((1U
<< (8)) - 1U)) << ((8) + (8))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (8)
)) & ((1U << (8)) - 1U)) << (8)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (8))
) & ((1U << (8)) - 1U)))
8461 ((attrcolor->color.green & 0xFF00) >> 8),((1U << ((8) + (8) + (8))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (8))) & ((1U
<< (8)) - 1U)) << ((8) + (8))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (8)
)) & ((1U << (8)) - 1U)) << (8)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (8))
) & ((1U << (8)) - 1U)))
8462 ((attrcolor->color.blue & 0xFF00) >> 8))((1U << ((8) + (8) + (8))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (8))) & ((1U
<< (8)) - 1U)) << ((8) + (8))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (8)
)) & ((1U << (8)) - 1U)) << (8)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (8))
) & ((1U << (8)) - 1U)))
;
8463 for (i = attr->start_index;
49
Loop condition is true. Entering loop body
8464 i < attr->end_index && i < n_cells;
47
Assuming 'i' is < field 'end_index'
48
Assuming 'i' is < 'n_cells'
8465 i++) {
8466 if (attr->klass->type
49.1
Field 'type' is equal to PANGO_ATTR_FOREGROUND
49.1
Field 'type' is equal to PANGO_ATTR_FOREGROUND
49.1
Field 'type' is equal to PANGO_ATTR_FOREGROUND
== PANGO_ATTR_FOREGROUND) {
50
Taking true branch
8467 cells[i].attr.set_fore(ival);
51
Calling 'BteCellAttr::set_fore'
8468 }
8469 if (attr->klass->type == PANGO_ATTR_BACKGROUND) {
8470 cells[i].attr.set_back(ival);
8471 }
8472 }
8473 break;
8474 case PANGO_ATTR_UNDERLINE_COLOR:
8475 attrcolor = (PangoAttrColor*) attr;
8476 ival = BTE_RGB_COLOR(4, 5, 4,((1U << ((4) + (5) + (4))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (4))) & ((1U
<< (4)) - 1U)) << ((5) + (4))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (5)
)) & ((1U << (5)) - 1U)) << (4)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (4))
) & ((1U << (4)) - 1U)))
8477 ((attrcolor->color.red & 0xFF00) >> 8),((1U << ((4) + (5) + (4))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (4))) & ((1U
<< (4)) - 1U)) << ((5) + (4))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (5)
)) & ((1U << (5)) - 1U)) << (4)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (4))
) & ((1U << (4)) - 1U)))
8478 ((attrcolor->color.green & 0xFF00) >> 8),((1U << ((4) + (5) + (4))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (4))) & ((1U
<< (4)) - 1U)) << ((5) + (4))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (5)
)) & ((1U << (5)) - 1U)) << (4)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (4))
) & ((1U << (4)) - 1U)))
8479 ((attrcolor->color.blue & 0xFF00) >> 8))((1U << ((4) + (5) + (4))) | ((((((attrcolor->color.
red & 0xFF00) >> 8)) >> (8 - (4))) & ((1U
<< (4)) - 1U)) << ((5) + (4))) | ((((((attrcolor
->color.green & 0xFF00) >> 8)) >> (8 - (5)
)) & ((1U << (5)) - 1U)) << (4)) | (((((attrcolor
->color.blue & 0xFF00) >> 8)) >> (8 - (4))
) & ((1U << (4)) - 1U)))
;
8480 for (i = attr->start_index;
8481 i < attr->end_index && i < n_cells;
8482 i++) {
8483 if (attr->klass->type == PANGO_ATTR_UNDERLINE) {
8484 cells[i].attr.set_deco(ival);
8485 }
8486 }
8487 break;
8488 case PANGO_ATTR_STRIKETHROUGH:
8489 attrint = (PangoAttrInt*) attr;
8490 ival = attrint->value;
8491 for (i = attr->start_index;
8492 (i < attr->end_index) && (i < n_cells);
8493 i++) {
8494 cells[i].attr.set_strikethrough(ival != FALSE(0));
8495 }
8496 break;
8497 case PANGO_ATTR_UNDERLINE:
8498 attrint = (PangoAttrInt*) attr;
8499 ival = attrint->value;
8500 for (i = attr->start_index;
8501 (i < attr->end_index) && (i < n_cells);
8502 i++) {
8503 unsigned int underline = 0;
8504 switch (ival) {
8505 case PANGO_UNDERLINE_SINGLE:
8506 underline = 1;
8507 break;
8508 case PANGO_UNDERLINE_DOUBLE:
8509 underline = 2;
8510 break;
8511 case PANGO_UNDERLINE_ERROR:
8512 underline = 3; /* wavy */
8513 break;
8514 case PANGO_UNDERLINE_NONE:
8515 case PANGO_UNDERLINE_LOW: /* FIXME */
8516 underline = 0;
8517 break;
8518 }
8519 cells[i].attr.set_underline(underline);
8520 }
8521 break;
8522 case PANGO_ATTR_WEIGHT:
8523 attrint = (PangoAttrInt*) attr;
8524 ival = attrint->value;
8525 for (i = attr->start_index;
8526 (i < attr->end_index) && (i < n_cells);
8527 i++) {
8528 cells[i].attr.set_bold(ival >= PANGO_WEIGHT_BOLD);
8529 }
8530 break;
8531 case PANGO_ATTR_STYLE:
8532 attrint = (PangoAttrInt*) attr;
8533 ival = attrint->value;
8534 for (i = attr->start_index;
8535 (i < attr->end_index) && (i < n_cells);
8536 i++) {
8537 cells[i].attr.set_italic(ival != PANGO_STYLE_NORMAL);
8538 }
8539 break;
8540 default:
8541 break;
8542 }
8543}
8544
8545/* Convert a PangoAttrList and a location in that list to settings in a
8546 * charcell structure. The cells array is assumed to contain enough items
8547 * so that all ranges in the attribute list can be mapped into the array, which
8548 * typically means that the cell array should have the same length as the
8549 * string (byte-wise) which the attributes describe. */
8550void
8551Terminal::translate_pango_cells(PangoAttrList *attrs,
8552 BteCell *cells,
8553 gsize n_cells)
8554{
8555 PangoAttribute *attr;
8556 PangoAttrIterator *attriter;
8557 GSList *list, *listiter;
8558 guint i;
8559
8560 for (i = 0; i < n_cells; i++) {
38
Assuming 'i' is >= 'n_cells'
39
Loop condition is false. Execution continues on line 8564
8561 cells[i] = m_color_defaults;
8562 }
8563
8564 attriter = pango_attr_list_get_iterator(attrs);
8565 if (attriter != NULL__null) {
40
Assuming 'attriter' is not equal to NULL
41
Taking true branch
8566 do {
8567 list = pango_attr_iterator_get_attrs(attriter);
8568 if (list != NULL__null) {
42
Assuming 'list' is not equal to NULL
43
Taking true branch
8569 for (listiter = list;
44
Loop condition is true. Entering loop body
8570 listiter
43.1
'listiter' is not equal to NULL
43.1
'listiter' is not equal to NULL
43.1
'listiter' is not equal to NULL
!= NULL__null;
8571 listiter = g_slist_next(listiter)((listiter) ? (((GSList *)(listiter))->next) : __null)) {
8572 attr = (PangoAttribute *)listiter->data;
8573 apply_pango_attr(attr, cells, n_cells);
45
Calling 'Terminal::apply_pango_attr'
8574 }
8575 attr = (PangoAttribute *)list->data;
8576 fudge_pango_colors(
8577 list,
8578 cells +
8579 attr->start_index,
8580 MIN(n_cells, attr->end_index)(((n_cells) < (attr->end_index)) ? (n_cells) : (attr->
end_index))
-
8581 attr->start_index);
8582 g_slist_free_full(list, (GDestroyNotify)pango_attribute_destroy);
8583 }
8584 } while (pango_attr_iterator_next(attriter) == TRUE(!(0)));
8585 pango_attr_iterator_destroy(attriter);
8586 }
8587}
8588
8589/* Draw the listed items using the given attributes. Tricky because the
8590 * attribute string is indexed by byte in the UTF-8 representation of the string
8591 * of characters. Because we draw a character at a time, this is slower. */
8592void
8593Terminal::draw_cells_with_attributes(bte::view::DrawingContext::TextRequest* items,
8594 gssize n,
8595 PangoAttrList *attrs,
8596 bool draw_default_bg,
8597 gint column_width,
8598 gint height)
8599{
8600 int i, j, cell_count;
8601 BteCell *cells;
8602 char scratch_buf[BTE_UTF8_BPC(4)];
8603 guint fore, back, deco;
8604
8605 /* Note: since this function is only called with the pre-edit text,
8606 * all the items contain gunichar only, not bteunistr. */
8607 // FIXMEchpe is that really true for all input methods?
8608
8609 uint32_t const attr_mask = m_allow_bold ? ~0 : ~BTE_ATTR_BOLD_MASK((((1U << ((1))) - 1U) << ((((0) + (4)) + (1)))));
31
Assuming field 'm_allow_bold' is false
32
'?' condition is false
8610
8611 for (i = 0, cell_count = 0; i < n; i++) {
33
Loop condition is true. Entering loop body
34
Loop condition is false. Execution continues on line 8614
8612 cell_count += g_unichar_to_utf8(items[i].c, scratch_buf);
8613 }
8614 cells = g_new(BteCell, cell_count)(BteCell *) (__extension__ ({ gsize __n = (gsize) (cell_count
); gsize __s = sizeof (BteCell); gpointer __p; if (__s == 1) __p
= g_malloc (__n); else if (__builtin_constant_p (__n) &&
(__s == 0 || __n <= (9223372036854775807L *2UL+1UL) / __s
)) __p = g_malloc (__n * __s); else __p = g_malloc_n (__n, __s
); __p; }))
;
35
Taking false branch
36
Uninitialized value stored to field 'm_colors'
8615 translate_pango_cells(attrs, cells, cell_count);
37
Calling 'Terminal::translate_pango_cells'
8616 for (i = 0, j = 0; i < n; i++) {
8617 determine_colors(&cells[j], false, &fore, &back, &deco);
8618 draw_cells(items + i, 1,
8619 fore,
8620 back,
8621 deco,
8622 TRUE(!(0)), draw_default_bg,
8623 cells[j].attr.attr & attr_mask,
8624 m_allow_hyperlink && cells[j].attr.hyperlink_idx != 0,
8625 FALSE(0), column_width, height);
8626 j += g_unichar_to_utf8(items[i].c, scratch_buf);
8627 }
8628 g_free(cells);
8629}
8630
8631void
8632Terminal::ringview_update()
8633{
8634 auto first_row = first_displayed_row();
8635 auto last_row = last_displayed_row();
8636 if (cursor_is_onscreen())
8637 last_row = std::max(last_row, m_screen->cursor.row);
8638
8639 m_ringview.set_ring (m_screen->row_data);
8640 m_ringview.set_rows (first_row, last_row - first_row + 1);
8641 m_ringview.set_width (m_column_count);
8642 m_ringview.set_enable_bidi (m_enable_bidi);
8643 m_ringview.set_enable_shaping (m_enable_shaping);
8644 m_ringview.update ();
8645}
8646
8647/* Paint the contents of a given row at the given location. Take advantage
8648 * of multiple-draw APIs by finding runs of characters with identical
8649 * attributes and bundling them together. */
8650void
8651Terminal::draw_rows(BteScreen *screen_,
8652 cairo_region_t const* region,
8653 bte::grid::row_t start_row,
8654 bte::grid::row_t end_row,
8655 gint start_y, /* must be the start of a row */
8656 gint column_width,
8657 gint row_height)
8658{
8659 bte::grid::row_t row;
8660 bte::grid::column_t i, j, lcol, vcol;
8661 int y;
8662 guint fore = BTE_DEFAULT_FG256, nfore, back = BTE_DEFAULT_BG257, nback, deco = BTE_DEFAULT_FG256, ndeco;
8663 gboolean hyperlink = FALSE(0), nhyperlink; /* non-hovered explicit hyperlink, needs dashed underlining */
8664 gboolean hilite = FALSE(0), nhilite; /* hovered explicit hyperlink or regex match, needs continuous underlining */
8665 gboolean selected;
8666 gboolean nrtl = FALSE(0), rtl; /* for debugging */
8667 uint32_t attr = 0, nattr;
8668 guint item_count;
8669 const BteCell *cell;
8670 BteRowData const* row_data;
8671 bte::base::BidiRow const* bidirow;
8672
8673 auto const column_count = m_column_count;
8674 uint32_t const attr_mask = m_allow_bold ? ~0 : ~BTE_ATTR_BOLD_MASK((((1U << ((1))) - 1U) << ((((0) + (4)) + (1)))));
8675
8676 /* Need to ensure the ringview is updated. */
8677 ringview_update();
8678
8679 auto items = g_newa(bte::view::DrawingContext::TextRequest, column_count)((bte::view::DrawingContext::TextRequest*) __builtin_alloca (
sizeof (bte::view::DrawingContext::TextRequest) * (gsize) (column_count
)))
;
8680
8681 /* Paint the background.
8682 * Do it first for all the cells we're about to paint, before drawing the glyphs,
8683 * so that overflowing bits of a glyph (to the right or downwards) won't be
8684 * chopped off by another cell's background, not even across changes of the
8685 * background or any other attribute.
8686 * Process each row independently. */
8687 int const rect_width = get_allocated_width();
8688
8689 /* The rect contains the area of the row, and is moved row-wise in the loop. */
8690 auto rect = cairo_rectangle_int_t{-m_padding.left, start_y, rect_width, row_height};
8691 for (row = start_row, y = start_y;
8692 row < end_row;
8693 row++, y += row_height, rect.y = y /* same as rect.y += row_height */) {
8694 /* Check whether we need to draw this row at all */
8695 if (cairo_region_contains_rectangle(region, &rect) == CAIRO_REGION_OVERLAP_OUT)
8696 continue;
8697
8698 row_data = find_row_data(row);
8699 bidirow = m_ringview.get_bidirow(row);
8700
8701 _BTE_DEBUG_IF (BTE_DEBUG_BIDI)if (0) {
8702 /* Debug: Highlight the paddings of RTL rows with a slightly different background. */
8703 if (bidirow->base_is_rtl()) {
8704 bte::color::rgb bg;
8705 rgb_from_index<8, 8, 8>(BTE_DEFAULT_BG257, bg);
8706 /* Go halfway towards #C0C0C0. */
8707 bg.red = (bg.red + 0xC000) / 2;
8708 bg.green = (bg.green + 0xC000) / 2;
8709 bg.blue = (bg.blue + 0xC000) / 2;
8710 m_draw.fill_rectangle(
8711 -m_padding.left,
8712 y,
8713 m_padding.left,
8714 row_height,
8715 &bg, BTE_DRAW_OPAQUE(1.0));
8716 m_draw.fill_rectangle(
8717 column_count * column_width,
8718 y,
8719 rect_width - m_padding.left - column_count * column_width,
8720 row_height,
8721 &bg, BTE_DRAW_OPAQUE(1.0));
8722 }
8723 }
8724
8725 i = j = 0;
8726 /* Walk the line.
8727 * Locate runs of identical bg colors within a row, and paint each run as a single rectangle. */
8728 do {
8729 /* Get the first cell's contents. */
8730 cell = row_data ? _bte_row_data_get (row_data, bidirow->vis2log(i)) : nullptr;
8731 /* Find the colors for this cell. */
8732 selected = cell_is_selected_vis(i, row);
8733 determine_colors(cell, selected, &fore, &back, &deco);
8734 rtl = bidirow->vis_is_rtl(i);
8735
8736 while (++j < column_count) {
8737 /* Retrieve the next cell. */
8738 cell = row_data ? _bte_row_data_get (row_data, bidirow->vis2log(j)) : nullptr;
8739 /* Resolve attributes to colors where possible and
8740 * compare visual attributes to the first character
8741 * in this chunk. */
8742 selected = cell_is_selected_vis(j, row);
8743 determine_colors(cell, selected, &nfore, &nback, &ndeco);
8744 nrtl = bidirow->vis_is_rtl(j);
8745 if (nback != back || (_bte_debug_on (BTE_DEBUG_BIDI) && nrtl != rtl)) {
8746 break;
8747 }
8748 }
8749 if (back != BTE_DEFAULT_BG257) {
8750 bte::color::rgb bg;
8751 rgb_from_index<8, 8, 8>(back, bg);
8752 m_draw.fill_rectangle(
8753 i * column_width,
8754 y,
8755 (j - i) * column_width,
8756 row_height,
8757 &bg, BTE_DRAW_OPAQUE(1.0));
8758 }
8759
8760 _BTE_DEBUG_IF (BTE_DEBUG_BIDI)if (0) {
8761 /* Debug: Highlight RTL letters and RTL rows with a slightly different background. */
8762 bte::color::rgb bg;
8763 rgb_from_index<8, 8, 8>(back, bg);
8764 /* Go halfway towards #C0C0C0. */
8765 bg.red = (bg.red + 0xC000) / 2;
8766 bg.green = (bg.green + 0xC000) / 2;
8767 bg.blue = (bg.blue + 0xC000) / 2;
8768 int y1 = y + round(row_height / 8.);
8769 int y2 = y + row_height - round(row_height / 8.);
8770 /* Paint the top and bottom eighth of the cell with this more gray background
8771 * if the paragraph has a resolved RTL base direction. */
8772 if (bidirow->base_is_rtl()) {
8773 m_draw.fill_rectangle(
8774 i * column_width,
8775 y,
8776 (j - i) * column_width,
8777 y1 - y,
8778 &bg, BTE_DRAW_OPAQUE(1.0));
8779 m_draw.fill_rectangle(
8780 i * column_width,
8781 y2,
8782 (j - i) * column_width,
8783 y + row_height - y2,
8784 &bg, BTE_DRAW_OPAQUE(1.0));
8785 }
8786 /* Paint the middle three quarters of the cell with this more gray background
8787 * if the current character has a resolved RTL direction. */
8788 if (rtl) {
8789 m_draw.fill_rectangle(
8790 i * column_width,
8791 y1,
8792 (j - i) * column_width,
8793 y2 - y1,
8794 &bg, BTE_DRAW_OPAQUE(1.0));
8795 }
8796 }
8797
8798 /* We'll need to continue at the first cell which didn't
8799 * match the first one in this set. */
8800 i = j;
8801 } while (i < column_count);
8802 }
8803
8804
8805 /* Render the text.
8806 * The rect contains the area of the row (enlarged a bit at the top and bottom
8807 * to allow the text to overdraw a bit), and is moved row-wise in the loop.
8808 */
8809 rect = cairo_rectangle_int_t{-m_padding.left,
8810 start_y - cell_overflow_top(),
8811 rect_width,
8812 row_height + cell_overflow_top() + cell_overflow_bottom()};
8813
8814 for (row = start_row, y = start_y;
8815 row < end_row;
8816 row++, y += row_height, rect.y += row_height) {
8817 /* Check whether we need to draw this row at all */
8818 if (cairo_region_contains_rectangle(region, &rect) == CAIRO_REGION_OVERLAP_OUT)
8819 continue;
8820
8821 row_data = find_row_data(row);
8822 if (row_data == NULL__null)
8823 continue; /* Skip row. */
8824
8825 /* Ensure that drawing is restricted to the cell (plus the overdraw area) */
8826 _bte_draw_autoclip_t clipper{m_draw, &rect};
8827
8828 bidirow = m_ringview.get_bidirow(row);
8829
8830 /* Walk the line in logical order.
8831 * Locate runs of identical attributes within a row, and draw each run using a single draw_cells() call. */
8832 item_count = 0;
8833 // FIXME No need for the "< column_count" safety cap once bug 135 is addressed.
8834 for (lcol = 0; lcol < row_data->len && lcol < column_count; ) {
8835 vcol = bidirow->log2vis(lcol);
8836
8837 /* Get the character cell's contents. */
8838 cell = _bte_row_data_get (row_data, lcol);
8839 g_assert(cell != nullptr)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (cell != nullptr) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 8839, ((const char*) (__PRETTY_FUNCTION__
)), "cell != nullptr"); } while (0)
;
8840
8841 nhyperlink = (m_allow_hyperlink && cell->attr.hyperlink_idx != 0);
8842 nhilite = (nhyperlink && cell->attr.hyperlink_idx == m_hyperlink_hover_idx) ||
8843 (!nhyperlink && regex_match_has_current() && m_match_span.contains(row, lcol));
8844 if (cell->c == 0 ||
8845 ((cell->c == ' ' || cell->c == '\t') && // FIXME '\t' is newly added now, double check
8846 cell->attr.has_none(BTE_ATTR_UNDERLINE_MASK((((1U << ((2))) - 1U) << ((((((0) + (4)) + (1)) +
(1)) + (1)))))
|
8847 BTE_ATTR_STRIKETHROUGH_MASK((((1U << ((1))) - 1U) << (((((((0) + (4)) + (1))
+ (1)) + (1)) + (2)))))
|
8848 BTE_ATTR_OVERLINE_MASK((((1U << ((1))) - 1U) << ((((((((0) + (4)) + (1)
) + (1)) + (1)) + (2)) + (1)))))
) &&
8849 !nhyperlink &&
8850 !nhilite) ||
8851 cell->attr.fragment() ||
8852 cell->attr.invisible()) {
8853 /* Skip empty or fragment cell. */
8854 lcol++;
8855 continue;
8856 }
8857
8858 /* Find the colors for this cell. */
8859 nattr = cell->attr.attr;
8860 selected = cell_is_selected_log(lcol, row);
8861 determine_colors(cell, selected, &nfore, &nback, &ndeco);
8862
8863 /* See if it no longer fits the run. */
8864 if (item_count > 0 &&
8865 (((attr ^ nattr) & (BTE_ATTR_BOLD_MASK((((1U << ((1))) - 1U) << ((((0) + (4)) + (1))))) |
8866 BTE_ATTR_ITALIC_MASK((((1U << ((1))) - 1U) << (((((0) + (4)) + (1)) +
(1)))))
|
8867 BTE_ATTR_UNDERLINE_MASK((((1U << ((2))) - 1U) << ((((((0) + (4)) + (1)) +
(1)) + (1)))))
|
8868 BTE_ATTR_STRIKETHROUGH_MASK((((1U << ((1))) - 1U) << (((((((0) + (4)) + (1))
+ (1)) + (1)) + (2)))))
|
8869 BTE_ATTR_OVERLINE_MASK((((1U << ((1))) - 1U) << ((((((((0) + (4)) + (1)
) + (1)) + (1)) + (2)) + (1)))))
|
8870 BTE_ATTR_BLINK_MASK((((1U << ((1))) - 1U) << ((((((((((0) + (4)) + (
1)) + (1)) + (1)) + (2)) + (1)) + (1)) + (1)))))
|
8871 BTE_ATTR_INVISIBLE_MASK((((1U << ((1))) - 1U) << ((((((((((((0) + (4)) +
(1)) + (1)) + (1)) + (2)) + (1)) + (1)) + (1)) + (1)) + (1))
)))
)) || // FIXME or just simply "attr != nattr"?
8872 fore != nfore ||
8873 back != nback ||
8874 deco != ndeco ||
8875 hyperlink != nhyperlink ||
8876 hilite != nhilite)) {
8877 /* Draw the completed run of cells and start a new one. */
8878 draw_cells(items, item_count,
8879 fore, back, deco, FALSE(0), FALSE(0),
8880 attr & attr_mask,
8881 hyperlink, hilite,
8882 column_width, row_height);
8883 item_count = 0;
8884 }
8885
8886 /* Combine with subsequent spacing marks. */
8887 bteunistr c = cell->c;
8888 j = lcol + cell->attr.columns();
8889 if (G_UNLIKELY (lcol == 0 && g_unichar_ismark (_bte_unistr_get_base (cell->c)))(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
lcol == 0 && g_unichar_ismark (_bte_unistr_get_base (
cell->c))) _g_boolean_var_ = 1; else _g_boolean_var_ = 0; _g_boolean_var_
; }), 0))
) {
8890 /* A rare special case: the first cell contains a spacing mark.
8891 * Place on top of a NBSP, along with additional spacing marks if any,
8892 * and display beginning at offscreen column -1.
8893 * Additional spacing marks, if any, will be combined by the loop below. */
8894 c = _bte_unistr_append_unistr (0x00A0, cell->c);
8895 lcol = -1;
8896 }
8897 // FIXME No need for the "< column_count" safety cap once bug 135 is addressed.
8898 while (j < row_data->len && j < column_count) {
8899 /* Combine with subsequent spacing marks. */
8900 cell = _bte_row_data_get (row_data, j);
8901 if (cell && !cell->attr.fragment() && g_unichar_ismark (_bte_unistr_get_base (cell->c))) {
8902 c = _bte_unistr_append_unistr (c, cell->c);
8903 j += cell->attr.columns();
8904 } else {
8905 break;
8906 }
8907 }
8908
8909 attr = nattr;
8910 fore = nfore;
8911 back = nback;
8912 deco = ndeco;
8913 hyperlink = nhyperlink;
8914 hilite = nhilite;
8915
8916 g_assert_cmpint (item_count, <, column_count)do { gint64 __n1 = (item_count), __n2 = (column_count); if (__n1
< __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/bte.cc"
, 8916, ((const char*) (__PRETTY_FUNCTION__)), "item_count" " "
"<" " " "column_count", (long double) __n1, "<", (long
double) __n2, 'i'); } while (0)
;
8917 items[item_count].c = bidirow->vis_get_shaped_char(vcol, c);
8918 items[item_count].columns = j - lcol;
8919 items[item_count].x = (vcol - (bidirow->vis_is_rtl(vcol) ? items[item_count].columns - 1 : 0)) * column_width;
8920 items[item_count].y = y;
8921 items[item_count].mirror = bidirow->vis_is_rtl(vcol);
8922 items[item_count].box_mirror = !!(row_data->attr.bidi_flags & BTE_BIDI_FLAG_BOX_MIRROR);
8923 item_count++;
8924
8925 g_assert_cmpint (j, >, lcol)do { gint64 __n1 = (j), __n2 = (lcol); if (__n1 > __n2) ; else
g_assertion_message_cmpnum ("BTE", "../src/bte.cc", 8925, ((
const char*) (__PRETTY_FUNCTION__)), "j" " " ">" " " "lcol"
, (long double) __n1, ">", (long double) __n2, 'i'); } while
(0)
;
8926 lcol = j;
8927 }
8928
8929 /* Draw the last run of cells in the row. */
8930 if (item_count > 0) {
8931 draw_cells(items, item_count,
8932 fore, back, deco, FALSE(0), FALSE(0),
8933 attr & attr_mask,
8934 hyperlink, hilite,
8935 column_width, row_height);
8936 }
8937 }
8938}
8939
8940void
8941Terminal::paint_cursor()
8942{
8943 bte::view::DrawingContext::TextRequest item;
8944 bte::grid::row_t drow;
8945 bte::grid::column_t lcol, vcol;
8946 int width, height, cursor_width;
8947 guint fore, back, deco;
8948 bte::color::rgb bg;
8949 int x, y;
8950 gboolean blink, selected, focus;
8951
8952 //FIXMEchpe this should already be reflected in the m_cursor_blink_state below
8953 if (!m_modes_private.DEC_TEXT_CURSOR())
8954 return;
8955
8956 if (m_im_preedit_active)
8957 return;
8958
8959 focus = m_has_focus;
8960 blink = m_cursor_blink_state;
8961
8962 if (focus && !blink)
8963 return;
8964
8965 lcol = m_screen->cursor.col;
8966 drow = m_screen->cursor.row;
8967 width = m_cell_width;
8968 height = m_cell_height;
8969
8970 if (!cursor_is_onscreen())
8971 return;
8972 if (CLAMP(lcol, 0, m_column_count - 1)(((lcol) > (m_column_count - 1)) ? (m_column_count - 1) : (
((lcol) < (0)) ? (0) : (lcol)))
!= lcol)
8973 return;
8974
8975 /* Need to ensure the ringview is updated. */
8976 ringview_update();
8977
8978 /* Find the first cell of the character "under" the cursor.
8979 * This is for CJK. For TAB, paint the cursor where it really is. */
8980 BteRowData const *row_data = find_row_data(drow);
8981 bte::base::BidiRow const *bidirow = m_ringview.get_bidirow(drow);
8982
8983 auto cell = find_charcell(lcol, drow);
8984 while (cell != NULL__null && cell->attr.fragment() && cell->c != '\t' && lcol > 0) {
8985 lcol--;
8986 cell = find_charcell(lcol, drow);
8987 }
8988
8989 /* Draw the cursor. */
8990 vcol = bidirow->log2vis(lcol);
8991 item.c = (cell && cell->c) ? bidirow->vis_get_shaped_char(vcol, cell->c) : ' ';
8992 item.columns = item.c == '\t' ? 1 : cell ? cell->attr.columns() : 1;
8993 item.x = (vcol - ((cell && bidirow->vis_is_rtl(vcol)) ? cell->attr.columns() - 1 : 0)) * width;
8994 item.y = row_to_pixel(drow);
8995 item.mirror = bidirow->vis_is_rtl(vcol);
8996 item.box_mirror = (row_data && (row_data->attr.bidi_flags & BTE_BIDI_FLAG_BOX_MIRROR));
8997 auto const attr = cell && cell->c ? cell->attr.attr : 0;
8998
8999 selected = cell_is_selected_log(lcol, drow);
9000 determine_cursor_colors(cell, selected, &fore, &back, &deco);
9001 rgb_from_index<8, 8, 8>(back, bg);
9002
9003 x = item.x;
9004 y = item.y;
9005
9006 switch (decscusr_cursor_shape()) {
9007
9008 case CursorShape::eIBEAM: {
9009 /* Draw at the very left of the cell (before the spacing), even in case of CJK.
9010 * IMO (egmont) not overrunning the letter improves readability, vertical movement
9011 * looks good (no zigzag even when a somewhat wider glyph that starts filling up
9012 * the left spacing, or CJK that begins further to the right is encountered),
9013 * and also this is where it looks good if background colors change, including
9014 * Shift+arrows highlighting experience in some editors. As per the behavior of
9015 * word processors, don't increase the height by the line spacing. */
9016 int stem_width;
9017
9018 stem_width = (int) (((float) (m_char_ascent + m_char_descent)) * m_cursor_aspect_ratio + 0.5);
9019 stem_width = CLAMP (stem_width, BTE_LINE_WIDTH, m_cell_width)(((stem_width) > (m_cell_width)) ? (m_cell_width) : (((stem_width
) < (1)) ? (1) : (stem_width)))
;
9020
9021 /* The I-beam goes to the right edge of the cell if its character has RTL resolved direction. */
9022 if (bidirow->vis_is_rtl(vcol))
9023 x += item.columns * m_cell_width - stem_width;
9024
9025 m_draw.fill_rectangle(
9026 x, y + m_char_padding.top, stem_width, m_char_ascent + m_char_descent,
9027 &bg, BTE_DRAW_OPAQUE(1.0));
9028
9029 /* Show the direction of the current character if the paragraph contains a mixture
9030 * of directions.
9031 * FIXME Do this for the other cursor shapes, too. Need to find a good visual design. */
9032 if (focus && bidirow->has_foreign())
9033 m_draw.fill_rectangle(
9034 bidirow->vis_is_rtl(vcol) ? x - stem_width : x + stem_width,
9035 y + m_char_padding.top,
9036 stem_width, stem_width,
9037 &bg, BTE_DRAW_OPAQUE(1.0));
9038 break;
9039 }
9040
9041 case CursorShape::eUNDERLINE: {
9042 /* The width is at least the overall width of the cell (or two cells) minus the two
9043 * half spacings on the two edges. That is, underlines under a CJK are more than twice
9044 * as wide as narrow characters in case of letter spacing. Plus, if necessary, the width
9045 * is increased to span under the entire glyph. Vertical position is not affected by
9046 * line spacing. */
9047
9048 int line_height, left, right;
9049
9050 /* use height (not width) so underline and ibeam will
9051 * be equally visible */
9052 line_height = (int) (((float) (m_char_ascent + m_char_descent)) * m_cursor_aspect_ratio + 0.5);
9053 line_height = CLAMP (line_height, BTE_LINE_WIDTH, m_char_ascent + m_char_descent)(((line_height) > (m_char_ascent + m_char_descent)) ? (m_char_ascent
+ m_char_descent) : (((line_height) < (1)) ? (1) : (line_height
)))
;
9054
9055 left = m_char_padding.left;
9056 right = item.columns * m_cell_width - m_char_padding.right;
9057
9058 if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
9059 int l, r;
9060 m_draw.get_char_edges(cell->c, cell->attr.columns(), attr, l, r);
9061 left = MIN(left, l)(((left) < (l)) ? (left) : (l));
9062 right = MAX(right, r)(((right) > (r)) ? (right) : (r));
9063 }
9064
9065 m_draw.fill_rectangle(
9066 x + left, y + m_cell_height - m_char_padding.bottom - line_height,
9067 right - left, line_height,
9068 &bg, BTE_DRAW_OPAQUE(1.0));
9069 break;
9070 }
9071
9072 case CursorShape::eBLOCK:
9073 /* Include the spacings in the cursor, see bug 781479 comments 39-44.
9074 * Make the cursor even wider if the glyph is wider. */
9075
9076 cursor_width = item.columns * width;
9077 if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
9078 int l, r;
9079 m_draw.get_char_edges(cell->c, cell->attr.columns(), attr, l /* unused */, r);
9080 cursor_width = MAX(cursor_width, r)(((cursor_width) > (r)) ? (cursor_width) : (r));
9081 }
9082
9083 uint32_t const attr_mask = m_allow_bold ? ~0 : ~BTE_ATTR_BOLD_MASK((((1U << ((1))) - 1U) << ((((0) + (4)) + (1)))));
9084
9085 if (focus) {
9086 /* just reverse the character under the cursor */
9087 m_draw.fill_rectangle(
9088 x, y,
9089 cursor_width, height,
9090 &bg, BTE_DRAW_OPAQUE(1.0));
9091
9092 if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
9093 draw_cells(
9094 &item, 1,
9095 fore, back, deco, TRUE(!(0)), FALSE(0),
9096 cell->attr.attr & attr_mask,
9097 m_allow_hyperlink && cell->attr.hyperlink_idx != 0,
9098 FALSE(0),
9099 width,
9100 height);
9101 }
9102
9103 } else {
9104 /* draw a box around the character */
9105 m_draw.draw_rectangle(
9106 x - BTE_LINE_WIDTH1,
9107 y - BTE_LINE_WIDTH1,
9108 cursor_width + 2*BTE_LINE_WIDTH1,
9109 height + 2*BTE_LINE_WIDTH1,
9110 &bg, BTE_DRAW_OPAQUE(1.0));
9111 }
9112
9113 break;
9114 }
9115}
9116
9117void
9118Terminal::paint_im_preedit_string()
9119{
9120 int vcol, columns;
9121 long row;
9122 long width, height;
9123 int i, len;
9124
9125 if (m_im_preedit.empty())
16
Assuming the condition is false
17
Taking false branch
9126 return;
9127
9128 /* Need to ensure the ringview is updated. */
9129 ringview_update();
9130
9131 /* Get the row's BiDi information. */
9132 row = m_screen->cursor.row;
9133 if (row < first_displayed_row() || row > last_displayed_row())
18
Assuming the condition is false
19
Assuming the condition is false
20
Taking false branch
9134 return;
9135 bte::base::BidiRow const *bidirow = m_ringview.get_bidirow(row);
9136
9137 /* Keep local copies of rendering information. */
9138 width = m_cell_width;
9139 height = m_cell_height;
9140
9141 /* Find out how many columns the pre-edit string takes up. */
9142 columns = get_preedit_width(false);
9143 len = get_preedit_length(false);
9144
9145 /* If the pre-edit string won't fit on the screen if we start
9146 * drawing it at the cursor's position, move it left. */
9147 vcol = bidirow->log2vis(m_screen->cursor.col);
9148 if (vcol + columns > m_column_count) {
21
Assuming the condition is false
22
Taking false branch
9149 vcol = MAX(0, m_column_count - columns)(((0) > (m_column_count - columns)) ? (0) : (m_column_count
- columns))
;
9150 }
9151
9152 /* Draw the preedit string, boxed. */
9153 if (len
22.1
'len' is > 0
22.1
'len' is > 0
22.1
'len' is > 0
> 0) {
23
Taking true branch
9154 const char *preedit = m_im_preedit.c_str();
9155 int preedit_cursor;
9156
9157 auto items = g_new0(bte::view::DrawingContext::TextRequest, len)(bte::view::DrawingContext::TextRequest *) (__extension__ ({ gsize
__n = (gsize) (len); gsize __s = sizeof (bte::view::DrawingContext
::TextRequest); gpointer __p; if (__s == 1) __p = g_malloc0 (
__n); else if (__builtin_constant_p (__n) && (__s == 0
|| __n <= (9223372036854775807L *2UL+1UL) / __s)) __p = g_malloc0
(__n * __s); else __p = g_malloc0_n (__n, __s); __p; }))
;
24
Taking false branch
9158 for (i = columns = 0; i < len; i++) {
25
Loop condition is true. Entering loop body
26
Loop condition is false. Execution continues on line 9167
9159 items[i].c = g_utf8_get_char(preedit);
9160 items[i].columns = _bte_unichar_width(items[i].c,
9161 m_utf8_ambiguous_width);
9162 items[i].x = (vcol + columns) * width;
9163 items[i].y = row_to_pixel(m_screen->cursor.row);
9164 columns += items[i].columns;
9165 preedit = g_utf8_next_char(preedit)(char *)((preedit) + g_utf8_skip[*(const guchar *)(preedit)]);
9166 }
9167 if (G_LIKELY(m_clear_background)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_clear_background) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1))
) {
27
Assuming field 'm_clear_background' is false
28
Taking false branch
29
Taking false branch
9168 m_draw.clear(
9169 vcol * width,
9170 row_to_pixel(m_screen->cursor.row),
9171 width * columns,
9172 height,
9173 get_color(BTE_DEFAULT_BG257), m_background_alpha);
9174 }
9175 draw_cells_with_attributes(
30
Calling 'Terminal::draw_cells_with_attributes'
9176 items, len,
9177 m_im_preedit_attrs.get(),
9178 TRUE(!(0)),
9179 width, height);
9180 preedit_cursor = m_im_preedit_cursor;
9181
9182 if (preedit_cursor >= 0 && preedit_cursor < len) {
9183 uint32_t fore, back, deco;
9184 bte_color_triple_get(m_color_defaults.attr.colors(), &fore, &back, &deco);
9185
9186 /* Cursored letter in reverse. */
9187 draw_cells(
9188 &items[preedit_cursor], 1,
9189 fore, back, deco,
9190 TRUE(!(0)), /* clear */
9191 TRUE(!(0)), /* draw_default_bg */
9192 BTE_ATTR_NONE(0U) | BTE_ATTR_BOXED(1U << (31)),
9193 FALSE(0), /* hyperlink */
9194 FALSE(0), /* hilite */
9195 width, height);
9196 }
9197 g_free(items);
9198 }
9199}
9200
9201void
9202Terminal::widget_draw(cairo_t *cr)
9203{
9204 cairo_rectangle_int_t clip_rect;
9205 cairo_region_t *region;
9206 int allocated_width, allocated_height;
9207 int extra_area_for_cursor;
9208 bool text_blink_enabled_now;
9209 gint64 now = 0;
9210
9211 if (!cdk_cairo_get_clip_rectangle (cr, &clip_rect))
1
Assuming the condition is false
2
Taking false branch
9212 return;
9213
9214 _bte_debug_print(BTE_DEBUG_LIFECYCLE, "bte_terminal_draw()\n")do { } while(0);
3
Loop condition is false. Exiting loop
9215 _bte_debug_print (BTE_DEBUG_WORK, "+")do { } while(0);
4
Loop condition is false. Exiting loop
9216 _bte_debug_print (BTE_DEBUG_UPDATES, "Draw (%d,%d)x(%d,%d)\n",do { } while(0)
5
Loop condition is false. Exiting loop
9217 clip_rect.x, clip_rect.y,do { } while(0)
9218 clip_rect.width, clip_rect.height)do { } while(0);
9219
9220 region = bte_cairo_get_clip_region (cr);
9221 if (region == NULL__null)
6
Assuming 'region' is not equal to NULL
7
Taking false branch
9222 return;
9223
9224 allocated_width = get_allocated_width();
9225 allocated_height = get_allocated_height();
9226
9227 /* Designate the start of the drawing operation and clear the area. */
9228 m_draw.set_cairo(cr);
9229
9230 if (G_LIKELY(m_clear_background)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_clear_background) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1))
) {
8
Assuming field 'm_clear_background' is false
9
Taking false branch
10
Taking false branch
9231 m_draw.clear(0, 0,
9232 allocated_width, allocated_height,
9233 get_color(BTE_DEFAULT_BG257), m_background_alpha);
9234 }
9235
9236 /* Clip vertically, for the sake of smooth scrolling. We want the top and bottom paddings to be unused.
9237 * Don't clip horizontally so that antialiasing can legally overflow to the right padding. */
9238 cairo_save(cr);
9239 cairo_rectangle(cr, 0, m_padding.top, allocated_width, allocated_height - m_padding.top - m_padding.bottom);
9240 cairo_clip(cr);
9241
9242 cairo_translate(cr, m_padding.left, m_padding.top);
9243
9244 /* Transform to view coordinates */
9245 cairo_region_translate(region, -m_padding.left, -m_padding.top);
9246
9247 /* Whether blinking text should be visible now */
9248 m_text_blink_state = true;
9249 text_blink_enabled_now = (unsigned)m_text_blink_mode & (unsigned)(m_has_focus ? TextBlinkMode::eFOCUSED : TextBlinkMode::eUNFOCUSED);
11
Assuming field 'm_has_focus' is false
12
'?' condition is false
9250 if (text_blink_enabled_now) {
13
Assuming 'text_blink_enabled_now' is false
14
Taking false branch
9251 now = g_get_monotonic_time() / 1000;
9252 if (now % (m_text_blink_cycle * 2) >= m_text_blink_cycle)
9253 m_text_blink_state = false;
9254 }
9255 /* Painting will flip this if it encounters any cell with blink attribute */
9256 m_text_to_blink = false;
9257
9258 /* and now paint them */
9259 auto const first_row = first_displayed_row();
9260 draw_rows(m_screen,
9261 region,
9262 first_row,
9263 last_displayed_row() + 1,
9264 row_to_pixel(first_row),
9265 m_cell_width,
9266 m_cell_height);
9267
9268 paint_im_preedit_string();
15
Calling 'Terminal::paint_im_preedit_string'
9269
9270 cairo_restore(cr);
9271
9272 /* Re-clip, allowing BTE_LINE_WIDTH more pixel rows for the outline cursor. */
9273 /* TODOegmont: It's really ugly to do it here. */
9274 cairo_save(cr);
9275 extra_area_for_cursor = (decscusr_cursor_shape() == CursorShape::eBLOCK && !m_has_focus) ? BTE_LINE_WIDTH1 : 0;
9276 cairo_rectangle(cr, 0, m_padding.top - extra_area_for_cursor, allocated_width, allocated_height - m_padding.top - m_padding.bottom + 2 * extra_area_for_cursor);
9277 cairo_clip(cr);
9278
9279 cairo_translate(cr, m_padding.left, m_padding.top);
9280
9281 paint_cursor();
9282
9283 cairo_restore(cr);
9284
9285 /* Done with various structures. */
9286 m_draw.set_cairo(nullptr);
9287
9288 cairo_region_destroy (region);
9289
9290 /* If painting encountered any cell with blink attribute, we might need to set up a timer.
9291 * Blinking is implemented using a one-shot (not repeating) timer that keeps getting reinstalled
9292 * here as long as blinking cells are encountered during (re)painting. This way there's no need
9293 * for an explicit step to stop the timer when blinking cells are no longer present, this happens
9294 * implicitly by the timer not getting reinstalled anymore (often after a final unnecessary but
9295 * harmless repaint). */
9296 if (G_UNLIKELY (m_text_to_blink && text_blink_enabled_now && !m_text_blink_timer)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
m_text_to_blink && text_blink_enabled_now && !
m_text_blink_timer) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
9297 m_text_blink_timer.schedule(m_text_blink_cycle - now % m_text_blink_cycle,
9298 bte::glib::Timer::Priority::eLOW);
9299
9300 m_invalidated_all = FALSE(0);
9301}
9302
9303/* Handle an expose event by painting the exposed area. */
9304static cairo_region_t *
9305bte_cairo_get_clip_region (cairo_t *cr)
9306{
9307 cairo_rectangle_list_t *list;
9308 cairo_region_t *region;
9309 int i;
9310
9311 list = cairo_copy_clip_rectangle_list (cr);
9312 if (list->status == CAIRO_STATUS_CLIP_NOT_REPRESENTABLE) {
9313 cairo_rectangle_int_t clip_rect;
9314
9315 cairo_rectangle_list_destroy (list);
9316
9317 if (!cdk_cairo_get_clip_rectangle (cr, &clip_rect))
9318 return NULL__null;
9319 return cairo_region_create_rectangle (&clip_rect);
9320 }
9321
9322
9323 region = cairo_region_create ();
9324 for (i = list->num_rectangles - 1; i >= 0; --i) {
9325 cairo_rectangle_t *rect = &list->rectangles[i];
9326 cairo_rectangle_int_t clip_rect;
9327
9328 clip_rect.x = floor (rect->x);
9329 clip_rect.y = floor (rect->y);
9330 clip_rect.width = ceil (rect->x + rect->width) - clip_rect.x;
9331 clip_rect.height = ceil (rect->y + rect->height) - clip_rect.y;
9332
9333 if (cairo_region_union_rectangle (region, &clip_rect) != CAIRO_STATUS_SUCCESS) {
9334 cairo_region_destroy (region);
9335 region = NULL__null;
9336 break;
9337 }
9338 }
9339
9340 cairo_rectangle_list_destroy (list);
9341 return region;
9342}
9343
9344bool
9345Terminal::widget_mouse_scroll(MouseEvent const& event)
9346{
9347 gdouble v;
9348 gint cnt, i;
9349 int button;
9350
9351 /* Need to ensure the ringview is updated. */
9352 ringview_update();
9353
9354 auto rowcol = confined_grid_coords_from_event(event);
9355
9356 m_modifiers = event.modifiers();
9357
9358 switch (event.scroll_direction()) {
9359 case MouseEvent::ScrollDirection::eUP:
9360 m_mouse_smooth_scroll_delta -= 1.;
9361 _bte_debug_print(BTE_DEBUG_EVENTS, "Scroll up\n")do { } while(0);
9362 break;
9363 case MouseEvent::ScrollDirection::eDOWN:
9364 m_mouse_smooth_scroll_delta += 1.;
9365 _bte_debug_print(BTE_DEBUG_EVENTS, "Scroll down\n")do { } while(0);
9366 break;
9367 case MouseEvent::ScrollDirection::eSMOOTH: {
9368 auto const delta_y = event.scroll_delta_y();
9369 m_mouse_smooth_scroll_delta += delta_y;
9370 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
9371 "Smooth scroll by %f, delta now at %f\n",do { } while(0)
9372 delta_y, m_mouse_smooth_scroll_delta)do { } while(0);
9373 break;
9374 }
9375 default:
9376 break;
9377 }
9378
9379 /* If we're running a mouse-aware application, map the scroll event
9380 * to a button press on buttons four and five. */
9381 if (m_mouse_tracking_mode != MouseTrackingMode::eNONE) {
9382 cnt = m_mouse_smooth_scroll_delta;
9383 if (cnt == 0)
9384 return true;
9385 m_mouse_smooth_scroll_delta -= cnt;
9386 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
9387 "Scroll application by %d lines, smooth scroll delta set back to %f\n",do { } while(0)
9388 cnt, m_mouse_smooth_scroll_delta)do { } while(0);
9389
9390 button = cnt > 0 ? 5 : 4;
9391 if (cnt < 0)
9392 cnt = -cnt;
9393 for (i = 0; i < cnt; i++) {
9394 /* Encode the parameters and send them to the app. */
9395 feed_mouse_event(rowcol,
9396 button,
9397 false /* not drag */,
9398 false /* not release */);
9399 }
9400 return true;
9401 }
9402
9403 v = MAX (1., ceil (ctk_adjustment_get_page_increment (m_vadjustment.get()) / 10.))(((1.) > (ceil (ctk_adjustment_get_page_increment (m_vadjustment
.get()) / 10.))) ? (1.) : (ceil (ctk_adjustment_get_page_increment
(m_vadjustment.get()) / 10.)))
;
9404 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
9405 "Scroll speed is %d lines per non-smooth scroll unit\n",do { } while(0)
9406 (int) v)do { } while(0);
9407 if (m_screen == &m_alternate_screen &&
9408 m_modes_private.XTERM_ALTBUF_SCROLL()) {
9409 char *normal;
9410 gsize normal_length;
9411
9412 cnt = v * m_mouse_smooth_scroll_delta;
9413 if (cnt == 0)
9414 return true;
9415 m_mouse_smooth_scroll_delta -= cnt / v;
9416 _bte_debug_print(BTE_DEBUG_EVENTS,do { } while(0)
9417 "Scroll by %d lines, smooth scroll delta set back to %f\n",do { } while(0)
9418 cnt, m_mouse_smooth_scroll_delta)do { } while(0);
9419
9420 /* In the alternate screen there is no scrolling,
9421 * so fake a few cursor keystrokes. */
9422
9423 _bte_keymap_map (
9424 cnt > 0 ? CDK_KEY_Down0xff54 : CDK_KEY_Up0xff52,
9425 m_modifiers,
9426 m_modes_private.DEC_APPLICATION_CURSOR_KEYS(),
9427 m_modes_private.DEC_APPLICATION_KEYPAD(),
9428 &normal,
9429 &normal_length);
9430 if (cnt < 0)
9431 cnt = -cnt;
9432 for (i = 0; i < cnt; i++) {
9433 send_child({normal, normal_length});
9434 }
9435 g_free (normal);
9436
9437 return true;
9438 } else {
9439 /* Perform a history scroll. */
9440 double dcnt = m_screen->scroll_delta + v * m_mouse_smooth_scroll_delta;
9441 queue_adjustment_value_changed_clamped(dcnt);
9442 m_mouse_smooth_scroll_delta = 0;
9443
9444 return true;
9445 }
9446
9447 return true;
9448}
9449
9450bool
9451Terminal::set_audible_bell(bool setting)
9452{
9453 if (setting == m_audible_bell)
9454 return false;
9455
9456 m_audible_bell = setting;
9457 return true;
9458}
9459
9460bool
9461Terminal::set_text_blink_mode(TextBlinkMode setting)
9462{
9463 if (setting == m_text_blink_mode)
9464 return false;
9465
9466 m_text_blink_mode = setting;
9467 invalidate_all();
9468
9469 return true;
9470}
9471
9472bool
9473Terminal::set_enable_bidi(bool setting)
9474{
9475 if (setting == m_enable_bidi)
9476 return false;
9477
9478 m_enable_bidi = setting;
9479 m_ringview.invalidate();
9480 invalidate_all();
9481
9482 /* Chances are that we can free up some BiDi/shaping buffers that we
9483 * won't need for a while. */
9484 if (!setting)
9485 m_ringview.pause();
9486
9487 return true;
9488}
9489
9490bool
9491Terminal::set_enable_shaping(bool setting)
9492{
9493 if (setting == m_enable_shaping)
9494 return false;
9495
9496 m_enable_shaping = setting;
9497 m_ringview.invalidate();
9498 invalidate_all();
9499
9500 /* Chances are that we can free up some BiDi/shaping buffers that we
9501 * won't need for a while. */
9502 if (!setting)
9503 m_ringview.pause();
9504
9505 return true;
9506}
9507
9508bool
9509Terminal::set_allow_bold(bool setting)
9510{
9511 if (setting == m_allow_bold)
9512 return false;
9513
9514 m_allow_bold = setting;
9515 invalidate_all();
9516
9517 return true;
9518}
9519
9520bool
9521Terminal::set_bold_is_bright(bool setting)
9522{
9523 if (setting == m_bold_is_bright)
9524 return false;
9525
9526 m_bold_is_bright = setting;
9527 invalidate_all();
9528
9529 return true;
9530}
9531
9532bool
9533Terminal::set_allow_hyperlink(bool setting)
9534{
9535 if (setting == m_allow_hyperlink)
9536 return false;
9537
9538 if (setting == false) {
9539 m_hyperlink_hover_idx = _bte_ring_get_hyperlink_at_position(m_screen->row_data, -1, -1, true, NULL__null);
9540 g_assert (m_hyperlink_hover_idx == 0)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_hyperlink_hover_idx == 0) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 9540, ((const char*) (__PRETTY_FUNCTION__
)), "m_hyperlink_hover_idx == 0"); } while (0)
;
9541 m_hyperlink_hover_uri = NULL__null;
9542 emit_hyperlink_hover_uri_changed(NULL__null); /* FIXME only emit if really changed */
9543 m_defaults.attr.hyperlink_idx = _bte_ring_get_hyperlink_idx(m_screen->row_data, NULL__null);
9544 g_assert (m_defaults.attr.hyperlink_idx == 0)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_
; if (m_defaults.attr.hyperlink_idx == 0) _g_boolean_var_ = 1
; else _g_boolean_var_ = 0; _g_boolean_var_; }), 1)) ; else g_assertion_message_expr
("BTE", "../src/bte.cc", 9544, ((const char*) (__PRETTY_FUNCTION__
)), "m_defaults.attr.hyperlink_idx == 0"); } while (0)
;
9545 }
9546
9547 m_allow_hyperlink = setting;
9548 invalidate_all();
9549
9550 return true;
9551}
9552
9553bool
9554Terminal::set_scroll_on_output(bool scroll)
9555{
9556 if (scroll == m_scroll_on_output)
9557 return false;
9558
9559 m_scroll_on_output = scroll;
9560 return true;
9561}
9562
9563bool
9564Terminal::set_scroll_on_keystroke(bool scroll)
9565{
9566 if (scroll == m_scroll_on_keystroke)
9567 return false;
9568
9569 m_scroll_on_keystroke = scroll;
9570 return true;
9571}
9572
9573bool
9574Terminal::set_rewrap_on_resize(bool rewrap)
9575{
9576 if (rewrap == m_rewrap_on_resize)
9577 return false;
9578
9579 m_rewrap_on_resize = rewrap;
9580 return true;
9581}
9582
9583void
9584Terminal::update_cursor_blinks()
9585{
9586 bool blink = false;
9587
9588 switch (decscusr_cursor_blink()) {
9589 case CursorBlinkMode::eSYSTEM:
9590 gboolean v;
9591 g_object_get(ctk_widget_get_settings(m_widget),
9592 "ctk-cursor-blink",
9593 &v, nullptr);
9594 blink = v != FALSE(0);
9595 break;
9596 case CursorBlinkMode::eON:
9597 blink = true;
9598 break;
9599 case CursorBlinkMode::eOFF:
9600 blink = false;
9601 break;
9602 }
9603
9604 if (m_cursor_blinks == blink)
9605 return;
9606
9607 m_cursor_blinks = blink;
9608 check_cursor_blink();
9609}
9610
9611bool
9612Terminal::set_cursor_blink_mode(CursorBlinkMode mode)
9613{
9614 if (mode == m_cursor_blink_mode)
9615 return false;
9616
9617 m_cursor_blink_mode = mode;
9618 update_cursor_blinks();
9619
9620 return true;
9621}
9622
9623bool
9624Terminal::set_cursor_shape(CursorShape shape)
9625{
9626 if (shape == m_cursor_shape)
9627 return false;
9628
9629 m_cursor_shape = shape;
9630 invalidate_cursor_once();
9631
9632 return true;
9633}
9634
9635/* DECSCUSR set cursor style */
9636bool
9637Terminal::set_cursor_style(CursorStyle style)
9638{
9639 if (m_cursor_style == style)
9640 return false;
9641
9642 m_cursor_style = style;
9643 update_cursor_blinks();
9644 /* and this will also make cursor shape match the DECSCUSR style */
9645 invalidate_cursor_once();
9646
9647 return true;
9648}
9649
9650/*
9651 * Terminal::decscusr_cursor_blink:
9652 *
9653 * Returns the cursor blink mode set by DECSCUSR. If DECSCUSR was never
9654 * called, or it set the blink mode to terminal default, this returns the
9655 * value set via API or in dconf. Internal use only.
9656 *
9657 * Return value: cursor blink mode
9658 */
9659Terminal::CursorBlinkMode
9660Terminal::decscusr_cursor_blink() const noexcept
9661{
9662 switch (m_cursor_style) {
9663 default:
9664 case CursorStyle::eTERMINAL_DEFAULT:
9665 return m_cursor_blink_mode;
9666 case CursorStyle::eBLINK_BLOCK:
9667 case CursorStyle::eBLINK_UNDERLINE:
9668 case CursorStyle::eBLINK_IBEAM:
9669 return CursorBlinkMode::eON;
9670 case CursorStyle::eSTEADY_BLOCK:
9671 case CursorStyle::eSTEADY_UNDERLINE:
9672 case CursorStyle::eSTEADY_IBEAM:
9673 return CursorBlinkMode::eOFF;
9674 }
9675}
9676
9677/*
9678 * Terminal::decscusr_cursor_shape:
9679 * @terminal: a #BteTerminal
9680 *
9681 * Returns the cursor shape set by DECSCUSR. If DECSCUSR was never called,
9682 * or it set the cursor shape to terminal default, this returns the value
9683 * set via API. Internal use only.
9684 *
9685 * Return value: cursor shape
9686 */
9687Terminal::CursorShape
9688Terminal::decscusr_cursor_shape() const noexcept
9689{
9690 switch (m_cursor_style) {
9691 default:
9692 case CursorStyle::eTERMINAL_DEFAULT:
9693 return m_cursor_shape;
9694 case CursorStyle::eBLINK_BLOCK:
9695 case CursorStyle::eSTEADY_BLOCK:
9696 return CursorShape::eBLOCK;
9697 case CursorStyle::eBLINK_UNDERLINE:
9698 case CursorStyle::eSTEADY_UNDERLINE:
9699 return CursorShape::eUNDERLINE;
9700 case CursorStyle::eBLINK_IBEAM:
9701 case CursorStyle::eSTEADY_IBEAM:
9702 return CursorShape::eIBEAM;
9703 }
9704}
9705
9706bool
9707Terminal::set_scrollback_lines(long lines)
9708{
9709 glong low, high, next;
9710 double scroll_delta;
9711 BteScreen *scrn;
9712
9713 if (lines < 0)
9714 lines = G_MAXLONG9223372036854775807L;
9715
9716#if 0
9717 /* FIXME: this breaks the scrollbar range, bug #562511 */
9718 if (lines == m_scrollback_lines)
9719 return false;
9720#endif
9721
9722 _bte_debug_print (BTE_DEBUG_MISC,do { } while(0)
9723 "Setting scrollback lines to %ld\n", lines)do { } while(0);
9724
9725 m_scrollback_lines = lines;
9726
9727 /* The main screen gets the full scrollback buffer. */
9728 scrn = &m_normal_screen;
9729 lines = MAX (lines, m_row_count)(((lines) > (m_row_count)) ? (lines) : (m_row_count));
9730 next = MAX (m_screen->cursor.row + 1,(((m_screen->cursor.row + 1) > (_bte_ring_next (scrn->
row_data))) ? (m_screen->cursor.row + 1) : (_bte_ring_next
(scrn->row_data)))
9731 _bte_ring_next (scrn->row_data))(((m_screen->cursor.row + 1) > (_bte_ring_next (scrn->
row_data))) ? (m_screen->cursor.row + 1) : (_bte_ring_next
(scrn->row_data)))
;
9732 _bte_ring_resize (scrn->row_data, lines);
9733 low = _bte_ring_delta (scrn->row_data);
9734 high = lines + MIN (G_MAXLONG - lines, low - m_row_count + 1)(((9223372036854775807L - lines) < (low - m_row_count + 1)
) ? (9223372036854775807L - lines) : (low - m_row_count + 1))
;
9735 scrn->insert_delta = CLAMP (scrn->insert_delta, low, high)(((scrn->insert_delta) > (high)) ? (high) : (((scrn->
insert_delta) < (low)) ? (low) : (scrn->insert_delta)))
;
9736 scrn->scroll_delta = CLAMP (scrn->scroll_delta, low, scrn->insert_delta)(((scrn->scroll_delta) > (scrn->insert_delta)) ? (scrn
->insert_delta) : (((scrn->scroll_delta) < (low)) ? (
low) : (scrn->scroll_delta)))
;
9737 next = MIN (next, scrn->insert_delta + m_row_count)(((next) < (scrn->insert_delta + m_row_count)) ? (next)
: (scrn->insert_delta + m_row_count))
;
9738 if (_bte_ring_next (scrn->row_data) > next){
9739 _bte_ring_shrink (scrn->row_data, next - low);
9740 }
9741
9742 /* The alternate scrn isn't allowed to scroll at all. */
9743 scrn = &m_alternate_screen;
9744 _bte_ring_resize (scrn->row_data, m_row_count);
9745 scrn->scroll_delta = _bte_ring_delta (scrn->row_data);
9746 scrn->insert_delta = _bte_ring_delta (scrn->row_data);
9747 if (_bte_ring_next (scrn->row_data) > scrn->insert_delta + m_row_count){
9748 _bte_ring_shrink (scrn->row_data, m_row_count);
9749 }
9750
9751 /* Adjust the scrollbar to the new location. */
9752 /* Hack: force a change in scroll_delta even if the value remains, so that
9753 bte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 676075. */
9754 scroll_delta = m_screen->scroll_delta;
9755 m_screen->scroll_delta = -1;
9756 queue_adjustment_value_changed(scroll_delta);
9757 adjust_adjustments_full();
9758
9759 return true;
9760}
9761
9762bool
9763Terminal::set_backspace_binding(EraseMode binding)
9764{
9765 if (binding == m_backspace_binding)
9766 return false;
9767
9768 m_backspace_binding = binding;
9769 return true;
9770}
9771
9772bool
9773Terminal::set_delete_binding(EraseMode binding)
9774{
9775 if (binding == m_delete_binding)
9776 return false;
9777
9778 m_delete_binding = binding;
9779 return true;
9780}
9781
9782bool
9783Terminal::set_mouse_autohide(bool autohide)
9784{
9785 if (autohide == m_mouse_autohide)
9786 return false;
9787
9788 m_mouse_autohide = autohide;
9789
9790 if (m_mouse_cursor_autohidden) {
9791 hyperlink_hilite_update();
9792 match_hilite_update();
9793 apply_mouse_cursor();
9794 }
9795 return true;
9796}
9797
9798void
9799Terminal::reset_decoder()
9800{
9801 switch (data_syntax()) {
9802 case DataSyntax::eECMA48_UTF8:
9803 m_utf8_decoder.reset();
9804 break;
9805
9806#ifdef WITH_ICU
9807 case DataSyntax::eECMA48_PCTERM:
9808 m_converter->decoder().reset();
9809 break;
9810#endif
9811
9812 default:
9813 g_assert_not_reached()do { g_assertion_message_expr ("BTE", "../src/bte.cc", 9813, (
(const char*) (__PRETTY_FUNCTION__)), __null); } while (0)
;
9814 }
9815}
9816
9817/*
9818 * Terminal::reset:
9819 * @clear_tabstops: whether to reset tabstops
9820 * @clear_history: whether to empty the terminal's scrollback buffer
9821 *
9822 * Resets as much of the terminal's internal state as possible, discarding any
9823 * unprocessed input data, resetting character attributes, cursor state,
9824 * national character set state, status line, terminal modes (insert/delete),
9825 * selection state, and encoding.
9826 *
9827 */
9828void
9829Terminal::reset(bool clear_tabstops,
9830 bool clear_history,
9831 bool from_api)
9832{
9833 if (from_api && !m_input_enabled)
9834 return;
9835
9836 auto const freezer = bte::glib::FreezeObjectNotify{m_terminal};
9837
9838 m_bell_pending = false;
9839
9840 /* Clear the output buffer. */
9841 _bte_byte_array_clear(m_outgoing)g_byte_array_set_size (m_outgoing, 0);
9842
9843 /* Reset charset substitution state. */
9844
9845 /* Reset decoder */
9846 reset_decoder();
9847
9848 /* Reset parser */
9849 m_parser.reset();
9850 m_last_graphic_character = 0;
9851
9852 /* Reset modes */
9853 m_modes_ecma.reset();
9854 m_modes_private.clear_saved();
9855 m_modes_private.reset();
9856
9857 /* Reset tabstops */
9858 if (clear_tabstops) {
9859 m_tabstops.reset();
9860 }
9861
9862 /* Window title stack */
9863 if (clear_history) {
9864 m_window_title_stack.clear();
9865 }
9866
9867 update_mouse_protocol();
9868
9869 /* Reset the color palette. Only the 256 indexed colors, not the special ones, as per xterm. */
9870 for (int i = 0; i < 256; i++)
9871 m_palette[i].sources[BTE_COLOR_SOURCE_ESCAPE0].is_set = FALSE(0);
9872 /* Reset the default attributes. Reset the alternate attribute because
9873 * it's not a real attribute, but we need to treat it as one here. */
9874 reset_default_attributes(true);
9875 /* Reset charset modes. */
9876 m_character_replacements[0] = BTE_CHARACTER_REPLACEMENT_NONE;
9877 m_character_replacements[1] = BTE_CHARACTER_REPLACEMENT_NONE;
9878 m_character_replacement = &m_character_replacements[0];
9879 /* Clear the scrollback buffers and reset the cursors. Switch to normal screen. */
9880 if (clear_history) {
9881 m_screen = &m_normal_screen;
9882 m_normal_screen.scroll_delta = m_normal_screen.insert_delta =
9883 _bte_ring_reset(m_normal_screen.row_data);
9884 m_normal_screen.cursor.row = m_normal_screen.insert_delta;
9885 m_normal_screen.cursor.col = 0;
9886 m_alternate_screen.scroll_delta = m_alternate_screen.insert_delta =
9887 _bte_ring_reset(m_alternate_screen.row_data);
9888 m_alternate_screen.cursor.row = m_alternate_screen.insert_delta;
9889 m_alternate_screen.cursor.col = 0;
9890 /* Adjust the scrollbar to the new location. */
9891 /* Hack: force a change in scroll_delta even if the value remains, so that
9892 bte_term_q_adj_val_changed() doesn't shortcut to no-op, see bug 730599. */
9893 m_screen->scroll_delta = -1;
9894 queue_adjustment_value_changed(m_screen->insert_delta);
9895 adjust_adjustments_full();
9896 }
9897 /* DECSCUSR cursor style */
9898 set_cursor_style(CursorStyle::eTERMINAL_DEFAULT);
9899 /* Reset restricted scrolling regions, leave insert mode, make
9900 * the cursor visible again. */
9901 m_scrolling_restricted = FALSE(0);
9902 /* Reset the visual bits of selection on hard reset, see bug 789954. */
9903 if (clear_history) {
9904 deselect_all();
9905 stop_autoscroll(); /* Required before setting m_selecting to false, see #105. */
9906 m_selecting = FALSE(0);
9907 m_selecting_had_delta = FALSE(0);
9908 m_selection_origin = m_selection_last = { -1, -1, 1 };
9909 m_selection_resolved.clear();
9910 }
9911
9912 /* Reset mouse motion events. */
9913 m_mouse_pressed_buttons = 0;
9914 m_mouse_handled_buttons = 0;
9915 m_mouse_last_position = bte::view::coords(-1, -1);
9916 m_mouse_smooth_scroll_delta = 0.;
9917 /* Clear modifiers. */
9918 m_modifiers = 0;
9919 /* Reset the saved cursor. */
9920 save_cursor(&m_normal_screen);
9921 save_cursor(&m_alternate_screen);
9922 /* BiDi */
9923 m_bidi_rtl = FALSE(0);
9924 /* Cause everything to be redrawn (or cleared). */
9925 invalidate_all();
9926
9927 /* Reset XTerm window controls */
9928 m_xterm_wm_iconified = false;
9929}
9930
9931void
9932Terminal::unset_pty(bool notify_widget)
9933{
9934 /* This may be called from inside or from widget,
9935 * and must notify the widget if not called from it.
9936 */
9937
9938 disconnect_pty_read();
9939 disconnect_pty_write();
9940
9941 m_child_exited_eos_wait_timer.abort();
9942
9943 /* Clear incoming and outgoing queues */
9944 m_input_bytes = 0;
9945 m_incoming_queue = {};
9946 _bte_byte_array_clear(m_outgoing)g_byte_array_set_size (m_outgoing, 0);
9947
9948 stop_processing(this); // FIXMEchpe only if m_incoming_queue.empty() !!!
9949
9950 reset_decoder();
9951
9952 m_pty.reset();
9953
9954 if (notify_widget && widget())
9955 widget()->unset_pty();
9956}
9957
9958bool
9959Terminal::set_pty(bte::base::Pty *new_pty)
9960{
9961 if (pty().get() == new_pty)
9962 return false;
9963
9964 if (pty()) {
9965 unset_pty(false /* don't notify widget */);
9966 }
9967
9968 m_pty = bte::base::make_ref(new_pty);
9969 if (!new_pty)
9970 return true;
9971
9972 set_size(m_column_count, m_row_count);
9973
9974 if (!pty()->set_utf8(data_syntax() == DataSyntax::eECMA48_UTF8)) {
9975 // nothing we can do here
9976 }
9977
9978 /* Open channels to listen for input on. */
9979 connect_pty_read();
9980
9981 return true;
9982}
9983
9984bool
9985Terminal::terminate_child() noexcept
9986{
9987 if (m_pty_pid == -1)
9988 return false;
9989
9990 auto pgrp = getpgid(m_pty_pid);
9991 if (pgrp != -1 && pgrp != getpgid(getpid())) {
9992 kill(-pgrp, SIGHUP1);
9993 }
9994
9995 kill(m_pty_pid, SIGHUP1);
9996 m_pty_pid = -1;
9997
9998 return true;
9999}
10000
10001/* We need this bit of glue to ensure that accessible objects will always
10002 * get signals. */
10003void
10004Terminal::subscribe_accessible_events()
10005{
10006#ifdef WITH_A11Y
10007 m_accessible_emit = true;
10008#endif
10009}
10010
10011void
10012Terminal::select_text(bte::grid::column_t start_col,
10013 bte::grid::row_t start_row,
10014 bte::grid::column_t end_col,
10015 bte::grid::row_t end_row)
10016{
10017 deselect_all();
10018
10019 m_selection_type = SelectionType::eCHAR;
10020 m_selecting_had_delta = true;
10021 m_selection_resolved.set ({ start_row, start_col },
10022 { end_row, end_col });
10023 widget_copy(BTE_SELECTION_PRIMARY, BTE_FORMAT_TEXT);
10024 emit_selection_changed();
10025
10026 invalidate_rows(start_row, end_row);
10027}
10028
10029void
10030Terminal::select_empty(bte::grid::column_t col,
10031 bte::grid::row_t row)
10032{
10033 select_text(col, row, col, row);
10034}
10035
10036static void
10037remove_process_timeout_source(void)
10038{
10039 if (process_timeout_tag == 0)
10040 return;
10041
10042 _bte_debug_print(BTE_DEBUG_TIMEOUT, "Removing process timeout\n")do { } while(0);
10043 g_source_remove (process_timeout_tag);
10044 process_timeout_tag = 0;
10045}
10046
10047static void
10048add_update_timeout(bte::terminal::Terminal* that)
10049{
10050 if (update_timeout_tag == 0) {
10051 _bte_debug_print (BTE_DEBUG_TIMEOUT,do { } while(0)
10052 "Starting update timeout\n")do { } while(0);
10053 update_timeout_tag =
10054 g_timeout_add_full (CDK_PRIORITY_REDRAW(100 + 20),
10055 BTE_UPDATE_TIMEOUT15,
10056 update_timeout, NULL__null,
10057 NULL__null);
10058 }
10059 if (!in_process_timeout) {
10060 remove_process_timeout_source();
10061 }
10062 if (that->m_active_terminals_link == nullptr) {
10063 _bte_debug_print (BTE_DEBUG_TIMEOUT,do { } while(0)
10064 "Adding terminal to active list\n")do { } while(0);
10065 that->m_active_terminals_link = g_active_terminals =
10066 g_list_prepend(g_active_terminals, that);
10067 }
10068}
10069
10070void
10071Terminal::reset_update_rects()
10072{
10073 g_array_set_size(m_update_rects, 0);
10074 m_invalidated_all = FALSE(0);
10075}
10076
10077static bool
10078remove_from_active_list(bte::terminal::Terminal* that)
10079{
10080 if (that->m_active_terminals_link == nullptr ||
10081 that->m_update_rects->len != 0)
10082 return false;
10083
10084 _bte_debug_print(BTE_DEBUG_TIMEOUT, "Removing terminal from active list\n")do { } while(0);
10085 g_active_terminals = g_list_delete_link(g_active_terminals, that->m_active_terminals_link);
10086 that->m_active_terminals_link = nullptr;
10087 return true;
10088}
10089
10090static void
10091stop_processing(bte::terminal::Terminal* that)
10092{
10093 if (!remove_from_active_list(that))
10094 return;
10095
10096 if (g_active_terminals != nullptr)
10097 return;
10098
10099 if (!in_process_timeout) {
10100 remove_process_timeout_source();
10101 }
10102 if (in_update_timeout == FALSE(0) &&
10103 update_timeout_tag != 0) {
10104 _bte_debug_print(BTE_DEBUG_TIMEOUT, "Removing update timeout\n")do { } while(0);
10105 g_source_remove (update_timeout_tag);
10106 update_timeout_tag = 0;
10107 }
10108}
10109
10110static void
10111remove_update_timeout(bte::terminal::Terminal* that)
10112{
10113 that->reset_update_rects();
10114 stop_processing(that);
10115}
10116
10117static void
10118add_process_timeout(bte::terminal::Terminal* that)
10119{
10120 _bte_debug_print(BTE_DEBUG_TIMEOUT,do { } while(0)
10121 "Adding terminal to active list\n")do { } while(0);
10122 that->m_active_terminals_link = g_active_terminals =
10123 g_list_prepend(g_active_terminals, that);
10124 if (update_timeout_tag == 0 &&
10125 process_timeout_tag == 0) {
10126 _bte_debug_print(BTE_DEBUG_TIMEOUT,do { } while(0)
10127 "Starting process timeout\n")do { } while(0);
10128 process_timeout_tag =
10129 g_timeout_add (BTE_DISPLAY_TIMEOUT10,
10130 process_timeout, NULL__null);
10131 }
10132}
10133
10134void
10135Terminal::start_processing()
10136{
10137 if (!is_processing())
10138 add_process_timeout(this);
10139}
10140
10141void
10142Terminal::emit_pending_signals()
10143{
10144 auto const freezer = bte::glib::FreezeObjectNotify{m_terminal};
10145
10146 emit_adjustment_changed();
10147
10148 if (m_window_title_changed) {
10149 if (m_window_title != m_window_title_pending) {
10150 m_window_title.swap(m_window_title_pending);
10151
10152 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10153 "Emitting `window-title-changed'.\n")do { } while(0);
10154 g_signal_emit(freezer.get(), signals[SIGNAL_WINDOW_TITLE_CHANGED], 0);
10155 g_object_notify_by_pspec(freezer.get(), pspecs[PROP_WINDOW_TITLE]);
10156 }
10157
10158 m_window_title_pending.clear();
10159 m_window_title_changed = false;
10160 }
10161
10162 if (m_icon_title_changed) {
10163 if (m_icon_title != m_icon_title_pending) {
10164 m_icon_title.swap(m_icon_title_pending);
10165
10166 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10167 "Emitting `icon-title-changed'.\n")do { } while(0);
10168 g_signal_emit(freezer.get(), signals[SIGNAL_ICON_TITLE_CHANGED], 0);
10169 g_object_notify_by_pspec(freezer.get(), pspecs[PROP_ICON_TITLE]);
10170 }
10171
10172 m_icon_title_pending.clear();
10173 m_icon_title_changed = false;
10174 }
10175
10176 if (m_current_directory_uri_changed) {
10177 if (m_current_directory_uri != m_current_directory_uri_pending) {
10178 m_current_directory_uri.swap(m_current_directory_uri_pending);
10179
10180 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10181 "Emitting `current-directory-uri-changed'.\n")do { } while(0);
10182 g_signal_emit(freezer.get(), signals[SIGNAL_CURRENT_DIRECTORY_URI_CHANGED], 0);
10183 g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_DIRECTORY_URI]);
10184 }
10185
10186 m_current_directory_uri_pending.clear();
10187 m_current_directory_uri_changed = false;
10188 }
10189
10190 if (m_current_file_uri_changed) {
10191 if (m_current_file_uri != m_current_file_uri_pending) {
10192 m_current_file_uri.swap(m_current_file_uri_pending);
10193
10194 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10195 "Emitting `current-file-uri-changed'.\n")do { } while(0);
10196 g_signal_emit(freezer.get(), signals[SIGNAL_CURRENT_FILE_URI_CHANGED], 0);
10197 g_object_notify_by_pspec(freezer.get(), pspecs[PROP_CURRENT_FILE_URI]);
10198 }
10199
10200 m_current_file_uri_pending.clear();
10201 m_current_file_uri_changed = false;
10202 }
10203
10204 /* Flush any pending "inserted" signals. */
10205
10206 if (m_cursor_moved_pending) {
10207 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10208 "Emitting `cursor-moved'.\n")do { } while(0);
10209 g_signal_emit(freezer.get(), signals[SIGNAL_CURSOR_MOVED], 0);
10210 m_cursor_moved_pending = false;
10211 }
10212 if (m_text_modified_flag) {
10213 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10214 "Emitting buffered `text-modified'.\n")do { } while(0);
10215 emit_text_modified();
10216 m_text_modified_flag = false;
10217 }
10218 if (m_text_inserted_flag) {
10219 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10220 "Emitting buffered `text-inserted'\n")do { } while(0);
10221 emit_text_inserted();
10222 m_text_inserted_flag = false;
10223 }
10224 if (m_text_deleted_flag) {
10225 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10226 "Emitting buffered `text-deleted'\n")do { } while(0);
10227 emit_text_deleted();
10228 m_text_deleted_flag = false;
10229 }
10230 if (m_contents_changed_pending) {
10231 /* Update hyperlink and dingus match set. */
10232 match_contents_clear();
10233 if (m_mouse_cursor_over_widget) {
10234 hyperlink_hilite_update();
10235 match_hilite_update();
10236 }
10237
10238 _bte_debug_print(BTE_DEBUG_SIGNALS,do { } while(0)
10239 "Emitting `contents-changed'.\n")do { } while(0);
10240 g_signal_emit(m_terminal, signals[SIGNAL_CONTENTS_CHANGED], 0);
10241 m_contents_changed_pending = false;
10242 }
10243 if (m_bell_pending) {
10244 auto const timestamp = g_get_monotonic_time();
10245 if ((timestamp - m_bell_timestamp) >= BTE_BELL_MINIMUM_TIME_DIFFERENCE(100000)) {
10246 beep();
10247 emit_bell();
10248
10249 m_bell_timestamp = timestamp;
10250 }
10251
10252 m_bell_pending = false;
10253 }
10254
10255 auto const eos = m_eos_pending;
10256 if (m_eos_pending) {
10257 queue_eof();
10258 m_eos_pending = false;
10259
10260 unset_pty();
10261 }
10262
10263 if (m_child_exited_after_eos_pending && eos) {
10264 /* The signal handler could destroy the terminal, so send the signal on idle */
10265 queue_child_exited();
10266 m_child_exited_after_eos_pending = false;
10267 }
10268}
10269
10270void
10271Terminal::time_process_incoming()
10272{
10273 g_timer_reset(process_timer);
10274 process_incoming();
10275 auto elapsed = g_timer_elapsed(process_timer, NULL__null) * 1000;
10276 gssize target = BTE_MAX_PROCESS_TIME100 / elapsed * m_input_bytes;
10277 m_max_input_bytes = (m_max_input_bytes + target) / 2;
10278}
10279
10280bool
10281Terminal::process(bool emit_adj_changed)
10282{
10283 if (pty()) {
10284 if (m_pty_input_active ||
10285 m_pty_input_source == 0) {
10286 m_pty_input_active = false;
10287 /* Do one read directly. FIXMEchpe: Why? */
10288 pty_io_read(pty()->fd(), G_IO_IN);
10289 }
10290 connect_pty_read();
10291 }
10292 if (emit_adj_changed)
10293 emit_adjustment_changed();
10294
10295 bool is_active = !m_incoming_queue.empty();
10296 if (is_active) {
10297 if (BTE_MAX_PROCESS_TIME100) {
10298 time_process_incoming();
10299 } else {
10300 process_incoming();
10301 }
10302 m_input_bytes = 0;
10303 } else
10304 emit_pending_signals();
10305
10306 return is_active;
10307}
10308
10309
10310/* We need to keep a reference to the terminals in the
10311 * g_active_terminals list while iterating over it, since
10312 * in some language bindings the callbacks we emit
10313 * during processing may cause their GC to run, causing
10314 * later elements in this list to be removed from the list.
10315 * See issue bte#270.
10316 */
10317
10318static void
10319unref_active_terminals(GList* list)
10320{
10321 g_list_free_full(list, GDestroyNotify(g_object_unref));
10322}
10323
10324static auto
10325ref_active_terminals() noexcept
10326{
10327 GList* list = nullptr;
10328 for (auto l = g_active_terminals; l != nullptr; l = l->next) {
10329 auto that = reinterpret_cast<bte::terminal::Terminal*>(l->data);
10330 list = g_list_prepend(list, g_object_ref(that->bte_terminal()));
10331 }
10332
10333 return std::unique_ptr<GList, decltype(&unref_active_terminals)>{list, &unref_active_terminals};
10334}
10335
10336/* This function is called after DISPLAY_TIMEOUT ms.
10337 * It makes sure initial output is never delayed by more than DISPLAY_TIMEOUT
10338 */
10339static gboolean
10340process_timeout (gpointer data) noexcept
10341try
10342{
10343 GList *l, *next;
10344 gboolean again;
10345
10346 in_process_timeout = TRUE(!(0));
10347
10348 _bte_debug_print (BTE_DEBUG_WORK, "<")do { } while(0);
10349 _bte_debug_print (BTE_DEBUG_TIMEOUT,do { } while(0)
10350 "Process timeout: %d active\n",do { } while(0)
10351 g_list_length(g_active_terminals))do { } while(0);
10352
10353 auto death_grip = ref_active_terminals();
10354
10355 for (l = g_active_terminals; l != NULL__null; l = next) {
10356 auto that = reinterpret_cast<bte::terminal::Terminal*>(l->data);
10357 bool active;
10358
10359 next = l->next;
10360
10361 if (l != g_active_terminals) {
10362 _bte_debug_print (BTE_DEBUG_WORK, "T")do { } while(0);
10363 }
10364
10365 // FIXMEchpe find out why we don't emit_adjustment_changed() here!!
10366 active = that->process(false);
10367
10368 if (!active) {
10369 remove_from_active_list(that);
10370 }
10371 }
10372
10373 _bte_debug_print (BTE_DEBUG_WORK, ">")do { } while(0);
10374
10375 if (g_active_terminals != nullptr && update_timeout_tag == 0) {
10376 again = TRUE(!(0));
10377 } else {
10378 _bte_debug_print(BTE_DEBUG_TIMEOUT,do { } while(0)
10379 "Stopping process timeout\n")do { } while(0);
10380 process_timeout_tag = 0;
10381 again = FALSE(0);
10382 }
10383
10384 in_process_timeout = FALSE(0);
10385
10386 if (again) {
10387 /* Force us to relinquish the CPU as the child is running
10388 * at full tilt and making us run to keep up...
10389 */
10390 g_usleep (0);
10391 } else if (update_timeout_tag == 0) {
10392 /* otherwise free up memory used to capture incoming data */
10393 bte::base::Chunk::prune();
10394 }
10395
10396 return again;
10397}
10398catch (...)
10399{
10400 bte::log_exception();
10401 return true; // false?
10402}
10403
10404bool
10405Terminal::invalidate_dirty_rects_and_process_updates()
10406{
10407 if (G_UNLIKELY(!widget_realized())(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!widget_realized()) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
10408 return false;
10409
10410 if (G_UNLIKELY (!m_update_rects->len)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
!m_update_rects->len) _g_boolean_var_ = 1; else _g_boolean_var_
= 0; _g_boolean_var_; }), 0))
)
10411 return false;
10412
10413 auto region = cairo_region_create();
10414 auto n_rects = m_update_rects->len;
10415 for (guint i = 0; i < n_rects; i++) {
10416 cairo_rectangle_int_t *rect = &g_array_index(m_update_rects, cairo_rectangle_int_t, i)(((cairo_rectangle_int_t*) (void *) (m_update_rects)->data
) [(i)])
;
10417 cairo_region_union_rectangle(region, rect);
10418 }
10419 g_array_set_size(m_update_rects, 0);
10420 m_invalidated_all = false;
10421
10422 auto allocation = get_allocated_rect();
10423 cairo_region_translate(region,
10424 allocation.x + m_padding.left,
10425 allocation.y + m_padding.top);
10426
10427 /* and perform the merge with the window visible area */
10428 ctk_widget_queue_draw_region(m_widget, region);
10429 cairo_region_destroy (region);
10430
10431 return true;
10432}
10433
10434static gboolean
10435update_repeat_timeout (gpointer data)
10436{
10437 GList *l, *next;
10438 bool again;
10439
10440 in_update_timeout = TRUE(!(0));
10441
10442 _bte_debug_print (BTE_DEBUG_WORK, "[")do { } while(0);
10443 _bte_debug_print (BTE_DEBUG_TIMEOUT,do { } while(0)
10444 "Repeat timeout: %d active\n",do { } while(0)
10445 g_list_length(g_active_terminals))do { } while(0);
10446
10447 auto death_grip = ref_active_terminals();
10448
10449 for (l = g_active_terminals; l != NULL__null; l = next) {
10450 auto that = reinterpret_cast<bte::terminal::Terminal*>(l->data);
10451
10452 next = l->next;
10453
10454 if (l != g_active_terminals) {
10455 _bte_debug_print (BTE_DEBUG_WORK, "T")do { } while(0);
10456 }
10457
10458 that->process(true);
10459
10460 again = that->invalidate_dirty_rects_and_process_updates();
10461 if (!again) {
10462 remove_from_active_list(that);
10463 }
10464 }
10465
10466 _bte_debug_print (BTE_DEBUG_WORK, "]")do { } while(0);
10467
10468 /* We only stop the timer if no update request was received in this
10469 * past cycle. Technically, always stop this timer object and maybe
10470 * reinstall a new one because we need to delay by the amount of time
10471 * it took to repaint the screen: bug 730732.
10472 */
10473 if (g_active_terminals == nullptr) {
10474 _bte_debug_print(BTE_DEBUG_TIMEOUT,do { } while(0)
10475 "Stopping update timeout\n")do { } while(0);
10476 update_timeout_tag = 0;
10477 again = false;
10478 } else {
10479 update_timeout_tag =
10480 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE200,
10481 BTE_UPDATE_REPEAT_TIMEOUT30,
10482 update_repeat_timeout, NULL__null,
10483 NULL__null);
10484 again = true;
10485 }
10486
10487 in_update_timeout = FALSE(0);
10488
10489 if (again) {
10490 /* Force us to relinquish the CPU as the child is running
10491 * at full tilt and making us run to keep up...
10492 */
10493 g_usleep (0);
10494 } else {
10495 /* otherwise free up memory used to capture incoming data */
10496 bte::base::Chunk::prune();
10497 }
10498
10499 return FALSE(0); /* If we need to go again, we already have a new timer for that. */
10500}
10501
10502static gboolean
10503update_timeout (gpointer data) noexcept
10504try
10505{
10506 GList *l, *next;
10507
10508 in_update_timeout = TRUE(!(0));
10509
10510 _bte_debug_print (BTE_DEBUG_WORK, "{")do { } while(0);
10511 _bte_debug_print (BTE_DEBUG_TIMEOUT,do { } while(0)
10512 "Update timeout: %d active\n",do { } while(0)
10513 g_list_length(g_active_terminals))do { } while(0);
10514
10515 remove_process_timeout_source();
10516
10517 for (l = g_active_terminals; l != NULL__null; l = next) {
10518 auto that = reinterpret_cast<bte::terminal::Terminal*>(l->data);
10519
10520 next = l->next;
10521
10522 if (l != g_active_terminals) {
10523 _bte_debug_print (BTE_DEBUG_WORK, "T")do { } while(0);
10524 }
10525
10526 that->process(true);
10527
10528 that->invalidate_dirty_rects_and_process_updates();
10529 }
10530
10531 _bte_debug_print (BTE_DEBUG_WORK, "}")do { } while(0);
10532
10533 /* Set a timer such that we do not invalidate for a while. */
10534 /* This limits the number of times we draw to ~40fps. */
10535 update_timeout_tag =
10536 g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE200,
10537 BTE_UPDATE_REPEAT_TIMEOUT30,
10538 update_repeat_timeout, NULL__null,
10539 NULL__null);
10540 in_update_timeout = FALSE(0);
10541
10542 return FALSE(0);
10543}
10544catch (...)
10545{
10546 bte::log_exception();
10547 return true; // false?
10548}
10549
10550bool
10551Terminal::write_contents_sync (GOutputStream *stream,
10552 BteWriteFlags flags,
10553 GCancellable *cancellable,
10554 GError **error)
10555{
10556 return _bte_ring_write_contents (m_screen->row_data,
10557 stream, flags,
10558 cancellable, error);
10559}
10560
10561/*
10562 * Buffer search
10563 */
10564
10565/* TODO Add properties & signals */
10566
10567/*
10568 * Terminal::search_set_regex:
10569 * @regex: (allow-none): a #BteRegex, or %nullptr
10570 * @flags: PCRE2 match flags, or 0
10571 *
10572 * Sets the regex to search for. Unsets the search regex when passed %nullptr.
10573 */
10574bool
10575Terminal::search_set_regex (bte::base::RefPtr<bte::base::Regex>&& regex,
10576 uint32_t flags)
10577{
10578 if (regex == m_search_regex &&
10579 flags == m_search_regex_match_flags)
10580 return false;
10581
10582 m_search_regex = std::move(regex);
10583 m_search_regex_match_flags = flags;
10584
10585 invalidate_all();
10586
10587 return true;
10588}
10589
10590bool
10591Terminal::search_set_wrap_around(bool wrap)
10592{
10593 if (wrap == m_search_wrap_around)
10594 return false;
10595
10596 m_search_wrap_around = wrap;
10597 return true;
10598}
10599
10600bool
10601Terminal::search_rows(pcre2_match_context_8 *match_context,
10602 pcre2_match_data_8 *match_data,
10603 bte::grid::row_t start_row,
10604 bte::grid::row_t end_row,
10605 bool backward)
10606{
10607 int start, end;
10608 long start_col, end_col;
10609 BteCharAttributes *ca;
10610 GArray *attrs;
10611 gdouble value, page_size;
10612
10613 auto row_text = get_text(start_row, 0,
10614 end_row, 0,
10615 false /* block */,
10616 true /* wrap */,
10617 nullptr);
10618
10619 int (* match_fn) (const pcre2_code_8 *,
10620 PCRE2_SPTR8, PCRE2_SIZEsize_t, PCRE2_SIZEsize_t, uint32_t,
10621 pcre2_match_data_8 *, pcre2_match_context_8 *);
10622 gsize *ovector, so, eo;
10623 int r;
10624
10625 if (m_search_regex->jited())
10626 match_fn = pcre2_jit_match_8;
10627 else
10628 match_fn = pcre2_match_8;
10629
10630 r = match_fn(m_search_regex->code(),
10631 (PCRE2_SPTR8)row_text->str, row_text->len , /* subject, length */
10632 0, /* start offset */
10633 m_search_regex_match_flags |
10634 PCRE2_NO_UTF_CHECK0x40000000u | PCRE2_NOTEMPTY0x00000004u | PCRE2_PARTIAL_SOFT0x00000010u /* FIXME: HARD? */,
10635 match_data,
10636 match_context);
10637
10638 if (r == PCRE2_ERROR_NOMATCH(-1)) {
10639 g_string_free (row_text, TRUE(!(0)));
10640 return false;
10641 }
10642 // FIXME: handle partial matches (PCRE2_ERROR_PARTIAL)
10643 if (r < 0) {
10644 g_string_free (row_text, TRUE(!(0)));
10645 return false;
10646 }
10647
10648 ovector = pcre2_get_ovector_pointer_8(match_data);
10649 so = ovector[0];
10650 eo = ovector[1];
10651 if (G_UNLIKELY(so == PCRE2_UNSET || eo == PCRE2_UNSET)(__builtin_expect (__extension__ ({ int _g_boolean_var_; if (
so == (~(size_t)0) || eo == (~(size_t)0)) _g_boolean_var_ = 1
; else _g_boolean_var_ = 0; _g_boolean_var_; }), 0))
) {
10652 g_string_free (row_text, TRUE(!(0)));
10653 return false;
10654 }
10655
10656 start = so;
10657 end = eo;
10658
10659 /* Fetch text again, with attributes */
10660 g_string_free(row_text, TRUE(!(0)));
10661 if (!m_search_attrs)
10662 m_search_attrs = g_array_new (FALSE(0), TRUE(!(0)), sizeof (BteCharAttributes));
10663 attrs = m_search_attrs;
10664 row_text = get_text(start_row, 0,
10665 end_row, 0,
10666 false /* block */,
10667 true /* wrap */,
10668 attrs);
10669
10670 ca = &g_array_index (attrs, BteCharAttributes, start)(((BteCharAttributes*) (void *) (attrs)->data) [(start)]);
10671 start_row = ca->row;
10672 start_col = ca->column;
10673 ca = &g_array_index (attrs, BteCharAttributes, end - 1)(((BteCharAttributes*) (void *) (attrs)->data) [(end - 1)]
)
;
10674 end_row = ca->row;
10675 end_col = ca->column + ca->columns;
10676
10677 g_string_free (row_text, TRUE(!(0)));
10678
10679 select_text(start_col, start_row, end_col, end_row);
10680 /* Quite possibly the math here should not access adjustment directly... */
10681 value = ctk_adjustment_get_value(m_vadjustment.get());
10682 page_size = ctk_adjustment_get_page_size(m_vadjustment.get());
10683 if (backward) {
10684 if (end_row < value || end_row > value + page_size - 1)
10685 queue_adjustment_value_changed_clamped(end_row - page_size + 1);
10686 } else {
10687 if (start_row < value || start_row > value + page_size - 1)
10688 queue_adjustment_value_changed_clamped(start_row);
10689 }
10690
10691 return true;
10692}
10693
10694bool
10695Terminal::search_rows_iter(pcre2_match_context_8 *match_context,
10696 pcre2_match_data_8 *match_data,
10697 bte::grid::row_t start_row,
10698 bte::grid::row_t end_row,
10699 bool backward)
10700{
10701 const BteRowData *row;
10702 long iter_start_row, iter_end_row;
10703
10704 if (backward) {
10705 iter_start_row = end_row;
10706 while (iter_start_row > start_row) {
10707 iter_end_row = iter_start_row;
10708
10709 do {
10710 iter_start_row--;
10711 row = find_row_data(iter_start_row);
10712 } while (row && row->attr.soft_wrapped);
10713
10714 if (search_rows(match_context, match_data,
10715 iter_start_row, iter_end_row, backward))
10716 return true;
10717 }
10718 } else {
10719 iter_end_row = start_row;
10720 while (iter_end_row < end_row) {
10721 iter_start_row = iter_end_row;
10722
10723 do {
10724 row = find_row_data(iter_end_row);
10725 iter_end_row++;
10726 } while (row && row->attr.soft_wrapped);
10727
10728 if (search_rows(match_context, match_data,
10729 iter_start_row, iter_end_row, backward))
10730 return true;
10731 }
10732 }
10733
10734 return false;
10735}
10736
10737bool
10738Terminal::search_find (bool backward)
10739{
10740 bte::grid::row_t buffer_start_row, buffer_end_row;
10741 bte::grid::row_t last_start_row, last_end_row;
10742 bool match_found = true;
10743
10744 if (!m_search_regex)
10745 return false;
10746
10747 /* TODO
10748 * Currently We only find one result per extended line, and ignore columns
10749 * Moreover, the whole search thing is implemented very inefficiently.
10750 */
10751
10752 auto match_context = create_match_context();
10753 auto match_data = pcre2_match_data_create_8(256 /* should be plenty */, nullptr /* general context */);
10754
10755 buffer_start_row = _bte_ring_delta (m_screen->row_data);
10756 buffer_end_row = _bte_ring_next (m_screen->row_data);
10757
10758 if (!m_selection_resolved.empty()) {
10759 last_start_row = m_selection_resolved.start_row();
10760 last_end_row = m_selection_resolved.end_row() + 1;
10761 } else {
10762 last_start_row = m_screen->scroll_delta + m_row_count;
10763 last_end_row = m_screen->scroll_delta;
10764 }
10765 last_start_row = MAX (buffer_start_row, last_start_row)(((buffer_start_row) > (last_start_row)) ? (buffer_start_row
) : (last_start_row))
;
10766 last_end_row = MIN (buffer_end_row, last_end_row)(((buffer_end_row) < (last_end_row)) ? (buffer_end_row) : (
last_end_row))
;
10767
10768 /* If search fails, we make an empty selection at the last searched
10769 * position... */
10770 if (backward) {
10771 if (search_rows_iter (match_context, match_data,
10772 buffer_start_row, last_start_row, backward))
10773 goto found;
10774 if (m_search_wrap_around &&
10775 search_rows_iter (match_context, match_data,
10776 last_end_row, buffer_end_row, backward))
10777 goto found;
10778 if (!m_selection_resolved.empty()) {
10779 if (m_search_wrap_around)
10780 select_empty(m_selection_resolved.start_column(), m_selection_resolved.start_row());
10781 else
10782 select_empty(-1, buffer_start_row - 1);
10783 }
10784 match_found = false;
10785 } else {
10786 if (search_rows_iter (match_context, match_data,
10787 last_end_row, buffer_end_row, backward))
10788 goto found;
10789 if (m_search_wrap_around &&
10790 search_rows_iter (match_context, match_data,
10791 buffer_start_row, last_start_row, backward))
10792 goto found;
10793 if (!m_selection_resolved.empty()) {
10794 if (m_search_wrap_around)
10795 select_empty(m_selection_resolved.end_column(), m_selection_resolved.end_row());
10796 else
10797 select_empty(0, buffer_end_row);
10798 }
10799 match_found = false;
10800 }
10801
10802 found:
10803
10804 pcre2_match_data_free_8(match_data);
10805 pcre2_match_context_free_8(match_context);
10806
10807 return match_found;
10808}
10809
10810/*
10811 * Terminal::set_input_enabled:
10812 * @enabled: whether to enable user input
10813 *
10814 * Enables or disables user input. When user input is disabled,
10815 * the terminal's child will not receive any key press, or mouse button
10816 * press or motion events sent to it.
10817 *
10818 * Returns: %true iff the setting changed
10819 */
10820bool
10821Terminal::set_input_enabled (bool enabled)
10822{
10823 if (enabled == m_input_enabled)
10824 return false;
10825
10826 m_input_enabled = enabled;
10827
10828 auto context = ctk_widget_get_style_context(m_widget);
10829
10830 /* FIXME: maybe hide cursor when input disabled, too? */
10831
10832 if (enabled) {
10833 if (m_has_focus)
10834 widget()->im_focus_in();
10835
10836 ctk_style_context_remove_class (context, CTK_STYLE_CLASS_READ_ONLY"read-only");
10837 } else {
10838 im_reset();
10839 if (m_has_focus)
10840 widget()->im_focus_out();
10841
10842 disconnect_pty_write();
10843 _bte_byte_array_clear(m_outgoing)g_byte_array_set_size (m_outgoing, 0);
10844
10845 ctk_style_context_add_class (context, CTK_STYLE_CLASS_READ_ONLY"read-only");
10846 }
10847
10848 return true;
10849}
10850
10851std::optional<std::vector<char32_t>>
10852Terminal::process_word_char_exceptions(std::string_view str_view) const noexcept
10853{
10854 auto str = str_view.data();
10855
10856 auto array = std::vector<char32_t>{};
10857 array.reserve(g_utf8_strlen(str, -1));
10858
10859 for (auto const* p = str; *p; p = g_utf8_next_char(p)(char *)((p) + g_utf8_skip[*(const guchar *)(p)])) {
10860 auto const c = g_utf8_get_char(p);
10861
10862 /* For forward compatibility reasons, we skip
10863 * characters that aren't supposed to be here,
10864 * instead of erroring out.
10865 */
10866 /* '-' must only be used* at the start of the string */
10867 if (c == (gunichar)'-' && p != str)
10868 continue;
10869 if (!g_unichar_isgraph(c))
10870 continue;
10871 if (g_unichar_isspace(c))
10872 continue;
10873 if (g_unichar_isalnum(c))
10874 continue;
10875
10876 array.push_back(c);
10877 }
10878
10879 /* Sort the result since we want to use bsearch on it */
10880 std::sort(std::begin(array), std::end(array));
10881
10882 /* Check that no character occurs twice */
10883 for (size_t i = 1; i < array.size(); ++i) {
10884 if (array[i-1] != array[i])
10885 continue;
10886
10887 return std::nullopt;
10888 }
10889
10890#if 0
10891 /* Debug */
10892 for (auto const c : array) {
10893 char utf[7];
10894 utf[g_unichar_to_utf8(c, utf)] = '\0';
10895 g_printerr("Word char exception: U+%04X %s\n", c, utf);
10896 }
10897#endif
10898
10899 return array;
10900}
10901
10902/*
10903 * Terminal::set_word_char_exceptions:
10904 * @exceptions: a string of ASCII punctuation characters, or %nullptr
10905 *
10906 * With this function you can provide a set of characters which will
10907 * be considered parts of a word when doing word-wise selection, in
10908 * addition to the default which only considers alphanumeric characters
10909 * part of a word.
10910 *
10911 * The characters in @exceptions must be non-alphanumeric, each character
10912 * must occur only once, and if @exceptions contains the character
10913 * U+002D HYPHEN-MINUS, it must be at the start of the string.
10914 *
10915 * Use %nullptr to reset the set of exception characters to the default.
10916 *
10917 * Returns: %true if the word char exceptions changed
10918 */
10919bool
10920Terminal::set_word_char_exceptions(std::optional<std::string_view> stropt)
10921{
10922 if (auto array = process_word_char_exceptions(stropt ? stropt.value() : WORD_CHAR_EXCEPTIONS_DEFAULT"-#%&+,./=?@\\_~\302\267"sv)) {
10923 m_word_char_exceptions = *array;
10924 return true;
10925 }
10926
10927 return false;
10928}
10929
10930void
10931Terminal::set_clear_background(bool setting)
10932{
10933 if (m_clear_background == setting)
10934 return;
10935
10936 m_clear_background = setting;
10937 invalidate_all();
10938}
10939
10940} // namespace terminal
10941} // namespace bte

../src/cell.hh

1/*
2 * Copyright (C) 2002 Red Hat, Inc.
3 * Copyright © 2018, 2019 Christian Persch
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library 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 GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20/* The interfaces in this file are subject to change at any time. */
21
22#pragma once
23
24#ifdef __clang__1
25/* Clang generates lots of warnings for the code below. However while
26 * the pointer in the BteCellAttr struct is indeed only 4-byte aligned,
27 * the BteCellAttr is only used when a member of a BteCell, where it is
28 * at offset 4, resulting in a sufficient overall alignment. So we can
29 * safely ignore the warning.
30 */
31#pragma clang diagnostic push
32#pragma clang diagnostic ignored "-Waddress-of-packed-member"
33#endif /* __clang__ */
34
35#include <string.h>
36
37#include "bteunistr.h"
38#include "btemacros.h"
39#include "btedefines.hh"
40
41#include "attr.hh"
42#include "color-triple.hh"
43
44#define BTE_TAB_WIDTH_MAX((1 << (4)) - 1) ((1 << BTE_ATTR_COLUMNS_BITS(4)) - 1)
45
46#define BTE_CELL_ATTR_COMMON_BYTES12 12 /* The number of common bytes in BteCellAttr and BteStreamCellAttr */
47
48/*
49 * BteCellAttr: A single cell style attributes
50 *
51 * When adding new attributes, keep in sync with BteStreamCellAttr and
52 * update BTE_CELL_ATTR_COMMON_BYTES accordingly.
53 * Also don't forget to update basic_cell below!
54 */
55
56#define CELL_ATTR_BOOL(lname,uname) \
57 inline void set_##lname(bool value) \
58 { \
59 bte_attr_set_bool(&attr, BTE_ATTR_##uname##_MASK, value); \
60 } \
61 \
62 inline constexpr bool lname() const \
63 { \
64 return bte_attr_get_bool(attr, BTE_ATTR_##uname##_SHIFT); \
65 }
66
67#define CELL_ATTR_UINT(lname,uname) \
68 inline void set_##lname(unsigned int value) \
69 { \
70 bte_attr_set_value(&attr, BTE_ATTR_##uname##_MASK, BTE_ATTR_##uname##_SHIFT, value); \
71 } \
72 \
73 inline constexpr uint32_t lname() const \
74 { \
75 return bte_attr_get_value(attr, BTE_ATTR_##uname##_VALUE_MASK, BTE_ATTR_##uname##_SHIFT); \
76 }
77
78typedef struct _BTE_GNUC_PACKED__attribute__((__packed__)) BteCellAttr {
79 uint32_t attr;
80
81 /* 4-byte boundary (8-byte boundary in BteCell) */
82 uint64_t m_colors; /* fore, back and deco (underline) colour */
83
84 /* 12-byte boundary (16-byte boundary in BteCell) */
85 uint32_t hyperlink_idx; /* a unique hyperlink index at a time for the ring's cells,
86 0 means not a hyperlink, BTE_HYPERLINK_IDX_TARGET_IN_STREAM
87 means the target is irrelevant/unknown at the moment.
88 If bitpacking, choose a size big enough to hold a different idx
89 for every cell in the ring but not yet in the stream
90 (currently the height rounded up to the next power of two, times width)
91 for supported BTE sizes, and update BTE_HYPERLINK_IDX_TARGET_IN_STREAM. */
92
93 /* Methods */
94
95 inline constexpr uint64_t colors() const { return m_colors; }
96
97 inline void copy_colors(BteCellAttr const& other)
98 {
99 m_colors = bte_color_triple_copy(other.colors());
100 }
101
102#define CELL_ATTR_COLOR(name) \
103 inline void set_##name(uint32_t value) \
104 { \
105 bte_color_triple_set_##name(&m_colors, value); \
106 } \
107 \
108 inline constexpr uint32_t name() const \
109 { \
110 return bte_color_triple_get_##name(m_colors); \
111 }
112
113 CELL_ATTR_COLOR(fore)
52
Calling 'bte_color_triple_set_fore'
114 CELL_ATTR_COLOR(back)
115 CELL_ATTR_COLOR(deco)
116#undef CELL_ATTR_COLOR
117
118 inline constexpr bool has_any(uint32_t mask) const
119 {
120 return !!(attr & mask);
121 }
122
123 inline constexpr bool has_all(uint32_t mask) const
124 {
125 return (attr & mask) == mask;
126 }
127
128 inline constexpr bool has_none(uint32_t mask) const
129 {
130 return !(attr & mask);
131 }
132
133 inline void unset(uint32_t mask)
134 {
135 attr &= ~mask;
136 }
137
138 CELL_ATTR_UINT(columns, COLUMNS)
139 CELL_ATTR_BOOL(fragment, FRAGMENT)
140 CELL_ATTR_BOOL(bold, BOLD)
141 CELL_ATTR_BOOL(italic, ITALIC)
142 CELL_ATTR_UINT(underline, UNDERLINE)
143 CELL_ATTR_BOOL(strikethrough, STRIKETHROUGH)
144 CELL_ATTR_BOOL(overline, OVERLINE)
145 CELL_ATTR_BOOL(reverse, REVERSE)
146 CELL_ATTR_BOOL(blink, BLINK)
147 CELL_ATTR_BOOL(dim, DIM)
148 CELL_ATTR_BOOL(invisible, INVISIBLE)
149 /* ATTR_BOOL(boxed, BOXED) */
150} BteCellAttr;
151static_assert(sizeof (BteCellAttr) == 16, "BteCellAttr has wrong size");
152static_assert(offsetof (BteCellAttr, hyperlink_idx)__builtin_offsetof(BteCellAttr, hyperlink_idx) == BTE_CELL_ATTR_COMMON_BYTES12, "BteCellAttr layout is wrong");
153
154/*
155 * BteStreamCellAttr: Variant of BteCellAttr to be stored in attr_stream.
156 *
157 * When adding new attributes, keep in sync with BteCellAttr and
158 * update BTE_CELL_ATTR_COMMON_BYTES accordingly.
159 */
160
161typedef struct _BTE_GNUC_PACKED__attribute__((__packed__)) _BteStreamCellAttr {
162 uint32_t attr; /* Same as BteCellAttr. We only access columns
163 * and fragment, however.
164 */
165 /* 4-byte boundary */
166 uint64_t colors;
167 /* 12-byte boundary */
168 guint16 hyperlink_length; /* make sure it fits BTE_HYPERLINK_TOTAL_LENGTH_MAX */
169
170 /* Methods */
171 CELL_ATTR_UINT(columns, COLUMNS)
172 CELL_ATTR_BOOL(fragment, FRAGMENT)
173} BteStreamCellAttr;
174static_assert(sizeof (BteStreamCellAttr) == 14, "BteStreamCellAttr has wrong size");
175static_assert(offsetof (BteStreamCellAttr, hyperlink_length)__builtin_offsetof(BteStreamCellAttr, hyperlink_length) == BTE_CELL_ATTR_COMMON_BYTES12, "BteStreamCellAttr layout is wrong");
176
177#undef CELL_ATTR_BOOL
178#undef CELL_ATTR_UINT
179
180/*
181 * BteCell: A single cell's data
182 */
183
184typedef struct _BTE_GNUC_PACKED__attribute__((__packed__)) _BteCell {
185 bteunistr c;
186 BteCellAttr attr;
187} BteCell;
188
189static_assert(sizeof (BteCell) == 20, "BteCell has wrong size");
190
191static const BteCell basic_cell = {
192 0,
193 {
194 BTE_ATTR_DEFAULT(((1) << (0))), /* attr */
195 BTE_COLOR_TRIPLE_INIT_DEFAULT((uint64_t(256) << (0) | uint64_t(257) << (25) | uint64_t
(256) << (50)))
, /* colors */
196 0, /* hyperlink_idx */
197 }
198};
199
200#ifdef __clang__1
201#pragma clang diagnostic pop
202#endif

../src/color-triple.hh

1/*
2 * Copyright © 2018 Christian Persch
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#pragma once
19
20#include <cstdint>
21
22typedef uint64_t bte_color_triple_t;
23
24#define BTE_COLOR_TRIPLE_FORE_SHIFT(0) (0)
25#define BTE_COLOR_TRIPLE_BACK_SHIFT(25) (25)
26#define BTE_COLOR_TRIPLE_DECO_SHIFT(50) (50)
27
28#define BTE_COLOR_TRIPLE_RGB_MASK(rb,gb,bb)((1ULL << ((rb) + (gb) + (bb) + 1)) - 1) ((1ULL << ((rb) + (gb) + (bb) + 1)) - 1)
29#define BTE_COLOR_TRIPLE_FORE_MASK(((1ULL << ((8) + (8) + (8) + 1)) - 1) << (0)) (BTE_COLOR_TRIPLE_RGB_MASK(8, 8, 8)((1ULL << ((8) + (8) + (8) + 1)) - 1) << BTE_COLOR_TRIPLE_FORE_SHIFT(0))
30#define BTE_COLOR_TRIPLE_BACK_MASK(((1ULL << ((8) + (8) + (8) + 1)) - 1) << (25)) (BTE_COLOR_TRIPLE_RGB_MASK(8, 8, 8)((1ULL << ((8) + (8) + (8) + 1)) - 1) << BTE_COLOR_TRIPLE_BACK_SHIFT(25))
31#define BTE_COLOR_TRIPLE_DECO_MASK(((1ULL << ((4) + (5) + (4) + 1)) - 1) << (50)) (BTE_COLOR_TRIPLE_RGB_MASK(4, 5, 4)((1ULL << ((4) + (5) + (4) + 1)) - 1) << BTE_COLOR_TRIPLE_DECO_SHIFT(50))
32
33#define BTE_COLOR_TRIPLE_INIT(fg,bg,dc)(uint64_t(fg) << (0) | uint64_t(bg) << (25) | uint64_t
(dc) << (50))
(uint64_t(fg) << BTE_COLOR_TRIPLE_FORE_SHIFT(0) | \
34 uint64_t(bg) << BTE_COLOR_TRIPLE_BACK_SHIFT(25) | \
35 uint64_t(dc) << BTE_COLOR_TRIPLE_DECO_SHIFT(50))
36
37#define BTE_COLOR_TRIPLE_INIT_DEFAULT((uint64_t(256) << (0) | uint64_t(257) << (25) | uint64_t
(256) << (50)))
(BTE_COLOR_TRIPLE_INIT(BTE_DEFAULT_FG, \(uint64_t(256) << (0) | uint64_t(257) << (25) | uint64_t
(256) << (50))
38 BTE_DEFAULT_BG, \(uint64_t(256) << (0) | uint64_t(257) << (25) | uint64_t
(256) << (50))
39 BTE_DEFAULT_FG)(uint64_t(256) << (0) | uint64_t(257) << (25) | uint64_t
(256) << (50))
)
40
41static constexpr inline bte_color_triple_t bte_color_triple_init(void)
42{
43 return BTE_COLOR_TRIPLE_INIT_DEFAULT((uint64_t(256) << (0) | uint64_t(257) << (25) | uint64_t
(256) << (50)))
;
44}
45
46static constexpr inline bte_color_triple_t bte_color_triple_copy(bte_color_triple_t ct)
47{
48 return ct;
49}
50
51static inline void bte_color_triple_set_fore(bte_color_triple_t* ct,
52 uint32_t fore)
53{
54 *ct = (*ct & ~BTE_COLOR_TRIPLE_FORE_MASK(((1ULL << ((8) + (8) + (8) + 1)) - 1) << (0))) | (uint64_t(fore)) << BTE_COLOR_TRIPLE_FORE_SHIFT(0);
53
The left operand of '&' is a garbage value
55}
56
57static inline void bte_color_triple_set_back(bte_color_triple_t* ct,
58 uint32_t back)
59{
60 *ct = (*ct & ~BTE_COLOR_TRIPLE_BACK_MASK(((1ULL << ((8) + (8) + (8) + 1)) - 1) << (25))) | (uint64_t(back)) << BTE_COLOR_TRIPLE_BACK_SHIFT(25);
61}
62
63static inline void bte_color_triple_set_deco(bte_color_triple_t* ct,
64 uint32_t deco)
65{
66 *ct = (*ct & ~BTE_COLOR_TRIPLE_DECO_MASK(((1ULL << ((4) + (5) + (4) + 1)) - 1) << (50))) | (uint64_t(deco)) << BTE_COLOR_TRIPLE_DECO_SHIFT(50);
67}
68
69static inline constexpr uint32_t bte_color_triple_get_fore(bte_color_triple_t ct)
70{
71 return uint32_t((ct >> BTE_COLOR_TRIPLE_FORE_SHIFT(0)) & BTE_COLOR_TRIPLE_RGB_MASK(8, 8, 8)((1ULL << ((8) + (8) + (8) + 1)) - 1));
72}
73
74static inline constexpr uint32_t bte_color_triple_get_back(bte_color_triple_t ct)
75{
76 return uint32_t((ct >> BTE_COLOR_TRIPLE_BACK_SHIFT(25)) & BTE_COLOR_TRIPLE_RGB_MASK(8, 8, 8)((1ULL << ((8) + (8) + (8) + 1)) - 1));
77}
78
79static inline constexpr uint32_t bte_color_triple_get_deco(bte_color_triple_t ct)
80{
81 return uint32_t((ct >> BTE_COLOR_TRIPLE_DECO_SHIFT(50)) & BTE_COLOR_TRIPLE_RGB_MASK(4, 5, 4)((1ULL << ((4) + (5) + (4) + 1)) - 1));
82}
83
84static inline void bte_color_triple_get(bte_color_triple_t ct,
85 uint32_t* fore,
86 uint32_t* back,
87 uint32_t* deco)
88{
89 *fore = bte_color_triple_get_fore(ct);
90 *back = bte_color_triple_get_back(ct);
91 *deco = bte_color_triple_get_deco(ct);
92}