diff options
28 files changed, 750 insertions, 125 deletions
@@ -112,6 +112,13 @@ endif LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/sbin +# Handling for EV_REL is disabled by default because some accelerometers +# send EV_REL events. Actual EV_REL devices are rare on modern hardware +# so it's cleaner just to disable it by default. +ifneq ($(BOARD_RECOVERY_NEEDS_REL_INPUT),) + LOCAL_CFLAGS += -DBOARD_RECOVERY_NEEDS_REL_INPUT +endif + ifeq ($(TARGET_USE_MDTP), true) LOCAL_CFLAGS += -DUSE_MDTP endif @@ -55,6 +55,10 @@ int Device::HandleMenuKey(int key, int visible) { return kNoAction; } + if (key & KEY_FLAG_ABS) { + return key; + } + switch (key) { case KEY_RIGHTSHIFT: case KEY_DOWN: @@ -19,6 +19,8 @@ #include "ui.h" +#define KEY_FLAG_ABS 0x8000 + class Device : public VoldWatcher { public: Device(RecoveryUI* ui) : ui_(ui) { } diff --git a/minui/graphics.cpp b/minui/graphics.cpp index 43c28ce..1f25531 100644 --- a/minui/graphics.cpp +++ b/minui/graphics.cpp @@ -41,6 +41,20 @@ struct GRFont { int cheight; }; +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#endif + +typedef struct { + char name[80]; + GRFont* font; +} font_item; + +static font_item gr_fonts[] = { + { "menu", NULL }, + { "log", NULL }, +}; + static GRFont* gr_font = NULL; static minui_backend* gr_backend = NULL; @@ -261,6 +275,17 @@ void gr_fill(int x1, int y1, int x2, int y2) } } +void gr_set_font(const char* name) +{ + unsigned int idx; + for (idx = 0; idx < ARRAY_SIZE(gr_fonts); ++idx) { + if (strcmp(name, gr_fonts[idx].name) == 0) { + gr_font = gr_fonts[idx].font; + break; + } + } +} + void gr_blit(GRSurface* source, int sx, int sy, int w, int h, int dx, int dy) { if (source == NULL) return; @@ -299,11 +324,14 @@ unsigned int gr_get_height(GRSurface* surface) { return surface->height; } -static void gr_init_font(void) +static void gr_init_one_font(int idx) { - gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1)); + char name[80]; + GRFont* gr_font = reinterpret_cast<GRFont*>(calloc(sizeof(*gr_font), 1)); + snprintf(name, sizeof(name), "font_%s", gr_fonts[idx].name); + gr_fonts[idx].font = gr_font; - int res = res_create_alpha_surface("font", &(gr_font->texture)); + int res = res_create_alpha_surface(name, &(gr_font->texture)); if (res == 0) { // The font image should be a 96x2 array of character images. The // columns are the printable ASCII characters 0x20 - 0x7f. The @@ -335,6 +363,15 @@ static void gr_init_font(void) } } +static void gr_init_font() +{ + unsigned int idx; + for (idx = 0; idx < ARRAY_SIZE(gr_fonts); ++idx) { + gr_init_one_font(idx); + } + gr_font = gr_fonts[0].font; +} + #if 0 // Exercises many of the gr_*() functions; useful for testing. static void gr_test() { diff --git a/minui/minui.h b/minui/minui.h index 7dec2d7..063fc86 100644 --- a/minui/minui.h +++ b/minui/minui.h @@ -45,6 +45,7 @@ void gr_fb_blank(bool blank); void gr_clear(); // clear entire surface to current color void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); void gr_fill(int x1, int y1, int x2, int y2); +void gr_set_font(const char* name); void gr_text(int x, int y, const char *s, bool bold); void gr_texticon(int x, int y, GRSurface* icon); int gr_measure(const char *s); diff --git a/recovery.cpp b/recovery.cpp index d3b1aec..f260854 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -644,6 +644,11 @@ get_menu_selection(const char* const * headers, const char* const * items, // accidentally trigger menu items. ui->FlushKeys(); + // Count items to detect valid values for absolute selection + int item_count = 0; + while (items[item_count] != NULL) + ++item_count; + ui->StartMenu(headers, items, initial_selection); int selected = initial_selection; int chosen_item = -1; @@ -669,6 +674,20 @@ get_menu_selection(const char* const * headers, const char* const * items, int action = device->HandleMenuKey(key, visible); + if (action >= 0) { + if ((action & ~KEY_FLAG_ABS) >= item_count) { + action = Device::kNoAction; + } + else { + // Absolute selection. Update selected item and give some + // feedback in the UI by selecting the item for a short time. + selected = action & ~KEY_FLAG_ABS; + action = Device::kInvokeItem; + selected = ui->SelectMenu(selected, true); + usleep(50*1000); + } + } + if (action < 0) { switch (action) { case Device::kHighlightUp: @@ -851,8 +870,15 @@ static bool wipe_cache(bool should_confirm, Device* device) { modified_flash = true; ui->Print("\n-- Wiping cache...\n"); + ui->DialogShowInfo("Wiping cache ..."); bool success = erase_volume("/cache"); ui->Print("Cache wipe %s.\n", success ? "complete" : "failed"); + if (!should_confirm || success) { + ui->DialogDismiss(); + } + else { + ui->DialogShowErrorLog("Cache wipe failed"); + } return success; } @@ -928,6 +954,9 @@ static int apply_from_storage(Device* device, const std::string& id, bool* wipe_ return INSTALL_NONE; } + ui->ClearText(); + ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); + ui->Print("\n-- Install %s ...\n", path); set_sdcard_update_bootloader_message(); void* token = start_sdcard_fuse(path); @@ -995,6 +1024,10 @@ refresh: status = apply_from_storage(device, id, &wipe_cache); } + if (status != INSTALL_SUCCESS && status != INSTALL_NONE) { + ui->DialogShowErrorLog("Install failed"); + } + return status; } @@ -1016,7 +1049,7 @@ prompt_and_wait(Device* device, int status) { case INSTALL_ERROR: case INSTALL_CORRUPT: - ui->SetBackground(RecoveryUI::ERROR); + ui->SetBackground(RecoveryUI::D_ERROR); break; } ui->SetProgressType(RecoveryUI::EMPTY); @@ -1067,7 +1100,7 @@ prompt_and_wait(Device* device, int status) { if (status >= 0 && status != INSTALL_NONE) { if (status != INSTALL_SUCCESS) { - ui->SetBackground(RecoveryUI::ERROR); + ui->SetBackground(RecoveryUI::D_ERROR); ui->Print("Installation aborted.\n"); copy_logs(); } else if (!ui->IsTextVisible()) { @@ -1455,7 +1488,7 @@ main(int argc, char **argv) { if (!sideload_auto_reboot && (status == INSTALL_ERROR || status == INSTALL_CORRUPT)) { copy_logs(); - ui->SetBackground(RecoveryUI::ERROR); + ui->SetBackground(RecoveryUI::D_ERROR); } Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; diff --git a/res-hdpi/images/font_log.png b/res-hdpi/images/font_log.png Binary files differnew file mode 100644 index 0000000..59b990a --- /dev/null +++ b/res-hdpi/images/font_log.png diff --git a/res-hdpi/images/font_menu.png b/res-hdpi/images/font_menu.png Binary files differnew file mode 100644 index 0000000..d1aad6d --- /dev/null +++ b/res-hdpi/images/font_menu.png diff --git a/res-hdpi/images/icon_header.png b/res-hdpi/images/icon_header.png Binary files differnew file mode 100644 index 0000000..634489f --- /dev/null +++ b/res-hdpi/images/icon_header.png diff --git a/res-mdpi/images/font_log.png b/res-mdpi/images/font_log.png Binary files differnew file mode 100644 index 0000000..bcbe242 --- /dev/null +++ b/res-mdpi/images/font_log.png diff --git a/res-mdpi/images/font_menu.png b/res-mdpi/images/font_menu.png Binary files differnew file mode 100644 index 0000000..59b990a --- /dev/null +++ b/res-mdpi/images/font_menu.png diff --git a/res-mdpi/images/icon_header.png b/res-mdpi/images/icon_header.png Binary files differnew file mode 100644 index 0000000..4ddb812 --- /dev/null +++ b/res-mdpi/images/icon_header.png diff --git a/res-xhdpi/images/font_log.png b/res-xhdpi/images/font_log.png Binary files differnew file mode 100644 index 0000000..d1aad6d --- /dev/null +++ b/res-xhdpi/images/font_log.png diff --git a/res-xhdpi/images/font_menu.png b/res-xhdpi/images/font_menu.png Binary files differnew file mode 100644 index 0000000..6aead73 --- /dev/null +++ b/res-xhdpi/images/font_menu.png diff --git a/res-xhdpi/images/icon_header.png b/res-xhdpi/images/icon_header.png Binary files differnew file mode 100644 index 0000000..3254528 --- /dev/null +++ b/res-xhdpi/images/icon_header.png diff --git a/res-xxhdpi/images/font_log.png b/res-xxhdpi/images/font_log.png Binary files differnew file mode 100644 index 0000000..6aead73 --- /dev/null +++ b/res-xxhdpi/images/font_log.png diff --git a/res-xxhdpi/images/font_menu.png b/res-xxhdpi/images/font_menu.png Binary files differnew file mode 100644 index 0000000..f3b54b3 --- /dev/null +++ b/res-xxhdpi/images/font_menu.png diff --git a/res-xxhdpi/images/icon_header.png b/res-xxhdpi/images/icon_header.png Binary files differnew file mode 100644 index 0000000..4b68cbc --- /dev/null +++ b/res-xxhdpi/images/icon_header.png diff --git a/res-xxxhdpi/images/font_log.png b/res-xxxhdpi/images/font_log.png Binary files differnew file mode 100644 index 0000000..ec8d899 --- /dev/null +++ b/res-xxxhdpi/images/font_log.png diff --git a/res-xxxhdpi/images/font_menu.png b/res-xxxhdpi/images/font_menu.png Binary files differnew file mode 100644 index 0000000..d7c326a --- /dev/null +++ b/res-xxxhdpi/images/font_menu.png diff --git a/res-xxxhdpi/images/icon_header.png b/res-xxxhdpi/images/icon_header.png Binary files differnew file mode 100644 index 0000000..b4aebd7 --- /dev/null +++ b/res-xxxhdpi/images/icon_header.png diff --git a/screen_ui.cpp b/screen_ui.cpp index a64bad6..464e77d 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -40,9 +40,6 @@ #include "screen_ui.h" #include "ui.h" -static int char_width; -static int char_height; - // Return the current time as a double (including fractions of a second). static double now() { struct timeval tv; @@ -60,6 +57,8 @@ ScreenRecoveryUI::ScreenRecoveryUI() : progressScopeSize(0), progress(0), pagesIdentical(false), + log_text_cols_(0), + log_text_rows_(0), text_cols_(0), text_rows_(0), text_(nullptr), @@ -70,6 +69,7 @@ ScreenRecoveryUI::ScreenRecoveryUI() : show_text_ever(false), dialog_icon(NONE), dialog_text(nullptr), + dialog_show_log(false), menu_(nullptr), show_menu(false), menu_items(0), @@ -82,6 +82,7 @@ ScreenRecoveryUI::ScreenRecoveryUI() : rainbow(false), wrap_count(0) { + headerIcon = nullptr; for (int i = 0; i < NR_ICONS; i++) { backgroundIcon[i] = nullptr; } @@ -136,7 +137,7 @@ void ScreenRecoveryUI::draw_background_locked(Icon icon) { // Draw the progress bar (if any) on the screen. Does not flip pages. // Should only be called with updateMutex locked. void ScreenRecoveryUI::draw_progress_locked() { - if (currentIcon == ERROR) return; + if (currentIcon == D_ERROR) return; if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { GRSurface* icon = installation[installingFrame]; @@ -222,7 +223,7 @@ void ScreenRecoveryUI::DrawHorizontalRule(int* y) { void ScreenRecoveryUI::DrawTextLine(int* y, const char* line, bool bold) { gr_text(4, *y, line, bold); - *y += char_height + 4; + *y += char_height_ + 4; } void ScreenRecoveryUI::DrawTextLines(int* y, const char* const* lines) { @@ -242,21 +243,73 @@ static const char* LONG_PRESS_HELP[] = { NULL }; +int ScreenRecoveryUI::draw_header_icon() +{ + GRSurface* surface = headerIcon; + int iw = header_width_; + int ih = header_height_; + int ix = (gr_fb_width() - iw) / 2; + int iy = 0; + gr_blit(surface, 0, 0, iw, ih, ix, iy); + return ih; +} + +void ScreenRecoveryUI::draw_menu_item(int textrow, const char *text, int selected) +{ + if (selected) { + SetColor(MENU_SEL_BG); + gr_fill(0, (textrow) * char_height_, + gr_fb_width(), (textrow+3) * char_height_ - 1); + SetColor(MENU_SEL_FG); + gr_text(4, (textrow+1) * char_height_ - 1, text, 0); + SetColor(MENU); + } + else { + SetColor(MENU); + gr_text(4, (textrow+1) * char_height_ - 1, text, 0); + } +} + void ScreenRecoveryUI::draw_dialog() { int x, y, w, h; - draw_background_locked(dialog_icon); + if (dialog_show_log) { + draw_background_locked(NONE); + } + else { + draw_background_locked(dialog_icon); + } + draw_header_icon(); int iconHeight = gr_get_height(backgroundIcon[dialog_icon]); - x = (gr_fb_width()/2 - (char_width*strlen(dialog_text))/2); - y = (gr_fb_height()/2 + iconHeight/2); + x = (gr_fb_width()/2 - (char_width_ * strlen(dialog_text))/2); + if (dialog_show_log) { + y = gr_get_height(headerIcon) + char_height_; + } + else { + y = (gr_fb_height()/2 + iconHeight/2); + } SetColor(ERROR_TEXT); gr_text(x, y, dialog_text, 0); + y += char_height_ + 2; + + if (dialog_show_log) { + int cx, cy; + gr_set_font("log"); + gr_font_size(&cx, &cy); - if (dialog_icon == ERROR) { + size_t row; + for (row = 0; row < log_text_rows_; ++row) { + gr_text(2, y, text_[row], 0); + y += cy + 2; + } + gr_set_font("menu"); + } + + if (dialog_icon == D_ERROR) { /* * This could be improved... * @@ -266,10 +319,10 @@ void ScreenRecoveryUI::draw_dialog() * Rect width 4px * Rect padding 8px */ - w = char_width*4; - h = char_height; + w = char_width_ * 4; + h = char_height_; x = gr_fb_width()/2 - w/2; - y = gr_fb_height() - h - 4*char_height; + y = gr_fb_height() - h - 4 * char_height_; SetColor(HEADER); gr_fill(x-(4+8), y-(4+8), x+w+(4+8), y+h+(4+8)); SetColor(MENU_SEL_BG); @@ -291,57 +344,42 @@ void ScreenRecoveryUI::draw_screen_locked() { draw_dialog(); return; } - gr_color(0, 0, 0, 255); gr_clear(); - int y = 0; - if (show_menu) { - char recovery_fingerprint[PROPERTY_VALUE_MAX]; - property_get("ro.bootimage.build.fingerprint", recovery_fingerprint, ""); - - SetColor(INFO); - DrawTextLine(&y, "Android Recovery", true); - for (auto& chunk : android::base::Split(recovery_fingerprint, ":")) { - DrawTextLine(&y, chunk.c_str(), false); + if (currentIcon == INSTALLING_UPDATE) { + size_t y = header_height_ + 4; + + draw_background_locked(currentIcon); + + SetColor(LOG); + int cx, cy; + gr_set_font("log"); + gr_font_size(&cx, &cy); + // display from the bottom up, until we hit the top of the + // screen or we've displayed the entire text buffer. + size_t ty, count; + int row = (text_first_row_ + log_text_rows_ - 1) % log_text_rows_; + for (ty = gr_fb_height() - cy, count = 0; + ty > y + 2 && count < log_text_rows_; + ty -= (cy + 2), ++count) { + gr_text(4, ty, text_[row], 0); + --row; + if (row < 0) row = log_text_rows_ - 1; } - DrawTextLines(&y, HasThreeButtons() ? REGULAR_HELP : LONG_PRESS_HELP); - - SetColor(HEADER); - DrawTextLines(&y, menu_headers_); - - SetColor(MENU); - DrawHorizontalRule(&y); - y += 4; - for (int i = 0; i < menu_items; ++i) { - if (i == menu_sel) { - // Draw the highlight bar. - SetColor(IsLongPress() ? MENU_SEL_BG_ACTIVE : MENU_SEL_BG); - gr_fill(0, y - 2, gr_fb_width(), y + char_height + 2); - // Bold white text for the selected item. - SetColor(MENU_SEL_FG); - gr_text(4, y, menu_[i], true); - SetColor(MENU); - } else { - gr_text(4, y, menu_[i], false); - } - y += char_height + 4; - } - DrawHorizontalRule(&y); + gr_set_font("menu"); + return; } - // display from the bottom up, until we hit the top of the - // screen, the bottom of the menu, or we've displayed the - // entire text buffer. - SetColor(LOG); - int row = (text_top_ + text_rows_ - 1) % text_rows_; - size_t count = 0; - for (int ty = gr_fb_height() - char_height; - ty >= y && count < text_rows_; - ty -= char_height, ++count) { - gr_text(0, ty, text_[row], false); - --row; - if (row < 0) row = text_rows_ - 1; + if (show_menu) { + draw_header_icon(); + int nr_items = menu_items - menu_show_start_; + if (nr_items > max_menu_rows_) + nr_items = max_menu_rows_; + for (int i = 0; i < nr_items; ++i) { + draw_menu_item(text_first_row_ + 3*i, menu_[menu_show_start_ + i], + ((menu_show_start_ + i) == menu_sel)); + } } } } @@ -449,9 +487,15 @@ static char** Alloc2d(size_t rows, size_t cols) { void ScreenRecoveryUI::Init() { gr_init(); - gr_font_size(&char_width, &char_height); - text_rows_ = gr_fb_height() / char_height; - text_cols_ = gr_fb_width() / char_width; + gr_set_font("log"); + gr_font_size(&log_char_width_, &log_char_height_); + gr_set_font("menu"); + gr_font_size(&char_width_, &char_height_); + text_rows_ = gr_fb_height() / char_height_; + text_cols_ = gr_fb_width() / char_width_; + + log_text_rows_ = gr_fb_height() / log_char_height_; + log_text_cols_ = gr_fb_width() / log_char_width_; text_ = Alloc2d(text_rows_, text_cols_ + 1); file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); @@ -460,13 +504,21 @@ void ScreenRecoveryUI::Init() { text_col_ = text_row_ = 0; text_top_ = 1; + LoadBitmap("icon_header", &headerIcon); + header_height_ = gr_get_height(headerIcon); + header_width_ = gr_get_width(headerIcon); + + text_first_row_ = (header_height_ / char_height_) + 1; + menu_item_start_ = text_first_row_ * char_height_; + max_menu_rows_ = (text_rows_ - text_first_row_) / 3; + backgroundIcon[NONE] = nullptr; LoadBitmapArray("icon_installing", &installing_frames, &installation); backgroundIcon[INSTALLING_UPDATE] = installing_frames ? installation[0] : nullptr; backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; - LoadBitmap("icon_info", &backgroundIcon[INFO]); - LoadBitmap("icon_error", &backgroundIcon[ERROR]); - backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + LoadBitmap("icon_info", &backgroundIcon[D_INFO]); + LoadBitmap("icon_error", &backgroundIcon[D_ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[D_ERROR]; LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); @@ -476,7 +528,7 @@ void ScreenRecoveryUI::Init() { LoadLocalizedBitmap("installing_text", &backgroundText[INSTALLING_UPDATE]); LoadLocalizedBitmap("erasing_text", &backgroundText[ERASING]); LoadLocalizedBitmap("no_command_text", &backgroundText[NO_COMMAND]); - LoadLocalizedBitmap("error_text", &backgroundText[ERROR]); + LoadLocalizedBitmap("error_text", &backgroundText[D_ERROR]); pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); @@ -574,13 +626,13 @@ void ScreenRecoveryUI::PrintV(const char* fmt, bool copy_to_stdout, va_list ap) } pthread_mutex_lock(&updateMutex); - if (text_rows_ > 0 && text_cols_ > 0) { + if (log_text_rows_ > 0 && log_text_cols_ > 0) { for (const char* ptr = str.c_str(); *ptr != '\0'; ++ptr) { - if (*ptr == '\n' || text_col_ >= text_cols_) { + if (*ptr == '\n' || text_col_ >= log_text_cols_) { text_[text_row_][text_col_] = '\0'; text_col_ = 0; - text_row_ = (text_row_ + 1) % text_rows_; - if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % text_rows_; + text_row_ = (text_row_ + 1) % log_text_rows_; + if (text_row_ == text_top_) text_top_ = (text_top_ + 1) % log_text_rows_; } if (*ptr != '\n') text_[text_row_][text_col_++] = *ptr; } @@ -707,7 +759,8 @@ void ScreenRecoveryUI::DialogShowInfo(const char* text) pthread_mutex_lock(&updateMutex); free(dialog_text); dialog_text = strdup(text); - dialog_icon = INFO; + dialog_show_log = false; + dialog_icon = D_INFO; update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -717,7 +770,19 @@ void ScreenRecoveryUI::DialogShowError(const char* text) pthread_mutex_lock(&updateMutex); free(dialog_text); dialog_text = strdup(text); - dialog_icon = ERROR; + dialog_show_log = false; + dialog_icon = D_ERROR; + update_screen_locked(); + pthread_mutex_unlock(&updateMutex); +} + +void ScreenRecoveryUI::DialogShowErrorLog(const char* text) +{ + pthread_mutex_lock(&updateMutex); + free(dialog_text); + dialog_text = strdup(text); + dialog_show_log = true; + dialog_icon = D_ERROR; update_screen_locked(); pthread_mutex_unlock(&updateMutex); } @@ -749,9 +814,12 @@ void ScreenRecoveryUI::StartMenu(const char* const * headers, const char* const pthread_mutex_unlock(&updateMutex); } -int ScreenRecoveryUI::SelectMenu(int sel) { +int ScreenRecoveryUI::SelectMenu(int sel, bool abs /* = false */) { int wrapped = 0; pthread_mutex_lock(&updateMutex); + if (abs) { + sel += menu_show_start_; + } if (show_menu) { int old_sel = menu_sel; menu_sel = sel; @@ -766,11 +834,17 @@ int ScreenRecoveryUI::SelectMenu(int sel) { } if (menu_sel < 0) { wrapped = -1; - menu_sel = menu_items - 1; + menu_sel = menu_items + menu_sel; } if (menu_sel >= menu_items) { wrapped = 1; - menu_sel = 0; + menu_sel = menu_sel - menu_items; + } + if (menu_sel < menu_show_start_ && menu_show_start_ > 0) { + menu_show_start_ = menu_sel; + } + if (menu_sel - menu_show_start_ >= max_menu_rows_) { + menu_show_start_ = menu_sel - max_menu_rows_ + 1; } sel = menu_sel; if (wrapped != 0) { diff --git a/screen_ui.h b/screen_ui.h index e198503..fff0f0e 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -54,14 +54,17 @@ class ScreenRecoveryUI : public RecoveryUI { void DialogShowInfo(const char* text); void DialogShowError(const char* text); + void DialogShowErrorLog(const char* text); int DialogShowing() const { return (dialog_text != NULL); } - bool DialogDismissable() const { return (dialog_icon == ERROR); } + bool DialogDismissable() const { return (dialog_icon == D_ERROR); } void DialogDismiss(); // menu display + virtual int MenuItemStart() const { return menu_item_start_; } + virtual int MenuItemHeight() const { return 3 * char_height_; } void StartMenu(const char* const * headers, const char* const * items, int initial_selection); - int SelectMenu(int sel); + int SelectMenu(int sel, bool abs = false); void EndMenu(); void KeyLongPress(int); @@ -80,6 +83,7 @@ class ScreenRecoveryUI : public RecoveryUI { bool rtl_locale; pthread_mutex_t updateMutex; + GRSurface* headerIcon; GRSurface* backgroundIcon[NR_ICONS]; GRSurface* backgroundText[NR_ICONS]; GRSurface** installation; @@ -96,6 +100,7 @@ class ScreenRecoveryUI : public RecoveryUI { // true when both graphics pages are the same (except for the progress bar). bool pagesIdentical; + size_t log_text_cols_, log_text_rows_; size_t text_cols_, text_rows_; // Log text overlay, displayed when a magic key is pressed. @@ -107,15 +112,21 @@ class ScreenRecoveryUI : public RecoveryUI { Icon dialog_icon; char *dialog_text; + bool dialog_show_log; char** menu_; const char* const* menu_headers_; bool show_menu; int menu_items, menu_sel; + int menu_show_start_; + int max_menu_rows_; + // An alternate text screen, swapped with 'text_' when we're viewing a log file. char** file_viewer_text_; + int menu_item_start_; + pthread_t progress_thread_; int animation_fps; @@ -128,8 +139,16 @@ class ScreenRecoveryUI : public RecoveryUI { bool rainbow; int wrap_count; + int log_char_height_, log_char_width_; + int char_height_, char_width_; + + int header_height_, header_width_; + int text_first_row_; + void draw_background_locked(Icon icon); void draw_progress_locked(); + int draw_header_icon(); + void draw_menu_item(int textrow, const char *text, int selected); void draw_dialog(); void draw_screen_locked(); void update_screen_locked(); @@ -31,6 +31,7 @@ #include <cutils/properties.h> #include <cutils/android_reboot.h> +#include <cutils/properties.h> #include "common.h" #include "roots.h" @@ -45,6 +46,11 @@ #define UI_WAIT_KEY_TIMEOUT_SEC 120 +/* Some extra input defines */ +#ifndef ABS_MT_ANGLE +#define ABS_MT_ANGLE 0x38 +#endif + static int string_split(char* s, char** fields, int maxfields) { int n = 0; @@ -62,9 +68,14 @@ static int string_split(char* s, char** fields, int maxfields) return n+1; } +struct ms_info { + RecoveryUI* ui; + MessageSocket* sock; +}; + static int message_socket_client_event(int fd, uint32_t epevents, void *data) { - MessageSocket* client = (MessageSocket*)data; + ms_info* info = (ms_info*)data; printf("message_socket client event\n"); if (!(epevents & EPOLLIN)) { @@ -73,12 +84,13 @@ static int message_socket_client_event(int fd, uint32_t epevents, void *data) char buf[256]; ssize_t nread; - nread = client->Read(buf, sizeof(buf)); + nread = info->sock->Read(buf, sizeof(buf)); if (nread <= 0) { ev_del_fd(fd); - self->DialogDismiss(); - client->Close(); - delete client; + info->ui->DialogDismiss(); + info->sock->Close(); + delete info->sock; + delete info; return 0; } @@ -96,10 +108,10 @@ static int message_socket_client_event(int fd, uint32_t epevents, void *data) printf("field[0]=%s, field[1]=%s\n", fields[0], fields[1]); if (strcmp(fields[0], "dialog") == 0) { if (strcmp(fields[1], "show") == 0 && nfields > 2) { - self->DialogShowInfo(fields[2]); + info->ui->DialogShowInfo(fields[2]); } if (strcmp(fields[1], "dismiss") == 0) { - self->DialogDismiss(); + info->ui->DialogDismiss(); } } @@ -108,12 +120,15 @@ static int message_socket_client_event(int fd, uint32_t epevents, void *data) static int message_socket_listen_event(int fd, uint32_t epevents, void *data) { - MessageSocket* ms = (MessageSocket*)data; - MessageSocket* client = ms->Accept(); + ms_info* info = (ms_info*)data; + MessageSocket* sock = info->sock->Accept(); printf("message_socket_listen_event: event on %d\n", fd); - if (client) { + if (sock) { printf("message_socket client connected\n"); - ev_add_fd(client->fd(), message_socket_client_event, client); + ms_info* clientinfo = new ms_info; + clientinfo->ui = info->ui; + clientinfo->sock = sock; + ev_add_fd(sock->fd(), message_socket_client_event, clientinfo); } return 0; } @@ -160,10 +175,14 @@ static void* InputThreadLoop(void*) { } void RecoveryUI::Init() { + calibrate_swipe(); ev_init(InputCallback, this); message_socket.ServerInit(); - ev_add_fd(message_socket.fd(), message_socket_listen_event, &message_socket); + ms_info* info = new ms_info; + info->ui = this; + info->sock = &message_socket; + ev_add_fd(message_socket.fd(), message_socket_listen_event, info); ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); @@ -176,31 +195,45 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { return -1; } - if (ev.type == EV_SYN) { - return 0; - } 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) { - ProcessKey(KEY_DOWN, 1); // press down key - ProcessKey(KEY_DOWN, 0); // and release it - rel_sum = 0; - } else if (rel_sum < -3) { - ProcessKey(KEY_UP, 1); // press up key - ProcessKey(KEY_UP, 0); // and release it - rel_sum = 0; - } + input_device* dev = NULL; + int n; + for (n = 0; n < MAX_NR_INPUT_DEVICES; ++n) { + if (input_devices[n].fd == fd) { + dev = &input_devices[n]; + break; } - } else { - rel_sum = 0; + if (input_devices[n].fd == -1) { + dev = &input_devices[n]; + memset(dev, 0, sizeof(input_device)); + dev->fd = fd; + dev->tracking_id = -1; + calibrate_touch(dev); + setup_vkeys(dev); + break; + } + } + if (!dev) { + LOGE("input_callback: no more available input devices\n"); + return -1; + } + + if (ev.type != EV_REL) { + dev->rel_sum = 0; } - if (ev.type == EV_KEY && ev.code <= KEY_MAX) { - ProcessKey(ev.code, ev.value); + switch (ev.type) { + case EV_SYN: + ProcessSyn(dev, ev.code, ev.value); + break; + case EV_ABS: + ProcessAbs(dev, ev.code, ev.value); + break; + case EV_REL: + ProcessRel(dev, ev.code, ev.value); + break; + case EV_KEY: + ProcessKey(dev, ev.code, ev.value); + break; } return 0; @@ -218,11 +251,14 @@ int RecoveryUI::OnInputEvent(int fd, uint32_t epevents) { // a key is registered. // // updown == 1 for key down events; 0 for key up events -void RecoveryUI::ProcessKey(int key_code, int updown) { +void RecoveryUI::ProcessKey(input_device* dev, int key_code, int updown) { bool register_key = false; bool long_press = false; bool reboot_enabled; + if (key_code > KEY_MAX) + return; + pthread_mutex_lock(&key_queue_mutex); key_pressed[key_code] = updown; if (updown) { @@ -272,6 +308,146 @@ void RecoveryUI::ProcessKey(int key_code, int updown) { } } +void RecoveryUI::ProcessSyn(input_device* dev, int code, int value) { + /* + * Type A device release: + * 1. Lack of position update + * 2. BTN_TOUCH | ABS_PRESSURE | SYN_MT_REPORT + * 3. SYN_REPORT + * + * Type B device release: + * 1. ABS_MT_TRACKING_ID == -1 for "first" slot + * 2. SYN_REPORT + */ + + if (code == SYN_MT_REPORT) { + if (!dev->in_touch && (dev->saw_pos_x && dev->saw_pos_y)) { +#ifdef DEBUG_TOUCH + LOGI("process_syn: type a press\n"); +#endif + handle_press(dev); + } + dev->saw_mt_report = true; + return; + } + if (code == SYN_REPORT) { + if (dev->in_touch) { + handle_gestures(dev); + } + else { + if (dev->saw_tracking_id) { +#ifdef DEBUG_TOUCH + LOGI("process_syn: type b press\n"); +#endif + handle_press(dev); + } + } + + /* Detect release */ + if (dev->saw_mt_report) { + if (dev->in_touch && !dev->saw_pos_x && !dev->saw_pos_y) { + /* type A release */ +#ifdef DEBUG_TOUCH + LOGI("process_syn: type a release\n"); +#endif + handle_release(dev); + dev->slot_first = 0; + } + } + else { + if (dev->in_touch && dev->saw_tracking_id && dev->tracking_id == -1 && + dev->slot_current == dev->slot_first) { + /* type B release */ +#ifdef DEBUG_TOUCH + LOGI("process_syn: type b release\n"); +#endif + handle_release(dev); + dev->slot_first = 0; + } + } + + dev->saw_pos_x = dev->saw_pos_y = false; + dev->saw_mt_report = dev->saw_tracking_id = false; + } +} + +void RecoveryUI::ProcessAbs(input_device* dev, int code, int value) { + if (code == ABS_MT_SLOT) { + dev->slot_current = value; + if (dev->slot_first == -1) { + dev->slot_first = value; + } + return; + } + if (code == ABS_MT_TRACKING_ID) { + /* + * Some devices send an initial ABS_MT_SLOT event before switching + * to type B events, so discard any type A state related to slot. + */ + dev->saw_tracking_id = true; + dev->slot_first = dev->slot_current = 0; + + if (value != dev->tracking_id) { + dev->tracking_id = value; + if (dev->tracking_id < 0) { + dev->slot_nr_active--; + } + else { + dev->slot_nr_active++; + } + } + return; + } + /* + * For type A devices, we "lock" onto the first coordinates by ignoring + * position updates from the time we see a SYN_MT_REPORT until the next + * SYN_REPORT + * + * For type B devices, we "lock" onto the first slot seen until all slots + * are released + */ + if (dev->slot_nr_active == 0) { + /* type A */ + if (dev->saw_pos_x && dev->saw_pos_y) { + return; + } + } + else { + if (dev->slot_current != dev->slot_first) { + return; + } + } + if (code == ABS_MT_POSITION_X) { + dev->saw_pos_x = true; + dev->touch_pos.x = value * fb_dimensions.x / (dev->touch_max.x - dev->touch_min.x); + } + else if (code == ABS_MT_POSITION_Y) { + dev->saw_pos_y = true; + dev->touch_pos.y = value * fb_dimensions.y / (dev->touch_max.y - dev->touch_min.y); + } +} + +void RecoveryUI::ProcessRel(input_device* dev, int code, int value) { +#ifdef BOARD_RECOVERY_NEEDS_REL_INPUT + if (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. + dev->rel_sum += value; + if (dev->rel_sum > 3) { + process_key(dev, KEY_DOWN, 1); // press down key + process_key(dev, KEY_DOWN, 0); // and release it + dev->rel_sum = 0; + } else if (dev->rel_sum < -3) { + process_key(dev, KEY_UP, 1); // press up key + process_key(dev, KEY_UP, 0); // and release it + dev->rel_sum = 0; + } + } +#endif +} + void* RecoveryUI::time_key_helper(void* cookie) { key_timer_t* info = (key_timer_t*) cookie; info->ui->time_key(info->key_code, info->count); @@ -290,6 +466,186 @@ void RecoveryUI::time_key(int key_code, int count) { if (long_press) KeyLongPress(key_code); } +void RecoveryUI::calibrate_touch(input_device* dev) { + fb_dimensions.x = gr_fb_width(); + fb_dimensions.y = gr_fb_height(); + + struct input_absinfo info; + memset(&info, 0, sizeof(info)); + if (ioctl(dev->fd, EVIOCGABS(ABS_MT_POSITION_X), &info) == 0) { + dev->touch_min.x = info.minimum; + dev->touch_max.x = info.maximum; + dev->touch_pos.x = info.value; + } + memset(&info, 0, sizeof(info)); + if (ioctl(dev->fd, EVIOCGABS(ABS_MT_POSITION_Y), &info) == 0) { + dev->touch_min.y = info.minimum; + dev->touch_max.y = info.maximum; + dev->touch_pos.y = info.value; + } +#ifdef DEBUG_TOUCH + LOGI("calibrate_touch: fd=%d, (%d,%d)-(%d,%d) pos (%d,%d)\n", dev->fd, + dev->touch_min.x, dev->touch_min.y, + dev->touch_max.x, dev->touch_max.y, + dev->touch_pos.x, dev->touch_pos.y); +#endif +} + +void RecoveryUI::setup_vkeys(input_device* dev) { + int n; + char name[256]; + char path[PATH_MAX]; + char buf[64*MAX_NR_VKEYS]; + + for (n = 0; n < MAX_NR_VKEYS; ++n) { + dev->virtual_keys[n].keycode = -1; + } + + memset(name, 0, sizeof(name)); + if (ioctl(dev->fd, EVIOCGNAME(sizeof(name)), name) < 0) { + LOGI("setup_vkeys: no vkeys\n"); + return; + } + sprintf(path, "/sys/board_properties/virtualkeys.%s", name); + int vkfd = open(path, O_RDONLY); + if (vkfd < 0) { + LOGI("setup_vkeys: could not open %s\n", path); + return; + } + ssize_t len = read(vkfd, buf, sizeof(buf)); + close(vkfd); + if (len <= 0) { + LOGE("setup_vkeys: could not read %s\n", path); + return; + } + buf[len] = '\0'; + + char* p = buf; + char* endp; + for (n = 0; n < MAX_NR_VKEYS && p < buf+len && *p == '0'; ++n) { + int val[6]; + int f; + for (f = 0; *p && f < 6; ++f) { + val[f] = strtol(p, &endp, 0); + if (p == endp) + break; + p = endp+1; + } + if (f != 6 || val[0] != 0x01) + break; + dev->virtual_keys[n].keycode = val[1]; + dev->virtual_keys[n].min.x = val[2] - val[4]/2; + dev->virtual_keys[n].min.y = val[3] - val[5]/2; + dev->virtual_keys[n].max.x = val[2] + val[4]/2; + dev->virtual_keys[n].max.y = val[3] + val[5]/2; + +#ifdef DEBUG_TOUCH + LOGI("vkey: fd=%d, [%d]=(%d,%d)-(%d,%d)\n", dev->fd, + dev->virtual_keys[n].keycode, + dev->virtual_keys[n].min.x, dev->virtual_keys[n].min.y, + dev->virtual_keys[n].max.x, dev->virtual_keys[n].max.y); +#endif + } +} + +void RecoveryUI::calibrate_swipe() { + char strvalue[PROPERTY_VALUE_MAX]; + int intvalue; + property_get("ro.sf.lcd_density", strvalue, "160"); + intvalue = atoi(strvalue); + int screen_density = (intvalue >= 160 ? intvalue : 160); + min_swipe_px.x = screen_density * 50 / 100; // Roughly 0.5in + min_swipe_px.y = screen_density * 30 / 100; // Roughly 0.3in +#ifdef DEBUG_TOUCH + LOGI("calibrate_swipe: density=%d, min_swipe=(%d,%d)\n", + screen_density, min_swipe_px.x, min_swipe_px.y); +#endif +} + +void RecoveryUI::handle_press(input_device* dev) { + dev->touch_start = dev->touch_track = dev->touch_pos; + dev->in_touch = true; + dev->in_swipe = false; +} + +void RecoveryUI::handle_release(input_device* dev) { + struct point diff = dev->touch_pos - dev->touch_start; + bool in_touch = dev->in_touch; + bool in_swipe = dev->in_swipe; + + dev->in_touch = dev->in_swipe = false; + + if (!in_swipe) { + int n; + for (n = 0; dev->virtual_keys[n].keycode != -1 && n < MAX_NR_VKEYS; ++n) { + vkey* vk = &dev->virtual_keys[n]; + if (dev->touch_start.x >= vk->min.x && dev->touch_start.x < vk->max.x && + dev->touch_start.y >= vk->min.y && dev->touch_start.y < vk->max.y) { +#ifdef DEBUG_TOUCH + LOGI("handle_release: vkey %d\n", vk->keycode); +#endif + EnqueueKey(vk->keycode); + return; + } + } + } + + if (DialogShowing()) { + if (DialogDismissable() && !dev->in_swipe) { + DialogDismiss(); + } + return; + } + + if (in_swipe) { + if (abs(diff.x) > abs(diff.y)) { + if (abs(diff.x) > min_swipe_px.x) { + int key = (diff.x > 0 ? KEY_ENTER : KEY_BACK); + ProcessKey(dev, key, 1); + ProcessKey(dev, key, 0); + } + } + else { + /* Vertical swipe, handled realtime */ + } + } + else { + int sel, start_menu_pos; + // Make sure touch pos is not less than menu start pos. + // No need to check if beyond end of menu items, since + // that is checked by get_menu_selection(). + start_menu_pos = MenuItemStart(); + if (dev->touch_pos.y >= start_menu_pos) { + sel = (dev->touch_pos.y - start_menu_pos)/MenuItemHeight(); + EnqueueKey(KEY_FLAG_ABS | sel); + } + } +} + +void RecoveryUI::handle_gestures(input_device* dev) { + struct point diff; + diff = dev->touch_pos - dev->touch_start; + + if (abs(diff.x) > abs(diff.y)) { + if (abs(diff.x) > min_swipe_px.x) { + /* Horizontal swipe, handle it on release */ + dev->in_swipe = true; + } + } + else { + diff.y = dev->touch_pos.y - dev->touch_track.y; + if (abs(diff.y) > MenuItemHeight()) { + dev->in_swipe = true; + if (!DialogShowing()) { + dev->touch_track = dev->touch_pos; + int key = (diff.y < 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; + ProcessKey(dev, key, 1); + ProcessKey(dev, key, 0); + } + } + } +} + void RecoveryUI::EnqueueKey(int key_code) { if (DialogShowing()) { if (DialogDismissable()) { @@ -24,6 +24,72 @@ #include "messagesocket.h" #include "voldclient.h" +#define MAX_NR_INPUT_DEVICES 8 +#define MAX_NR_VKEYS 8 + +/* + * Simple representation of a (x,y) coordinate with convenience operators + */ +struct point { + point() : x(0), y(0) {} + point operator+(const point& rhs) const { + point tmp; + tmp.x = x + rhs.x; + tmp.y = y + rhs.y; + return tmp; + } + point operator-(const point& rhs) const { + point tmp; + tmp.x = x - rhs.x; + tmp.y = y - rhs.y; + return tmp; + } + + int x; + int y; +}; + +/* + * Virtual key representation. Valid when keycode != -1. + */ +struct vkey { + vkey() : keycode(-1) {} + int keycode; + point min; + point max; +}; + +/* + * Input device representation. Valid when fd != -1. + * This holds all information and state related to a given input device. + */ +struct input_device { + input_device() : fd(-1) {} + + int fd; + vkey virtual_keys[MAX_NR_VKEYS]; + point touch_min; + point touch_max; + + int rel_sum; // Accumulated relative movement + + bool saw_pos_x; // Did sequence have ABS_MT_POSITION_X? + bool saw_pos_y; // Did sequence have ABS_MT_POSITION_Y? + bool saw_mt_report; // Did sequence have SYN_MT_REPORT? + bool saw_tracking_id; // Did sequence have SYN_TRACKING_ID? + bool in_touch; // Are we in a touch event? + bool in_swipe; // Are we in a swipe event? + + point touch_pos; // Current touch coordinates + point touch_start; // Coordinates of touch start + point touch_track; // Last tracked coordinates + + int slot_nr_active; + int slot_first; + int slot_current; + int tracking_id; +}; + // Abstract class for controlling the user interface during recovery. class RecoveryUI { public: @@ -40,7 +106,7 @@ class RecoveryUI { virtual void SetLocale(const char* locale) = 0; // Set the overall recovery state ("background image"). - enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, INFO, ERROR, NR_ICONS }; + enum Icon { NONE, INSTALLING_UPDATE, ERASING, NO_COMMAND, D_INFO, D_ERROR, NR_ICONS }; virtual void SetBackground(Icon icon) = 0; // --- progress indicator --- @@ -71,9 +137,11 @@ class RecoveryUI { virtual void PrintOnScreenOnly(const char* fmt, ...) __printflike(2, 3) = 0; virtual void ShowFile(const char* filename) = 0; + virtual void ClearText() = 0; virtual void DialogShowInfo(const char* text) = 0; virtual void DialogShowError(const char* text) = 0; + virtual void DialogShowErrorLog(const char* text) = 0; virtual int DialogShowing() const = 0; virtual bool DialogDismissable() const = 0; virtual void DialogDismiss() = 0; @@ -119,6 +187,9 @@ class RecoveryUI { // --- menu display --- + virtual int MenuItemStart() const = 0; + virtual int MenuItemHeight() const = 0; + // 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). @@ -127,7 +198,7 @@ class RecoveryUI { // Set the menu highlight to the given index, wrapping if necessary. // Returns the actual item selected. - virtual int SelectMenu(int sel) = 0; + virtual int SelectMenu(int sel, bool abs = false) = 0; // End menu mode, resetting the text overlay so that ui_print() // statements will be displayed. @@ -159,6 +230,11 @@ private: bool has_up_key; bool has_down_key; + input_device input_devices[MAX_NR_INPUT_DEVICES]; + + point fb_dimensions; + point min_swipe_px; + struct key_timer_t { RecoveryUI* ui; int key_code; @@ -173,7 +249,10 @@ private: static int InputCallback(int fd, uint32_t epevents, void* data); int OnInputEvent(int fd, uint32_t epevents); - void ProcessKey(int key_code, int updown); + void ProcessKey(input_device* dev, int key_code, int updown); + void ProcessSyn(input_device* dev, int code, int value); + void ProcessAbs(input_device* dev, int code, int value); + void ProcessRel(input_device* dev, int code, int value); bool IsUsbConnected(); @@ -181,6 +260,14 @@ private: static void* time_key_helper(void* cookie); void time_key(int key_code, int count); + + void process_touch(int fd, struct input_event *ev); + void calibrate_touch(input_device* dev); + void setup_vkeys(input_device* dev); + void calibrate_swipe(); + void handle_press(input_device* dev); + void handle_release(input_device* dev); + void handle_gestures(input_device* dev); }; #endif // RECOVERY_UI_H diff --git a/verifier_test.cpp b/verifier_test.cpp index ed3dce1..d97e115 100644 --- a/verifier_test.cpp +++ b/verifier_test.cpp @@ -148,17 +148,22 @@ class FakeUI : public RecoveryUI { va_end(ap); } void ShowFile(const char*) { } + void ClearText() {} virtual void DialogShowInfo(const char* text) {} virtual void DialogShowError(const char* text) {} + virtual void DialogShowErrorLog(const char* text) {} virtual int DialogShowing() const { return 0; } bool DialogDismissable() const { return false; } virtual void DialogDismiss() {} void StartMenu(const char* const * headers, const char* const * items, int initial_selection) { } - int SelectMenu(int sel) { return 0; } + int SelectMenu(int sel, bool abs = false) { return 0; } void EndMenu() { } + + virtual int MenuItemStart() const { return 0; } + virtual int MenuItemHeight() const { return 0; } }; void diff --git a/wear_ui.cpp b/wear_ui.cpp index 4a07f39..9d4ec09 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -122,7 +122,7 @@ void WearRecoveryUI::draw_background_locked(Icon icon) // Should only be called with updateMutex locked. void WearRecoveryUI::draw_progress_locked() { - if (currentIcon == ERROR) return; + if (currentIcon == D_ERROR) return; if (progressBarType != DETERMINATE) return; int width = progress_bar_width; @@ -348,8 +348,8 @@ void WearRecoveryUI::Init() LoadBitmap("icon_installing", &backgroundIcon[INSTALLING_UPDATE]); backgroundIcon[ERASING] = backgroundIcon[INSTALLING_UPDATE]; - LoadBitmap("icon_error", &backgroundIcon[ERROR]); - backgroundIcon[NO_COMMAND] = backgroundIcon[ERROR]; + LoadBitmap("icon_error", &backgroundIcon[D_ERROR]); + backgroundIcon[NO_COMMAND] = backgroundIcon[D_ERROR]; introFrames = (GRSurface**)malloc(intro_frames * sizeof(GRSurface*)); for (int i = 0; i < intro_frames; ++i) { @@ -503,7 +503,7 @@ void WearRecoveryUI::StartMenu(const char* const * headers, const char* const * pthread_mutex_unlock(&updateMutex); } -int WearRecoveryUI::SelectMenu(int sel) { +int WearRecoveryUI::SelectMenu(int sel, bool abs /* = false */) { int old_sel; pthread_mutex_lock(&updateMutex); if (show_menu > 0) { @@ -53,7 +53,7 @@ class WearRecoveryUI : public RecoveryUI { // menu display void StartMenu(const char* const * headers, const char* const * items, int initial_selection); - int SelectMenu(int sel); + int SelectMenu(int sel, bool abs = false); void EndMenu(); void Redraw(); |