GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/tmux/screen-redraw.c Lines: 0 307 0.0 %
Date: 2017-11-13 Branches: 0 272 0.0 %

Line Branch Exec Source
1
/* $OpenBSD: screen-redraw.c,v 1.47 2017/10/16 19:30:53 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
21
#include <string.h>
22
23
#include "tmux.h"
24
25
static int	screen_redraw_cell_border1(struct window_pane *, u_int, u_int);
26
static int	screen_redraw_cell_border(struct client *, u_int, u_int);
27
static int	screen_redraw_check_cell(struct client *, u_int, u_int, int,
28
		    struct window_pane **);
29
static int	screen_redraw_check_is(u_int, u_int, int, int, struct window *,
30
		    struct window_pane *, struct window_pane *);
31
32
static int 	screen_redraw_make_pane_status(struct client *, struct window *,
33
		    struct window_pane *);
34
static void	screen_redraw_draw_pane_status(struct client *, int);
35
36
static void	screen_redraw_draw_borders(struct client *, int, u_int, u_int);
37
static void	screen_redraw_draw_panes(struct client *, u_int, u_int);
38
static void	screen_redraw_draw_status(struct client *, u_int, u_int);
39
static void	screen_redraw_draw_number(struct client *, struct window_pane *,
40
		    u_int, u_int);
41
42
#define CELL_INSIDE 0
43
#define CELL_LEFTRIGHT 1
44
#define CELL_TOPBOTTOM 2
45
#define CELL_TOPLEFT 3
46
#define CELL_TOPRIGHT 4
47
#define CELL_BOTTOMLEFT 5
48
#define CELL_BOTTOMRIGHT 6
49
#define CELL_TOPJOIN 7
50
#define CELL_BOTTOMJOIN 8
51
#define CELL_LEFTJOIN 9
52
#define CELL_RIGHTJOIN 10
53
#define CELL_JOIN 11
54
#define CELL_OUTSIDE 12
55
56
#define CELL_BORDERS " xqlkmjwvtun~"
57
58
#define CELL_STATUS_OFF 0
59
#define CELL_STATUS_TOP 1
60
#define CELL_STATUS_BOTTOM 2
61
62
/* Check if cell is on the border of a particular pane. */
63
static int
64
screen_redraw_cell_border1(struct window_pane *wp, u_int px, u_int py)
65
{
66
	/* Inside pane. */
67
	if (px >= wp->xoff && px < wp->xoff + wp->sx &&
68
	    py >= wp->yoff && py < wp->yoff + wp->sy)
69
		return (0);
70
71
	/* Left/right borders. */
72
	if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= wp->yoff + wp->sy) {
73
		if (wp->xoff != 0 && px == wp->xoff - 1)
74
			return (1);
75
		if (px == wp->xoff + wp->sx)
76
			return (2);
77
	}
78
79
	/* Top/bottom borders. */
80
	if ((wp->xoff == 0 || px >= wp->xoff - 1) && px <= wp->xoff + wp->sx) {
81
		if (wp->yoff != 0 && py == wp->yoff - 1)
82
			return (3);
83
		if (py == wp->yoff + wp->sy)
84
			return (4);
85
	}
86
87
	/* Outside pane. */
88
	return (-1);
89
}
90
91
/* Check if a cell is on the pane border. */
92
static int
93
screen_redraw_cell_border(struct client *c, u_int px, u_int py)
94
{
95
	struct window		*w = c->session->curw->window;
96
	struct window_pane	*wp;
97
	int			 retval;
98
99
	/* Check all the panes. */
100
	TAILQ_FOREACH(wp, &w->panes, entry) {
101
		if (!window_pane_visible(wp))
102
			continue;
103
		if ((retval = screen_redraw_cell_border1(wp, px, py)) != -1)
104
			return (!!retval);
105
	}
106
107
	return (0);
108
}
109
110
/* Check if cell inside a pane. */
111
static int
112
screen_redraw_check_cell(struct client *c, u_int px, u_int py, int pane_status,
113
    struct window_pane **wpp)
