1 #include "../../arcan_shmif.h"
2 #include "../../arcan_tui.h"
3 #include "../tui_int.h"
4 #include "../screen/libtsm.h"
5 #include "../screen/libtsm_int.h"
6 #include "../screen/utf8.c"
7 
8 #include <stdio.h>
9 #include <inttypes.h>
10 #include <math.h>
11 #include <errno.h>
12 
select_copy(struct tui_context * tui)13 static void select_copy(struct tui_context* tui)
14 {
15 	char* sel = NULL;
16 	ssize_t len;
17 /*
18  * The tsm_screen selection code is really icky and well-deserving of a
19  * rewrite. The 'select_townd' toggle here is that the selection function
20  * originally converted to utf8, while we work with UCS4 locally
21  */
22 	int dst = atomic_load(&paste_destination);
23 	if (tui->select_townd && -1 != dst){
24 		len = tsm_screen_selection_copy(tui->screen, &sel, false);
25 		if (!len || len <= 1)
26 			return;
27 
28 /* spinlock on block, we have already _Static_assert on PIPE_BUF */
29 		while (len >= 4){
30 			int rv = write(dst, sel, 4);
31 			if (-1 == rv){
32 				if (errno == EINVAL)
33 					break;
34 				else
35 					continue;
36 			}
37 			len -= 4;
38 			sel += 4;
39 		}
40 
41 /* always send a new line (even if it doesn't go through) */
42 		uint32_t ch = '\n';
43 		write(dst, &ch, 4);
44 		return;
45 	}
46 
47 	len = tsm_screen_selection_copy(tui->screen, &sel, true);
48 	if (!sel || len <= 1)
49 		return;
50 
51 	len--;
52 
53 /* empty cells gets marked as NULL, but that would cut the copy short */
54 	for (size_t i = 0; i < len; i++){
55 		if (sel[i] == '\0')
56 			sel[i] = ' ';
57 	}
58 
59 	tui_clipboard_push(tui, sel, len);
60 	free(sel);
61 }
62 
page_up(struct tui_context * tui)63 static bool page_up(struct tui_context* tui)
64 {
65 	if (!tui || (tui->flags & TUI_ALTERNATE))
66 		return true;
67 
68 	tui->cursor_upd = true;
69 	tui->cursor_off = true;
70 	arcan_tui_scroll_up(tui, tui->rows);
71 	return true;
72 }
73 
page_down(struct tui_context * tui)74 static bool page_down(struct tui_context* tui)
75 {
76 	if (!tui || (tui->flags & TUI_ALTERNATE))
77 		return true;
78 
79 	if (tui->sbofs > 0){
80 		tui->sbofs -= tui->rows;
81 		tui->sbofs = tui->sbofs < 0 ? 0 : tui->sbofs;
82 		tui->cursor_upd = true;
83 		tui->cursor_off = true;
84 	}
85 
86 	if (tui->sbofs <= 0){
87 		tui->cursor_off = false;
88 		tui->cursor_upd = true;
89 		tsm_screen_sb_reset(tui->screen);
90 	}
91 
92 	arcan_tui_scroll_down(tui, tui->rows);
93 
94 	return true;
95 }
96 
mod_to_scroll(int mods,int screenh)97 static int mod_to_scroll(int mods, int screenh)
98 {
99 	int rv = 1;
100 	if (mods & ARKMOD_LSHIFT)
101 		rv = screenh >> 1;
102 	if (mods & ARKMOD_RSHIFT)
103 		rv += screenh >> 1;
104 	if (mods & ARKMOD_LCTRL)
105 		rv += screenh >> 1;
106 	if (mods & ARKMOD_RCTRL)
107 		rv += screenh >> 1;
108 	return rv;
109 }
110 
copy_window(struct tui_context * tui)111 static bool copy_window(struct tui_context* tui)
112 {
113 /* if no pending copy-window request, make a copy of the active screen
114  * and spawn a dispatch thread for it */
115 	if (tui->pending_copy_window)
116 		return true;
117 
118 	if (tsm_screen_save(
119 		tui->screen, true, &tui->pending_copy_window)){
120 		arcan_shmif_enqueue(&tui->acon, &(struct arcan_event){
121 			.ext.kind = ARCAN_EVENT(SEGREQ),
122 			.ext.segreq.kind = SEGID_TUI,
123 			.ext.segreq.id = REQID_COPYWINDOW
124 		});
125 	}
126 
127 	return true;
128 }
129 
scroll_up(struct tui_context * tui)130 static bool scroll_up(struct tui_context* tui)
131 {
132 	if (!tui || (tui->flags & TUI_ALTERNATE))
133 		return true;
134 
135 	int nf = mod_to_scroll(tui->modifiers, tui->rows);
136 	arcan_tui_scroll_up(tui, nf);
137 	return true;
138 }
139 
scroll_down(struct tui_context * tui)140 static bool scroll_down(struct tui_context* tui)
141 {
142 	if (!tui || (tui->flags & TUI_ALTERNATE))
143 		return true;
144 
145 	int nf = mod_to_scroll(tui->modifiers, tui->rows);
146 	if (tui->sbofs > 0){
147 		arcan_tui_scroll_down(tui, nf);
148 				return true;
149 	}
150 	return false;
151 }
152 
move_up(struct tui_context * tui)153 static bool move_up(struct tui_context* tui)
154 {
155 	if (tui->scroll_lock){
156 		page_up(tui);
157 		return true;
158 	}
159 /*	else if (tui->modifiers & (TUIK_LMETA | TUIK_RMETA)){
160 		if (tui->modifiers & (TUIK_LSHIFT | TUIK_RSHIFT))
161 			page_up(tui);
162 		else{
163 			tsm_screen_sb_up(tui->screen, 1);
164 			tui->sbofs += 1;
165 			tui->dirty |= DIRTY_PENDING;
166 		}
167 		return true;
168 	}
169  else */
170 
171 	return false;
172 }
173 
move_down(struct tui_context * tui)174 static bool move_down(struct tui_context* tui)
175 {
176 	if (tui->scroll_lock){
177 		page_down(tui);
178 		return true;
179 	}
180 /*
181  * else if (tui->modifiers & (TUIK_LMETA | TUIK_RMETA)){
182 		if (tui->modifiers & (TUIK_LSHIFT | TUIK_RSHIFT))
183 			page_up(tui);
184 		else{
185 			tsm_screen_sb_down(tui->screen, 1);
186 			tui->sbofs -= 1;
187 			tui->dirty |= DIRTY_PENDING;
188 		}
189 		return true;
190 	}
191 	else
192 */
193 	return false;
194 }
195 
select_at(struct tui_context * tui)196 static bool select_at(struct tui_context* tui)
197 {
198 	tsm_screen_selection_reset(tui->screen);
199 	unsigned sx, sy, ex, ey;
200 	int rv = tsm_screen_get_word(tui->screen,
201 		tui->mouse_x, tui->mouse_y, &sx, &sy, &ex, &ey);
202 
203 	if (0 == rv){
204 		tsm_screen_selection_reset(tui->screen);
205 		tsm_screen_selection_start(tui->screen, sx, sy);
206 		tsm_screen_selection_target(tui->screen, ex, ey);
207 		select_copy(tui);
208 		tui->dirty |= DIRTY_PARTIAL;
209 	}
210 
211 	tui->in_select = false;
212 	tui->dirty |= DIRTY_CURSOR;
213 	return true;
214 }
215 
select_row(struct tui_context * tui)216 static bool select_row(struct tui_context* tui)
217 {
218 	tsm_screen_selection_reset(tui->screen);
219 	int row = tsm_screen_get_cursor_y(tui->screen);
220 	tsm_screen_selection_start(tui->screen, 0, row);
221 	tsm_screen_selection_target(tui->screen, tui->cols-1, row);
222 	select_copy(tui);
223 	tui->dirty |= DIRTY_PARTIAL;
224 	tui->dirty |= DIRTY_CURSOR;
225 	tui->in_select = false;
226 	return true;
227 }
228 
scroll_lock(struct tui_context * tui)229 static bool scroll_lock(struct tui_context* tui)
230 {
231 	tui->scroll_lock = !tui->scroll_lock;
232 	if (!tui->scroll_lock){
233 		tui->sbofs = 0;
234 		tsm_screen_sb_reset(tui->screen);
235 		tui->cursor_upd = true;
236 		tui->cursor_off = false;
237 		tui->dirty |= DIRTY_PARTIAL;
238 	}
239 	return true;
240 }
241 
mouse_forward(struct tui_context * tui)242 static bool mouse_forward(struct tui_context* tui)
243 {
244 	tui->mouse_forward = !tui->mouse_forward;
245 	return true;
246 }
247 
sel_sw(struct tui_context * tui)248 static bool sel_sw(struct tui_context* tui)
249 {
250 	tui->select_townd = !tui->select_townd;
251 	return true;
252 }
253 
254 struct lent {
255 	int ctx;
256 	const char* lbl;
257 	const char* descr;
258 	uint8_t vsym[5];
259 	bool(*ptr)(struct tui_context*);
260 	uint16_t initial;
261 	uint16_t modifiers;
262 };
263 
264 #ifdef _DEBUG
265 #include <stdio.h>
dump_dbg(struct tui_context * tui)266 static bool dump_dbg(struct tui_context* tui)
267 {
268 /* dump front-delta, front, back to different files */
269 	uint8_t* rbuf = NULL;
270 	size_t rbuf_sz = 0;
271 	tui_screen_tpack(tui,
272 		(struct tpack_gen_opts){.full = true, .synch = false}, &rbuf, &rbuf_sz);
273 
274 	char buf[64];
275 	snprintf(buf, 64, "/tmp/tui.%d.delta.front.tpack", getpid());
276 	FILE* fout = fopen(buf, "w");
277 	if (fout){
278 		fwrite(rbuf, rbuf_sz, 1, fout);
279 		fclose(fout);
280 	}
281 
282 	tui_screen_tpack(tui,
283 		(struct tpack_gen_opts){.full = true, .back = true}, &rbuf, &rbuf_sz);
284 	snprintf(buf, 64, "/tmp/tui.%d.full.back.tpack", getpid());
285 	fout = fopen(buf, "w");
286 	if (fout){
287 		fwrite(rbuf, rbuf_sz, 1, fout);
288 		fclose(fout);
289 	}
290 
291 	tui_screen_tpack(tui,
292 		(struct tpack_gen_opts){0}, &rbuf, &rbuf_sz);
293 	snprintf(buf, 64, "/tmp/tui.%d.full.front.tpack", getpid());
294 	fout = fopen(buf, "w");
295 	if (fout){
296 		fwrite(rbuf, rbuf_sz, 1, fout);
297 		fclose(fout);
298 	}
299 
300 	return true;
301 }
302 #endif
303 
304 static const struct lent labels[] = {
305 	{1, "LINE_UP", "Scroll 1 row up", {}, scroll_up}, /* u+2191 */
306 	{1, "LINE_DOWN", "Scroll 1 row down", {}, scroll_down}, /* u+2192 */
307 	{1, "PAGE_UP", "Scroll one page up", {0xe2, 0x87, 0x9e}, page_up}, /* u+21de */
308 	{1, "PAGE_DOWN", "Scroll one page down", {0xe2, 0x87, 0x9e}, page_down}, /* u+21df */
309 	{0, "COPY_AT", "Copy word at cursor", {}, select_at}, /* u+21f8 */
310 	{0, "COPY_ROW", "Copy cursor row", {}, select_row}, /* u+21a6 */
311 	{0, "MOUSE_FORWARD", "Toggle mouse forwarding", {}, mouse_forward}, /* u+ */
312 	{1, "SCROLL_LOCK", "Arrow- keys to pageup/down", {}, scroll_lock, TUIK_SCROLLLOCK}, /* u+ */
313 	{1, "UP", "(scroll-lock) page up, UP keysym", {}, move_up}, /* u+ */
314 	{1, "DOWN", "(scroll-lock) page down, DOWN keysym", {}, move_down}, /* u+ */
315 	{0, "COPY_WND", "Copy window and scrollback", {}, copy_window}, /* u+ */
316 	{2, "SELECT_TOGGLE", "Switch select destination (wnd, clipboard)", {}, sel_sw}, /* u+ */
317 #ifdef _DEBUG
318 	{1, "DUMP", "Create a buffer/raster snapshot (/tmp/tui.pid.xxx)", {}, dump_dbg},
319 #endif
320 	{0}
321 };
322 
tui_expose_labels(struct tui_context * tui)323 void tui_expose_labels(struct tui_context* tui)
324 {
325 	const struct lent* cur = labels;
326 	arcan_event ev = {
327 		.category = EVENT_EXTERNAL,
328 		.ext.kind = ARCAN_EVENT(LABELHINT),
329 		.ext.labelhint.idatatype = EVENT_IDATATYPE_DIGITAL
330 	};
331 
332 /* send an empty label first as a reset */
333 	arcan_shmif_enqueue(&tui->acon, &ev);
334 
335 /* then forward to a possible callback handler */
336 	size_t ind = 0;
337 	if (tui->handlers.query_label){
338 		while (true){
339 			struct tui_labelent dstlbl = {};
340 			if (!tui->handlers.query_label(tui,
341 			ind++, "ENG", "ENG", &dstlbl, tui->handlers.tag))
342 				break;
343 
344 			snprintf(ev.ext.labelhint.label,
345 				COUNT_OF(ev.ext.labelhint.label), "%s", dstlbl.label);
346 			snprintf(ev.ext.labelhint.descr,
347 				COUNT_OF(ev.ext.labelhint.descr), "%s", dstlbl.descr);
348 			ev.ext.labelhint.subv = dstlbl.subv;
349 			ev.ext.labelhint.idatatype = dstlbl.idatatype ? dstlbl.idatatype : EVENT_IDATATYPE_DIGITAL;
350 			ev.ext.labelhint.modifiers = dstlbl.modifiers;
351 			ev.ext.labelhint.initial = dstlbl.initial;
352 			snprintf((char*)ev.ext.labelhint.vsym,
353 				COUNT_OF(ev.ext.labelhint.vsym), "%s", dstlbl.vsym);
354 			arcan_shmif_enqueue(&tui->acon, &ev);
355 		}
356 	}
357 
358 /* expose a set of basic built-in controls shared by all users, and this is
359  * dependent, for now, on the mode of the context.  The reason is that 'line-'
360  * oriented mode with it's special scrolling, selection etc. complexity should
361  * be refactored and pushed to a separate layer. */
362 	while(cur->lbl){
363 		switch(cur->ctx){
364 		case 0:
365 /* all */
366 		break;
367 		case 1:
368 /* not in 'alternate' */
369 			if (tui->flags & TUI_ALTERNATE){
370 				cur++;
371 				continue;
372 			}
373 		break;
374 		case 2:
375 /* only when not in copywnd */
376 			if (!tui->subseg){
377 				cur++;
378 				continue;
379 			}
380 		break;
381 		}
382 
383 		snprintf(ev.ext.labelhint.label,
384 			COUNT_OF(ev.ext.labelhint.label), "%s", cur->lbl);
385 		snprintf(ev.ext.labelhint.descr,
386 			COUNT_OF(ev.ext.labelhint.descr), "%s", cur->descr);
387 		snprintf((char*)ev.ext.labelhint.vsym,
388 			COUNT_OF(ev.ext.labelhint.vsym), "%s", cur->vsym);
389 		cur++;
390 
391 		ev.ext.labelhint.initial = cur->initial;
392 		ev.ext.labelhint.modifiers = cur->modifiers;
393 		arcan_shmif_enqueue(&tui->acon, &ev);
394 	}
395 }
396 
update_mods(int mods,int sym,bool pressed)397 static int update_mods(int mods, int sym, bool pressed)
398 {
399 	if (pressed)
400 	switch(sym){
401 	case TUIK_LSHIFT: return mods | ARKMOD_LSHIFT;
402 	case TUIK_RSHIFT: return mods | ARKMOD_RSHIFT;
403 	case TUIK_LCTRL: return mods | ARKMOD_LCTRL;
404 	case TUIK_RCTRL: return mods | ARKMOD_RCTRL;
405 	case TUIK_COMPOSE:
406 	case TUIK_LMETA: return mods | ARKMOD_LMETA;
407 	case TUIK_RMETA: return mods | ARKMOD_RMETA;
408 	default:
409 		return mods;
410 	}
411 	else
412 	switch(sym){
413 	case TUIK_LSHIFT: return mods & (~ARKMOD_LSHIFT);
414 	case TUIK_RSHIFT: return mods & (~ARKMOD_RSHIFT);
415 	case TUIK_LCTRL: return mods & (~ARKMOD_LCTRL);
416 	case TUIK_RCTRL: return mods & (~ARKMOD_RCTRL);
417 	case TUIK_COMPOSE:
418 	case TUIK_LMETA: return mods & (~ARKMOD_LMETA);
419 	case TUIK_RMETA: return mods & (~ARKMOD_RMETA);
420 	default:
421 		return mods;
422 	}
423 }
424 
consume_label(struct tui_context * tui,arcan_ioevent * ioev,const char * label)425 static bool consume_label(struct tui_context* tui,
426 	arcan_ioevent* ioev, const char* label)
427 {
428 	const struct lent* cur = labels;
429 
430 /* priority to our normal label handlers, and if those fail, forward */
431 	while(cur->lbl){
432 		if (strcmp(label, cur->lbl) == 0){
433 			if (cur->ptr(tui))
434 				return true;
435 			else
436 				break;
437 		}
438 		cur++;
439 	}
440 
441 	bool res = false;
442 	if (tui->handlers.input_label){
443 		res |= tui->handlers.input_label(tui, label, true, tui->handlers.tag);
444 
445 /* also send release if the forward was ok */
446 		if (res)
447 			tui->handlers.input_label(tui, label, false, tui->handlers.tag);
448 	}
449 
450 	return res;
451 }
452 
forward_mouse(struct tui_context * tui)453 static bool forward_mouse(struct tui_context* tui)
454 {
455 	bool forward = tui->mouse_forward;
456 	if (
457 			!(tui->flags & TUI_MOUSE_FULL) &&
458 			(tui->modifiers & (TUIM_LCTRL | TUIM_RCTRL))){
459 		return !forward;
460 	}
461 	return forward;
462 }
463 
tui_input_event(struct tui_context * tui,arcan_ioevent * ioev,const char * label)464 void tui_input_event(
465 	struct tui_context* tui, arcan_ioevent* ioev, const char* label)
466 {
467 	if (ioev->datatype == EVENT_IDATATYPE_TRANSLATED){
468 		bool pressed = ioev->input.translated.active;
469 		int sym = ioev->input.translated.keysym;
470 		int oldm = tui->modifiers;
471 		tui->modifiers = update_mods(tui->modifiers, sym, pressed);
472 
473 /* note that after this point we always fake 'release' and forward as a
474  * press->release on the same label within consume label */
475 		if (!pressed)
476 			return;
477 
478 		if (tui->in_select){
479 			tui->in_select = false;
480 			tsm_screen_selection_reset(tui->screen);
481 		}
482 		tui->inact_timer = -4;
483 		if (label[0] && consume_label(tui, ioev, label))
484 			return;
485 
486 /* modifiers doesn't get set for the symbol itself which is a problem
487  * for when we want to forward modifier data to another handler like mbtn */
488 		if (sym >= 300 && sym <= 314)
489 			return;
490 
491 /* reset scrollback on normal input */
492 		if (oldm == tui->modifiers && tui->sbofs != 0){
493 			tui->cursor_upd = true;
494 			tui->cursor_off = false;
495 			tui->sbofs = 0;
496 			tsm_screen_sb_reset(tui->screen);
497 			tui->dirty |= DIRTY_PARTIAL;
498 		}
499 
500 /* check the incoming utf8 if it's valid, if so forward and if the handler
501  * consumed the value, leave the function */
502 		int len = 0;
503 		bool valid = true;
504 		uint32_t codepoint = 0, state = 0;
505 		while (len < 5 && ioev->input.translated.utf8[len]){
506 			if (UTF8_REJECT == utf8_decode(&state, &codepoint,
507 				ioev->input.translated.utf8[len])){
508 				valid = false;
509 				break;
510 			}
511 			len++;
512 		}
513 
514 /* disallow the private-use area */
515 		if ((codepoint >= 0xe000 && codepoint <= 0xf8ff))
516 			valid = false;
517 
518 		if (valid && ioev->input.translated.utf8[0] && tui->handlers.input_utf8){
519 			if (tui->handlers.input_utf8 && tui->handlers.input_utf8(tui,
520 					(const char*)ioev->input.translated.utf8,
521 					len, tui->handlers.tag))
522 				return;
523 		}
524 
525 /* otherwise, forward as much of the key as we know */
526 		if (tui->handlers.input_key)
527 			tui->handlers.input_key(tui,
528 				sym,
529 				ioev->input.translated.scancode,
530 				ioev->input.translated.modifiers,
531 				ioev->subid, tui->handlers.tag
532 			);
533 	}
534 	else if (ioev->devkind == EVENT_IDEVKIND_MOUSE){
535 		if (ioev->datatype == EVENT_IDATATYPE_ANALOG){
536 			if (ioev->subid == 0){
537 				tui->mouse_x = ioev->input.analog.axisval[0] / tui->cell_w;
538 			}
539 			else if (ioev->subid == 1){
540 				int yv = ioev->input.analog.axisval[0];
541 				tui->mouse_y = yv / tui->cell_h;
542 
543 				bool upd = false;
544 				if (tui->mouse_x != tui->lm_x){
545 					tui->lm_x = tui->mouse_x;
546 					upd = true;
547 				}
548 				if (tui->mouse_y != tui->lm_y){
549 					tui->lm_y = tui->mouse_y;
550 					upd = true;
551 				}
552 
553 				if (forward_mouse(tui) && tui->handlers.input_mouse_motion){
554 					if (upd)
555 					tui->handlers.input_mouse_motion(tui, false,
556 						tui->mouse_x, tui->mouse_y, tui->modifiers, tui->handlers.tag);
557 					return;
558 				}
559 
560 				if (!tui->in_select)
561 					return;
562 
563 /* we use the upper / lower regions as triggers for scrollback + selection,
564  * with a magnitude based on how far "off" we are */
565 				if (yv < 0.3 * tui->cell_h)
566 					tui->scrollback = -1 * (1 + yv / tui->cell_h);
567 				else if (yv > tui->rows * tui->cell_h + 0.3 * tui->cell_h)
568 					tui->scrollback = 1 + (yv - tui->rows * tui->cell_h) / tui->cell_h;
569 				else
570 					tui->scrollback = 0;
571 
572 /* in select and drag negative in window or half-size - then use ticker
573  * to scroll and an accelerated scrollback */
574 				if (upd){
575 					tsm_screen_selection_target(tui->screen, tui->lm_x, tui->lm_y);
576 					tui->dirty |= DIRTY_PARTIAL | DIRTY_CURSOR;
577 				}
578 /* in select? check if motion tile is different than old, if so,
579  * tsm_selection_target */
580 			}
581 		}
582 /* press? press-point tsm_screen_selection_start,
583  * release and press-tile ~= release_tile? copy */
584 		else if (ioev->datatype == EVENT_IDATATYPE_DIGITAL){
585 			if (ioev->subid){
586 				if (ioev->input.digital.active)
587 					tui->mouse_btnmask |=  (1 << (ioev->subid-1));
588 				else
589 					tui->mouse_btnmask &= ~(1 << (ioev->subid-1));
590 			}
591 			if (forward_mouse(tui) && tui->handlers.input_mouse_button){
592 				tui->handlers.input_mouse_button(tui, tui->mouse_x,
593 					tui->mouse_y, ioev->subid, ioev->input.digital.active,
594 					tui->modifiers, tui->handlers.tag
595 				);
596 				return;
597 			}
598 
599 			if (ioev->flags & ARCAN_IOFL_GESTURE){
600 				if (strcmp(ioev->label, "dblclick") == 0){
601 /* select row if double doubleclick */
602 					if (tui->last_dbl_x == tui->mouse_x &&
603 						tui->last_dbl_y == tui->mouse_y){
604 						tsm_screen_selection_reset(tui->screen);
605 						tsm_screen_selection_start(tui->screen, 0, tui->mouse_y);
606 						tsm_screen_selection_target(
607 							tui->screen, tui->cols-1, tui->mouse_y);
608 						select_copy(tui);
609 						tui->dirty |= DIRTY_PARTIAL;
610 						tui->in_select = false;
611 					}
612 /* select word */
613 					else{
614 						unsigned sx, sy, ex, ey;
615 						sx = sy = ex = ey = 0;
616 						int rv = tsm_screen_get_word(tui->screen,
617 							tui->mouse_x, tui->mouse_y, &sx, &sy, &ex, &ey);
618 						if (0 == rv){
619 							tsm_screen_selection_reset(tui->screen);
620 							tsm_screen_selection_start(tui->screen, sx, sy);
621 							tsm_screen_selection_target(tui->screen, ex, ey);
622 							select_copy(tui);
623 							tui->dirty |= DIRTY_PARTIAL | DIRTY_CURSOR;
624 							tui->in_select = false;
625 						}
626 					}
627 
628 					tui->last_dbl_x = tui->mouse_x;
629 					tui->last_dbl_y = tui->mouse_y;
630 				}
631 				else if (strcmp(ioev->label, "click") == 0){
632 /* TODO: forward to cfg->nal? */
633 				}
634 				return;
635 			}
636 
637 /* scroll or select?
638  * NOTE: should also consider a way to specify magnitude */
639 			if (ioev->subid == TUIBTN_WHEEL_UP){
640 				if (ioev->input.digital.active){
641 
642 /* normal ALTSCREEN wheel doesn't really make sense, unless in
643  * drag-select, map that to stepping selected row up/down?)
644  * clients can still switch to manual mouse mode to get the other behavior */
645 					if ((tui->flags & TUI_ALTERNATE)){
646 						tui->handlers.input_key(tui,
647 							((tui->modifiers & (ARKMOD_LSHIFT | ARKMOD_RSHIFT)) ? TUIK_PAGEUP : TUIK_UP),
648 							ioev->input.translated.scancode,
649 							0,
650 							ioev->subid, tui->handlers.tag
651 						);
652 					}
653 					else
654 						scroll_up(tui);
655 				}
656 			}
657 			else if (ioev->subid == TUIBTN_WHEEL_DOWN){
658 				if (ioev->input.digital.active){
659 					if ((tui->flags & TUI_ALTERNATE)){
660 						tui->handlers.input_key(tui,
661 							((tui->modifiers & (ARKMOD_LSHIFT | ARKMOD_RSHIFT)) ? TUIK_PAGEDOWN : TUIK_DOWN),
662 							ioev->input.translated.scancode,
663 							0,
664 							ioev->subid, tui->handlers.tag
665 						);
666 					}
667 					else
668 						scroll_down(tui);
669 				}
670 			}
671 			else if (ioev->input.digital.active){
672 				tsm_screen_selection_start(tui->screen, tui->mouse_x, tui->mouse_y);
673 				tui->bsel_x = tui->mouse_x;
674 				tui->bsel_y = tui->mouse_y;
675 				tui->lm_x = tui->mouse_x;
676 				tui->lm_y = tui->mouse_y;
677 				tui->in_select = true;
678 			}
679 			else{
680 				if (tui->mouse_x != tui->bsel_x || tui->mouse_y != tui->bsel_y)
681 					select_copy(tui);
682 
683 				tsm_screen_selection_reset(tui->screen);
684 				tui->in_select = false;
685 				tui->dirty |= DIRTY_PARTIAL | DIRTY_CURSOR;
686 			}
687 		}
688 		else if (tui->handlers.input_misc)
689 			tui->handlers.input_misc(tui, ioev, tui->handlers.tag);
690 	}
691 }
692