diff --git a/common.h b/common.h index 76d5747b..e17f76a4 100644 --- a/common.h +++ b/common.h @@ -26,12 +26,24 @@ void ui_init(); int ui_wait_key(); // waits for a key/button press, returns the code int ui_key_pressed(int key); // returns >0 if the code is currently pressed int ui_text_visible(); // returns >0 if text log is currently visible +void ui_clear_key_queue(); // Write a message to the on-screen log shown with Alt-L (also to stderr). // The screen is small, and users may need to report these messages to support, // so keep the output short and not too cryptic. void ui_print(const char *fmt, ...); +// Display some header text followed by a menu of items, which appears +// at the top of the screen (in place of any scrolling ui_print() +// output, if necessary). +void ui_start_menu(char** headers, char** items); +// Set the menu highlight to the given index, and return it (capped to +// the range [0..numitems). +int ui_menu_select(int sel); +// End menu mode, resetting the text overlay so that ui_print() +// statements will be displayed. +void ui_end_menu(); + // Set the icon (normally the only thing visible besides the progress bar). enum { BACKGROUND_ICON_NONE, diff --git a/minui/graphics.c b/minui/graphics.c index 05e87630..06c5fdfc 100644 --- a/minui/graphics.c +++ b/minui/graphics.c @@ -43,6 +43,7 @@ static GRFont *gr_font = 0; static GGLContext *gr_context = 0; static GGLSurface gr_font_texture; static GGLSurface gr_framebuffer[2]; +static GGLSurface gr_mem_surface; static unsigned gr_active_fb = 0; static int gr_fb_fd = -1; @@ -55,7 +56,7 @@ static int get_framebuffer(GGLSurface *fb) int fd; struct fb_fix_screeninfo fi; void *bits; - + fd = open("/dev/graphics/fb0", O_RDWR); if (fd < 0) { perror("cannot open fb0"); @@ -87,9 +88,9 @@ static int get_framebuffer(GGLSurface *fb) fb->stride = vi.xres; fb->data = bits; fb->format = GGL_PIXEL_FORMAT_RGB_565; - + fb++; - + fb->version = sizeof(*fb); fb->width = vi.xres; fb->height = vi.yres; @@ -100,6 +101,15 @@ static int get_framebuffer(GGLSurface *fb) return fd; } +static void get_memory_surface(GGLSurface* ms) { + ms->version = sizeof(*ms); + ms->width = vi.xres; + ms->height = vi.yres; + ms->stride = vi.xres; + ms->data = malloc(vi.xres * vi.yres * 2); + ms->format = GGL_PIXEL_FORMAT_RGB_565; +} + static void set_active_framebuffer(unsigned n) { if (n > 1) return; @@ -113,14 +123,16 @@ static void set_active_framebuffer(unsigned n) void gr_flip(void) { GGLContext *gl = gr_context; - - /* currently active buffer becomes the backbuffer */ - gl->colorBuffer(gl, gr_framebuffer + gr_active_fb); - /* swap front and back buffers */ + /* swap front and back buffers */ gr_active_fb = (gr_active_fb + 1) & 1; - /* inform the display driver */ + /* copy data from the in-memory surface to the buffer we're about + * to make active. */ + memcpy(gr_framebuffer[gr_active_fb].data, gr_mem_surface.data, + vi.xres * vi.yres * 2); + + /* inform the display driver */ set_active_framebuffer(gr_active_fb); } @@ -147,13 +159,13 @@ int gr_text(int x, int y, const char *s) unsigned off; y -= font->ascent; - + gl->bindTexture(gl, &font->texture); gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); gl->enable(gl, GGL_TEXTURE_2D); - + while((off = *s++)) { off -= 32; if (off < 96) { @@ -209,10 +221,10 @@ static void gr_init_font(void) unsigned char *in, data; gr_font = calloc(sizeof(*gr_font), 1); - ftex = &gr_font->texture; + ftex = &gr_font->texture; bits = malloc(font.width * font.height); - + ftex->version = sizeof(*ftex); ftex->width = font.width; ftex->height = font.height; @@ -225,7 +237,7 @@ static void gr_init_font(void) memset(bits, (data & 0x80) ? 255 : 0, data & 0x7f); bits += (data & 0x7f); } - + gr_font->cwidth = font.cwidth; gr_font->cheight = font.cheight; gr_font->ascent = font.cheight - 2; @@ -254,13 +266,16 @@ int gr_init(void) return -1; } + get_memory_surface(&gr_mem_surface); + fprintf(stderr, "framebuffer: fd %d (%d x %d)\n", gr_fb_fd, gr_framebuffer[0].width, gr_framebuffer[0].height); /* start with 0 as front (displayed) and 1 as back (drawing) */ gr_active_fb = 0; set_active_framebuffer(0); - gl->colorBuffer(gl, gr_framebuffer + 1); + gl->colorBuffer(gl, &gr_mem_surface); + gl->activeTexture(gl, 0); gl->enable(gl, GGL_BLEND); @@ -273,7 +288,9 @@ void gr_exit(void) { close(gr_fb_fd); gr_fb_fd = -1; - + + free(gr_mem_surface.data); + ioctl(gr_vt_fd, KDSETMODE, (void*) KD_TEXT); close(gr_vt_fd); gr_vt_fd = -1; @@ -291,5 +308,5 @@ int gr_fb_height(void) gr_pixel *gr_fb_data(void) { - return (unsigned short *) gr_framebuffer[1 - gr_active_fb].data; + return (unsigned short *) gr_mem_surface.data; } diff --git a/recovery.c b/recovery.c index 3d44df3b..221ee297 100644 --- a/recovery.c +++ b/recovery.c @@ -291,17 +291,32 @@ erase_root(const char *root) static void prompt_and_wait() { - ui_print("\n" - "Home+Back - reboot system now\n" - "Alt+L - toggle log text display\n" - "Alt+S - apply sdcard:update.zip\n" - "Alt+W - wipe data/factory reset\n"); + char* headers[] = { "Android system recovery utility", + "", + "Use trackball to highlight;", + "click to select.", + "", + NULL }; + // these constants correspond to elements of the items[] list. +#define ITEM_REBOOT 0 +#define ITEM_APPLY_SDCARD 1 +#define ITEM_WIPE_DATA 2 + char* items[] = { "reboot system now [Home+Back]", + "apply sdcard:update.zip [Alt+S]", + "wipe data/factory reset [Alt+W]", + NULL }; + + ui_start_menu(headers, items); + int selected = 0; + int chosen_item = -1; + + finish_recovery(NULL); + ui_reset_progress(); for (;;) { - finish_recovery(NULL); - ui_reset_progress(); int key = ui_wait_key(); int alt = ui_key_pressed(KEY_LEFTALT) || ui_key_pressed(KEY_RIGHTALT); + int visible = ui_text_visible(); if (key == KEY_DREAM_BACK && ui_key_pressed(KEY_DREAM_HOME)) { // Wait for the keys to be released, to avoid triggering @@ -310,23 +325,64 @@ prompt_and_wait() ui_key_pressed(KEY_DREAM_HOME)) { usleep(1000); } - break; + chosen_item = ITEM_REBOOT; } else if (alt && key == KEY_W) { - ui_print("\n"); - erase_root("DATA:"); - erase_root("CACHE:"); - ui_print("Data wipe complete.\n"); - if (!ui_text_visible()) break; + chosen_item = ITEM_WIPE_DATA; } else if (alt && key == KEY_S) { - ui_print("\nInstalling from sdcard...\n"); - int status = install_package(SDCARD_PACKAGE_FILE); - if (status != INSTALL_SUCCESS) { - ui_set_background(BACKGROUND_ICON_ERROR); - ui_print("Installation aborted.\n"); - } else if (!ui_text_visible()) { - break; // reboot if logs aren't visible + chosen_item = ITEM_APPLY_SDCARD; + } else if ((key == KEY_DOWN || key == KEY_VOLUMEDOWN) && visible) { + ++selected; + selected = ui_menu_select(selected); + } else if ((key == KEY_UP || key == KEY_VOLUMEUP) && visible) { + --selected; + selected = ui_menu_select(selected); + } else if (key == BTN_MOUSE && visible) { + chosen_item = selected; + } + + if (chosen_item >= 0) { + // turn off the menu, letting ui_print() to scroll output + // on the screen. + ui_end_menu(); + + switch (chosen_item) { + case ITEM_REBOOT: + return; + + case ITEM_WIPE_DATA: + ui_print("\n-- Wiping data...\n"); + erase_root("DATA:"); + erase_root("CACHE:"); + ui_print("Data wipe complete.\n"); + if (!ui_text_visible()) return; + break; + + case ITEM_APPLY_SDCARD: + ui_print("\n-- Install from sdcard...\n"); + int status = install_package(SDCARD_PACKAGE_FILE); + if (status != INSTALL_SUCCESS) { + ui_set_background(BACKGROUND_ICON_ERROR); + ui_print("Installation aborted.\n"); + } else if (!ui_text_visible()) { + return; // reboot if logs aren't visible + } else { + ui_print("Install from sdcard complete.\n"); + } + break; } - ui_print("\nPress Home+Back to reboot\n"); + + // if we didn't return from this function to reboot, show + // the menu again. + ui_start_menu(headers, items); + selected = 0; + chosen_item = -1; + + finish_recovery(NULL); + ui_reset_progress(); + + // throw away keys pressed while the command was running, + // so user doesn't accidentally trigger menu items. + ui_clear_key_queue(); } } } @@ -348,7 +404,6 @@ main(int argc, char **argv) fprintf(stderr, "Starting recovery on %s", ctime(&start)); ui_init(); - ui_print("Android system recovery utility\n"); get_args(&argc, &argv); int previous_runs = 0; diff --git a/ui.c b/ui.c index b9869aed..5d06561c 100644 --- a/ui.c +++ b/ui.c @@ -89,6 +89,10 @@ static int text_cols = 0, text_rows = 0; static int text_col = 0, text_row = 0, text_top = 0; static int show_text = 0; +static char menu[MAX_ROWS][MAX_COLS]; +static int show_menu = 0; +static int menu_top = 0, menu_items = 0, menu_sel = 0; + // Key event input queue static pthread_mutex_t key_queue_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t key_queue_cond = PTHREAD_COND_INITIALIZER; @@ -154,6 +158,12 @@ static void draw_progress_locked() } } +static void draw_text_line(int row, const char* t) { + if (t[0] != '\0') { + gr_text(0, (row+1)*CHAR_HEIGHT-1, t); + } +} + // Redraw everything on the screen. Does not flip pages. // Should only be called with gUpdateMutex locked. static void draw_screen_locked(void) @@ -163,13 +173,32 @@ static void draw_screen_locked(void) if (show_text) { gr_color(0, 0, 0, 160); - gr_fill(0, 0, gr_fb_width(), text_rows * CHAR_HEIGHT); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + int i = 0; + if (show_menu) { + gr_color(64, 96, 255, 255); + gr_fill(0, (menu_top+menu_sel) * CHAR_HEIGHT, + gr_fb_width(), (menu_top+menu_sel+1)*CHAR_HEIGHT+1); + + for (; i < menu_top + menu_items; ++i) { + if (i == menu_top + menu_sel) { + gr_color(255, 255, 255, 255); + draw_text_line(i, menu[i]); + gr_color(64, 96, 255, 255); + } else { + draw_text_line(i, menu[i]); + } + } + gr_fill(0, i*CHAR_HEIGHT+CHAR_HEIGHT/2-1, + gr_fb_width(), i*CHAR_HEIGHT+CHAR_HEIGHT/2+1); + ++i; + } gr_color(255, 255, 0, 255); - int i; - for (i = 0; i < text_rows; ++i) { - const char* line = text[(i + text_top) % text_rows]; - if (line[0] != '\0') gr_text(0, (i + 1) * CHAR_HEIGHT - 1, line); + + for (; i < text_rows; ++i) { + draw_text_line(i, text[(i+text_top) % text_rows]); } } } @@ -228,13 +257,50 @@ static void *progress_thread(void *cookie) // Reads input events, handles special hot keys, and adds to the key queue. static void *input_thread(void *cookie) { + int rel_sum = 0; + int fake_key = 0; for (;;) { // wait for the next key event struct input_event ev; - do { ev_get(&ev, 0); } while (ev.type != EV_KEY || ev.code > KEY_MAX); + do { + ev_get(&ev, 0); + + if (ev.type == EV_SYN) { + continue; + } else if (ev.type == EV_REL) { + if (ev.code == REL_Y) { + // accumulate the up or down motion reported by + // the trackball. When it exceeds a threshold + // (positive or negative), fake an up/down + // key event. + rel_sum += ev.value; + if (rel_sum > 3) { + fake_key = 1; + ev.type = EV_KEY; + ev.code = KEY_DOWN; + ev.value = 1; + rel_sum = 0; + } else if (rel_sum < -3) { + fake_key = 1; + ev.type = EV_KEY; + ev.code = KEY_UP; + ev.value = 1; + rel_sum = 0; + } + } + } else { + rel_sum = 0; + } + } while (ev.type != EV_KEY || ev.code > KEY_MAX); pthread_mutex_lock(&key_queue_mutex); - key_pressed[ev.code] = ev.value; + if (!fake_key) { + // our "fake" keys only report a key-down event (no + // key-up), so don't record them in the key_pressed + // table. + key_pressed[ev.code] = ev.value; + } + fake_key = 0; const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]); if (ev.value > 0 && key_queue_len < queue_max) { key_queue[key_queue_len++] = ev.code; @@ -242,9 +308,10 @@ static void *input_thread(void *cookie) } pthread_mutex_unlock(&key_queue_mutex); - // Alt+L: toggle log display + // Alt+L or Home+End: toggle log display int alt = key_pressed[KEY_LEFTALT] || key_pressed[KEY_RIGHTALT]; - if (alt && ev.code == KEY_L && ev.value > 0) { + if ((alt && ev.code == KEY_L && ev.value > 0) || + (key_pressed[KEY_HOME] && ev.code == KEY_END && ev.value > 0)) { pthread_mutex_lock(&gUpdateMutex); show_text = !show_text; update_screen_locked(); @@ -269,6 +336,7 @@ void ui_init(void) text_col = text_row = 0; text_rows = gr_fb_height() / CHAR_HEIGHT; if (text_rows > MAX_ROWS) text_rows = MAX_ROWS; + text_top = 1; text_cols = gr_fb_width() / CHAR_WIDTH; if (text_cols > MAX_COLS - 1) text_cols = MAX_COLS - 1; @@ -392,6 +460,54 @@ void ui_print(const char *fmt, ...) pthread_mutex_unlock(&gUpdateMutex); } +void ui_start_menu(char** headers, char** items) { + int i; + pthread_mutex_lock(&gUpdateMutex); + if (text_rows > 0 && text_cols > 0) { + for (i = 0; i < text_rows; ++i) { + if (headers[i] == NULL) break; + strncpy(menu[i], headers[i], text_cols-1); + menu[i][text_cols-1] = '\0'; + } + menu_top = i; + for (; i < text_rows; ++i) { + if (items[i-menu_top] == NULL) break; + strncpy(menu[i], items[i-menu_top], text_cols-1); + menu[i][text_cols-1] = '\0'; + } + menu_items = i - menu_top; + show_menu = 1; + menu_sel = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); +} + +int ui_menu_select(int sel) { + int old_sel; + pthread_mutex_lock(&gUpdateMutex); + if (show_menu > 0) { + old_sel = menu_sel; + menu_sel = sel; + if (menu_sel < 0) menu_sel = 0; + if (menu_sel >= menu_items) menu_sel = menu_items-1; + sel = menu_sel; + if (menu_sel != old_sel) update_screen_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); + return sel; +} + +void ui_end_menu() { + int i; + pthread_mutex_lock(&gUpdateMutex); + if (show_menu > 0 && text_rows > 0 && text_cols > 0) { + show_menu = 0; + update_screen_locked(); + } + pthread_mutex_unlock(&gUpdateMutex); +} + int ui_text_visible() { pthread_mutex_lock(&gUpdateMutex); @@ -418,3 +534,9 @@ int ui_key_pressed(int key) // This is a volatile static array, don't bother locking return key_pressed[key]; } + +void ui_clear_key_queue() { + pthread_mutex_lock(&key_queue_mutex); + key_queue_len = 0; + pthread_mutex_unlock(&key_queue_mutex); +}