114
{
115
	struct window		*w = c->session->curw->window;
116
	struct window_pane	*wp;
117
	int			 borders;
118
	u_int			 right, line;
119
120
	*wpp = NULL;
121
122
	if (px > w->sx || py > w->sy)
123
		return (CELL_OUTSIDE);
124
125
	if (pane_status != CELL_STATUS_OFF) {
126
		TAILQ_FOREACH(wp, &w->panes, entry) {
127
			if (!window_pane_visible(wp))
128
				continue;
129
130
			if (pane_status == CELL_STATUS_TOP)
131
				line = wp->yoff - 1;
132
			else
133
				line = wp->yoff + wp->sy;
134
			right = wp->xoff + 2 + wp->status_size - 1;
135
136
			if (py == line && px >= wp->xoff + 2 && px <= right)
137
				return (CELL_INSIDE);
138
		}
139
	}
140
141
	TAILQ_FOREACH(wp, &w->panes, entry) {
142
		if (!window_pane_visible(wp))
143
			continue;
144
		*wpp = wp;
145
146
		/* If outside the pane and its border, skip it. */
147
		if ((wp->xoff != 0 && px < wp->xoff - 1) ||
148
		    px > wp->xoff + wp->sx ||
149
		    (wp->yoff != 0 && py < wp->yoff - 1) ||
150
		    py > wp->yoff + wp->sy)
151
			continue;
152
153
		/* If definitely inside, return so. */
154
		if (!screen_redraw_cell_border(c, px, py))
155
			return (CELL_INSIDE);
156
157
		/*
158
		 * Construct a bitmask of whether the cells to the left (bit
159
		 * 4), right, top, and bottom (bit 1) of this cell are borders.
160
		 */
161
		borders = 0;
162
		if (px == 0 || screen_redraw_cell_border(c, px - 1, py))
163
			borders |= 8;
164
		if (px <= w->sx && screen_redraw_cell_border(c, px + 1, py))
165
			borders |= 4;
166
		if (pane_status == CELL_STATUS_TOP) {
167
			if (py != 0 && screen_redraw_cell_border(c, px, py - 1))
168
				borders |= 2;
169
		} else {
170
			if (py == 0 || screen_redraw_cell_border(c, px, py - 1))
171
				borders |= 2;
172
		}
173
		if (py <= w->sy && screen_redraw_cell_border(c, px, py + 1))
174
			borders |= 1;
175
176
		/*
177
		 * Figure out what kind of border this cell is. Only one bit
178
		 * set doesn't make sense (can't have a border cell with no
179
		 * others connected).
180
		 */
181
		switch (borders) {
182
		case 15:	/* 1111, left right top bottom */
183
			return (CELL_JOIN);
184
		case 14:	/* 1110, left right top */
185
			return (CELL_BOTTOMJOIN);
186
		case 13:	/* 1101, left right bottom */
187
			return (CELL_TOPJOIN);
188
		case 12:	/* 1100, left right */
189
			return (CELL_TOPBOTTOM);
190
		case 11:	/* 1011, left top bottom */
191
			return (CELL_RIGHTJOIN);
192
		case 10:	/* 1010, left top */
193
			return (CELL_BOTTOMRIGHT);
194
		case 9:		/* 1001, left bottom */
195
			return (CELL_TOPRIGHT);
196
		case 7:		/* 0111, right top bottom */
197
			return (CELL_LEFTJOIN);
198
		case 6:		/* 0110, right top */
199
			return (CELL_BOTTOMLEFT);
200
		case 5:		/* 0101, right bottom */
201
			return (CELL_TOPLEFT);
202
		case 3:		/* 0011, top bottom */
203
			return (CELL_LEFTRIGHT);
204
		}
205
	}
206
207
	return (CELL_OUTSIDE);
208
}
209
210
/* Check if the border of a particular pane. */
211
static int
212
screen_redraw_check_is(u_int px, u_int py, int type, int pane_status,
213
    struct window *w, struct window_pane *wantwp, struct window_pane *wp)
