source: mainline/uspace/lib/clui/tinput.c@ 0eff68e

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 0eff68e was 79ae36dd, checked in by Martin Decky <martin@…>, 14 years ago

new async framework with integrated exchange tracking

  • strict isolation between low-level IPC and high-level async framework with integrated exchange tracking
    • each IPC connection is represented by an async_sess_t structure
    • each IPC exchange is represented by an async_exch_t structure
    • exchange management is either based on atomic messages (EXCHANGE_ATOMIC), locking (EXCHANGE_SERIALIZE) or connection cloning (EXCHANGE_CLONE)
  • async_obsolete: temporary compatibility layer to keep old async clients working (several pieces of code are currently broken, but only non-essential functionality)
  • IPC_M_PHONE_HANGUP is now method no. 0 (for elegant boolean evaluation)
  • IPC_M_DEBUG_ALL has been renamed to IPC_M_DEBUG
  • IPC_M_PING has been removed (VFS protocol now has VFS_IN_PING)
  • console routines in libc have been rewritten for better abstraction
  • additional use for libc-private header files (FILE structure opaque to the client)
  • various cstyle changes (typos, indentation, missing externs in header files, improved comments, etc.)
  • Property mode set to 100644
File size: 15.0 KB
Line 
1/*
2 * Copyright (c) 2010 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <str.h>
32#include <io/console.h>
33#include <io/keycode.h>
34#include <io/style.h>
35#include <io/color.h>
36#include <vfs/vfs.h>
37#include <clipboard.h>
38#include <macros.h>
39#include <errno.h>
40#include <assert.h>
41#include <bool.h>
42#include <tinput.h>
43
44/** Seek direction */
45typedef enum {
46 seek_backward = -1,
47 seek_forward = 1
48} seek_dir_t;
49
50static void tinput_init(tinput_t *);
51static void tinput_insert_string(tinput_t *, const char *);
52static void tinput_sel_get_bounds(tinput_t *, size_t *, size_t *);
53static bool tinput_sel_active(tinput_t *);
54static void tinput_sel_all(tinput_t *);
55static void tinput_sel_delete(tinput_t *);
56static void tinput_key_ctrl(tinput_t *, kbd_event_t *);
57static void tinput_key_shift(tinput_t *, kbd_event_t *);
58static void tinput_key_ctrl_shift(tinput_t *, kbd_event_t *);
59static void tinput_key_unmod(tinput_t *, kbd_event_t *);
60static void tinput_pre_seek(tinput_t *, bool);
61static void tinput_post_seek(tinput_t *, bool);
62
63/** Create a new text input field. */
64tinput_t *tinput_new(void)
65{
66 tinput_t *ti;
67
68 ti = malloc(sizeof(tinput_t));
69 if (ti == NULL)
70 return NULL;
71
72 tinput_init(ti);
73 return ti;
74}
75
76/** Destroy text input field. */
77void tinput_destroy(tinput_t *ti)
78{
79 free(ti);
80}
81
82static void tinput_display_tail(tinput_t *ti, size_t start, size_t pad)
83{
84 wchar_t dbuf[INPUT_MAX_SIZE + 1];
85
86 size_t sa;
87 size_t sb;
88 tinput_sel_get_bounds(ti, &sa, &sb);
89
90 console_set_pos(ti->console, (ti->col0 + start) % ti->con_cols,
91 ti->row0 + (ti->col0 + start) / ti->con_cols);
92 console_set_style(ti->console, STYLE_NORMAL);
93
94 size_t p = start;
95 if (p < sa) {
96 memcpy(dbuf, ti->buffer + p, (sa - p) * sizeof(wchar_t));
97 dbuf[sa - p] = '\0';
98 printf("%ls", dbuf);
99 p = sa;
100 }
101
102 if (p < sb) {
103 console_flush(ti->console);
104 console_set_style(ti->console, STYLE_SELECTED);
105
106 memcpy(dbuf, ti->buffer + p,
107 (sb - p) * sizeof(wchar_t));
108 dbuf[sb - p] = '\0';
109 printf("%ls", dbuf);
110 p = sb;
111 }
112
113 console_flush(ti->console);
114 console_set_style(ti->console, STYLE_NORMAL);
115
116 if (p < ti->nc) {
117 memcpy(dbuf, ti->buffer + p,
118 (ti->nc - p) * sizeof(wchar_t));
119 dbuf[ti->nc - p] = '\0';
120 printf("%ls", dbuf);
121 }
122
123 for (p = 0; p < pad; p++)
124 putchar(' ');
125
126 console_flush(ti->console);
127}
128
129static char *tinput_get_str(tinput_t *ti)
130{
131 return wstr_to_astr(ti->buffer);
132}
133
134static void tinput_position_caret(tinput_t *ti)
135{
136 console_set_pos(ti->console, (ti->col0 + ti->pos) % ti->con_cols,
137 ti->row0 + (ti->col0 + ti->pos) / ti->con_cols);
138}
139
140/** Update row0 in case the screen could have scrolled. */
141static void tinput_update_origin(tinput_t *ti)
142{
143 sysarg_t width = ti->col0 + ti->nc;
144 sysarg_t rows = (width / ti->con_cols) + 1;
145
146 /* Update row0 if the screen scrolled. */
147 if (ti->row0 + rows > ti->con_rows)
148 ti->row0 = ti->con_rows - rows;
149}
150
151static void tinput_insert_char(tinput_t *ti, wchar_t c)
152{
153 if (ti->nc == INPUT_MAX_SIZE)
154 return;
155
156 sysarg_t new_width = ti->col0 + ti->nc + 1;
157 if (new_width % ti->con_cols == 0) {
158 /* Advancing to new line. */
159 sysarg_t new_height = (new_width / ti->con_cols) + 1;
160 if (new_height >= ti->con_rows) {
161 /* Disallow text longer than 1 page for now. */
162 return;
163 }
164 }
165
166 size_t i;
167 for (i = ti->nc; i > ti->pos; i--)
168 ti->buffer[i] = ti->buffer[i - 1];
169
170 ti->buffer[ti->pos] = c;
171 ti->pos += 1;
172 ti->nc += 1;
173 ti->buffer[ti->nc] = '\0';
174 ti->sel_start = ti->pos;
175
176 tinput_display_tail(ti, ti->pos - 1, 0);
177 tinput_update_origin(ti);
178 tinput_position_caret(ti);
179}
180
181static void tinput_insert_string(tinput_t *ti, const char *str)
182{
183 size_t ilen = min(str_length(str), INPUT_MAX_SIZE - ti->nc);
184 if (ilen == 0)
185 return;
186
187 sysarg_t new_width = ti->col0 + ti->nc + ilen;
188 sysarg_t new_height = (new_width / ti->con_cols) + 1;
189 if (new_height >= ti->con_rows) {
190 /* Disallow text longer than 1 page for now. */
191 return;
192 }
193
194 if (ti->nc > 0) {
195 size_t i;
196 for (i = ti->nc; i > ti->pos; i--)
197 ti->buffer[i + ilen - 1] = ti->buffer[i - 1];
198 }
199
200 size_t off = 0;
201 size_t i = 0;
202 while (i < ilen) {
203 wchar_t c = str_decode(str, &off, STR_NO_LIMIT);
204 if (c == '\0')
205 break;
206
207 /* Filter out non-printable chars. */
208 if (c < 32)
209 c = 32;
210
211 ti->buffer[ti->pos + i] = c;
212 i++;
213 }
214
215 ti->pos += ilen;
216 ti->nc += ilen;
217 ti->buffer[ti->nc] = '\0';
218 ti->sel_start = ti->pos;
219
220 tinput_display_tail(ti, ti->pos - ilen, 0);
221 tinput_update_origin(ti);
222 tinput_position_caret(ti);
223}
224
225static void tinput_backspace(tinput_t *ti)
226{
227 if (tinput_sel_active(ti)) {
228 tinput_sel_delete(ti);
229 return;
230 }
231
232 if (ti->pos == 0)
233 return;
234
235 size_t i;
236 for (i = ti->pos; i < ti->nc; i++)
237 ti->buffer[i - 1] = ti->buffer[i];
238
239 ti->pos -= 1;
240 ti->nc -= 1;
241 ti->buffer[ti->nc] = '\0';
242 ti->sel_start = ti->pos;
243
244 tinput_display_tail(ti, ti->pos, 1);
245 tinput_position_caret(ti);
246}
247
248static void tinput_delete(tinput_t *ti)
249{
250 if (tinput_sel_active(ti)) {
251 tinput_sel_delete(ti);
252 return;
253 }
254
255 if (ti->pos == ti->nc)
256 return;
257
258 ti->pos += 1;
259 ti->sel_start = ti->pos;
260
261 tinput_backspace(ti);
262}
263
264static void tinput_seek_cell(tinput_t *ti, seek_dir_t dir, bool shift_held)
265{
266 tinput_pre_seek(ti, shift_held);
267
268 if (dir == seek_forward) {
269 if (ti->pos < ti->nc)
270 ti->pos += 1;
271 } else {
272 if (ti->pos > 0)
273 ti->pos -= 1;
274 }
275
276 tinput_post_seek(ti, shift_held);
277}
278
279static void tinput_seek_word(tinput_t *ti, seek_dir_t dir, bool shift_held)
280{
281 tinput_pre_seek(ti, shift_held);
282
283 if (dir == seek_forward) {
284 if (ti->pos == ti->nc)
285 return;
286
287 while (true) {
288 ti->pos += 1;
289
290 if (ti->pos == ti->nc)
291 break;
292
293 if ((ti->buffer[ti->pos - 1] == ' ') &&
294 (ti->buffer[ti->pos] != ' '))
295 break;
296 }
297 } else {
298 if (ti->pos == 0)
299 return;
300
301 while (true) {
302 ti->pos -= 1;
303
304 if (ti->pos == 0)
305 break;
306
307 if (ti->buffer[ti->pos - 1] == ' ' &&
308 ti->buffer[ti->pos] != ' ')
309 break;
310 }
311
312 }
313
314 tinput_post_seek(ti, shift_held);
315}
316
317static void tinput_seek_vertical(tinput_t *ti, seek_dir_t dir, bool shift_held)
318{
319 tinput_pre_seek(ti, shift_held);
320
321 if (dir == seek_forward) {
322 if (ti->pos + ti->con_cols <= ti->nc)
323 ti->pos = ti->pos + ti->con_cols;
324 } else {
325 if (ti->pos >= ti->con_cols)
326 ti->pos = ti->pos - ti->con_cols;
327 }
328
329 tinput_post_seek(ti, shift_held);
330}
331
332static void tinput_seek_max(tinput_t *ti, seek_dir_t dir, bool shift_held)
333{
334 tinput_pre_seek(ti, shift_held);
335
336 if (dir == seek_backward)
337 ti->pos = 0;
338 else
339 ti->pos = ti->nc;
340
341 tinput_post_seek(ti, shift_held);
342}
343
344static void tinput_pre_seek(tinput_t *ti, bool shift_held)
345{
346 if ((tinput_sel_active(ti)) && (!shift_held)) {
347 /* Unselect and redraw. */
348 ti->sel_start = ti->pos;
349 tinput_display_tail(ti, 0, 0);
350 tinput_position_caret(ti);
351 }
352}
353
354static void tinput_post_seek(tinput_t *ti, bool shift_held)
355{
356 if (shift_held) {
357 /* Selecting text. Need redraw. */
358 tinput_display_tail(ti, 0, 0);
359 } else {
360 /* Shift not held. Keep selection empty. */
361 ti->sel_start = ti->pos;
362 }
363
364 tinput_position_caret(ti);
365}
366
367static void tinput_history_insert(tinput_t *ti, char *str)
368{
369 if (ti->hnum < HISTORY_LEN) {
370 ti->hnum += 1;
371 } else {
372 if (ti->history[HISTORY_LEN] != NULL)
373 free(ti->history[HISTORY_LEN]);
374 }
375
376 size_t i;
377 for (i = ti->hnum; i > 1; i--)
378 ti->history[i] = ti->history[i - 1];
379
380 ti->history[1] = str_dup(str);
381
382 if (ti->history[0] != NULL) {
383 free(ti->history[0]);
384 ti->history[0] = NULL;
385 }
386}
387
388static void tinput_set_str(tinput_t *ti, char *str)
389{
390 str_to_wstr(ti->buffer, INPUT_MAX_SIZE, str);
391 ti->nc = wstr_length(ti->buffer);
392 ti->pos = ti->nc;
393 ti->sel_start = ti->pos;
394}
395
396static void tinput_sel_get_bounds(tinput_t *ti, size_t *sa, size_t *sb)
397{
398 if (ti->sel_start < ti->pos) {
399 *sa = ti->sel_start;
400 *sb = ti->pos;
401 } else {
402 *sa = ti->pos;
403 *sb = ti->sel_start;
404 }
405}
406
407static bool tinput_sel_active(tinput_t *ti)
408{
409 return (ti->sel_start != ti->pos);
410}
411
412static void tinput_sel_all(tinput_t *ti)
413{
414 ti->sel_start = 0;
415 ti->pos = ti->nc;
416 tinput_display_tail(ti, 0, 0);
417 tinput_position_caret(ti);
418}
419
420static void tinput_sel_delete(tinput_t *ti)
421{
422 size_t sa;
423 size_t sb;
424
425 tinput_sel_get_bounds(ti, &sa, &sb);
426 if (sa == sb)
427 return;
428
429 memmove(ti->buffer + sa, ti->buffer + sb,
430 (ti->nc - sb) * sizeof(wchar_t));
431
432 ti->pos = ti->sel_start = sa;
433 ti->nc -= (sb - sa);
434 ti->buffer[ti->nc] = '\0';
435
436 tinput_display_tail(ti, sa, sb - sa);
437 tinput_position_caret(ti);
438}
439
440static void tinput_sel_copy_to_cb(tinput_t *ti)
441{
442 size_t sa;
443 size_t sb;
444
445 tinput_sel_get_bounds(ti, &sa, &sb);
446
447 char *str;
448
449 if (sb < ti->nc) {
450 wchar_t tmp_c = ti->buffer[sb];
451 ti->buffer[sb] = '\0';
452 str = wstr_to_astr(ti->buffer + sa);
453 ti->buffer[sb] = tmp_c;
454 } else
455 str = wstr_to_astr(ti->buffer + sa);
456
457 if (str == NULL)
458 goto error;
459
460 if (clipboard_put_str(str) != EOK)
461 goto error;
462
463 free(str);
464 return;
465
466error:
467 /* TODO: Give the user some kind of warning. */
468 return;
469}
470
471static void tinput_paste_from_cb(tinput_t *ti)
472{
473 char *str;
474 int rc = clipboard_get_str(&str);
475
476 if ((rc != EOK) || (str == NULL)) {
477 /* TODO: Give the user some kind of warning. */
478 return;
479 }
480
481 tinput_insert_string(ti, str);
482 free(str);
483}
484
485static void tinput_history_seek(tinput_t *ti, int offs)
486{
487 if (offs >= 0) {
488 if (ti->hpos + offs > ti->hnum)
489 return;
490 } else {
491 if (ti->hpos < (size_t) -offs)
492 return;
493 }
494
495 if (ti->history[ti->hpos] != NULL) {
496 free(ti->history[ti->hpos]);
497 ti->history[ti->hpos] = NULL;
498 }
499
500 ti->history[ti->hpos] = tinput_get_str(ti);
501 ti->hpos += offs;
502
503 int pad = (int) ti->nc - str_length(ti->history[ti->hpos]);
504 if (pad < 0)
505 pad = 0;
506
507 tinput_set_str(ti, ti->history[ti->hpos]);
508 tinput_display_tail(ti, 0, pad);
509 tinput_update_origin(ti);
510 tinput_position_caret(ti);
511}
512
513/** Initialize text input field.
514 *
515 * Must be called before using the field. It clears the history.
516 */
517static void tinput_init(tinput_t *ti)
518{
519 ti->console = console_init(stdin, stdout);
520 ti->hnum = 0;
521 ti->hpos = 0;
522 ti->history[0] = NULL;
523}
524
525/** Read in one line of input.
526 *
527 * @param ti Text input.
528 * @param dstr Place to save pointer to new string.
529 *
530 * @return EOK on success
531 * @return ENOENT if user requested abort
532 * @return EIO if communication with console failed
533 *
534 */
535int tinput_read(tinput_t *ti, char **dstr)
536{
537 console_flush(ti->console);
538 if (console_get_size(ti->console, &ti->con_cols, &ti->con_rows) != EOK)
539 return EIO;
540
541 if (console_get_pos(ti->console, &ti->col0, &ti->row0) != EOK)
542 return EIO;
543
544 ti->pos = 0;
545 ti->sel_start = 0;
546 ti->nc = 0;
547 ti->buffer[0] = '\0';
548 ti->done = false;
549 ti->exit_clui = false;
550
551 while (!ti->done) {
552 console_flush(ti->console);
553
554 kbd_event_t ev;
555 if (!console_get_kbd_event(ti->console, &ev))
556 return EIO;
557
558 if (ev.type != KEY_PRESS)
559 continue;
560
561 if (((ev.mods & KM_CTRL) != 0) &&
562 ((ev.mods & (KM_ALT | KM_SHIFT)) == 0))
563 tinput_key_ctrl(ti, &ev);
564
565 if (((ev.mods & KM_SHIFT) != 0) &&
566 ((ev.mods & (KM_CTRL | KM_ALT)) == 0))
567 tinput_key_shift(ti, &ev);
568
569 if (((ev.mods & KM_CTRL) != 0) &&
570 ((ev.mods & KM_SHIFT) != 0) &&
571 ((ev.mods & KM_ALT) == 0))
572 tinput_key_ctrl_shift(ti, &ev);
573
574 if ((ev.mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)
575 tinput_key_unmod(ti, &ev);
576
577 if (ev.c >= ' ') {
578 tinput_sel_delete(ti);
579 tinput_insert_char(ti, ev.c);
580 }
581 }
582
583 if (ti->exit_clui)
584 return ENOENT;
585
586 ti->pos = ti->nc;
587 tinput_position_caret(ti);
588 putchar('\n');
589
590 char *str = tinput_get_str(ti);
591 if (str_cmp(str, "") != 0)
592 tinput_history_insert(ti, str);
593
594 ti->hpos = 0;
595
596 *dstr = str;
597 return EOK;
598}
599
600static void tinput_key_ctrl(tinput_t *ti, kbd_event_t *ev)
601{
602 switch (ev->key) {
603 case KC_LEFT:
604 tinput_seek_word(ti, seek_backward, false);
605 break;
606 case KC_RIGHT:
607 tinput_seek_word(ti, seek_forward, false);
608 break;
609 case KC_UP:
610 tinput_seek_vertical(ti, seek_backward, false);
611 break;
612 case KC_DOWN:
613 tinput_seek_vertical(ti, seek_forward, false);
614 break;
615 case KC_X:
616 tinput_sel_copy_to_cb(ti);
617 tinput_sel_delete(ti);
618 break;
619 case KC_C:
620 tinput_sel_copy_to_cb(ti);
621 break;
622 case KC_V:
623 tinput_sel_delete(ti);
624 tinput_paste_from_cb(ti);
625 break;
626 case KC_A:
627 tinput_sel_all(ti);
628 break;
629 case KC_Q:
630 /* Signal libary client to quit interactive loop. */
631 ti->done = true;
632 ti->exit_clui = true;
633 break;
634 default:
635 break;
636 }
637}
638
639static void tinput_key_ctrl_shift(tinput_t *ti, kbd_event_t *ev)
640{
641 switch (ev->key) {
642 case KC_LEFT:
643 tinput_seek_word(ti, seek_backward, true);
644 break;
645 case KC_RIGHT:
646 tinput_seek_word(ti, seek_forward, true);
647 break;
648 case KC_UP:
649 tinput_seek_vertical(ti, seek_backward, true);
650 break;
651 case KC_DOWN:
652 tinput_seek_vertical(ti, seek_forward, true);
653 break;
654 default:
655 break;
656 }
657}
658
659static void tinput_key_shift(tinput_t *ti, kbd_event_t *ev)
660{
661 switch (ev->key) {
662 case KC_LEFT:
663 tinput_seek_cell(ti, seek_backward, true);
664 break;
665 case KC_RIGHT:
666 tinput_seek_cell(ti, seek_forward, true);
667 break;
668 case KC_UP:
669 tinput_seek_vertical(ti, seek_backward, true);
670 break;
671 case KC_DOWN:
672 tinput_seek_vertical(ti, seek_forward, true);
673 break;
674 case KC_HOME:
675 tinput_seek_max(ti, seek_backward, true);
676 break;
677 case KC_END:
678 tinput_seek_max(ti, seek_forward, true);
679 break;
680 default:
681 break;
682 }
683}
684
685static void tinput_key_unmod(tinput_t *ti, kbd_event_t *ev)
686{
687 switch (ev->key) {
688 case KC_ENTER:
689 case KC_NENTER:
690 ti->done = true;
691 break;
692 case KC_BACKSPACE:
693 tinput_backspace(ti);
694 break;
695 case KC_DELETE:
696 tinput_delete(ti);
697 break;
698 case KC_LEFT:
699 tinput_seek_cell(ti, seek_backward, false);
700 break;
701 case KC_RIGHT:
702 tinput_seek_cell(ti, seek_forward, false);
703 break;
704 case KC_HOME:
705 tinput_seek_max(ti, seek_backward, false);
706 break;
707 case KC_END:
708 tinput_seek_max(ti, seek_forward, false);
709 break;
710 case KC_UP:
711 tinput_history_seek(ti, 1);
712 break;
713 case KC_DOWN:
714 tinput_history_seek(ti, -1);
715 break;
716 default:
717 break;
718 }
719}
Note: See TracBrowser for help on using the repository browser.