Return-Path: From: Jerzy Kasenberg To: CC: Jerzy Kasenberg Subject: [PATCH 2/5] android: Add line editing to haltest Date: Mon, 14 Oct 2013 11:59:51 +0200 Message-ID: <1381744794-17994-3-git-send-email-jerzy.kasenberg@tieto.com> In-Reply-To: <1381744794-17994-1-git-send-email-jerzy.kasenberg@tieto.com> References: <1381744794-17994-1-git-send-email-jerzy.kasenberg@tieto.com> MIME-Version: 1.0 Content-Type: text/plain Sender: linux-bluetooth-owner@vger.kernel.org List-ID: Android does not have readline. This patch allows to edit command line. --- android/Android.mk | 1 + android/hal_client/haltest.c | 39 +++- android/hal_client/terminal.c | 434 +++++++++++++++++++++++++++++++++++++++++ android/hal_client/terminal.h | 65 ++++++ 4 files changed, 536 insertions(+), 3 deletions(-) create mode 100644 android/hal_client/terminal.c create mode 100644 android/hal_client/terminal.h diff --git a/android/Android.mk b/android/Android.mk index c6bab81..c75187d 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -39,6 +39,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ hal_client/haltest.c \ hal_client/pollhandler.c \ + hal_client/terminal.c \ LOCAL_SHARED_LIBRARIES := libhardware diff --git a/android/hal_client/haltest.c b/android/hal_client/haltest.c index 8a733a2..a618cf0 100644 --- a/android/hal_client/haltest.c +++ b/android/hal_client/haltest.c @@ -25,8 +25,40 @@ #include #include +#include "terminal.h" #include "pollhandler.h" +/* + * This function changes input parameter line_buffer so it has + * null termination after each token (due to strtok) + * Output argv is filled with pointers to arguments + * returns number of tokens parsed - argc + */ +static int command_line_to_argv(char *line_buffer, + char *argv[], int argv_size) +{ + static const char *token_breaks = "\r\n\t "; + char *token; + int argc = 0; + + token = strtok(line_buffer, token_breaks); + while (token != NULL && argc < (int)argv_size) { + argv[argc++] = token; + token = strtok(NULL, token_breaks); + } + + return argc; +} + +static void process_line(char *line_buffer) +{ + char *argv[10]; + int argc; + + argc = command_line_to_argv(line_buffer, argv, 10); + + /* TODO: process command line */ +} /* called when there is something on stdin */ static void stdin_handler(struct pollfd *pollfd) @@ -39,15 +71,16 @@ static void stdin_handler(struct pollfd *pollfd) if (count > 0) { int i; - for (i = 0; i < count; ++i) { - /* TODO: process input */ - } + for (i = 0; i < count; ++i) + terminal_process_char(buf[i], process_line); } } } int main(int argc, char **argv) { + terminal_setup(); + /* Register command line handler */ poll_register_fd(0, POLLIN, stdin_handler); diff --git a/android/hal_client/terminal.c b/android/hal_client/terminal.c new file mode 100644 index 0000000..e48a413 --- /dev/null +++ b/android/hal_client/terminal.c @@ -0,0 +1,434 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 Intel Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include "terminal.h" + +/* + * Character sequences recognized by code in this file + * Leading ESC 0x1B is not included + */ +#define SEQ_INSERT "[2~" +#define SEQ_DELETE "[3~" +#define SEQ_HOME "OH" +#define SEQ_END "OF" +#define SEQ_PGUP "[5~" +#define SEQ_PGDOWN "[6~" +#define SEQ_LEFT "[D" +#define SEQ_RIGHT "[C" +#define SEQ_UP "[A" +#define SEQ_DOWN "[B" +#define SEQ_STAB "[Z" +#define SEQ_M_n "n" +#define SEQ_M_p "p" +#define SEQ_CLEFT "[1;5D" +#define SEQ_CRIGHT "[1;5C" +#define SEQ_CUP "[1;5A" +#define SEQ_CDOWN "[1;5B" +#define SEQ_SLEFT "[1;2D" +#define SEQ_SRIGHT "[1;2C" +#define SEQ_SUP "[1;2A" +#define SEQ_SDOWN "[1;2B" +#define SEQ_MLEFT "[1;3D" +#define SEQ_MRIGHT "[1;3C" +#define SEQ_MUP "[1;3A" +#define SEQ_MDOWN "[1;3B" + +#define KEY_SEQUENCE(k) { KEY_##k, SEQ_##k } +struct ansii_sequence { + int code; + const char *sequence; +}; + +/* Table connects single int key codes with character sequences */ +static const struct ansii_sequence ansii_sequnces[] = { + KEY_SEQUENCE(INSERT), + KEY_SEQUENCE(DELETE), + KEY_SEQUENCE(HOME), + KEY_SEQUENCE(END), + KEY_SEQUENCE(PGUP), + KEY_SEQUENCE(PGDOWN), + KEY_SEQUENCE(LEFT), + KEY_SEQUENCE(RIGHT), + KEY_SEQUENCE(UP), + KEY_SEQUENCE(DOWN), + KEY_SEQUENCE(CLEFT), + KEY_SEQUENCE(CRIGHT), + KEY_SEQUENCE(CUP), + KEY_SEQUENCE(CDOWN), + KEY_SEQUENCE(SLEFT), + KEY_SEQUENCE(SRIGHT), + KEY_SEQUENCE(SUP), + KEY_SEQUENCE(SDOWN), + KEY_SEQUENCE(MLEFT), + KEY_SEQUENCE(MRIGHT), + KEY_SEQUENCE(MUP), + KEY_SEQUENCE(MDOWN), + KEY_SEQUENCE(STAB), + KEY_SEQUENCE(M_p), + KEY_SEQUENCE(M_n), + { 0, NULL } +}; + +#define isseqence(c) ((c) == 0x1B) + +/* + * Number of characters that consist of ANSII sequence + * Should not be less then longest string in ansii_sequnces + */ +#define MAX_ASCII_SEQUENCE 10 + +static char current_sequence[MAX_ASCII_SEQUENCE]; +static int current_sequence_len = -1; + +/* single line typed by user goes here */ +static char line_buf[LINE_BUF_MAX]; +/* index of cursor in input line */ +static int line_buf_ix = 0; +/* current length of input line */ +static int line_len = 0; + +/* + * Moves cursor to right or left + * + * n - positive - moves cursor right + * n - negative - moves cursor left + */ +static void terminal_move_cursor(int n) +{ + if (n < 0) { + for (; n < 0; n++) + putchar('\b'); + } else if (n > 0) + printf("%*s", n, line_buf + line_buf_ix); +} + +/* Draw command line */ +void terminal_draw_command_line(void) +{ + /* + * this needs to be checked here since line_buf is not cleard + * before parsing event though line_len and line_buf_ix are + */ + if (line_len > 0) + printf(">%s", line_buf); + else + putchar('>'); + + /* move cursor to it's place */ + terminal_move_cursor(line_len - line_buf_ix); +} + +/* inserts string into command line at cursor position */ +void terminal_insert_into_command_line(const char *p) +{ + int len = strlen(p); + + if (line_len == line_buf_ix) { + strcat(line_buf, p); + printf("%s", p); + line_buf_ix = line_len = line_len + len; + } else { + memmove(line_buf + line_buf_ix + len, + line_buf + line_buf_ix, line_len - line_buf_ix + 1); + memmove(line_buf + line_buf_ix, p, len); + printf("%s", line_buf + line_buf_ix); + line_buf_ix += len; + line_len += len; + terminal_move_cursor(line_buf_ix - line_len); + } +} + +/* Prints string and redraws command line */ +int terminal_print(const char *format, ...) +{ + va_list args; + int ret; + + va_start(args, format); + + ret = terminal_vprint(format, args); + + va_end(args); + return ret; +} + +/* Prints string and redraws command line */ +int terminal_vprint(const char *format, va_list args) +{ + int ret; + + printf("\r%*s\r", (int)line_len + 1, " "); + + ret = vprintf(format, args); + + terminal_draw_command_line(); + + return ret; +} + +/* + * Converts terminal character sequences to single value representing + * keyboard keys + */ +static int terminal_convert_sequence(int c) +{ + int i; + + /* Not in sequence yet? */ + if (current_sequence_len == -1) { + /* Is ansii sequence detected by 0x1B ? */ + if (isseqence(c)) { + current_sequence_len++; + return 0; + } + return c; + } + /* Inside sequence */ + current_sequence[current_sequence_len++] = c; + current_sequence[current_sequence_len] = '\0'; + for (i = 0; ansii_sequnces[i].code; ++i) { + /* Matches so far? */ + if (0 != strncmp(current_sequence, + ansii_sequnces[i].sequence, current_sequence_len)) + continue; + + /* Matches as a whole? */ + if (ansii_sequnces[i].sequence[current_sequence_len] == 0) { + current_sequence_len = -1; + return ansii_sequnces[i].code; + } + /* partial match (not whole sequence yet) */ + return 0; + } + terminal_print("ansii char 0x%X %c\n", c); + /* + * Sequence does not match + * mark that no in sequence any more, return char + */ + current_sequence_len = -1; + return c; +} + +void terminal_process_char(int c, void (*process_line)(char *line)) +{ + int refresh_from = -1; + int old_pos; + + c = terminal_convert_sequence(c); + + switch (c) { + case 0: + break; + case KEY_LEFT: + /* if not at the beginning move to previous character */ + if (line_buf_ix <= 0) + break; + line_buf_ix--; + terminal_move_cursor(-1); + break; + case KEY_RIGHT: + /* + * If not at the end, just print current character + * and modify position + */ + if (line_buf_ix < line_len) + putchar(line_buf[line_buf_ix++]); + break; + case KEY_HOME: + /* move to beginning of line and update position */ + putchar('\r'); + putchar('>'); + line_buf_ix = 0; + break; + case KEY_END: + /* if not at the end of line */ + if (line_buf_ix < line_len) { + /* print everything from cursor */ + printf("%s", line_buf + line_buf_ix); + /* just modify current position */ + line_buf_ix = line_len; + } + break; + case KEY_DELETE: + /* delete character under cursor if not at the very end */ + if (line_buf_ix >= line_len) + break; + /* + * Prepare buffer with one character missing + * trailing 0 is moved + */ + line_len--; + memmove(line_buf + line_buf_ix, + line_buf + line_buf_ix + 1, + line_len - line_buf_ix + 1); + /* print rest of line from current cursor position */ + printf("%s \b", line_buf + line_buf_ix); + /* move back cursor */ + terminal_move_cursor(line_buf_ix - line_len); + break; + case KEY_CLEFT: + /* + * Move by word left + * + * Are we at the beginning of line? + */ + if (line_buf_ix <= 0) + break; + + old_pos = line_buf_ix; + line_buf_ix--; + /* skip spaces left */ + while (line_buf_ix && isspace(line_buf[line_buf_ix])) + line_buf_ix--; + /* skip all non spaces to the left */ + while (line_buf_ix > 0 && + !isspace(line_buf[line_buf_ix - 1])) + line_buf_ix--; + /* move cursor to new position */ + terminal_move_cursor(line_buf_ix - old_pos); + break; + case KEY_CRIGHT: + /* + * Move by word right + * + * are we at the end of line? + */ + if (line_buf_ix >= line_len) + break; + + old_pos = line_buf_ix; + /* skip all spaces */ + while (line_buf_ix < line_len && + isspace(line_buf[line_buf_ix])) + line_buf_ix++; + /* skip all non spaces */ + while (line_buf_ix < line_len && + !isspace(line_buf[line_buf_ix])) + line_buf_ix++; + /* + * Move cursor to right by printing text + * between old cursor and new + */ + if (line_buf_ix > old_pos) + printf("%.*s", (int)(line_buf_ix - old_pos), + line_buf + old_pos); + break; + case KEY_UP: + case KEY_DOWN: + break; + case '\n': + case '\r': + line_len = line_buf_ix = 0; + /* print new line */ + putchar(c); + process_line(line_buf); + /* clear current line */ + line_buf[0] = '\0'; + putchar('>'); + break; + case '\t': + /* tab processing */ + /* TODO Add completion here */ + break; + case KEY_BACKSPACE: + if (line_buf_ix <= 0) + break; + + if (line_buf_ix == line_len) { + printf("\b \b"); + line_len = --line_buf_ix; + line_buf[line_len] = 0; + } else { + putchar('\b'); + refresh_from = --line_buf_ix; + line_len--; + memmove(line_buf + line_buf_ix, + line_buf + line_buf_ix + 1, + line_len - line_buf_ix + 1); + } + break; + case KEY_INSERT: + case KEY_PGUP: + case KEY_PGDOWN: + case KEY_CUP: + case KEY_CDOWN: + case KEY_SLEFT: + case KEY_SRIGHT: + case KEY_MLEFT: + case KEY_MRIGHT: + case KEY_MUP: + case KEY_MDOWN: + case KEY_STAB: + case KEY_M_n: + case KEY_M_p: + break; + default: + if (!isprint(c)) { + /* + * TODO: remove this print once all meaningful sequences + * are identified + */ + printf("char-0x%02x\n", c); + break; + } + if (line_buf_ix < LINE_BUF_MAX - 1) { + if (line_len == line_buf_ix) { + putchar(c); + line_buf[line_buf_ix++] = (char)c; + line_len++; + line_buf[line_len] = '\0'; + } else { + memmove(line_buf + line_buf_ix + 1, + line_buf + line_buf_ix, + line_len - line_buf_ix + 1); + line_buf[line_buf_ix] = c; + refresh_from = line_buf_ix++; + line_len++; + } + } + break; + } + + if (refresh_from >= 0) { + printf("%s \b", line_buf + refresh_from); + terminal_move_cursor(line_buf_ix - line_len); + } +} + +void terminal_setup(void) +{ + struct termios tios; + + /* Turn off echo since all editing is done by hand */ + tcgetattr(0, &tios); + tios.c_lflag &= ~(ICANON | ECHO); + tcsetattr(0, TCSANOW, &tios); + putchar('>'); +} + diff --git a/android/hal_client/terminal.h b/android/hal_client/terminal.h new file mode 100644 index 0000000..073ebf0 --- /dev/null +++ b/android/hal_client/terminal.h @@ -0,0 +1,65 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 Intel Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +/* size of supported line */ +#define LINE_BUF_MAX 1024 + +enum key_codes { + KEY_BACKSPACE = 0x7F, + KEY_INSERT = 1000, /* arbitrary value */ + KEY_DELETE, + KEY_HOME, + KEY_END, + KEY_PGUP, + KEY_PGDOWN, + KEY_LEFT, + KEY_RIGHT, + KEY_UP, + KEY_DOWN, + KEY_CLEFT, + KEY_CRIGHT, + KEY_CUP, + KEY_CDOWN, + KEY_SLEFT, + KEY_SRIGHT, + KEY_SUP, + KEY_SDOWN, + KEY_MLEFT, + KEY_MRIGHT, + KEY_MUP, + KEY_MDOWN, + KEY_STAB, + KEY_M_p, + KEY_M_n +}; + +void terminal_setup(void); +int terminal_print(const char *format, ...); +int terminal_vprint(const char *format, va_list args); +void terminal_process_char(int c, void (*process_line)(char *line)); +void terminal_insert_into_command_line(const char *p); +void terminal_draw_command_line(void); + +void process_tab(const char *line, int len); -- 1.7.9.5