214
{
215
	int	border;
216
217
	/* Is this off the active pane border? */
218
	border = screen_redraw_cell_border1(wantwp, px, py);
219
	if (border == 0 || border == -1)
220
		return (0);
221
	if (pane_status == CELL_STATUS_TOP && border == 4)
222
		return (0);
223
	if (pane_status == CELL_STATUS_BOTTOM && border == 3)
224
		return (0);
225
226
	/* If there are more than two panes, that's enough. */
227
	if (window_count_panes(w) != 2)
228
		return (1);
229
230
	/* Else if the cell is not a border cell, forget it. */
231
	if (wp == NULL || (type == CELL_OUTSIDE || type == CELL_INSIDE))
232
		return (1);
233
234
	/* With status lines mark the entire line. */
235
	if (pane_status != CELL_STATUS_OFF)
236
		return (1);
237
238
	/* Check if the pane covers the whole width. */
239
	if (wp->xoff == 0 && wp->sx == w->sx) {
240
		/* This can either be the top pane or the bottom pane. */
241
		if (wp->yoff == 0) { /* top pane */
242
			if (wp == wantwp)
243
				return (px <= wp->sx / 2);
244
			return (px > wp->sx / 2);
245
		}
246
		return (0);
247
	}
248
249
	/* Check if the pane covers the whole height. */
250
	if (wp->yoff == 0 && wp->sy == w->sy) {
251
		/* This can either be the left pane or the right pane. */
252
		if (wp->xoff == 0) { /* left pane */
253
			if (wp == wantwp)
254
				return (py <= wp->sy / 2);
255
			return (py > wp->sy / 2);
256
		}
257
		return (0);
258
	}
259
260
	return (1);
261
}
262
263
/* Update pane status. */
264
static int
265
screen_redraw_make_pane_status(struct client *c, struct window *w,
266
    struct window_pane *wp)
267
{
268
	struct grid_cell	 gc;
269
	const char		*fmt;
270
	struct format_tree	*ft;
271
	char			*out;
272
	size_t			 outlen;
273
	struct screen_write_ctx	 ctx;
274
	struct screen		 old;
275
276
	if (wp == w->active)
277
		style_apply(&gc, w->options, "pane-active-border-style");
278
	else
279
		style_apply(&gc, w->options, "pane-border-style");
280
281
	fmt = options_get_string(w->options, "pane-border-format");
282
283
	ft = format_create(c, NULL, FORMAT_PANE|wp->id, 0);
284
	format_defaults(ft, c, NULL, NULL, wp);
285
286
	memcpy(&old, &wp->status_screen, sizeof old);
287
	screen_init(&wp->status_screen, wp->sx, 1, 0);
288
	wp->status_screen.mode = 0;
289
290
	out = format_expand(ft, fmt);
291
	outlen = screen_write_cstrlen("%s", out);
292
	if (outlen > wp->sx - 4)
293
		outlen = wp->sx - 4;
294
	screen_resize(&wp->status_screen, outlen, 1, 0);
295
296
	screen_write_start(&ctx, NULL, &wp->status_screen);
297
	screen_write_cursormove(&ctx, 0, 0);
298
	screen_write_clearline(&ctx, 8);
299
	screen_write_cnputs(&ctx, outlen, &gc, "%s", out);
300
	screen_write_stop(&ctx);
301
302
	format_free(ft);
303
304
	wp->status_size = outlen;
305
306
	if (grid_compare(wp->status_screen.grid, old.grid) == 0) {
307
		screen_free(&old);
308
		return (0);
309
	}
310
	screen_free(&old);
311
	return (1);
312
}
313
314
/* Draw pane status. */
315
static void
316
screen_redraw_draw_pane_status(struct client *c, int pane_status)
317
{
318
	struct window		*w = c->session->curw->window;
319
	struct options		*oo = c->session->options;
320
	struct tty		*tty = &c->tty;
321
	struct window_pane	*wp;
322
	int			 spos;
323
	u_int			 yoff;
324
325
	spos = options_get_number(oo, "status-position");
326
	TAILQ_FOREACH(wp, &w->panes, entry) {
327
		if (!window_pane_visible(wp))
328
			continue;
329
		if (pane_status == CELL_STATUS_TOP)
330
			yoff = wp->yoff - 1;
331
		else
332
			yoff = wp->yoff + wp->sy;
333
		if (spos == 0)
334
			yoff += 1;
335
336
		tty_draw_line(tty, NULL, &wp->status_screen, 0, wp->xoff + 2,
337
		    yoff);
338
	}
339
	tty_cursor(tty, 0, 0);
340
}
341
342
/* Update status line and change flags if unchanged. */
343
void
344
screen_redraw_update(struct client *c)
345
{
346
	struct window		*w = c->session->curw->window;
347
	struct window_pane	*wp;
348
	struct options		*wo = w->options;
349
	int			 redraw;
350
351
	if (c->message_string != NULL)
352
		redraw = status_message_redraw(c);
353
	else if (c->prompt_string != NULL)
354
		redraw = status_prompt_redraw(c);
355
	else
356
		redraw = status_redraw(c);
357
	if (!redraw)
358
		c->flags &= ~CLIENT_STATUS;
359
360
	if (options_get_number(wo, "pane-border-status") != CELL_STATUS_OFF) {
361
		redraw = 0;
362
		TAILQ_FOREACH(wp, &w->panes, entry) {
363
			if (screen_redraw_make_pane_status(c, w, wp))
364
				redraw = 1;
365
		}
366
		if (redraw)
367
			c->flags |= CLIENT_BORDERS;
368
	}
369
}
370
371
/* Redraw entire screen. */
372
void
373
screen_redraw_screen(struct client *c, int draw_panes, int draw_status,
374
    int draw_borders)
375
{
376
	struct options		*oo = c->session->options;
377
	struct tty		*tty = &c->tty;
378
	struct window		*w = c->session->curw->window;
379
	struct options		*wo = w->options;
380
	u_int			 top, lines;
381
	int	 		 position, pane_status;
382
383
	if (c->flags & CLIENT_SUSPENDED)
384
		return;
385
386
	if (c->flags & CLIENT_STATUSOFF)
387
		lines = 0;
388
	else
389
		lines = status_line_size(c->session);
390
	if (c->message_string != NULL || c->prompt_string != NULL)
391
		lines = (lines == 0) ? 1 : lines;
392
393
	position = options_get_number(oo, "status-position");
394
	if (lines != 0 && position == 0)
395
		top = 1;
396
	else
397
		top = 0;
398
399
	if (lines == 0)
400
		draw_status = 0;
401
402
	if (draw_borders) {
403
		pane_status = options_get_number(wo, "pane-border-status");
404
		screen_redraw_draw_borders(c, pane_status, lines, top);
405
		if (pane_status != CELL_STATUS_OFF)
406
			screen_redraw_draw_pane_status(c, pane_status);
407
	}
408
	if (draw_panes)
409
		screen_redraw_draw_panes(c, lines, top);
410
	if (draw_status)
411
		screen_redraw_draw_status(c, lines, top);
412
	tty_reset(tty);
413
}
414
415
/* Draw a single pane. */
416
void
417
screen_redraw_pane(struct client *c, struct window_pane *wp)
418
{
419
	u_int	i, yoff;
420
421
	if (!window_pane_visible(wp))
422
		return;
423
424
	yoff = wp->yoff;
425
	if (status_at_line(c) == 0)
426
		yoff += status_line_size(c->session);
427
428
	log_debug("%s: redraw pane %%%u (at %u,%u)", c->name, wp->id,
429
	    wp->xoff, yoff);
430
431
	for (i = 0; i < wp->sy; i++)
432
		tty_draw_pane(&c->tty, wp, i, wp->xoff, yoff);
433
	tty_reset(&c->tty);
434
}
435
436
/* Draw the borders. */
437
static void
438
screen_redraw_draw_borders(struct client *c, int pane_status, u_int lines,
439
    u_int top)
440
{
441
	struct session		*s = c->session;
442
	struct window		*w = s->curw->window;
443
	struct options		*oo = w->options;
444
	struct tty		*tty = &c->tty;
445
	struct window_pane	*wp;
446
	struct grid_cell	 m_active_gc, active_gc, m_other_gc, other_gc;
447
	struct grid_cell	 msg_gc;
448
	u_int		 	 i, j, type, msgx = 0, msgy = 0;
449
	int			 active, small, flags;
450
	char			 msg[256];
451
	const char		*tmp;
452
	size_t			 msglen = 0;
453
454
	small = (tty->sy - lines + top > w->sy) || (tty->sx > w->sx);
455
	if (small) {
456
		flags = w->flags & (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT);
457
		if (flags == (WINDOW_FORCEWIDTH|WINDOW_FORCEHEIGHT))
458
			tmp = "force-width, force-height";
459
		else if (flags == WINDOW_FORCEWIDTH)
460
			tmp = "force-width";
461
		else if (flags == WINDOW_FORCEHEIGHT)
462
			tmp = "force-height";
463
		else if (c->flags & CLIENT_STATUSOFF)
464
			tmp = "status line";
465
		else
466
			tmp = "a smaller client";
467
		xsnprintf(msg, sizeof msg, "(size %ux%u from %s)",
468
		    w->sx, w->sy, tmp);
469
		msglen = strlen(msg);
470
471
		if (tty->sy - 1 - lines + top > w->sy && tty->sx >= msglen) {
472
			msgx = tty->sx - msglen;
473
			msgy = tty->sy - 1 - lines + top;
474
		} else if (tty->sx - w->sx > msglen) {
475
			msgx = tty->sx - msglen;
476
			msgy = tty->sy - 1 - lines + top;
477
		} else
478
			small = 0;
479
	}
480
481
	style_apply(&other_gc, oo, "pane-border-style");
482
	style_apply(&active_gc, oo, "pane-active-border-style");
483
	active_gc.attr = other_gc.attr = GRID_ATTR_CHARSET;
484
485
	memcpy(&m_other_gc, &other_gc, sizeof m_other_gc);
486
	m_other_gc.attr ^= GRID_ATTR_REVERSE;
487
	memcpy(&m_active_gc, &active_gc, sizeof m_active_gc);
488
	m_active_gc.attr ^= GRID_ATTR_REVERSE;
489
490
	for (j = 0; j < tty->sy - lines; j++) {
491
		for (i = 0; i < tty->sx; i++) {
492
			type = screen_redraw_check_cell(c, i, j, pane_status,
493
			    &wp);
494
			if (type == CELL_INSIDE)
495
				continue;
496
			if (type == CELL_OUTSIDE && small &&
497
			    i > msgx && j == msgy)
498
				continue;
499
			active = screen_redraw_check_is(i, j, type, pane_status,
500
			    w, w->active, wp);
501
			if (server_is_marked(s, s->curw, marked_pane.wp) &&
502
			    screen_redraw_check_is(i, j, type, pane_status, w,
503
			    marked_pane.wp, wp)) {
504
				if (active)
505
					tty_attributes(tty, &m_active_gc, NULL);
506
				else
507
					tty_attributes(tty, &m_other_gc, NULL);
508
			} else if (active)
509
				tty_attributes(tty, &active_gc, NULL);
510
			else
511
				tty_attributes(tty, &other_gc, NULL);
512
			if (top)
513
				tty_cursor(tty, i, lines + j);
514
			else
515
				tty_cursor(tty, i, j);
516
			tty_putc(tty, CELL_BORDERS[type]);
517
		}
518
	}
519
520
	if (small) {
521
		memcpy(&msg_gc, &grid_default_cell, sizeof msg_gc);
522
		tty_attributes(tty, &msg_gc, NULL);
523
		tty_cursor(tty, msgx, msgy);
524
		tty_puts(tty, msg);
525
	}
526
}
527
528
/* Draw the panes. */
529
static void
530
screen_redraw_draw_panes(struct client *c, u_int lines, u_int top)
531
{
532
	struct window		*w = c->session->curw->window;
533
	struct tty		*tty = &c->tty;
534
	struct window_pane	*wp;
535
	u_int		 	 i, y;
536
537
	if (top)
538
		y = lines;
539
	else
540
		y = 0;
541
542
	TAILQ_FOREACH(wp, &w->panes, entry) {
543
		if (!window_pane_visible(wp))
544
			continue;
545
		for (i = 0; i < wp->sy; i++)
546
			tty_draw_pane(tty, wp, i, wp->xoff, y + wp->yoff);
547
		if (c->flags & CLIENT_IDENTIFY)
548
			screen_redraw_draw_number(c, wp, lines, top);
549
	}
550
}
551
552
/* Draw the status line. */
553
static void
554
screen_redraw_draw_status(struct client *c, u_int lines, u_int top)
555
{
556
	struct tty	*tty = &c->tty;
557
	u_int		 i, y;
558
559
	if (top)
560
		y = 0;
561
	else
562
		y = tty->sy - lines;
563
	for (i = 0; i < lines; i++)
564
		tty_draw_line(tty, NULL, &c->status, i, 0, y);
565
}
566
567
/* Draw number on a pane. */
568
static void
569
screen_redraw_draw_number(struct client *c, struct window_pane *wp,
570
    u_int lines, u_int top)
571
{
572
	struct tty		*tty = &c->tty;
573
	struct session		*s = c->session;
574
	struct options		*oo = s->options;
575
	struct window		*w = wp->window;
576
	struct grid_cell	 gc;
577
	u_int			 idx, px, py, i, j, xoff, yoff;
578
	int			 colour, active_colour;
579
	char			 buf[16], *ptr;
580
	size_t			 len;
581
582
	if (window_pane_index(wp, &idx) != 0)
583
		fatalx("index not found");
584
	len = xsnprintf(buf, sizeof buf, "%u", idx);
585
586
	if (wp->sx < len)
587
		return;
588
	colour = options_get_number(oo, "display-panes-colour");
589
	active_colour = options_get_number(oo, "display-panes-active-colour");
590
591
	px = wp->sx / 2; py = wp->sy / 2;
592
	xoff = wp->xoff; yoff = wp->yoff;
593
594
	if (top)
595
		yoff += lines;
596
597
	if (wp->sx < len * 6 || wp->sy < 5) {
598
		tty_cursor(tty, xoff + px - len / 2, yoff + py);
599
		goto draw_text;
600
	}
601
602
	px -= len * 3;
603
	py -= 2;
604
605
	memcpy(&gc, &grid_default_cell, sizeof gc);
606
	if (w->active == wp)
607
		gc.bg = active_colour;
608
	else
609
		gc.bg = colour;
610
	gc.flags |= GRID_FLAG_NOPALETTE;
611
612
	tty_attributes(tty, &gc, wp);
613
	for (ptr = buf; *ptr != '\0'; ptr++) {
614
		if (*ptr < '0' || *ptr > '9')
615
			continue;
616
		idx = *ptr - '0';
617
618
		for (j = 0; j < 5; j++) {
619
			for (i = px; i < px + 5; i++) {
620
				tty_cursor(tty, xoff + i, yoff + py + j);
621
				if (window_clock_table[idx][j][i - px])
622
					tty_putc(tty, ' ');
623
			}
624
		}
625
		px += 6;
626
	}
627
628
	len = xsnprintf(buf, sizeof buf, "%ux%u", wp->sx, wp->sy);
629
	if (wp->sx < len || wp->sy < 6)
630
		return;
631
	tty_cursor(tty, xoff + wp->sx - len, yoff);
632
633
draw_text:
634
	memcpy(&gc, &grid_default_cell, sizeof gc);
635
	if (w->active == wp)
636
		gc.fg = active_colour;
637
	else
638
		gc.fg = colour;
639
	gc.flags |= GRID_FLAG_NOPALETTE;
640
641
	tty_attributes(tty, &gc, wp);
642
	tty_puts(tty, buf);
643
644
	tty_cursor(tty, 0, 0);
645
}